Audacity 3.2.0
ScienFilter.cpp
Go to the documentation of this file.
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*******************************************************************//****************************************************************//*******************************************************************/
23#include "ScienFilter.h"
24#include "EffectEditor.h"
25#include "LoadEffects.h"
26
27#include <wx/setup.h> // for wxUSE_* macros
28
29#include <wx/brush.h>
30#include <wx/choice.h>
31#include <wx/dcclient.h>
32#include <wx/dcmemory.h>
33#include <wx/settings.h>
34#include <wx/slider.h>
35#include <wx/stattext.h>
36#include <wx/utils.h>
37#include <wx/valgen.h>
38
39#include "AColor.h"
40#include "AllThemeResources.h"
42#include "ShuttleGui.h"
43#include "Theme.h"
44#include "../widgets/valnum.h"
45#include "../widgets/RulerPanel.h"
46#include "../widgets/IntFormat.h"
47#include "../widgets/LinearDBFormat.h"
48#include "WindowAccessible.h"
49
50enum
51{
61};
62
63// true argument means don't automatically enable this effect
64namespace
65{
67}
68
69BEGIN_EVENT_TABLE(EffectScienFilter, wxEvtHandler)
71
81
82std::unique_ptr<EffectEditor> EffectScienFilter::PopulateOrExchange(
84 const EffectOutputs *)
85{
86 mUIParent = S.GetParent();
87 S.AddSpace(5);
88 S.SetSizerProportion(1);
89 S.StartMultiColumn(3, wxEXPAND);
90 {
91 S.SetStretchyCol(1);
92 S.SetStretchyRow(0);
93
94 // -------------------------------------------------------------------
95 // ROW 1: Freq response panel and sliders for vertical scale
96 // -------------------------------------------------------------------
97
98 S.StartVerticalLay();
99 {
100 mdBRuler = safenew RulerPanel(
101 S.GetParent(), wxID_ANY, wxVERTICAL,
102 wxSize{ 100, 100 }, // Ruler can't handle small sizes
103 RulerPanel::Range{ 30.0, -120.0 },
105 XO("dB"),
107 .LabelEdges(true)
108 );
109
110 S.SetBorder(1);
111 S.AddSpace(1, 1);
112 S.Prop(1)
113 .Position(wxALIGN_RIGHT | wxTOP)
114 .AddWindow(mdBRuler);
115 S.AddSpace(1, 1);
116 }
117 S.EndVerticalLay();
118
120 S.GetParent(), wxID_ANY,
121 this, mLoFreq, mNyquist
122 );
123
124 S.SetBorder(5);
125 S.Prop(1)
126 .Position(wxEXPAND | wxRIGHT)
127 .MinSize( { -1, -1 } )
128 .AddWindow(mPanel);
129
130 S.StartVerticalLay();
131 {
132 S.AddVariableText(XO("+ dB"), false, wxCENTER);
133 mdBMaxSlider = S.Id(ID_dBMax)
134 .Name(XO("Max dB"))
135 .Style(wxSL_VERTICAL | wxSL_INVERSE)
136 .AddSlider( {}, 10, 20, 0);
137#if wxUSE_ACCESSIBILITY
138 mdBMaxSlider->SetAccessible(safenew SliderAx(mdBMaxSlider, XO("%d dB")));
139#endif
140 mdBMinSlider = S.Id(ID_dBMin)
141 .Name(XO("Min dB"))
142 .Style(wxSL_VERTICAL | wxSL_INVERSE)
143 .AddSlider( {}, -10, -10, -120);
144#if wxUSE_ACCESSIBILITY
145 mdBMinSlider->SetAccessible(safenew SliderAx(mdBMinSlider, XO("%d dB")));
146#endif
147
148 S.AddVariableText(XO("- dB"), false, wxCENTER);
149 }
150 S.EndVerticalLay();
151
152 // -------------------------------------------------------------------
153 // ROW 2: Frequency ruler
154 // -------------------------------------------------------------------
155
156 S.AddSpace(1, 1);
157
158 mfreqRuler = safenew RulerPanel(
159 S.GetParent(), wxID_ANY, wxHORIZONTAL,
160 wxSize{ 100, 100 }, // Ruler can't handle small sizes
161 RulerPanel::Range{ mLoFreq, mNyquist },
163 {},
165 .Log(true)
166 .Flip(true)
167 .LabelEdges(true)
168 );
169
170 S.Prop(1)
171 .Position(wxEXPAND | wxALIGN_LEFT | wxRIGHT)
172 .AddWindow(mfreqRuler);
173
174 S.AddSpace(1, 1);
175
176 // -------------------------------------------------------------------
177 // ROW 3 and 4: Type, Order, Ripple, Subtype, Cutoff
178 // -------------------------------------------------------------------
179
180 S.AddSpace(1, 1);
181 S.SetSizerProportion(0);
182 S.StartMultiColumn(8, wxALIGN_CENTER);
183 {
184 wxASSERT(nTypes == WXSIZEOF(kTypeStrings));
185
186 mFilterTypeCtl = S.Id(ID_Type)
187 .Focus()
188 .Validator<wxGenericValidator>(&mFilterType)
189 .MinSize( { -1, -1 } )
190 .AddChoice(XXO("&Filter Type:"),
191 Msgids(kTypeStrings, nTypes)
192 );
193
194 mFilterOrderCtl = S.Id(ID_Order)
195 .Validator<wxGenericValidator>(&mOrderIndex)
196 .MinSize( { -1, -1 } )
197 /*i18n-hint: 'Order' means the complexity of the filter, and is a number between 1 and 10.*/
198 .AddChoice(XXO("O&rder:"),
199 []{
200 TranslatableStrings orders;
201 for (int i = 1; i <= 10; i++)
202 orders.emplace_back( Verbatim("%d").Format( i ) );
203 return orders;
204 }()
205 );
206 S.AddSpace(1, 1);
207
208 mRippleCtlP = S.AddVariableText( XO("&Passband Ripple:"),
209 false, wxALL | wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
210 mRippleCtl = S.Id(ID_Ripple)
211 .Name(XO("Passband Ripple (dB)"))
212 .Validator<FloatingPointValidator<float>>(
213 1, &mRipple, NumValidatorStyle::DEFAULT,
214 Passband.min, Passband.max)
215 .AddTextBox( {}, L"", 10);
216 mRippleCtlU = S.AddVariableText(XO("dB"),
217 false, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
218
219 mFilterSubTypeCtl = S.Id(ID_SubType)
220 .Validator<wxGenericValidator>(&mFilterSubtype)
221 .MinSize( { -1, -1 } )
222 .AddChoice(XXO("&Subtype:"),
223 Msgids(kSubTypeStrings, nSubTypes)
224 );
225
226 mCutoffCtl = S.Id(ID_Cutoff)
227 .Name(XO("Cutoff (Hz)"))
228 .Validator<FloatingPointValidator<float>>(
229 1, &mCutoff, NumValidatorStyle::DEFAULT,
230 Cutoff.min, mNyquist - 1)
231 .AddTextBox(XXO("C&utoff:"), L"", 10);
232 S.AddUnits(XO("Hz"));
233
234 mStopbandRippleCtlP =
235 S.AddVariableText(XO("Minimum S&topband Attenuation:"),
236 false, wxALL | wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
237 mStopbandRippleCtl = S.Id(ID_StopbandRipple)
238 .Name(XO("Minimum S&topband Attenuation (dB)"))
239 .Validator<FloatingPointValidator<float>>(
240 1, &mStopbandRipple, NumValidatorStyle::DEFAULT,
241 Stopband.min, Stopband.max)
242 .AddTextBox( {}, L"", 10);
243 mStopbandRippleCtlU =
244 S.AddVariableText(XO("dB"),
245 false, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
246 }
247 S.EndMultiColumn();
248 S.AddSpace(1, 1);
249 }
250 S.EndMultiColumn();
251
252 return nullptr;
253}
254
255//
256// Populate the window with relevant variables
257//
259{
260 mOrderIndex = mOrder - 1;
261
262 if (!mUIParent->TransferDataToWindow())
263 {
264 return false;
265 }
266
267 mdBMinSlider->SetValue((int) mdBMin);
268 mdBMin = 0.0; // force refresh in TransferGraphLimitsFromWindow()
269
270 mdBMaxSlider->SetValue((int) mdBMax);
271 mdBMax = 0.0; // force refresh in TransferGraphLimitsFromWindow()
272
274
276}
277
279{
280 if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
281 {
282 return false;
283 }
284
285 mOrder = mOrderIndex + 1;
286
287 CalcFilter();
288
289 return true;
290}
291
292// EffectScienFilter implementation
293
294//
295// Retrieve data from the window
296//
298{
299 // Read the sliders and send to the panel
300 wxString tip;
301
302 bool rr = false;
303 int dB = mdBMinSlider->GetValue();
304 if (dB != mdBMin) {
305 rr = true;
306 mdBMin = dB;
307 tip.Printf(_("%d dB"), (int)mdBMin);
308 mdBMinSlider->SetToolTip(tip);
309 }
310
311 dB = mdBMaxSlider->GetValue();
312 if (dB != mdBMax) {
313 rr = true;
314 mdBMax = dB;
315 tip.Printf(_("%d dB"),(int)mdBMax);
316 mdBMaxSlider->SetToolTip(tip);
317 }
318
319 if (rr) {
321 }
322
323 // Refresh ruler if values have changed
324 if (rr) {
325 int w1, w2, h;
326 mdBRuler->ruler.GetMaxSize(&w1, &h);
328 mdBRuler->ruler.GetMaxSize(&w2, &h);
329 if( w1 != w2 ) // Reduces flicker
330 {
331 mdBRuler->SetSize(wxSize(w2,h));
332 mUIParent->Layout();
333 mfreqRuler->Refresh(false);
334 }
335 mdBRuler->Refresh(false);
336 }
337
338 mPanel->Refresh(false);
339
340 return true;
341}
342
343void EffectScienFilter::OnOrder(wxCommandEvent & WXUNUSED(evt))
344{
345 mOrderIndex = mFilterOrderCtl->GetSelection();
346 mOrder = mOrderIndex + 1; // 0..n-1 -> 1..n
347 mPanel->Refresh(false);
348}
349
350void EffectScienFilter::OnFilterType(wxCommandEvent & WXUNUSED(evt))
351{
352 mFilterType = mFilterTypeCtl->GetSelection();
354 mPanel->Refresh(false);
355}
356
357void EffectScienFilter::OnFilterSubtype(wxCommandEvent & WXUNUSED(evt))
358{
359 mFilterSubtype = mFilterSubTypeCtl->GetSelection();
360 mPanel->Refresh(false);
361}
362
363void EffectScienFilter::OnCutoff(wxCommandEvent & WXUNUSED(evt))
364{
366 mUIParent, mUIParent->TransferDataFromWindow()))
367 {
368 return;
369 }
370
371 mPanel->Refresh(false);
372}
373
374void EffectScienFilter::OnRipple(wxCommandEvent & WXUNUSED(evt))
375{
377 mUIParent, mUIParent->TransferDataFromWindow()))
378 {
379 return;
380 }
381
382 mPanel->Refresh(false);
383}
384
385void EffectScienFilter::OnStopbandRipple(wxCommandEvent & WXUNUSED(evt))
386{
388 mUIParent, mUIParent->TransferDataFromWindow()))
389 {
390 return;
391 }
392
393 mPanel->Refresh(false);
394}
395
396void EffectScienFilter::OnSliderDBMIN(wxCommandEvent & WXUNUSED(evt))
397{
399}
400
401void EffectScienFilter::OnSliderDBMAX(wxCommandEvent & WXUNUSED(evt))
402{
404}
405
406void EffectScienFilter::OnSize(wxSizeEvent & evt)
407{
408 // On Windows the Passband and Stopband boxes do not refresh properly
409 // on a resize...no idea why.
410 mUIParent->Refresh();
411 evt.Skip();
412}
413
415{
416 bool ripple;
417 bool stop;
418
419 if (FilterType == kButterworth) // Butterworth
420 {
421 ripple = false;
422 stop = false;
423 }
424 else if (FilterType == kChebyshevTypeI) // Chebyshev Type1
425 {
426 ripple = true;
427 stop = false;
428 }
429 else // Chebyshev Type2
430 {
431 ripple = false;
432 stop = true;
433 }
434
435 mRippleCtlP->Enable(ripple);
436 mRippleCtl->Enable(ripple);
437 mRippleCtlU->Enable(ripple);
438 mStopbandRippleCtlP->Enable(stop);
439 mStopbandRippleCtl->Enable(stop);
440 mStopbandRippleCtlU->Enable(stop);
441}
442
443//----------------------------------------------------------------------------
444// EffectScienFilterPanel
445//----------------------------------------------------------------------------
446
447BEGIN_EVENT_TABLE(EffectScienFilterPanel, wxPanelWrapper)
451
453 wxWindow *parent, wxWindowID winid,
454 EffectScienFilter *effect, double lo, double hi)
455: wxPanelWrapper(parent, winid, wxDefaultPosition, wxSize(400, 200))
456{
457 mEffect = effect;
458 mParent = parent;
459
460 mBitmap = NULL;
461 mWidth = 0;
462 mHeight = 0;
463 mLoFreq = 0.0;
464 mHiFreq = 0.0;
465 mDbMin = 0.0;
466 mDbMax = 0.0;
467
468 SetFreqRange(lo, hi);
469}
470
472{
473}
474
475void EffectScienFilterPanel::SetFreqRange(double lo, double hi)
476{
477 mLoFreq = lo;
478 mHiFreq = hi;
479 Refresh(false);
480}
481
483{
484 mDbMin = min;
485 mDbMax = max;
486 Refresh(false);
487}
488
490{
491 return false;
492}
493
495{
496 return false;
497}
498
499void EffectScienFilterPanel::OnSize(wxSizeEvent & WXUNUSED(evt))
500{
501 Refresh(false);
502}
503
504void EffectScienFilterPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
505{
506 wxPaintDC dc(this);
507 int width, height;
508 GetSize(&width, &height);
509
510 if (!mBitmap || mWidth != width || mHeight != height)
511 {
512 mWidth = width;
513 mHeight = height;
514 mBitmap = std::make_unique<wxBitmap>(mWidth, mHeight,24);
515 }
516
517 wxBrush bkgndBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE));
518
519 wxMemoryDC memDC;
520 memDC.SelectObject(*mBitmap);
521
522 wxRect bkgndRect;
523 bkgndRect.x = 0;
524 bkgndRect.y = 0;
525 bkgndRect.width = mWidth;
526 bkgndRect.height = mHeight;
527 memDC.SetBrush(bkgndBrush);
528 memDC.SetPen(*wxTRANSPARENT_PEN);
529 memDC.DrawRectangle(bkgndRect);
530
531 bkgndRect.y = mHeight;
532 memDC.DrawRectangle(bkgndRect);
533
534 wxRect border;
535 border.x = 0;
536 border.y = 0;
537 border.width = mWidth;
538 border.height = mHeight;
539
540 memDC.SetBrush(*wxWHITE_BRUSH);
541 memDC.SetPen(*wxBLACK_PEN);
542 memDC.DrawRectangle(border);
543
544 mEnvRect = border;
545 mEnvRect.Deflate(2, 2);
546
547 // Pure blue x-axis line
548 memDC.SetPen(wxPen(theTheme.Colour(clrGraphLines), 1, wxPENSTYLE_SOLID));
549 int center = (int) (mEnvRect.height * mDbMax / (mDbMax - mDbMin) + 0.5);
550 AColor::Line(memDC,
551 mEnvRect.GetLeft(), mEnvRect.y + center,
552 mEnvRect.GetRight(), mEnvRect.y + center);
553
554 //Now draw the actual response that you will get.
555 //mFilterFunc has a linear scale, window has a log one so we have to fiddle about
556 memDC.SetPen(wxPen(theTheme.Colour(clrResponseLines), 3, wxPENSTYLE_SOLID));
557 double scale = (double) mEnvRect.height / (mDbMax - mDbMin); // pixels per dB
558 double yF; // gain at this freq
559
560 double loLog = log10(mLoFreq);
561 double step = log10(mHiFreq) - loLog;
562 step /= ((double) mEnvRect.width - 1.0);
563 double freq; // actual freq corresponding to x position
564 int x, y, xlast = 0, ylast = 0;
565 for (int i = 0; i < mEnvRect.width; i++)
566 {
567 x = mEnvRect.x + i;
568 freq = pow(10.0, loLog + i * step); //Hz
569 yF = mEffect->FilterMagnAtFreq (freq);
570 yF = LINEAR_TO_DB(yF);
571
572 if (yF < mDbMin)
573 {
574 yF = mDbMin;
575 }
576
577 yF = center-scale * yF;
578 if (yF > mEnvRect.height)
579 {
580 yF = (double) mEnvRect.height - 1.0;
581 }
582 if (yF < 0.0)
583 {
584 yF = 0.0;
585 }
586 y = (int) (yF + 0.5);
587
588 if (i != 0 && (y < mEnvRect.height - 1 || ylast < mEnvRect.y + mEnvRect.height - 1))
589 {
590 AColor::Line(memDC, xlast, ylast, x, mEnvRect.y + y);
591 }
592 xlast = x;
593 ylast = mEnvRect.y + y;
594 }
595
596 memDC.SetPen(*wxBLACK_PEN);
597 mEffect->mfreqRuler->ruler.DrawGrid(memDC, mEnvRect.height + 2, true, true, 0, 1);
598 mEffect->mdBRuler->ruler.DrawGrid(memDC, mEnvRect.width + 2, true, true, 1, 2);
599
600 dc.Blit(0, 0, mWidth, mHeight, &memDC, 0, 0, wxCOPY, FALSE);
601
602 memDC.SelectObject(wxNullBitmap);
603}
END_EVENT_TABLE()
int min(int a, int b)
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
@ nTypes
#define _(s)
Definition: Internat.h:73
#define safenew
Definition: MemoryX.h:10
#define LINEAR_TO_DB(x)
Definition: MemoryX.h:339
@ ID_Cutoff
Definition: ScienFilter.cpp:59
@ ID_SubType
Definition: ScienFilter.cpp:56
@ ID_dBMax
Definition: ScienFilter.cpp:53
@ ID_Ripple
Definition: ScienFilter.cpp:58
@ ID_StopbandRipple
Definition: ScienFilter.cpp:60
@ ID_Order
Definition: ScienFilter.cpp:57
@ ID_FilterPanel
Definition: ScienFilter.cpp:52
@ ID_Type
Definition: ScienFilter.cpp:55
@ ID_dBMin
Definition: ScienFilter.cpp:54
TranslatableStrings Msgids(const EnumValueSymbol strings[], size_t nStrings)
Convenience function often useful when adding choice controls.
THEME_API Theme theTheme
Definition: Theme.cpp:82
#define S(N)
Definition: ToChars.cpp:64
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
std::vector< TranslatableString > TranslatableStrings
static void Line(wxDC &dc, wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2)
Definition: AColor.cpp:194
static bool EnableApply(wxWindow *parent, bool enable=true)
Enable or disable the Apply button of the dialog that contains parent.
Performs effect computation.
Hold values to send to effect output meters.
void OnFilterSubtype(wxCommandEvent &evt)
EffectScienFilterPanel * mPanel
Definition: ScienFilter.h:63
void OnSliderDBMAX(wxCommandEvent &evt)
wxStaticText * mRippleCtlU
Definition: ScienFilter.h:69
void OnStopbandRipple(wxCommandEvent &evt)
void OnCutoff(wxCommandEvent &evt)
wxTextCtrl * mRippleCtl
Definition: ScienFilter.h:68
void EnableDisableRippleCtl(int FilterType)
wxChoice * mFilterSubTypeCtl
Definition: ScienFilter.h:78
bool TransferDataFromWindow(EffectSettings &settings) override
wxSlider * mdBMaxSlider
Definition: ScienFilter.h:65
wxChoice * mFilterTypeCtl
Definition: ScienFilter.h:77
void OnFilterType(wxCommandEvent &evt)
wxChoice * mFilterOrderCtl
Definition: ScienFilter.h:79
wxWeakRef< wxWindow > mUIParent
Definition: ScienFilter.h:61
wxStaticText * mStopbandRippleCtlP
Definition: ScienFilter.h:73
RulerPanel * mdBRuler
Definition: ScienFilter.h:81
wxTextCtrl * mStopbandRippleCtl
Definition: ScienFilter.h:74
bool TransferGraphLimitsFromWindow()
bool TransferDataToWindow(const EffectSettings &settings) override
void OnSliderDBMIN(wxCommandEvent &evt)
void OnOrder(wxCommandEvent &evt)
void OnRipple(wxCommandEvent &evt)
RulerPanel * mfreqRuler
Definition: ScienFilter.h:82
wxStaticText * mRippleCtlP
Definition: ScienFilter.h:67
wxStaticText * mStopbandRippleCtlU
Definition: ScienFilter.h:75
void OnSize(wxSizeEvent &evt)
wxSlider * mdBMinSlider
Definition: ScienFilter.h:64
EffectScienFilterPanel is used with EffectScienFilter and controls a graph for EffectScienFilter.
Definition: ScienFilter.h:88
void OnPaint(wxPaintEvent &evt)
void OnSize(wxSizeEvent &evt)
virtual ~EffectScienFilterPanel()
void SetDbRange(double min, double max)
bool AcceptsFocus() const
bool AcceptsFocusFromKeyboard() const
EffectScienFilter * mEffect
Definition: ScienFilter.h:108
std::unique_ptr< wxBitmap > mBitmap
Definition: ScienFilter.h:117
void SetFreqRange(double lo, double hi)
static const IntFormat & Instance()
Definition: IntFormat.cpp:14
static const LinearDBFormat & Instance()
void DrawGrid(wxDC &dc, int length, bool minor=true, bool major=true, int xOffset=0, int yOffset=0) const
Definition: Ruler.cpp:530
void GetMaxSize(wxCoord *width, wxCoord *height)
Definition: Ruler.cpp:612
void SetRange(double min, double max)
Definition: Ruler.cpp:152
RulerPanel class allows you to work with a Ruler like any other wxWindow.
Definition: RulerPanel.h:19
Ruler ruler
Definition: RulerPanel.h:79
std::pair< double, double > Range
Definition: RulerPanel.h:23
float FilterMagnAtFreq(float Freq)
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:640
wxColour & Colour(int iIndex)
TranslatableString & Format(Args &&...args) &
Capture variadic format arguments (by copy) when there is no plural.
A Validator is an object which checks whether a wxVariant satisfies a certain criterion....
Definition: Validators.h:54
BuiltinEffectsModule::Registration< EffectScienFilter > reg(false)
STL namespace.
Externalized state of a plug-in.
Options & LabelEdges(bool l)
Definition: RulerPanel.h:41
Options & Flip(bool f)
Definition: RulerPanel.h:38
Options & Log(bool l)
Definition: RulerPanel.h:35