1 /**********************************************************************
2 
3   Audacity: A Digital Audio Editor
4 
5   Effect/ScienFilter.cpp
6 
7   Norm C
8   Mitch Golden
9   Vaughan Johnson (Preview)
10 
11 *******************************************************************//**
12 
13 \file ScienFilter.cpp
14 \brief Implements EffectScienFilter, EffectScienFilterPanel.
15 
16 *//****************************************************************//**
17 
18 \class EffectScienFilter
19 \brief An Effect that applies 'classical' IIR filters.
20 
21   Performs IIR filtering that emulates analog filters, specifically
22   Butterworth, Chebyshev Type I and Type II. Highpass and lowpass filters
23   are supported, as are filter orders from 1 to 10.
24 
25   The filter is applied using biquads
26 
27 *//****************************************************************//**
28 
29 \class EffectScienFilterPanel
30 \brief EffectScienFilterPanel is used with EffectScienFilter and controls
31 a graph for EffectScienFilter.
32 
33 *//*******************************************************************/
34 
35 
36 #include "ScienFilter.h"
37 #include "LoadEffects.h"
38 
39 #include <math.h>
40 #include <float.h>
41 
42 #include <wx/setup.h> // for wxUSE_* macros
43 
44 #include <wx/brush.h>
45 #include <wx/choice.h>
46 #include <wx/dcclient.h>
47 #include <wx/dcmemory.h>
48 #include <wx/intl.h>
49 #include <wx/settings.h>
50 #include <wx/slider.h>
51 #include <wx/stattext.h>
52 #include <wx/utils.h>
53 #include <wx/valgen.h>
54 
55 #include "AColor.h"
56 #include "AllThemeResources.h"
57 #include "PlatformCompatibility.h"
58 #include "Prefs.h"
59 #include "Project.h"
60 #include "../Shuttle.h"
61 #include "../ShuttleGui.h"
62 #include "Theme.h"
63 #include "../WaveTrack.h"
64 #include "../widgets/valnum.h"
65 #include "../widgets/AudacityMessageBox.h"
66 #include "../widgets/Ruler.h"
67 #include "../widgets/WindowAccessible.h"
68 
69 #if !defined(M_PI)
70 #define PI = 3.1415926535897932384626433832795
71 #else
72 #define PI M_PI
73 #endif
74 #define square(a) ((a)*(a))
75 
76 enum
77 {
78    ID_FilterPanel = 10000,
79    ID_dBMax,
80    ID_dBMin,
81    ID_Type,
82    ID_SubType,
83    ID_Order,
84    ID_Ripple,
85    ID_Cutoff,
86    ID_StopbandRipple
87 };
88 
89 enum kTypes
90 {
91    kButterworth,
92    kChebyshevTypeI,
93    kChebyshevTypeII,
94    nTypes
95 };
96 
97 static const EnumValueSymbol kTypeStrings[nTypes] =
98 {
99    /*i18n-hint: Butterworth is the name of the person after whom the filter type is named.*/
100    { XO("Butterworth") },
101    /*i18n-hint: Chebyshev is the name of the person after whom the filter type is named.*/
102    { XO("Chebyshev Type I") },
103    /*i18n-hint: Chebyshev is the name of the person after whom the filter type is named.*/
104    { XO("Chebyshev Type II") }
105 };
106 
107 enum kSubTypes
108 {
109    kLowPass  = Biquad::kLowPass,
110    kHighPass = Biquad::kHighPass,
111    nSubTypes = Biquad::nSubTypes
112 };
113 
114 static const EnumValueSymbol kSubTypeStrings[nSubTypes] =
115 {
116    // These are acceptable dual purpose internal/visible names
117    { XO("Lowpass") },
118    { XO("Highpass") }
119 };
120 
121 static_assert(nSubTypes == WXSIZEOF(kSubTypeStrings), "size mismatch");
122 
123 // Define keys, defaults, minimums, and maximums for the effect parameters
124 //
125 //     Name       Type     Key                     Def            Min   Max               Scale
126 Param( Type,      int,     wxT("FilterType"),       kButterworth,  0,    nTypes - 1,    1  );
127 Param( Subtype,   int,     wxT("FilterSubtype"),    kLowPass,      0,    nSubTypes - 1, 1  );
128 Param( Order,     int,     wxT("Order"),            1,             1,    10,               1  );
129 Param( Cutoff,    float,   wxT("Cutoff"),           1000.0,        1.0,  FLT_MAX,          1  );
130 Param( Passband,  float,   wxT("PassbandRipple"),   1.0,           0.0,  100.0,            1  );
131 Param( Stopband,  float,   wxT("StopbandRipple"),   30.0,          0.0,  100.0,            1  );
132 
133 //----------------------------------------------------------------------------
134 // EffectScienFilter
135 //----------------------------------------------------------------------------
136 
137 const ComponentInterfaceSymbol EffectScienFilter::Symbol
138 { XO("Classic Filters") };
139 
140 #ifdef EXPERIMENTAL_SCIENCE_FILTERS
141 // true argument means don't automatically enable this effect
142 namespace{ BuiltinEffectsModule::Registration< EffectScienFilter > reg( true ); }
143 #endif
144 
BEGIN_EVENT_TABLE(EffectScienFilter,wxEvtHandler)145 BEGIN_EVENT_TABLE(EffectScienFilter, wxEvtHandler)
146    EVT_SIZE(EffectScienFilter::OnSize)
147 
148    EVT_SLIDER(ID_dBMax, EffectScienFilter::OnSliderDBMAX)
149    EVT_SLIDER(ID_dBMin, EffectScienFilter::OnSliderDBMIN)
150    EVT_CHOICE(ID_Order, EffectScienFilter::OnOrder)
151    EVT_CHOICE(ID_Type, EffectScienFilter::OnFilterType)
152    EVT_CHOICE(ID_SubType, EffectScienFilter::OnFilterSubtype)
153    EVT_TEXT(ID_Cutoff, EffectScienFilter::OnCutoff)
154    EVT_TEXT(ID_Ripple, EffectScienFilter::OnRipple)
155    EVT_TEXT(ID_StopbandRipple, EffectScienFilter::OnStopbandRipple)
156 END_EVENT_TABLE()
157 
158 EffectScienFilter::EffectScienFilter()
159 {
160    mOrder = DEF_Order;
161    mFilterType = DEF_Type;
162    mFilterSubtype = DEF_Subtype;
163    mCutoff = DEF_Cutoff;
164    mRipple = DEF_Passband;
165    mStopbandRipple = DEF_Stopband;
166 
167    SetLinearEffectFlag(true);
168 
169    mOrderIndex = mOrder - 1;
170 
171    mdBMin = -30.0;
172    mdBMax = 30.0;
173 
174    mLoFreq = 20;              // Lowest frequency to display in response graph
175    mNyquist = 44100.0 / 2.0;  // only used during initialization, updated when effect is used
176 }
177 
~EffectScienFilter()178 EffectScienFilter::~EffectScienFilter()
179 {
180 }
181 
182 // ComponentInterface implementation
183 
GetSymbol()184 ComponentInterfaceSymbol EffectScienFilter::GetSymbol()
185 {
186    return Symbol;
187 }
188 
GetDescription()189 TranslatableString EffectScienFilter::GetDescription()
190 {
191    /* i18n-hint: "infinite impulse response" */
192    return XO("Performs IIR filtering that emulates analog filters");
193 }
194 
ManualPage()195 ManualPageID EffectScienFilter::ManualPage()
196 {
197    return L"Classic_Filters";
198 }
199 
200 
201 // EffectDefinitionInterface implementation
202 
GetType()203 EffectType EffectScienFilter::GetType()
204 {
205    return EffectTypeProcess;
206 }
207 
208 // EffectClientInterface implementation
209 
GetAudioInCount()210 unsigned EffectScienFilter::GetAudioInCount()
211 {
212    return 1;
213 }
214 
GetAudioOutCount()215 unsigned EffectScienFilter::GetAudioOutCount()
216 {
217    return 1;
218 }
219 
ProcessInitialize(sampleCount WXUNUSED (totalLen),ChannelNames WXUNUSED (chanMap))220 bool EffectScienFilter::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap))
221 {
222    for (int iPair = 0; iPair < (mOrder + 1) / 2; iPair++)
223       mpBiquad[iPair].Reset();
224 
225    return true;
226 }
227 
ProcessBlock(float ** inBlock,float ** outBlock,size_t blockLen)228 size_t EffectScienFilter::ProcessBlock(float **inBlock, float **outBlock, size_t blockLen)
229 {
230    float *ibuf = inBlock[0];
231    for (int iPair = 0; iPair < (mOrder + 1) / 2; iPair++)
232    {
233       mpBiquad[iPair].Process(ibuf, outBlock[0], blockLen);
234       ibuf = outBlock[0];
235    }
236 
237    return blockLen;
238 }
DefineParams(ShuttleParams & S)239 bool EffectScienFilter::DefineParams( ShuttleParams & S ){
240    S.SHUTTLE_ENUM_PARAM( mFilterType, Type, kTypeStrings, nTypes );
241    S.SHUTTLE_ENUM_PARAM( mFilterSubtype, Subtype, kSubTypeStrings, nSubTypes );
242    S.SHUTTLE_PARAM( mOrder, Order );
243    S.SHUTTLE_PARAM( mCutoff, Cutoff );
244    S.SHUTTLE_PARAM( mRipple, Passband );
245    S.SHUTTLE_PARAM( mStopbandRipple, Stopband );
246    return true;
247 }
248 
GetAutomationParameters(CommandParameters & parms)249 bool EffectScienFilter::GetAutomationParameters(CommandParameters & parms)
250 {
251    parms.Write(KEY_Type, kTypeStrings[mFilterType].Internal());
252    parms.Write(KEY_Subtype, kSubTypeStrings[mFilterSubtype].Internal());
253    parms.Write(KEY_Order, mOrder);
254    parms.WriteFloat(KEY_Cutoff, mCutoff);
255    parms.WriteFloat(KEY_Passband, mRipple);
256    parms.WriteFloat(KEY_Stopband, mStopbandRipple);
257 
258    return true;
259 }
260 
SetAutomationParameters(CommandParameters & parms)261 bool EffectScienFilter::SetAutomationParameters(CommandParameters & parms)
262 {
263    ReadAndVerifyEnum(Type, kTypeStrings, nTypes);
264    ReadAndVerifyEnum(Subtype, kSubTypeStrings, nSubTypes);
265    ReadAndVerifyInt(Order);
266    ReadAndVerifyFloat(Cutoff);
267    ReadAndVerifyFloat(Passband);
268    ReadAndVerifyFloat(Stopband);
269 
270    mFilterType = Type;
271    mFilterSubtype = Subtype;
272    mOrder = Order;
273    mCutoff = Cutoff;
274    mRipple = Passband;
275    mStopbandRipple = Stopband;
276 
277    mOrderIndex = mOrder - 1;
278 
279    CalcFilter();
280 
281    return true;
282 }
283 
284 // Effect implementation
285 
Startup()286 bool EffectScienFilter::Startup()
287 {
288    wxString base = wxT("/SciFilter/");
289 
290    // Migrate settings from 2.1.0 or before
291 
292    // Already migrated, so bail
293    if (gPrefs->Exists(base + wxT("Migrated")))
294    {
295       return true;
296    }
297 
298    // Load the old "current" settings
299    if (gPrefs->Exists(base))
300    {
301 	   double dTemp;
302       gPrefs->Read(base + wxT("Order"), &mOrder, 1);
303       mOrder = wxMax (1, mOrder);
304       mOrder = wxMin (MAX_Order, mOrder);
305       gPrefs->Read(base + wxT("FilterType"), &mFilterType, 0);
306       mFilterType = wxMax (0, mFilterType);
307       mFilterType = wxMin (2, mFilterType);
308       gPrefs->Read(base + wxT("FilterSubtype"), &mFilterSubtype, 0);
309       mFilterSubtype = wxMax (0, mFilterSubtype);
310       mFilterSubtype = wxMin (1, mFilterSubtype);
311       gPrefs->Read(base + wxT("Cutoff"), &dTemp, 1000.0);
312       mCutoff = (float)dTemp;
313       mCutoff = wxMax (1, mCutoff);
314       mCutoff = wxMin (100000, mCutoff);
315       gPrefs->Read(base + wxT("Ripple"), &dTemp, 1.0);
316       mRipple = dTemp;
317       mRipple = wxMax (0, mRipple);
318       mRipple = wxMin (100, mRipple);
319       gPrefs->Read(base + wxT("StopbandRipple"), &dTemp, 30.0);
320       mStopbandRipple = dTemp;
321       mStopbandRipple = wxMax (0, mStopbandRipple);
322       mStopbandRipple = wxMin (100, mStopbandRipple);
323 
324       SaveUserPreset(GetCurrentSettingsGroup());
325 
326       // Do not migrate again
327       gPrefs->Write(base + wxT("Migrated"), true);
328       gPrefs->Flush();
329    }
330 
331    return true;
332 }
333 
Init()334 bool EffectScienFilter::Init()
335 {
336    int selcount = 0;
337    double rate = 0.0;
338 
339    auto trackRange = inputTracks()->Selected< const WaveTrack >();
340 
341    {
342       auto t = *trackRange.begin();
343       mNyquist =
344          (t
345             ? t->GetRate()
346             : mProjectRate)
347          / 2.0;
348    }
349 
350    for (auto t : trackRange)
351    {
352       if (selcount == 0)
353       {
354          rate = t->GetRate();
355       }
356       else
357       {
358          if (t->GetRate() != rate)
359          {
360             Effect::MessageBox(
361                XO(
362 "To apply a filter, all selected tracks must have the same sample rate.") );
363             return false;
364          }
365       }
366       selcount++;
367    }
368 
369    return true;
370 }
371 
PopulateOrExchange(ShuttleGui & S)372 void EffectScienFilter::PopulateOrExchange(ShuttleGui & S)
373 {
374    S.AddSpace(5);
375    S.SetSizerProportion(1);
376    S.StartMultiColumn(3, wxEXPAND);
377    {
378       S.SetStretchyCol(1);
379       S.SetStretchyRow(0);
380 
381       // -------------------------------------------------------------------
382       // ROW 1: Freq response panel and sliders for vertical scale
383       // -------------------------------------------------------------------
384 
385       S.StartVerticalLay();
386       {
387          mdBRuler = safenew RulerPanel(
388             S.GetParent(), wxID_ANY, wxVERTICAL,
389             wxSize{ 100, 100 }, // Ruler can't handle small sizes
390             RulerPanel::Range{ 30.0, -120.0 },
391             Ruler::LinearDBFormat,
392             XO("dB"),
393             RulerPanel::Options{}
394                .LabelEdges(true)
395          );
396 
397          S.SetBorder(1);
398          S.AddSpace(1, 1);
399          S.Prop(1)
400             .Position(wxALIGN_RIGHT | wxTOP)
401             .AddWindow(mdBRuler);
402          S.AddSpace(1, 1);
403       }
404       S.EndVerticalLay();
405 
406       mPanel = safenew EffectScienFilterPanel(
407          S.GetParent(), wxID_ANY,
408          this, mLoFreq, mNyquist
409       );
410 
411       S.SetBorder(5);
412       S.Prop(1)
413          .Position(wxEXPAND | wxRIGHT)
414          .MinSize( { -1, -1 } )
415          .AddWindow(mPanel);
416 
417       S.StartVerticalLay();
418       {
419          S.AddVariableText(XO("+ dB"), false, wxCENTER);
420          mdBMaxSlider = S.Id(ID_dBMax)
421             .Name(XO("Max dB"))
422             .Style(wxSL_VERTICAL | wxSL_INVERSE)
423             .AddSlider( {}, 10, 20, 0);
424 #if wxUSE_ACCESSIBILITY
425          mdBMaxSlider->SetAccessible(safenew SliderAx(mdBMaxSlider, XO("%d dB")));
426 #endif
427          mdBMinSlider = S.Id(ID_dBMin)
428             .Name(XO("Min dB"))
429             .Style(wxSL_VERTICAL | wxSL_INVERSE)
430             .AddSlider( {}, -10, -10, -120);
431 #if wxUSE_ACCESSIBILITY
432          mdBMinSlider->SetAccessible(safenew SliderAx(mdBMinSlider, XO("%d dB")));
433 #endif
434 
435          S.AddVariableText(XO("- dB"), false, wxCENTER);
436       }
437       S.EndVerticalLay();
438 
439       // -------------------------------------------------------------------
440       // ROW 2: Frequency ruler
441       // -------------------------------------------------------------------
442 
443       S.AddSpace(1, 1);
444 
445       mfreqRuler = safenew RulerPanel(
446          S.GetParent(), wxID_ANY, wxHORIZONTAL,
447          wxSize{ 100, 100 }, // Ruler can't handle small sizes
448          RulerPanel::Range{ mLoFreq, mNyquist },
449          Ruler::IntFormat,
450          {},
451          RulerPanel::Options{}
452             .Log(true)
453             .Flip(true)
454             .LabelEdges(true)
455       );
456 
457       S.Prop(1)
458          .Position(wxEXPAND | wxALIGN_LEFT | wxRIGHT)
459          .AddWindow(mfreqRuler);
460 
461       S.AddSpace(1, 1);
462 
463       // -------------------------------------------------------------------
464       // ROW 3 and 4: Type, Order, Ripple, Subtype, Cutoff
465       // -------------------------------------------------------------------
466 
467       S.AddSpace(1, 1);
468       S.SetSizerProportion(0);
469       S.StartMultiColumn(8, wxALIGN_CENTER);
470       {
471          wxASSERT(nTypes == WXSIZEOF(kTypeStrings));
472 
473          mFilterTypeCtl = S.Id(ID_Type)
474             .Focus()
475             .Validator<wxGenericValidator>(&mFilterType)
476             .MinSize( { -1, -1 } )
477             .AddChoice(XXO("&Filter Type:"),
478                Msgids(kTypeStrings, nTypes)
479             );
480 
481          mFilterOrderCtl = S.Id(ID_Order)
482             .Validator<wxGenericValidator>(&mOrderIndex)
483             .MinSize( { -1, -1 } )
484             /*i18n-hint: 'Order' means the complexity of the filter, and is a number between 1 and 10.*/
485             .AddChoice(XXO("O&rder:"),
486                []{
487                   TranslatableStrings orders;
488                   for (int i = 1; i <= 10; i++)
489                      orders.emplace_back( Verbatim("%d").Format( i ) );
490                   return orders;
491                }()
492             );
493          S.AddSpace(1, 1);
494 
495          mRippleCtlP = S.AddVariableText( XO("&Passband Ripple:"),
496             false, wxALL | wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
497          mRippleCtl = S.Id(ID_Ripple)
498             .Name(XO("Passband Ripple (dB)"))
499             .Validator<FloatingPointValidator<float>>(
500                1, &mRipple, NumValidatorStyle::DEFAULT,
501                MIN_Passband, MAX_Passband)
502             .AddTextBox( {}, wxT(""), 10);
503          mRippleCtlU = S.AddVariableText(XO("dB"),
504             false, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
505 
506          mFilterSubTypeCtl = S.Id(ID_SubType)
507             .Validator<wxGenericValidator>(&mFilterSubtype)
508             .MinSize( { -1, -1 } )
509             .AddChoice(XXO("&Subtype:"),
510                Msgids(kSubTypeStrings, nSubTypes)
511             );
512 
513          mCutoffCtl = S.Id(ID_Cutoff)
514             .Name(XO("Cutoff (Hz)"))
515             .Validator<FloatingPointValidator<float>>(
516                1, &mCutoff, NumValidatorStyle::DEFAULT,
517                MIN_Cutoff, mNyquist - 1)
518             .AddTextBox(XXO("C&utoff:"), wxT(""), 10);
519          S.AddUnits(XO("Hz"));
520 
521          mStopbandRippleCtlP =
522             S.AddVariableText(XO("Minimum S&topband Attenuation:"),
523             false, wxALL | wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
524          mStopbandRippleCtl = S.Id(ID_StopbandRipple)
525             .Name(XO("Minimum S&topband Attenuation (dB)"))
526             .Validator<FloatingPointValidator<float>>(
527                1, &mStopbandRipple, NumValidatorStyle::DEFAULT,
528                MIN_Stopband, MAX_Stopband)
529             .AddTextBox( {}, wxT(""), 10);
530          mStopbandRippleCtlU =
531             S.AddVariableText(XO("dB"),
532             false, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
533       }
534       S.EndMultiColumn();
535       S.AddSpace(1, 1);
536    }
537    S.EndMultiColumn();
538 
539    return;
540 }
541 
542 //
543 // Populate the window with relevant variables
544 //
TransferDataToWindow()545 bool EffectScienFilter::TransferDataToWindow()
546 {
547    mOrderIndex = mOrder - 1;
548 
549    if (!mUIParent->TransferDataToWindow())
550    {
551       return false;
552    }
553 
554    mdBMinSlider->SetValue((int) mdBMin);
555    mdBMin = 0.0;                     // force refresh in TransferGraphLimitsFromWindow()
556 
557    mdBMaxSlider->SetValue((int) mdBMax);
558    mdBMax = 0.0;                    // force refresh in TransferGraphLimitsFromWindow()
559 
560    EnableDisableRippleCtl(mFilterType);
561 
562    return TransferGraphLimitsFromWindow();
563 }
564 
TransferDataFromWindow()565 bool EffectScienFilter::TransferDataFromWindow()
566 {
567    if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
568    {
569       return false;
570    }
571 
572    mOrder = mOrderIndex + 1;
573 
574    CalcFilter();
575 
576    return true;
577 }
578 
579 // EffectScienFilter implementation
580 
581 //
582 // Retrieve data from the window
583 //
TransferGraphLimitsFromWindow()584 bool EffectScienFilter::TransferGraphLimitsFromWindow()
585 {
586    // Read the sliders and send to the panel
587    wxString tip;
588 
589    bool rr = false;
590    int dB = mdBMinSlider->GetValue();
591    if (dB != mdBMin) {
592       rr = true;
593       mdBMin = dB;
594       tip.Printf(_("%d dB"), (int)mdBMin);
595       mdBMinSlider->SetToolTip(tip);
596    }
597 
598    dB = mdBMaxSlider->GetValue();
599    if (dB != mdBMax) {
600       rr = true;
601       mdBMax = dB;
602       tip.Printf(_("%d dB"),(int)mdBMax);
603       mdBMaxSlider->SetToolTip(tip);
604    }
605 
606    if (rr) {
607       mPanel->SetDbRange(mdBMin, mdBMax);
608    }
609 
610    // Refresh ruler if values have changed
611    if (rr) {
612       int w1, w2, h;
613       mdBRuler->ruler.GetMaxSize(&w1, &h);
614       mdBRuler->ruler.SetRange(mdBMax, mdBMin);
615       mdBRuler->ruler.GetMaxSize(&w2, &h);
616       if( w1 != w2 )   // Reduces flicker
617       {
618          mdBRuler->SetSize(wxSize(w2,h));
619          mUIParent->Layout();
620          mfreqRuler->Refresh(false);
621       }
622       mdBRuler->Refresh(false);
623    }
624 
625    mPanel->Refresh(false);
626 
627    return true;
628 }
629 
CalcFilter()630 void EffectScienFilter::CalcFilter()
631 {
632    switch (mFilterType)
633    {
634    case kButterworth:
635       mpBiquad = Biquad::CalcButterworthFilter(mOrder, mNyquist, mCutoff, mFilterSubtype);
636       break;
637    case kChebyshevTypeI:
638       mpBiquad = Biquad::CalcChebyshevType1Filter(mOrder, mNyquist, mCutoff, mRipple, mFilterSubtype);
639       break;
640    case kChebyshevTypeII:
641       mpBiquad = Biquad::CalcChebyshevType2Filter(mOrder, mNyquist, mCutoff, mStopbandRipple, mFilterSubtype);
642       break;
643    }
644 }
645 
FilterMagnAtFreq(float Freq)646 float EffectScienFilter::FilterMagnAtFreq(float Freq)
647 {
648    float Magn;
649    if (Freq >= mNyquist)
650       Freq = mNyquist - 1;	// prevent tan(PI/2)
651    float FreqWarped = tan (PI * Freq/(2*mNyquist));
652    if (mCutoff >= mNyquist)
653       mCutoff = mNyquist - 1;
654    float CutoffWarped = tan (PI * mCutoff/(2*mNyquist));
655    float fOverflowThresh = pow (10.0, 12.0 / (2*mOrder));    // once we exceed 10^12 there's not much to be gained and overflow could happen
656 
657    switch (mFilterType)
658    {
659    case kButterworth:		// Butterworth
660    default:
661       switch (mFilterSubtype)
662       {
663       case kLowPass:	// lowpass
664       default:
665          if (FreqWarped/CutoffWarped > fOverflowThresh)	// prevent pow() overflow
666             Magn = 0;
667          else
668             Magn = sqrt (1 / (1 + pow (FreqWarped/CutoffWarped, 2*mOrder)));
669          break;
670       case kHighPass:	// highpass
671          if (FreqWarped/CutoffWarped > fOverflowThresh)
672             Magn = 1;
673          else
674             Magn = sqrt (pow (FreqWarped/CutoffWarped, 2*mOrder) / (1 + pow (FreqWarped/CutoffWarped, 2*mOrder)));
675          break;
676       }
677       break;
678 
679    case kChebyshevTypeI:     // Chebyshev Type 1
680       double eps; eps = sqrt(pow (10.0, wxMax(0.001, mRipple)/10.0) - 1);
681       double chebyPolyVal;
682       switch (mFilterSubtype)
683       {
684       case 0:	// lowpass
685       default:
686          chebyPolyVal = Biquad::ChebyPoly(mOrder, FreqWarped/CutoffWarped);
687          Magn = sqrt (1 / (1 + square(eps) * square(chebyPolyVal)));
688          break;
689       case 1:
690          chebyPolyVal = Biquad::ChebyPoly(mOrder, CutoffWarped/FreqWarped);
691          Magn = sqrt (1 / (1 + square(eps) * square(chebyPolyVal)));
692          break;
693       }
694       break;
695 
696    case kChebyshevTypeII:     // Chebyshev Type 2
697       eps = 1 / sqrt(pow (10.0, wxMax(0.001, mStopbandRipple)/10.0) - 1);
698       switch (mFilterSubtype)
699       {
700       case kLowPass:	// lowpass
701       default:
702          chebyPolyVal = Biquad::ChebyPoly(mOrder, CutoffWarped/FreqWarped);
703          Magn = sqrt (1 / (1 + 1 / (square(eps) * square(chebyPolyVal))));
704          break;
705       case kHighPass:
706          chebyPolyVal = Biquad::ChebyPoly(mOrder, FreqWarped/CutoffWarped);
707          Magn = sqrt (1 / (1 + 1 / (square(eps) * square(chebyPolyVal))));
708          break;
709       }
710       break;
711    }
712 
713    return Magn;
714 }
715 
OnOrder(wxCommandEvent & WXUNUSED (evt))716 void EffectScienFilter::OnOrder(wxCommandEvent & WXUNUSED(evt))
717 {
718    mOrderIndex = mFilterOrderCtl->GetSelection();
719    mOrder = mOrderIndex + 1;	// 0..n-1 -> 1..n
720    mPanel->Refresh(false);
721 }
722 
OnFilterType(wxCommandEvent & WXUNUSED (evt))723 void EffectScienFilter::OnFilterType(wxCommandEvent & WXUNUSED(evt))
724 {
725    mFilterType = mFilterTypeCtl->GetSelection();
726    EnableDisableRippleCtl(mFilterType);
727    mPanel->Refresh(false);
728 }
729 
OnFilterSubtype(wxCommandEvent & WXUNUSED (evt))730 void EffectScienFilter::OnFilterSubtype(wxCommandEvent & WXUNUSED(evt))
731 {
732    mFilterSubtype = mFilterSubTypeCtl->GetSelection();
733    mPanel->Refresh(false);
734 }
735 
OnCutoff(wxCommandEvent & WXUNUSED (evt))736 void EffectScienFilter::OnCutoff(wxCommandEvent & WXUNUSED(evt))
737 {
738    if (!EnableApply(mUIParent->TransferDataFromWindow()))
739    {
740       return;
741    }
742 
743    mPanel->Refresh(false);
744 }
745 
OnRipple(wxCommandEvent & WXUNUSED (evt))746 void EffectScienFilter::OnRipple(wxCommandEvent & WXUNUSED(evt))
747 {
748    if (!EnableApply(mUIParent->TransferDataFromWindow()))
749    {
750       return;
751    }
752 
753    mPanel->Refresh(false);
754 }
755 
OnStopbandRipple(wxCommandEvent & WXUNUSED (evt))756 void EffectScienFilter::OnStopbandRipple(wxCommandEvent & WXUNUSED(evt))
757 {
758    if (!EnableApply(mUIParent->TransferDataFromWindow()))
759    {
760       return;
761    }
762 
763    mPanel->Refresh(false);
764 }
765 
OnSliderDBMIN(wxCommandEvent & WXUNUSED (evt))766 void EffectScienFilter::OnSliderDBMIN(wxCommandEvent & WXUNUSED(evt))
767 {
768    TransferGraphLimitsFromWindow();
769 }
770 
OnSliderDBMAX(wxCommandEvent & WXUNUSED (evt))771 void EffectScienFilter::OnSliderDBMAX(wxCommandEvent & WXUNUSED(evt))
772 {
773    TransferGraphLimitsFromWindow();
774 }
775 
OnSize(wxSizeEvent & evt)776 void EffectScienFilter::OnSize(wxSizeEvent & evt)
777 {
778    // On Windows the Passband and Stopband boxes do not refresh properly
779    // on a resize...no idea why.
780    mUIParent->Refresh();
781    evt.Skip();
782 }
783 
EnableDisableRippleCtl(int FilterType)784 void EffectScienFilter::EnableDisableRippleCtl(int FilterType)
785 {
786    bool ripple;
787    bool stop;
788 
789    if (FilterType == kButterworth)    // Butterworth
790    {
791       ripple = false;
792       stop = false;
793    }
794    else if (FilterType == kChebyshevTypeI)    // Chebyshev Type1
795    {
796       ripple = true;
797       stop = false;
798    }
799    else                        // Chebyshev Type2
800    {
801       ripple = false;
802       stop = true;
803    }
804 
805    mRippleCtlP->Enable(ripple);
806    mRippleCtl->Enable(ripple);
807    mRippleCtlU->Enable(ripple);
808    mStopbandRippleCtlP->Enable(stop);
809    mStopbandRippleCtl->Enable(stop);
810    mStopbandRippleCtlU->Enable(stop);
811 }
812 
813 //----------------------------------------------------------------------------
814 // EffectScienFilterPanel
815 //----------------------------------------------------------------------------
816 
BEGIN_EVENT_TABLE(EffectScienFilterPanel,wxPanelWrapper)817 BEGIN_EVENT_TABLE(EffectScienFilterPanel, wxPanelWrapper)
818     EVT_PAINT(EffectScienFilterPanel::OnPaint)
819     EVT_SIZE(EffectScienFilterPanel::OnSize)
820 END_EVENT_TABLE()
821 
822 EffectScienFilterPanel::EffectScienFilterPanel(
823    wxWindow *parent, wxWindowID winid,
824    EffectScienFilter *effect, double lo, double hi)
825 :  wxPanelWrapper(parent, winid, wxDefaultPosition, wxSize(400, 200))
826 {
827    mEffect = effect;
828    mParent = parent;
829 
830    mBitmap = NULL;
831    mWidth = 0;
832    mHeight = 0;
833    mLoFreq = 0.0;
834    mHiFreq = 0.0;
835    mDbMin = 0.0;
836    mDbMax = 0.0;
837 
838    SetFreqRange(lo, hi);
839 }
840 
~EffectScienFilterPanel()841 EffectScienFilterPanel::~EffectScienFilterPanel()
842 {
843 }
844 
SetFreqRange(double lo,double hi)845 void EffectScienFilterPanel::SetFreqRange(double lo, double hi)
846 {
847    mLoFreq = lo;
848    mHiFreq = hi;
849    Refresh(false);
850 }
851 
SetDbRange(double min,double max)852 void EffectScienFilterPanel::SetDbRange(double min, double max)
853 {
854    mDbMin = min;
855    mDbMax = max;
856    Refresh(false);
857 }
858 
AcceptsFocus() const859 bool EffectScienFilterPanel::AcceptsFocus() const
860 {
861    return false;
862 }
863 
AcceptsFocusFromKeyboard() const864 bool EffectScienFilterPanel::AcceptsFocusFromKeyboard() const
865 {
866    return false;
867 }
868 
OnSize(wxSizeEvent & WXUNUSED (evt))869 void EffectScienFilterPanel::OnSize(wxSizeEvent & WXUNUSED(evt))
870 {
871    Refresh(false);
872 }
873 
OnPaint(wxPaintEvent & WXUNUSED (evt))874 void EffectScienFilterPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
875 {
876    wxPaintDC dc(this);
877    int width, height;
878    GetSize(&width, &height);
879 
880    if (!mBitmap || mWidth != width || mHeight != height)
881    {
882       mWidth = width;
883       mHeight = height;
884       mBitmap = std::make_unique<wxBitmap>(mWidth, mHeight,24);
885    }
886 
887    wxBrush bkgndBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE));
888 
889    wxMemoryDC memDC;
890    memDC.SelectObject(*mBitmap);
891 
892    wxRect bkgndRect;
893    bkgndRect.x = 0;
894    bkgndRect.y = 0;
895    bkgndRect.width = mWidth;
896    bkgndRect.height = mHeight;
897    memDC.SetBrush(bkgndBrush);
898    memDC.SetPen(*wxTRANSPARENT_PEN);
899    memDC.DrawRectangle(bkgndRect);
900 
901    bkgndRect.y = mHeight;
902    memDC.DrawRectangle(bkgndRect);
903 
904    wxRect border;
905    border.x = 0;
906    border.y = 0;
907    border.width = mWidth;
908    border.height = mHeight;
909 
910    memDC.SetBrush(*wxWHITE_BRUSH);
911    memDC.SetPen(*wxBLACK_PEN);
912    memDC.DrawRectangle(border);
913 
914    mEnvRect = border;
915    mEnvRect.Deflate(2, 2);
916 
917    // Pure blue x-axis line
918    memDC.SetPen(wxPen(theTheme.Colour(clrGraphLines), 1, wxPENSTYLE_SOLID));
919    int center = (int) (mEnvRect.height * mDbMax / (mDbMax - mDbMin) + 0.5);
920    AColor::Line(memDC,
921                 mEnvRect.GetLeft(), mEnvRect.y + center,
922                 mEnvRect.GetRight(), mEnvRect.y + center);
923 
924    //Now draw the actual response that you will get.
925    //mFilterFunc has a linear scale, window has a log one so we have to fiddle about
926    memDC.SetPen(wxPen(theTheme.Colour(clrResponseLines), 3, wxPENSTYLE_SOLID));
927    double scale = (double) mEnvRect.height / (mDbMax - mDbMin);    // pixels per dB
928    double yF;                                                     // gain at this freq
929 
930    double loLog = log10(mLoFreq);
931    double step = log10(mHiFreq) - loLog;
932    step /= ((double) mEnvRect.width - 1.0);
933    double freq;                                    // actual freq corresponding to x position
934    int x, y, xlast = 0, ylast = 0;
935    for (int i = 0; i < mEnvRect.width; i++)
936    {
937       x = mEnvRect.x + i;
938       freq = pow(10.0, loLog + i * step);          //Hz
939       yF = mEffect->FilterMagnAtFreq (freq);
940       yF = LINEAR_TO_DB(yF);
941 
942       if (yF < mDbMin)
943       {
944          yF = mDbMin;
945       }
946 
947       yF = center-scale * yF;
948       if (yF > mEnvRect.height)
949       {
950          yF = (double) mEnvRect.height - 1.0;
951       }
952       if (yF < 0.0)
953       {
954          yF = 0.0;
955       }
956       y = (int) (yF + 0.5);
957 
958       if (i != 0 && (y < mEnvRect.height - 1 || ylast < mEnvRect.y + mEnvRect.height - 1))
959       {
960          AColor::Line(memDC, xlast, ylast, x, mEnvRect.y + y);
961       }
962       xlast = x;
963       ylast = mEnvRect.y + y;
964    }
965 
966    memDC.SetPen(*wxBLACK_PEN);
967    mEffect->mfreqRuler->ruler.DrawGrid(memDC, mEnvRect.height + 2, true, true, 0, 1);
968    mEffect->mdBRuler->ruler.DrawGrid(memDC, mEnvRect.width + 2, true, true, 1, 2);
969 
970    dc.Blit(0, 0, mWidth, mHeight, &memDC, 0, 0, wxCOPY, FALSE);
971 
972    memDC.SelectObject(wxNullBitmap);
973 }
974