1 /**********************************************************************
2
3 Audacity: A Digital Audio Editor
4 Audacity(R) is copyright (c) 1999-2016 Audacity Team.
5 License: GPL v2. See License.txt.
6
7 BassTreble.cpp
8 Steve Daulton
9
10 ******************************************************************//**
11
12 \class EffectBassTreble
13 \brief A high shelf and low shelf filter.
14
15 *//*******************************************************************/
16
17
18 #include "BassTreble.h"
19 #include "LoadEffects.h"
20
21 #include <math.h>
22 #include <algorithm>
23
24 #include <wx/button.h>
25 #include <wx/checkbox.h>
26 #include <wx/intl.h>
27 #include <wx/panel.h>
28 #include <wx/sizer.h>
29 #include <wx/slider.h>
30
31 #include "Prefs.h"
32 #include "../Shuttle.h"
33 #include "../ShuttleGui.h"
34 #include "../WaveTrack.h"
35 #include "../widgets/valnum.h"
36
37 enum
38 {
39 ID_Bass = 10000,
40 ID_Treble,
41 ID_Gain,
42 ID_Link
43 };
44
45 // Define keys, defaults, minimums, and maximums for the effect parameters
46 //
47 // Name Type Key Def Min Max Scale
48 Param( Bass, double, wxT("Bass"), 0.0, -30.0, 30.0, 1 );
49 Param( Treble, double, wxT("Treble"), 0.0, -30.0, 30.0, 1 );
50 Param( Gain, double, wxT("Gain"), 0.0, -30.0, 30.0, 1 );
51 Param( Link, bool, wxT("Link Sliders"), false, false, true, 1 );
52
53 // Used to communicate the type of the filter.
54 enum kShelfType
55 {
56 kBass,
57 kTreble
58 };
59
60 const ComponentInterfaceSymbol EffectBassTreble::Symbol
61 { XO("Bass and Treble") };
62
63 namespace{ BuiltinEffectsModule::Registration< EffectBassTreble > reg; }
64
BEGIN_EVENT_TABLE(EffectBassTreble,wxEvtHandler)65 BEGIN_EVENT_TABLE(EffectBassTreble, wxEvtHandler)
66 EVT_SLIDER(ID_Bass, EffectBassTreble::OnBassSlider)
67 EVT_SLIDER(ID_Treble, EffectBassTreble::OnTrebleSlider)
68 EVT_SLIDER(ID_Gain, EffectBassTreble::OnGainSlider)
69 EVT_TEXT(ID_Bass, EffectBassTreble::OnBassText)
70 EVT_TEXT(ID_Treble, EffectBassTreble::OnTrebleText)
71 EVT_TEXT(ID_Gain, EffectBassTreble::OnGainText)
72 EVT_CHECKBOX(ID_Link, EffectBassTreble::OnLinkCheckbox)
73 END_EVENT_TABLE()
74
75 EffectBassTreble::EffectBassTreble()
76 {
77 mBass = DEF_Bass;
78 mTreble = DEF_Treble;
79 mGain = DEF_Gain;
80 mLink = DEF_Link;
81
82 SetLinearEffectFlag(true);
83 }
84
~EffectBassTreble()85 EffectBassTreble::~EffectBassTreble()
86 {
87 }
88
89 // ComponentInterface implementation
90
GetSymbol()91 ComponentInterfaceSymbol EffectBassTreble::GetSymbol()
92 {
93 return Symbol;
94 }
95
GetDescription()96 TranslatableString EffectBassTreble::GetDescription()
97 {
98 return XO("Simple tone control effect");
99 }
100
ManualPage()101 ManualPageID EffectBassTreble::ManualPage()
102 {
103 return L"Bass_and_Treble";
104 }
105
106 // EffectDefinitionInterface implementation
107
GetType()108 EffectType EffectBassTreble::GetType()
109 {
110 return EffectTypeProcess;
111 }
112
SupportsRealtime()113 bool EffectBassTreble::SupportsRealtime()
114 {
115 #if defined(EXPERIMENTAL_REALTIME_AUDACITY_EFFECTS)
116 return true;
117 #else
118 return false;
119 #endif
120 }
121
122
123 // EffectClientInterface implementation
124
GetAudioInCount()125 unsigned EffectBassTreble::GetAudioInCount()
126 {
127 return 1;
128 }
129
GetAudioOutCount()130 unsigned EffectBassTreble::GetAudioOutCount()
131 {
132 return 1;
133 }
134
ProcessInitialize(sampleCount WXUNUSED (totalLen),ChannelNames WXUNUSED (chanMap))135 bool EffectBassTreble::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap))
136 {
137 InstanceInit(mMaster, mSampleRate);
138
139 return true;
140 }
141
ProcessBlock(float ** inBlock,float ** outBlock,size_t blockLen)142 size_t EffectBassTreble::ProcessBlock(float **inBlock, float **outBlock, size_t blockLen)
143 {
144 return InstanceProcess(mMaster, inBlock, outBlock, blockLen);
145 }
146
RealtimeInitialize()147 bool EffectBassTreble::RealtimeInitialize()
148 {
149 SetBlockSize(512);
150
151 mSlaves.clear();
152
153 return true;
154 }
155
RealtimeAddProcessor(unsigned WXUNUSED (numChannels),float sampleRate)156 bool EffectBassTreble::RealtimeAddProcessor(unsigned WXUNUSED(numChannels), float sampleRate)
157 {
158 EffectBassTrebleState slave;
159
160 InstanceInit(slave, sampleRate);
161
162 mSlaves.push_back(slave);
163
164 return true;
165 }
166
RealtimeFinalize()167 bool EffectBassTreble::RealtimeFinalize()
168 {
169 mSlaves.clear();
170
171 return true;
172 }
173
RealtimeProcess(int group,float ** inbuf,float ** outbuf,size_t numSamples)174 size_t EffectBassTreble::RealtimeProcess(int group,
175 float **inbuf,
176 float **outbuf,
177 size_t numSamples)
178 {
179 return InstanceProcess(mSlaves[group], inbuf, outbuf, numSamples);
180 }
DefineParams(ShuttleParams & S)181 bool EffectBassTreble::DefineParams( ShuttleParams & S ){
182 S.SHUTTLE_PARAM( mBass, Bass );
183 S.SHUTTLE_PARAM( mTreble, Treble );
184 S.SHUTTLE_PARAM( mGain, Gain );
185 S.SHUTTLE_PARAM( mLink, Link );
186 return true;
187 }
188
GetAutomationParameters(CommandParameters & parms)189 bool EffectBassTreble::GetAutomationParameters(CommandParameters & parms)
190 {
191 parms.Write(KEY_Bass, mBass);
192 parms.Write(KEY_Treble, mTreble);
193 parms.Write(KEY_Gain, mGain);
194 parms.Write(KEY_Link, mLink);
195
196 return true;
197 }
198
SetAutomationParameters(CommandParameters & parms)199 bool EffectBassTreble::SetAutomationParameters(CommandParameters & parms)
200 {
201 ReadAndVerifyDouble(Bass);
202 ReadAndVerifyDouble(Treble);
203 ReadAndVerifyDouble(Gain);
204 ReadAndVerifyBool(Link);
205
206 mBass = Bass;
207 mTreble = Treble;
208 mGain = Gain;
209 mLink = Link;
210
211 return true;
212 }
213
CheckWhetherSkipEffect()214 bool EffectBassTreble::CheckWhetherSkipEffect()
215 {
216 return (mBass == 0.0 && mTreble == 0.0 && mGain == 0.0);
217 }
218
219
220 // Effect implementation
221
PopulateOrExchange(ShuttleGui & S)222 void EffectBassTreble::PopulateOrExchange(ShuttleGui & S)
223 {
224 S.SetBorder(5);
225 S.AddSpace(0, 5);
226
227 S.StartStatic(XO("Tone controls"));
228 {
229 S.StartMultiColumn(3, wxEXPAND);
230 {
231 S.SetStretchyCol(2);
232
233 // Bass control
234 mBassT = S.Id(ID_Bass)
235 .Name(XO("Bass (dB):"))
236 .Validator<FloatingPointValidator<double>>(
237 1, &mBass, NumValidatorStyle::DEFAULT, MIN_Bass, MAX_Bass)
238 .AddTextBox(XXO("Ba&ss (dB):"), wxT(""), 10);
239
240 mBassS = S.Id(ID_Bass)
241 .Name(XO("Bass"))
242 .Style(wxSL_HORIZONTAL)
243 .AddSlider( {}, 0, MAX_Bass * SCL_Bass, MIN_Bass * SCL_Bass);
244
245 // Treble control
246 mTrebleT = S.Id(ID_Treble)
247 .Validator<FloatingPointValidator<double>>(
248 1, &mTreble, NumValidatorStyle::DEFAULT, MIN_Treble, MAX_Treble)
249 .AddTextBox(XXO("&Treble (dB):"), wxT(""), 10);
250
251 mTrebleS = S.Id(ID_Treble)
252 .Name(XO("Treble"))
253 .Style(wxSL_HORIZONTAL)
254 .AddSlider( {}, 0, MAX_Treble * SCL_Treble, MIN_Treble * SCL_Treble);
255 }
256 S.EndMultiColumn();
257 }
258 S.EndStatic();
259
260 S.StartStatic(XO("Output"));
261 {
262 S.StartMultiColumn(3, wxEXPAND);
263 {
264 S.SetStretchyCol(2);
265
266 // Gain control
267 mGainT = S.Id(ID_Gain)
268 .Validator<FloatingPointValidator<double>>(
269 1, &mGain, NumValidatorStyle::DEFAULT, MIN_Gain, MAX_Gain)
270 .AddTextBox(XXO("&Volume (dB):"), wxT(""), 10);
271
272 mGainS = S.Id(ID_Gain)
273 .Name(XO("Level"))
274 .Style(wxSL_HORIZONTAL)
275 .AddSlider( {}, 0, MAX_Gain * SCL_Gain, MIN_Gain * SCL_Gain);
276 }
277 S.EndMultiColumn();
278
279 S.StartMultiColumn(2, wxCENTER);
280 {
281 // Link checkbox
282 mLinkCheckBox = S.Id(ID_Link).AddCheckBox(XXO("&Link Volume control to Tone controls"),
283 DEF_Link);
284 }
285 S.EndMultiColumn();
286 }
287 S.EndStatic();
288 }
289
TransferDataToWindow()290 bool EffectBassTreble::TransferDataToWindow()
291 {
292 if (!mUIParent->TransferDataToWindow())
293 {
294 return false;
295 }
296
297 mBassS->SetValue((int) (mBass * SCL_Bass));
298 mTrebleS->SetValue((int) mTreble *SCL_Treble);
299 mGainS->SetValue((int) mGain * SCL_Gain);
300 mLinkCheckBox->SetValue(mLink);
301
302 return true;
303 }
304
TransferDataFromWindow()305 bool EffectBassTreble::TransferDataFromWindow()
306 {
307 if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
308 {
309 return false;
310 }
311
312 return true;
313 }
314
315
316 // EffectBassTreble implementation
317
InstanceInit(EffectBassTrebleState & data,float sampleRate)318 void EffectBassTreble::InstanceInit(EffectBassTrebleState & data, float sampleRate)
319 {
320 data.samplerate = sampleRate;
321 data.slope = 0.4f; // same slope for both filters
322 data.hzBass = 250.0f; // could be tunable in a more advanced version
323 data.hzTreble = 4000.0f; // could be tunable in a more advanced version
324
325 data.a0Bass = 1;
326 data.a1Bass = 0;
327 data.a2Bass = 0;
328 data.b0Bass = 0;
329 data.b1Bass = 0;
330 data.b2Bass = 0;
331
332 data.a0Treble = 1;
333 data.a1Treble = 0;
334 data.a2Treble = 0;
335 data.b0Treble = 0;
336 data.b1Treble = 0;
337 data.b2Treble = 0;
338
339 data.xn1Bass = 0;
340 data.xn2Bass = 0;
341 data.yn1Bass = 0;
342 data.yn2Bass = 0;
343
344 data.xn1Treble = 0;
345 data.xn2Treble = 0;
346 data.yn1Treble = 0;
347 data.yn2Treble = 0;
348
349 data.bass = -1;
350 data.treble = -1;
351 data.gain = DB_TO_LINEAR(mGain);
352
353 }
354
355
356 // EffectClientInterface implementation
357
358
InstanceProcess(EffectBassTrebleState & data,float ** inBlock,float ** outBlock,size_t blockLen)359 size_t EffectBassTreble::InstanceProcess(EffectBassTrebleState & data,
360 float **inBlock,
361 float **outBlock,
362 size_t blockLen)
363 {
364 float *ibuf = inBlock[0];
365 float *obuf = outBlock[0];
366
367 // Set value to ensure correct rounding
368 double oldBass = DB_TO_LINEAR(mBass);
369 double oldTreble = DB_TO_LINEAR(mTreble);
370
371 data.gain = DB_TO_LINEAR(mGain);
372
373 // Compute coefficients of the low shelf biquand IIR filter
374 if (data.bass != oldBass)
375 Coefficients(data.hzBass, data.slope, mBass, data.samplerate, kBass,
376 data.a0Bass, data.a1Bass, data.a2Bass,
377 data.b0Bass, data.b1Bass, data.b2Bass);
378
379 // Compute coefficients of the high shelf biquand IIR filter
380 if (data.treble != oldTreble)
381 Coefficients(data.hzTreble, data.slope, mTreble, data.samplerate, kTreble,
382 data.a0Treble, data.a1Treble, data.a2Treble,
383 data.b0Treble, data.b1Treble, data.b2Treble);
384
385 for (decltype(blockLen) i = 0; i < blockLen; i++) {
386 obuf[i] = DoFilter(data, ibuf[i]) * data.gain;
387 }
388
389 return blockLen;
390 }
391
392
393
394 // Effect implementation
395
396
Coefficients(double hz,double slope,double gain,double samplerate,int type,double & a0,double & a1,double & a2,double & b0,double & b1,double & b2)397 void EffectBassTreble::Coefficients(double hz, double slope, double gain, double samplerate, int type,
398 double& a0, double& a1, double& a2,
399 double& b0, double& b1, double& b2)
400 {
401 double w = 2 * M_PI * hz / samplerate;
402 double a = exp(log(10.0) * gain / 40);
403 double b = sqrt((a * a + 1) / slope - (pow((a - 1), 2)));
404
405 if (type == kBass)
406 {
407 b0 = a * ((a + 1) - (a - 1) * cos(w) + b * sin(w));
408 b1 = 2 * a * ((a - 1) - (a + 1) * cos(w));
409 b2 = a * ((a + 1) - (a - 1) * cos(w) - b * sin(w));
410 a0 = ((a + 1) + (a - 1) * cos(w) + b * sin(w));
411 a1 = -2 * ((a - 1) + (a + 1) * cos(w));
412 a2 = (a + 1) + (a - 1) * cos(w) - b * sin(w);
413 }
414 else //assumed kTreble
415 {
416 b0 = a * ((a + 1) + (a - 1) * cos(w) + b * sin(w));
417 b1 = -2 * a * ((a - 1) + (a + 1) * cos(w));
418 b2 = a * ((a + 1) + (a - 1) * cos(w) - b * sin(w));
419 a0 = ((a + 1) - (a - 1) * cos(w) + b * sin(w));
420 a1 = 2 * ((a - 1) - (a + 1) * cos(w));
421 a2 = (a + 1) - (a - 1) * cos(w) - b * sin(w);
422 }
423 }
424
DoFilter(EffectBassTrebleState & data,float in)425 float EffectBassTreble::DoFilter(EffectBassTrebleState & data, float in)
426 {
427 // Bass filter
428 float out = (data.b0Bass * in + data.b1Bass * data.xn1Bass + data.b2Bass * data.xn2Bass -
429 data.a1Bass * data.yn1Bass - data.a2Bass * data.yn2Bass) / data.a0Bass;
430 data.xn2Bass = data.xn1Bass;
431 data.xn1Bass = in;
432 data.yn2Bass = data.yn1Bass;
433 data.yn1Bass = out;
434
435 // Treble filter
436 in = out;
437 out = (data.b0Treble * in + data.b1Treble * data.xn1Treble + data.b2Treble * data.xn2Treble -
438 data.a1Treble * data.yn1Treble - data.a2Treble * data.yn2Treble) / data.a0Treble;
439 data.xn2Treble = data.xn1Treble;
440 data.xn1Treble = in;
441 data.yn2Treble = data.yn1Treble;
442 data.yn1Treble = out;
443
444 return out;
445 }
446
447
OnBassText(wxCommandEvent & WXUNUSED (evt))448 void EffectBassTreble::OnBassText(wxCommandEvent & WXUNUSED(evt))
449 {
450 double oldBass = mBass;
451
452 if (!EnableApply(mUIParent->TransferDataFromWindow()))
453 {
454 return;
455 }
456
457 if (mLink) UpdateGain(oldBass, kBass);
458 mBassS->SetValue((int) (mBass * SCL_Bass));
459 }
460
OnTrebleText(wxCommandEvent & WXUNUSED (evt))461 void EffectBassTreble::OnTrebleText(wxCommandEvent & WXUNUSED(evt))
462 {
463 double oldTreble = mTreble;
464
465 if (!EnableApply(mUIParent->TransferDataFromWindow()))
466 {
467 return;
468 }
469
470 if (mLink) UpdateGain(oldTreble, kTreble);
471 mTrebleS->SetValue((int) (mTreble * SCL_Treble));
472 }
473
OnGainText(wxCommandEvent & WXUNUSED (evt))474 void EffectBassTreble::OnGainText(wxCommandEvent & WXUNUSED(evt))
475 {
476 if (!EnableApply(mUIParent->TransferDataFromWindow()))
477 {
478 return;
479 }
480
481 mGainS->SetValue((int) (mGain * SCL_Gain));
482 }
483
OnBassSlider(wxCommandEvent & evt)484 void EffectBassTreble::OnBassSlider(wxCommandEvent & evt)
485 {
486 double oldBass = mBass;
487 mBass = (double) evt.GetInt() / SCL_Bass;
488 mBassT->GetValidator()->TransferToWindow();
489
490 if (mLink) UpdateGain(oldBass, kBass);
491 EnableApply(mUIParent->Validate());
492 }
493
OnTrebleSlider(wxCommandEvent & evt)494 void EffectBassTreble::OnTrebleSlider(wxCommandEvent & evt)
495 {
496 double oldTreble = mTreble;
497 mTreble = (double) evt.GetInt() / SCL_Treble;
498 mTrebleT->GetValidator()->TransferToWindow();
499
500 if (mLink) UpdateGain(oldTreble, kTreble);
501 EnableApply(mUIParent->Validate());
502 }
503
OnGainSlider(wxCommandEvent & evt)504 void EffectBassTreble::OnGainSlider(wxCommandEvent & evt)
505 {
506 mGain = (double) evt.GetInt() / SCL_Gain;
507 mGainT->GetValidator()->TransferToWindow();
508
509 EnableApply(mUIParent->Validate());
510 }
511
OnLinkCheckbox(wxCommandEvent &)512 void EffectBassTreble::OnLinkCheckbox(wxCommandEvent& /*evt*/)
513 {
514 mLink = mLinkCheckBox->GetValue();
515 }
516
UpdateGain(double oldVal,int control)517 void EffectBassTreble::UpdateGain(double oldVal, int control)
518 {
519 double newVal;
520 oldVal = (oldVal > 0)? oldVal / 2.0 : oldVal / 4.0;
521
522 if (control == kBass)
523 newVal = (mBass > 0)? mBass / 2.0 : mBass / 4.0;
524 else
525 newVal = (mTreble > 0)? mTreble / 2.0 : mTreble / 4.0;
526
527 mGain -= newVal - oldVal;
528 mGain = std::min(MAX_Gain, std::max(MIN_Gain, mGain));
529
530 mGainS->SetValue(mGain);
531 mGainT->GetValidator()->TransferToWindow();
532
533 }
534