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