Audacity 3.2.0
EqualizationUI.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 EqualizationUI.cpp
6
7 Mitch Golden
8 Vaughan Johnson (Preview)
9 Martyn Shaw (FIR filters, response curve, graphic EQ)
10
11 Paul Licameli split from Equalization.cpp
12
13**********************************************************************/
14#include "EqualizationUI.h"
16#include "EqualizationPanel.h"
17#include "EffectEditor.h"
18#include <wx/button.h>
19#include <wx/choice.h>
20#include <wx/radiobut.h>
21#include <wx/sizer.h>
22#include <wx/checkbox.h>
23#include <wx/stattext.h>
24#include "ShuttleGui.h"
25#include "../widgets/RulerPanel.h"
26#include "../widgets/LinearUpdater.h"
27#include "../widgets/LogarithmicUpdater.h"
28#include "../widgets/IntFormat.h"
29#include "../widgets/LinearDBFormat.h"
30
31#if wxUSE_ACCESSIBILITY
32#include "WindowAccessible.h"
33#endif
34
35BEGIN_EVENT_TABLE(EqualizationUI, wxEvtHandler)
36 EVT_SIZE( EqualizationUI::OnSize )
38
40
41namespace
42{
43class EqualizationUIEditor : public EffectEditor, protected wxEvtHandler
44{
45public:
51 EqualizationUI& ui, EffectUIServices& services,
52 EffectSettingsAccess& access, wxWindow* pParent = nullptr)
53 : EffectEditor { services, access }
54 , mEqualizationUI { ui }
55 , mpParent { pParent }
56 {
57 if (mpParent)
58 mpParent->PushEventHandler(&ui);
59 }
62 {
63 Disconnect();
64 }
66 bool ValidateUI() override
67 {
68 bool result {};
69 mAccess.ModifySettings(
71 {
72 result = mEqualizationUI.ValidateUI(settings);
73 return nullptr;
74 });
75 return result;
76 }
77
78 void Disconnect() override
79 {
80 if (mpParent)
81 {
82 mpParent->PopEventHandler();
83 mpParent = nullptr;
84 }
85 }
86
87protected:
89 wxWindow* mpParent {};
90};
91} // namespace
92
94{
95 const auto &parameters = mCurvesList.mParameters;
96 const auto &curveName = parameters.mCurveName;
97 auto &logEnvelope = parameters.mLogEnvelope;
98 const auto &curves = mCurvesList.mCurves;
99
100 // If editing a macro, we don't want to be using the unnamed curve so
101 // we offer to save it.
102
103 if (mDisallowCustom && curveName == wxT("unnamed"))
104 {
105 // PRL: This is unreachable. mDisallowCustom is always false.
106
108 mName,
109 XO("To use this filter curve in a macro, please choose a new name for it.\nChoose the 'Save/Manage Curves...' button and rename the 'unnamed' curve, then use that one."),
110 XO("Filter Curve EQ needs a different name") );
111 return false;
112 }
113
114 EQCurveWriter{ curves }.SaveCurves();
115
116 parameters.SaveConfig(mManager);
117
118 return true;
119}
120
121std::unique_ptr<EffectEditor> EqualizationUI::PopulateOrExchange(
123 const EffectOutputs *)
124{
125 auto &parameters = mCurvesList.mParameters;
126 const auto &M = parameters.mM;
127 const auto &loFreq = parameters.mLoFreq;
128 const auto &hiFreq = parameters.mHiFreq;
129 const auto &curves = mCurvesList.mCurves;
130
131 auto &drawMode = parameters.mDrawMode;
132
133 S.SetBorder(0);
134
135 S.SetSizerProportion(1);
136 S.Prop(1).StartMultiColumn(1, wxEXPAND);
137 {
138 S.SetStretchyCol(0);
139 //S.SetStretchyRow(0); // The 5px Top border
140 S.SetStretchyRow(1); // The Graph
141 S.SetStretchyRow(2); // The EQ sliders
142 szrV = S.GetSizer();
143
144 // -------------------------------------------------------------------
145 // ROW 0: Top border
146 // -------------------------------------------------------------------
147 S.AddSpace(5);
148
149 // -------------------------------------------------------------------
150 // ROW 1: Equalization panel and sliders for vertical scale
151 // -------------------------------------------------------------------
152 S.SetSizerProportion(1);
153 S.Prop(1).StartMultiColumn(3, wxEXPAND);
154 {
155 S.SetStretchyCol(1);
156 S.SetStretchyRow(0);
157 szr1 = S.GetSizer();
158
159 S.StartVerticalLay(wxEXPAND, 1);
160 {
161 // Inserted into sizer later, but the EQ panel needs to point to it
163 S.GetParent(), wxID_ANY, wxHORIZONTAL,
164 wxSize{ 100, 100 }, // Ruler can't handle small sizes
165 RulerPanel::Range{ loFreq, hiFreq },
167 XO("Hz"),
169 .Log(true)
170 .Flip(true)
171 .LabelEdges(true)
172 .TicksAtExtremes(true)
173 .TickColour( { 0, 0, 0 } )
174 );
175
177 S.GetParent(), wxID_ANY, wxVERTICAL,
178 wxSize{ 100, 100 }, // Ruler can't handle small sizes
179 RulerPanel::Range{ 60.0, -120.0 },
181 XO("dB"),
183 .LabelEdges(true)
184 .TicksAtExtremes(true)
185 .TickColour( { 0, 0, 0 } )
186 );
187
188 S.Prop(0).AddSpace(0, 1);
189 S.Prop(1)
190 .Position(wxEXPAND)
191 .AddWindow(mdBRuler);
192 S.AddSpace(0, 1);
193 }
194 S.EndVerticalLay();
195
196 parameters.ChooseEnvelope().Flatten(0.);
197 parameters.ChooseEnvelope().SetTrackLen(1.0);
198 mPanel = safenew EqualizationPanel(S.GetParent(), wxID_ANY,
200 S.Prop(1)
201 .Position(wxEXPAND)
202 .MinSize( { wxDefaultCoord, wxDefaultCoord } )
203 .AddWindow(mPanel);
204
205 S.SetBorder(5);
206 S.StartVerticalLay();
207 {
208 S.AddVariableText(XO("+ dB"), false, wxCENTER);
210 .Name(XO("Max dB"))
211 .Style(wxSL_VERTICAL | wxSL_INVERSE)
212 .AddSlider( {}, 30, 60, 0);
213#if wxUSE_ACCESSIBILITY
214 mdBMaxSlider->SetAccessible(safenew SliderAx(mdBMaxSlider, XO("%d dB")));
215#endif
216 BindTo(*mdBMaxSlider, wxEVT_SLIDER,
218
220 .Name(XO("Min dB"))
221 .Style(wxSL_VERTICAL | wxSL_INVERSE)
222 .AddSlider( {}, -30, -10, -120);
223 S.AddVariableText(XO("- dB"), false, wxCENTER);
224#if wxUSE_ACCESSIBILITY
225 mdBMinSlider->SetAccessible(safenew SliderAx(mdBMinSlider, XO("%d dB")));
226#endif
227 BindTo(*mdBMinSlider, wxEVT_SLIDER,
229 }
230 S.EndVerticalLay();
231 S.SetBorder(0);
232
233 // -------------------------------------------------------------------
234 // Frequency ruler below graph
235 // -------------------------------------------------------------------
236
237 // Column 1 is empty
238 S.AddSpace(1, 1);
239
240 S.SetBorder(1);
241 S.Prop(1)
242 .Position(wxEXPAND | wxALIGN_LEFT | wxALIGN_TOP | wxLEFT)
243 .AddWindow(mFreqRuler);
244 S.SetBorder(0);
245
246 // Column 3 is empty
247 S.AddSpace(1, 1);
248 }
249 S.EndMultiColumn();
250
251 // -------------------------------------------------------------------
252 // ROW 2: Graphic EQ
253 // -------------------------------------------------------------------
254 S.SetSizerProportion(1);
255 S.StartHorizontalLay(wxEXPAND, 1);
256 {
257 szrG = S.GetSizer();
258
259 // Panel used to host the sliders since they will be positioned manually.
260 //mGraphicPanel = S.Prop(1)
261 //.Position(wxEXPAND)
262 //.Size( { -1, 150 } )
263 //.StartPanel();
264 S.AddSpace(15,0);
265 {
267 S.AddSpace(15,0);
268 } //S.EndPanel();
269 }
270 S.EndHorizontalLay();
271
272 // -------------------------------------------------------------------
273 // ROW 4: Various controls
274 // -------------------------------------------------------------------
275 S.SetSizerProportion(1);
276 S.Prop(1).StartMultiColumn(7, wxALIGN_CENTER_HORIZONTAL);
277 {
278 S.SetBorder(5);
279
280 S.AddSpace(5, 5);
281
282 if( mOptions == kEqLegacy )
283 {
284 S.StartHorizontalLay(wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
285 {
286 S.AddPrompt(XXO("&EQ Type:"));
287 }
288 S.EndHorizontalLay();
289
290 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 1);
291 {
292 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 1);
293 {
294 mDraw = S
295 .Name(XO("Draw Curves"))
296 .AddRadioButton(XXO("&Draw"));
297 BindTo(*mDraw, wxEVT_RADIOBUTTON,
299
300 mGraphic = S
301 .Name(XO("Graphic EQ"))
302 .AddRadioButtonToGroup(XXO("&Graphic"));
303 BindTo(*mGraphic, wxEVT_RADIOBUTTON,
305 }
306 S.EndHorizontalLay();
307 }
308 S.EndHorizontalLay();
309 }
310
311 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 0);
312 {
313 szrH = S.GetSizer();
314
315 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 1);
316 {
317 szrI = S.GetSizer();
318
320 .Name(XO("Interpolation type"))
321 .AddChoice( {},
324 0 );
325#if wxUSE_ACCESSIBILITY
326 // so that name can be set on a standard control
328#endif
329 BindTo(*mInterpChoice, wxEVT_CHOICE,
331 }
332 S.EndHorizontalLay();
333
334 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 1);
335 {
336 szrL = S.GetSizer();
337
338 mLinFreq = S
339 .Name(XO("Linear Frequency Scale"))
340 .AddCheckBox(XXO("Li&near Frequency Scale"), false);
341 BindTo(*mLinFreq, wxEVT_CHECKBOX,
343 }
344 S.EndHorizontalLay();
345 }
346 S.EndHorizontalLay();
347
348 // -------------------------------------------------------------------
349 // Filter length grouping
350 // -------------------------------------------------------------------
351
352 if( mOptions == kEqLegacy ){
353 S.StartHorizontalLay(wxEXPAND, 0);
354 {
355 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 0);
356 {
357 S.AddPrompt(XXO("Length of &Filter:"));
358 }
359 S.EndHorizontalLay();
360
361 S.StartHorizontalLay(wxEXPAND, 1);
362 {
363 mMSlider = S
364 .Name(XO("Length of Filter"))
365 .Style(wxSL_HORIZONTAL)
366 .AddSlider( {}, (M - 1) / 2, 4095, 10);
367 BindTo(*mMSlider, wxEVT_SLIDER,
369 }
370 S.EndHorizontalLay();
371
372 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 0);
373 {
374 wxString label;
375 label.Printf(wxT("%ld"), M);
376 mMText = S.Name( Verbatim( label ) )
377 // fix for bug 577 (NVDA/Narrator screen readers do not
378 // read static text in dialogs)
379 .AddVariableText( Verbatim( label ) );
380 }
381 S.EndHorizontalLay();
382 }
383 S.EndHorizontalLay();
384
385 S.AddSpace(1, 1);
386 }
387
388 S.AddSpace(5, 5);
389
390 if( mOptions == kEqLegacy ){
391 S.AddSpace(5, 5);
392 S.StartHorizontalLay(wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
393 {
394 S.AddPrompt(XXO("&Select Curve:"));
395 }
396 S.EndHorizontalLay();
397
398 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 1);
399 {
400 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 1);
401 {
402 mCurve = S
403 .Name(XO("Select Curve"))
404 .AddChoice( {},
405 [&curves]{
407 for (const auto &curve : curves)
408 names.push_back( Verbatim( curve.Name ) );
409 return names;
410 }()
411 );
412 BindTo(*mCurve, wxEVT_CHOICE,
414 }
415 S.EndHorizontalLay();
416 }
417 S.EndHorizontalLay();
418
419 const auto pButton = S
420 .AddButton(XXO("S&ave/Manage Curves..."));
421 BindTo(*pButton, wxEVT_BUTTON, &EqualizationUI::OnManage);
422 }
423
424 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 1);
425 {
426 auto pButton = S
427 .AddButton(XXO("Fla&tten"));
428 BindTo(*pButton, wxEVT_BUTTON, &EqualizationUI::OnClear);
429
430 pButton = S
431 .AddButton(XXO("&Invert"));
432 BindTo(*pButton, wxEVT_BUTTON, &EqualizationUI::OnInvert);
433
434 mGridOnOff = S
435 .Name(XO("Show grid lines"))
436 .AddCheckBox(XXO("Show g&rid lines"), false);
437 BindTo(*mGridOnOff, wxEVT_CHECKBOX,
439 }
440 S.EndHorizontalLay();
441
442 S.AddSpace(5, 5);
443 }
444 S.EndMultiColumn();
445 }
446 S.EndMultiColumn();
447
448 mUIParent->SetAutoLayout(false);
450 mUIParent->Layout();
451
453 drawMode = true;
455 drawMode = false;
456
457 // "show" settings for graphics mode before setting the size of the dialog
458 // as this needs more space than draw mode
459 szrV->Show(szrG,!drawMode); // eq sliders
460 szrH->Show(szrI,true); // interpolation choice
461 szrH->Show(szrL,false); // linear freq checkbox
462
464 mPanel->Show( false );
465 wxSize sz = szrV->GetMinSize();
466 sz += wxSize( 30, 0);
467 mUIParent->SetMinSize(sz);
468 }
469 else{
470 mPanel->Show( true );
471 szrV->Show(szr1, true);
472 // This sizing calculation is hacky.
473 // Rather than set the true minimum size we set a size we would
474 // like to have.
475 // This makes the default size of the dialog good, but has the
476 // downside that the user can't adjust the dialog smaller.
477 wxSize sz = szrV->GetMinSize();
478 sz += wxSize( 400, 100);
479 szrV->SetMinSize(sz);
480 }
482
483 return std::make_unique<EqualizationUIEditor>(*this, mUIServices, access, mUIParent);
484}
485
487{
488 auto &parameters = mCurvesList.mParameters;
489 const auto &lin = parameters.mLin;
490 const auto &drawGrid = parameters.mDrawGrid;
491 const auto &M = parameters.mM;
492 const auto &dBMin = parameters.mdBMin;
493 const auto &dBMax = parameters.mdBMax;
494 const auto &interp = parameters.mInterp;
495
496 auto &drawMode = parameters.mDrawMode;
497
498 // Set log or lin freq scale (affects interpolation as well)
499 mLinFreq->SetValue( lin );
500 wxCommandEvent dummyEvent;
501 OnLinFreq(dummyEvent); // causes a CalcFilter
502
503 mGridOnOff->SetValue( drawGrid ); // checks/unchecks the box on the interface
504
505 if( mMSlider )
506 mMSlider->SetValue((M - 1) / 2);
507
508 mdBMinSlider->SetValue((int)dBMin);
509 mdBMaxSlider->SetValue((int)dBMax);
510
511 // Reload the curve names
512 UpdateCurves();
513
514 // Set graphic interpolation mode
515 mInterpChoice->SetSelection(interp);
516
517 // Override draw mode, if we're not displaying the radio buttons.
519 drawMode = true;
521 drawMode = false;
522
523 if( mDraw )
524 mDraw->SetValue(drawMode);
525 szrV->Show(szr1,mOptions != kEqOptionGraphic); // Graph
526 szrV->Show(szrG,!drawMode); // eq sliders
527 szrH->Show(szrI,mOptions == kEqLegacy ); // interpolation choice
528 szrH->Show(szrL, drawMode); // linear freq checkbox
529 if( mGraphic)
530 mGraphic->SetValue(!drawMode);
531 mGridOnOff->Show( drawMode );
532
533 // Set Graphic (Fader) or Draw mode
534 if (!drawMode)
536
537 UpdateRuler();
538
539 mUIParent->Layout();
540 wxGetTopLevelParent(mUIParent)->Layout();
541
542 return true;
543}
544
546{
547 const auto &parameters = mCurvesList.mParameters;
548 const auto &dBMin = parameters.mdBMin;
549 const auto &dBMax = parameters.mdBMax;
550
551 // Refresh ruler when values have changed
552 int w1, w2, h;
553 mdBRuler->ruler.GetMaxSize(&w1, &h);
554 mdBRuler->ruler.SetRange(dBMax, dBMin);
555 mdBRuler->ruler.GetMaxSize(&w2, &h);
556 if( w1 != w2 ) // Reduces flicker
557 {
558 mdBRuler->SetSize(wxSize(w2,h));
559 mFreqRuler->Refresh(false);
560 }
561 mdBRuler->Refresh(false);
562
563 mPanel->Refresh(false);
564}
565
566//
567// Make the passed curve index the active one
568//
569void EqualizationUI::setCurve(int currentCurve)
570{
571 auto &parameters = mCurvesList.mParameters;
572 constexpr auto loFreqI = EqualizationFilter::loFreqI;
573
574 const auto &lin = parameters.mLin;
575 const auto &hiFreq = parameters.mHiFreq;
576 auto &curves = mCurvesList.mCurves;
577
578 // Set current choice
579 wxASSERT( currentCurve < (int) curves.size() );
580 mCurvesList.Select(currentCurve);
581
582 int numPoints = (int) curves[currentCurve].points.size();
583
584 auto &env = parameters.ChooseEnvelope();
585 env.Flatten(0.);
586 env.SetTrackLen(1.0);
587
588 // Handle special case of no points.
589 if (numPoints == 0) {
591 return;
592 }
593
594 double when, value;
595
596 // Handle special case 1 point.
597 if (numPoints == 1) {
598 // only one point, so ensure it is in range then return.
599 when = curves[currentCurve].points[0].Freq;
600 if (lin) {
601 when = when / hiFreq;
602 }
603 else { // log scale
604 // We don't go below loFreqI (20 Hz) in log view.
605 double loLog = log10((double)loFreqI);
606 double hiLog = log10(hiFreq);
607 double denom = hiLog - loLog;
608 when =
609 (log10(std::max<double>(loFreqI, when))
610 - loLog) / denom;
611 }
612 value = curves[currentCurve].points[0].dB;
613 env.Insert(std::min(1.0, std::max(0.0, when)), value);
615 return;
616 }
617
618 // We have at least two points, so ensure they are in frequency order.
619 std::sort(curves[currentCurve].points.begin(),
620 curves[currentCurve].points.end());
621
622 if (curves[currentCurve].points[0].Freq < 0) {
623 // Corrupt or invalid curve, so bail.
625 return;
626 }
627
628 if(lin) { // linear Hz scale
629 for(int pointCount = 0; pointCount < numPoints; pointCount++) {
630 when = curves[currentCurve].points[pointCount].Freq / hiFreq;
631 value = curves[currentCurve].points[pointCount].dB;
632 if(when <= 1) {
633 env.Insert(when, value);
634 if (when == 1)
635 break;
636 }
637 else {
638 // There are more points at higher freqs,
639 // so interpolate next one then stop.
640 when = 1.0;
641 double nextDB = curves[currentCurve].points[pointCount].dB;
642 if (pointCount > 0) {
643 double nextF = curves[currentCurve].points[pointCount].Freq;
644 double lastF = curves[currentCurve].points[pointCount-1].Freq;
645 double lastDB = curves[currentCurve].points[pointCount-1].dB;
646 value = lastDB +
647 ((nextDB - lastDB) *
648 ((hiFreq - lastF) / (nextF - lastF)));
649 }
650 else
651 value = nextDB;
652 env.Insert(when, value);
653 break;
654 }
655 }
656 }
657 else { // log Hz scale
658 double loLog = log10((double) loFreqI);
659 double hiLog = log10(hiFreq);
660 double denom = hiLog - loLog;
661 int firstAbove20Hz;
662
663 // log scale EQ starts at 20 Hz (threshold of hearing).
664 // so find the first point (if any) above 20 Hz.
665 for (firstAbove20Hz = 0; firstAbove20Hz < numPoints; firstAbove20Hz++) {
666 if (curves[currentCurve].points[firstAbove20Hz].Freq > loFreqI)
667 break;
668 }
669
670 if (firstAbove20Hz == numPoints) {
671 // All points below 20 Hz, so just use final point.
672 when = 0.0;
673 value = curves[currentCurve].points[numPoints-1].dB;
674 env.Insert(when, value);
676 return;
677 }
678
679 if (firstAbove20Hz > 0) {
680 // At least one point is before 20 Hz and there are more
681 // beyond 20 Hz, so interpolate the first
682 double prevF = curves[currentCurve].points[firstAbove20Hz-1].Freq;
683 prevF = log10(std::max(1.0, prevF)); // log zero is bad.
684 double prevDB = curves[currentCurve].points[firstAbove20Hz-1].dB;
685 double nextF = log10(curves[currentCurve].points[firstAbove20Hz].Freq);
686 double nextDB = curves[currentCurve].points[firstAbove20Hz].dB;
687 when = 0.0;
688 value = nextDB - ((nextDB - prevDB) * ((nextF - loLog) / (nextF - prevF)));
689 env.Insert(when, value);
690 }
691
692 // Now get the rest.
693 for(int pointCount = firstAbove20Hz; pointCount < numPoints; pointCount++)
694 {
695 double flog = log10(curves[currentCurve].points[pointCount].Freq);
696 wxASSERT(curves[currentCurve].points[pointCount].Freq >= loFreqI);
697
698 when = (flog - loLog)/denom;
699 value = curves[currentCurve].points[pointCount].dB;
700 if(when <= 1.0) {
701 env.Insert(when, value);
702 }
703 else {
704 // This looks weird when adjusting curve in Draw mode if
705 // there is a point off-screen.
706
707 /*
708 // we have a point beyond fs/2. Insert it so that env code can use it.
709 // but just this one, we have no use for the rest
710 env.SetTrackLen(when); // can't Insert if the envelope isn't long enough
711 env.Insert(when, value);
712 break;
713 */
714
715 // interpolate the final point instead
716 when = 1.0;
717 if (pointCount > 0) {
718 double lastDB = curves[currentCurve].points[pointCount-1].dB;
719 double logLastF =
720 log10(curves[currentCurve].points[pointCount-1].Freq);
721 value = lastDB +
722 ((value - lastDB) *
723 ((log10(hiFreq) - logLastF) / (flog - logLastF)));
724 }
725 env.Insert(when, value);
726 break;
727 }
728 }
729 }
731}
732
734{
735 const auto &curves = mCurvesList.mCurves;
736 setCurve((int) curves.size() - 1);
737}
738
739void EqualizationUI::setCurve(const wxString &curveName)
740{
741 const auto &curves = mCurvesList.mCurves;
742 unsigned i = 0;
743 for( i = 0; i < curves.size(); i++ )
744 if( curveName == curves[ i ].Name )
745 break;
746 if( i == curves.size())
747 {
749 XO("Requested curve not found, using 'unnamed'"),
750 XO("Curve not found"),
751 wxOK|wxICON_ERROR);
752 setCurve();
753 }
754 else
755 setCurve( i );
756}
757
759//
760// All EffectEqualization methods beyond this point interact with the UI, so
761// can't be called while the UI is not displayed.
762//
764
766{
767 auto &parameters = mCurvesList.mParameters;
768 auto &curveName = parameters.mCurveName;
769 const auto &curves = mCurvesList.mCurves;
770
771 // Reload the curve names
772 if( mCurve )
773 mCurve->Clear();
774 bool selectedCurveExists = false;
775 for (size_t i = 0, cnt = curves.size(); i < cnt; i++)
776 {
777 if (curveName == curves[ i ].Name)
778 selectedCurveExists = true;
779 if( mCurve )
780 mCurve->Append(curves[ i ].Name);
781 }
782 // In rare circumstances, curveName may not exist (bug 1891)
783 if (!selectedCurveExists)
784 curveName = curves[ (int)curves.size() - 1 ].Name;
785 if( mCurve )
786 mCurve->SetStringSelection(curveName);
787
788 // Allow the control to resize
789 if( mCurve )
790 mCurve->SetMinSize({-1, -1});
791
792 // Set initial curve
793 setCurve( curveName );
794}
795
797{
798 auto &parameters = mCurvesList.mParameters;
799 const auto &lin = parameters.mLin;
800 auto &linEnvelope = parameters.mLinEnvelope;
801 auto &logEnvelope = parameters.mLogEnvelope;
802 const auto &hiFreq = parameters.mHiFreq;
803
804 size_t numPoints = logEnvelope.GetNumberOfPoints();
805 Doubles when{ numPoints };
806 Doubles value{ numPoints };
807 double deltadB = 0.1;
808 double dx, dy, dx1, dy1, err;
809
810 logEnvelope.GetPoints( when.get(), value.get(), numPoints );
811
812 // set 'unnamed' as the selected curve
814
815 bool flag = true;
816 while (flag)
817 {
818 flag = false;
819 int numDeleted = 0;
820 logEnvelope.GetPoints( when.get(), value.get(), numPoints );
821 for (size_t j = 0; j + 2 < numPoints; j++)
822 {
823 dx = when[j+2+numDeleted] - when[j+numDeleted];
824 dy = value[j+2+numDeleted] - value[j+numDeleted];
825 dx1 = when[j+numDeleted+1] - when[j+numDeleted];
826 dy1 = dy * dx1 / dx;
827 err = fabs(value[j+numDeleted+1] - (value[j+numDeleted] + dy1));
828 if( err < deltadB )
829 { // within < deltadB dB?
830 logEnvelope.Delete(j+1);
831 numPoints--;
832 numDeleted++;
833 flag = true;
834 }
835 }
836 }
837
838 if(lin) // do not use IsLinear() here
839 {
842 mFreqRuler->ruler.SetRange(0, hiFreq);
843 }
844
845 szrV->Show(szrG,false);
846 szrH->Show(szrI,false);
847 szrH->Show(szrL,true);
848
849 mUIParent->Layout();
850 wxGetTopLevelParent(mUIParent)->Layout();
851 mCurvesList.ForceRecalc(); // it may have changed slightly due to the deletion of points
852}
853
855{
856 auto &parameters = mCurvesList.mParameters;
857 const auto &lin = parameters.mLin;
858 auto &linEnvelope = parameters.mLinEnvelope;
859 auto &logEnvelope = parameters.mLogEnvelope;
860 const auto &loFreq = parameters.mLoFreq;
861 const auto &hiFreq = parameters.mHiFreq;
862
863 auto &drawMode = parameters.mDrawMode;
864
865 if(lin) //going from lin to log freq scale - do not use IsLinear() here
866 { // add some extra points to the linear envelope for the graphic to follow
867 double step = pow(2., 1./12.); // twelve steps per octave
868 double when,value;
869 for(double freq=10.; freq<hiFreq; freq*=step)
870 {
871 when = freq/hiFreq;
872 value = linEnvelope.GetValue(when);
873 linEnvelope.Insert(when, value);
874 }
875
878 mFreqRuler->ruler.SetRange(loFreq, hiFreq);
879 }
880
881 mBands.ErrMin(); //move sliders to minimise error
882
883 szrV->Show(szrG,true); // eq sliders
884 szrH->Show(szrI,mOptions == kEqLegacy ); // interpolation choice
885 szrH->Show(szrL,false); // linear freq checkbox
886
887 mUIParent->Layout();
888 wxGetTopLevelParent(mUIParent)->Layout();
889 mUIParent->Layout();
890 wxGetTopLevelParent(mUIParent)->Layout();
891
892 mBands.GraphicEQ(logEnvelope);
893 drawMode = false;
894}
895
896void EqualizationUI::OnSize(wxSizeEvent & event)
897{
898 mUIParent->Layout();
899 event.Skip();
900}
901
902void EqualizationUI::OnInterp(wxCommandEvent & WXUNUSED(event))
903{
904 auto &parameters = mCurvesList.mParameters;
905 bool bIsGraphic = !parameters.mDrawMode;
906 if (bIsGraphic)
907 {
908 mBands.GraphicEQ(parameters.mLogEnvelope);
910 }
911 parameters.mInterp = mInterpChoice->GetSelection();
912}
913
914void EqualizationUI::OnDrawMode(wxCommandEvent & WXUNUSED(event))
915{
917 UpdateDraw();
918}
919
920void EqualizationUI::OnGraphicMode(wxCommandEvent & WXUNUSED(event))
921{
924}
925
926void EqualizationUI::OnSliderM(wxCommandEvent & WXUNUSED(event))
927{
928 auto &M = mCurvesList.mParameters.mM;
929
930 size_t m = 2 * mMSlider->GetValue() + 1;
931 // Must be odd
932 wxASSERT( (m & 1) == 1 );
933
934 if (m != M) {
935 M = m;
936 wxString tip;
937 tip.Printf(wxT("%d"), (int)M);
938 mMText->SetLabel(tip);
939 mMText->SetName(mMText->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
940 mMSlider->SetToolTip(tip);
941
943 }
944}
945
946void EqualizationUI::OnSliderDBMIN(wxCommandEvent & WXUNUSED(event))
947{
948 auto &dBMin = mCurvesList.mParameters.mdBMin;
949
950 float dB = mdBMinSlider->GetValue();
951 if (dB != dBMin) {
952 dBMin = dB;
953 wxString tip;
954 tip.Printf(_("%d dB"), (int)dBMin);
955 mdBMinSlider->SetToolTip(tip);
956 UpdateRuler();
957 }
958}
959
960void EqualizationUI::OnSliderDBMAX(wxCommandEvent & WXUNUSED(event))
961{
962 auto &dBMax = mCurvesList.mParameters.mdBMax;
963
964 float dB = mdBMaxSlider->GetValue();
965 if (dB != dBMax) {
966 dBMax = dB;
967 wxString tip;
968 tip.Printf(_("%d dB"), (int)dBMax);
969 mdBMaxSlider->SetToolTip(tip);
970 UpdateRuler();
971 }
972}
973
974//
975// New curve was selected
976//
977void EqualizationUI::OnCurve(wxCommandEvent & WXUNUSED(event))
978{
979 // Select NEW curve
980 wxASSERT( mCurve != NULL );
981 setCurve( mCurve->GetCurrentSelection() );
984}
985
986//
987// User wants to modify the list in some way
988//
989void EqualizationUI::OnManage(wxCommandEvent & WXUNUSED(event))
990{
991 auto &curves = mCurvesList.mCurves;
993 curves, mCurve->GetSelection());
994 if (d.ShowModal()) {
995 wxGetTopLevelParent(mUIParent)->Layout();
996 setCurve(d.GetItem());
997 }
998
999 // Reload the curve names
1000 UpdateCurves();
1001
1002 // Allow control to resize
1003 mUIParent->Layout();
1004}
1005
1006void EqualizationUI::OnClear(wxCommandEvent & WXUNUSED(event))
1007{
1008 mBands.Flatten();
1009}
1010
1011void EqualizationUI::OnInvert(wxCommandEvent & WXUNUSED(event))
1012{
1013 mBands.Invert();
1014}
1015
1016void EqualizationUI::OnGridOnOff(wxCommandEvent & WXUNUSED(event))
1017{
1019 mPanel->Refresh(false);
1020}
1021
1022void EqualizationUI::OnLinFreq(wxCommandEvent & WXUNUSED(event))
1023{
1024 auto &parameters = mCurvesList.mParameters;
1025 auto &lin = parameters.mLin;
1026 const auto &loFreq = parameters.mLoFreq;
1027 const auto &hiFreq = parameters.mHiFreq;
1028
1029 lin = mLinFreq->IsChecked();
1030 if(parameters.IsLinear()) //going from log to lin freq scale
1031 {
1033 mFreqRuler->ruler.SetRange(0, hiFreq);
1035 lin = true;
1036 }
1037 else //going from lin to log freq scale
1038 {
1040 mFreqRuler->ruler.SetRange(loFreq, hiFreq);
1042 lin = false;
1043 }
1044 mFreqRuler->Refresh(false);
1046}
1047
1048void EqualizationUI::OnIdle(wxIdleEvent &event)
1049{
1050 event.Skip();
1051 if (mCurve)
1052 mCurve->SetStringSelection(mCurvesList.mParameters.mCurveName);
1053}
wxT("CloseDown"))
END_EVENT_TABLE()
int min(int a, int b)
const int kEqLegacy
const int kEqOptionCurve
const int kEqOptionGraphic
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
#define _(s)
Definition: Internat.h:73
#define safenew
Definition: MemoryX.h:9
TranslatableStrings Msgids(const EnumValueSymbol strings[], size_t nStrings)
Convenience function often useful when adding choice controls.
TranslatableString label
Definition: TagsEditor.cpp:165
static TranslatableStrings names
Definition: TagsEditor.cpp:153
#define S(N)
Definition: ToChars.cpp:64
static Settings & settings()
Definition: TrackInfo.cpp:69
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
std::vector< TranslatableString > TranslatableStrings
static std::once_flag flag
Serializer of curves into XML files.
void SaveCurves(const wxString &fileName={})
Performs effect computation.
Hold values to send to effect output meters.
EqualizationCurvesDialog manages the available preset curves.
EqualizationPanel is used with EqualizationDialog and controls a graph for EffectEqualization....
void OnIdle(wxIdleEvent &event)
void OnSliderDBMAX(wxCommandEvent &event)
void OnDrawMode(wxCommandEvent &event)
void OnManage(wxCommandEvent &event)
EqualizationCurvesList & mCurvesList
void OnGraphicMode(wxCommandEvent &event)
wxRadioButton * mGraphic
wxWeakRef< EqualizationPanel > mPanel
wxCheckBox * mGridOnOff
void OnClear(wxCommandEvent &event)
wxChoice * mInterpChoice
EffectSettingsManager & mManager
wxStaticText * mMText
RulerPanel * mFreqRuler
void OnLinFreq(wxCommandEvent &event)
void OnInterp(wxCommandEvent &event)
EffectUIServices & mUIServices
void OnCurve(wxCommandEvent &event)
void BindTo(wxEvtHandler &src, const EventTag &eventType, void(Class::*pmf)(Event &))
const int mOptions
void OnSliderDBMIN(wxCommandEvent &event)
EqualizationBandSliders mBands
bool ValidateUI(EffectSettings &settings)
wxCheckBox * mLinFreq
void OnInvert(wxCommandEvent &event)
const wxWeakRef< wxWindow > & mUIParent
wxSlider * mdBMinSlider
wxSlider * mdBMaxSlider
void OnSliderM(wxCommandEvent &event)
std::unique_ptr< EffectEditor > PopulateOrExchange(ShuttleGui &S, EffectInstance &instance, EffectSettingsAccess &access, const EffectOutputs *pOutputs)
wxSlider * mMSlider
bool TransferDataToWindow(const EffectSettings &settings)
RulerPanel * mdBRuler
void OnSize(wxSizeEvent &event)
wxRadioButton * mDraw
TranslatableString mName
wxWeakRef< wxChoice > mCurve
void OnGridOnOff(wxCommandEvent &event)
static const IntFormat & Instance()
Definition: IntFormat.cpp:14
static const LinearDBFormat & Instance()
static const LinearUpdater & Instance()
static const LogarithmicUpdater & Instance()
void SetUpdater(const RulerUpdater *pUpdater)
Definition: Ruler.cpp:112
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
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:640
An alternative to using wxWindowAccessible, which in wxWidgets 3.1.1 contained GetParent() which was ...
void Disconnect() override
On the first call only, may disconnect from further event handling.
bool ValidateUI() override
Calls mServices.ValidateUI()
EqualizationUIEditor(EqualizationUI &ui, EffectUIServices &services, EffectSettingsAccess &access, wxWindow *pParent=nullptr)
int DoMessageBox(const TranslatableString &name, const TranslatableString &msg, const TranslatableString &titleStr, long style=wxOK|wxCENTRE)
Externalized state of a plug-in.
void AddBandSliders(ShuttleGui &S)
EqualizationFilter & mParameters
static constexpr int loFreqI
static const EnumValueSymbol kInterpStrings[nInterpolations]
Options & LabelEdges(bool l)
Definition: RulerPanel.h:41
Options & Log(bool l)
Definition: RulerPanel.h:35