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