Audacity 3.2.0
Equalization.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 EffectEqualization.cpp
6
7 Mitch Golden
8 Vaughan Johnson (Preview)
9 Martyn Shaw (FIR filters, response curve, graphic EQ)
10
11*******************************************************************//****************************************************************//****************************************************************//****************************************************************//****************************************************************//*******************************************************************/
53
54
55
56#include "Equalization.h"
57#include "LoadEffects.h"
58
59#include <math.h>
60#include <vector>
61
62#include <wx/setup.h> // for wxUSE_* macros
63
64#include <wx/bitmap.h>
65#include <wx/button.h>
66#include <wx/brush.h>
67#include <wx/button.h> // not really needed here
68#include <wx/dcclient.h>
69#include <wx/dcmemory.h>
70#include <wx/event.h>
71#include <wx/listctrl.h>
72#include <wx/log.h>
73#include <wx/image.h>
74#include <wx/intl.h>
75#include <wx/choice.h>
76#include <wx/radiobut.h>
77#include <wx/slider.h>
78#include <wx/stattext.h>
79#include <wx/string.h>
80#include <wx/textdlg.h>
81#include <wx/ffile.h>
82#include <wx/filefn.h>
83#include <wx/stdpaths.h>
84#include <wx/settings.h>
85#include <wx/sizer.h>
86#include <wx/checkbox.h>
87#include <wx/tooltip.h>
88#include <wx/utils.h>
89
90#include "AColor.h"
91#include "ConfigInterface.h"
92#include "../ShuttleAutomation.h"
93#include "../ShuttleGui.h"
95#include "FileNames.h"
96#include "Envelope.h"
97#include "../EnvelopeEditor.h"
98#include "FFT.h"
99#include "Prefs.h"
100#include "Project.h"
101#include "Theme.h"
102#include "../TrackArtist.h"
103#include "../WaveClip.h"
104#include "ViewInfo.h"
105#include "../WaveTrack.h"
106#include "../widgets/Ruler.h"
107#include "../widgets/AudacityTextEntryDialog.h"
108#include "XMLFileReader.h"
109#include "AllThemeResources.h"
110#include "float_cast.h"
111
112#if wxUSE_ACCESSIBILITY
113#include "../widgets/WindowAccessible.h"
114#endif
115
116#include "../widgets/FileDialog/FileDialog.h"
117
118#ifdef EXPERIMENTAL_EQ_SSE_THREADED
119#include "Equalization48x.h"
120#endif
121
122
123enum
124{
125 ID_Length = 10000,
139#ifdef EXPERIMENTAL_EQ_SSE_THREADED
140 ID_DefaultMath,
141 ID_SSE,
142 ID_SSEThreaded,
143 ID_AVX,
144 ID_AVXThreaded,
145 ID_Bench,
146#endif
147 ID_Slider, // needs to come last
148};
149
150// Increment whenever EQCurves.xml is updated
151#define EQCURVES_VERSION 1
152#define EQCURVES_REVISION 0
153#define UPDATE_ALL 0 // 0 = merge NEW presets only, 1 = Update all factory presets.
154
156{
157 // These are acceptable dual purpose internal/visible names
158
159 /* i18n-hint: Technical term for a kind of curve.*/
160 { XO("B-spline") },
161 { XO("Cosine") },
162 { XO("Cubic") }
163};
164
165static const double kThirdOct[] =
166{
167 20., 25., 31., 40., 50., 63., 80., 100., 125., 160., 200.,
168 250., 315., 400., 500., 630., 800., 1000., 1250., 1600., 2000.,
169 2500., 3150., 4000., 5000., 6300., 8000., 10000., 12500., 16000., 20000.,
170};
171
173{
176 // CurveName,
177 InterpLin,
178 // Pretty sure the interpolation name shouldn't have been interpreted when
179 // specified in chains, but must keep it that way for compatibility.
181 > parameters {
183 bool updating){
184 if (updating) {
185 if (effect.mInterp >= nInterpolations)
186 effect.mInterp -= nInterpolations;
187 effect.mEnvelope =
188 (effect.mLin ? effect.mLinEnvelope : effect.mLogEnvelope).get();
189 }
190 return true;
191 }
192 };
193 return parameters;
194}
195
197// EffectEqualization
198//----------------------------------------------------------------------------
199
201{ XO("Equalization") };
202
203// namespace{ BuiltinEffectsModule::Registration< EffectEqualization > reg; }
204
205// "Filter Curve EQ" in the user-facing string, but preserve the old
206// internal string
208{ wxT("Filter Curve"), XO("Filter Curve EQ") };
209
211
213{ wxT("Graphic EQ"), XO("Graphic EQ") };
214
216
217BEGIN_EVENT_TABLE(EffectEqualization, wxEvtHandler)
219
225 wxEVT_COMMAND_SLIDER_UPDATED,
226 EffectEqualization::OnSlider)
227 EVT_CHOICE( ID_Interp, EffectEqualization::OnInterp )
228
229 EVT_CHOICE( ID_Curve, EffectEqualization::OnCurve )
233
234 EVT_RADIOBUTTON(ID_Draw, EffectEqualization::OnDrawMode)
235 EVT_RADIOBUTTON(ID_Graphic, EffectEqualization::OnGraphicMode)
236 EVT_CHECKBOX(ID_Linear, EffectEqualization::OnLinFreq)
237 EVT_CHECKBOX(ID_Grid, EffectEqualization::OnGridOnOff)
238
239#ifdef EXPERIMENTAL_EQ_SSE_THREADED
240 EVT_RADIOBUTTON(ID_DefaultMath, EffectEqualization::OnProcessingRadio)
241 EVT_RADIOBUTTON(ID_SSE, EffectEqualization::OnProcessingRadio)
242 EVT_RADIOBUTTON(ID_SSEThreaded, EffectEqualization::OnProcessingRadio)
243 EVT_RADIOBUTTON(ID_AVX, EffectEqualization::OnProcessingRadio)
244 EVT_RADIOBUTTON(ID_AVXThreaded, EffectEqualization::OnProcessingRadio)
245 EVT_BUTTON(ID_Bench, EffectEqualization::OnBench)
246#endif
248
250 : mFFTBuffer{ windowSize }
251 , mFilterFuncR{ windowSize }
252 , mFilterFuncI{ windowSize }
253{
254 Parameters().Reset(*this);
255
256 mOptions = Options;
257 mGraphic = NULL;
258 mDraw = NULL;
259 mCurve = NULL;
260 mPanel = NULL;
261 mMSlider = NULL;
262
263 hFFT = GetFFT(windowSize);
264
265 SetLinearEffectFlag(true);
266
267 mCurveName = CurveName.def;
268
269 GetConfig(GetDefinition(), PluginSettings::Private,
270 CurrentSettingsGroup(), wxT("dBMin"), mdBMin, dBMin.def);
271 GetConfig(GetDefinition(), PluginSettings::Private,
272 CurrentSettingsGroup(), wxT("dBMax"), mdBMax, dBMax.def);
273 GetConfig(GetDefinition(), PluginSettings::Private,
274 CurrentSettingsGroup(), wxT("DrawMode"), mDrawMode, DrawMode.def);
275 GetConfig(GetDefinition(), PluginSettings::Private,
276 CurrentSettingsGroup(), wxT("DrawGrid"), mDrawGrid, DrawGrid.def);
277
278 mLogEnvelope = std::make_unique<Envelope>
279 (false,
280 dBMin.min, dBMax.max, // MB: this is the highest possible range
281 0.0);
282 mLogEnvelope->SetTrackLen(1.0);
283
284 mLinEnvelope = std::make_unique<Envelope>
285 (false,
286 dBMin.min, dBMax.max, // MB: this is the highest possible range
287 0.0);
288 mLinEnvelope->SetTrackLen(1.0);
289
290 mEnvelope = (mLin ? mLinEnvelope : mLogEnvelope).get();
291
292 mWindowSize = windowSize;
293
294 mDirty = false;
295 mDisallowCustom = false;
296
297 // Load the EQ curves
298 LoadCurves();
299
300 // Note: initial curve is set in TransferDataToWindow
301
302 mBandsInUse = NUMBER_OF_BANDS;
303 //double loLog = log10(mLoFreq);
304 //double stepLog = (log10(mHiFreq) - loLog)/((double)NUM_PTS-1.);
305 for(int i=0; i<NUM_PTS-1; i++)
306 mWhens[i] = (double)i/(NUM_PTS-1.);
307 mWhens[NUM_PTS-1] = 1.;
308 mWhenSliders[NUMBER_OF_BANDS] = 1.;
309 mEQVals[NUMBER_OF_BANDS] = 0.;
310
311#ifdef EXPERIMENTAL_EQ_SSE_THREADED
312 bool useSSE;
313 GetPrivateConfig(CurrentSettingsGroup(), wxT("/SSE/GUI"), useSSE, false);
314 if(useSSE && !mEffectEqualization48x)
315 mEffectEqualization48x = std::make_unique<EffectEqualization48x>();
316 else if(!useSSE)
317 mEffectEqualization48x.reset();
318 mBench=false;
319#endif
320
321 // We expect these Hi and Lo frequencies to be overridden by Init().
322 // Don't use inputTracks(). See bug 2321.
323#if 0
324 auto trackList = inputTracks();
325 const auto t = trackList
326 ? *trackList->Any< const WaveTrack >().first
327 : nullptr
328 ;
329 mHiFreq =
330 (t
331 ? t->GetRate()
332 : mProjectRate)
333 / 2.0;
334#endif
335 mHiFreq = mProjectRate / 2.0;
336 mLoFreq = loFreqI;
337}
338
339
341{
342}
343
344// ComponentInterface implementation
345
347{
350 if( mOptions == kEqOptionCurve )
353}
354
356{
357 return XO("Adjusts the volume levels of particular frequencies");
358}
359
361{
362 // Bug 2509: Must use _ and not space in names.
364 return L"Graphic_EQ";
365 if( mOptions == kEqOptionCurve )
366 return L"Filter_Curve_EQ";
367 return L"Equalization";
368}
369
370// EffectDefinitionInterface implementation
371
373{
374 return EffectTypeProcess;
375}
376
378 ConstSettingsVisitor &visitor, const EffectSettings &settings) const
379{
381
382 // Curve point parameters -- how many isn't known statically
383 if( dynamic_cast<ShuttleGetAutomation*>(&visitor)) {
384 int numPoints = mCurves[ 0 ].points.size();
385 int point;
386 for( point = 0; point < numPoints; point++ )
387 {
388 const wxString nameFreq = wxString::Format("f%i",point);
389 const wxString nameVal = wxString::Format("v%i",point);
390 visitor.Define( mCurves[ 0 ].points[ point ].Freq, nameFreq,
391 0.0, 0.0, 0.0, 0.0 );
392 visitor.Define( mCurves[ 0 ].points[ point ].dB, nameVal,
393 0.0, 0.0, 0.0, 0.0 );
394 }
395 }
396 return true;
397}
398
401{
403
404 // Curve point parameters -- how many isn't known statically
405 {
406 mCurves[0].points.clear();
407
408 for (int i = 0; i < 200; i++)
409 {
410 const wxString nameFreq = wxString::Format("f%i",i);
411 const wxString nameVal = wxString::Format("v%i",i);
412 double f = -1000.0;
413 double d = 0.0;
414 visitor.Define( f, nameFreq, 0.0, -10000.0, 1000000.0, 0.0 );
415 visitor.Define( d, nameVal, 0.0, -10000.0, 10000.0, 0.0 );
416 if( f <= 0.0 )
417 break;
418 mCurves[0].points.push_back( EQPoint( f,d ));
419 }
420 setCurve( 0 );
421 }
422 return true;
423}
424
426{
427 // To do: externalize state so const_cast isn't needed
428 return const_cast<EffectEqualization&>(*this).DoLoadFactoryDefaults(settings);
429}
430
432{
433 mdBMin = dBMin.def;
434 mdBMax = dBMax.def;
437
439 mDrawMode = true;
441 mDrawMode = false;
442
444}
445
446// Constants determining who the prests are for.
447const bool kCURVE = false;
448const bool kBOTH = true;
449
450static const struct
451{
452 const bool bForBoth; // more extended set is used for Filter EQ
453 // See Bug 2254 for rationale.
455 const wxChar *values;
456}
458{
459 { kCURVE, XO("100Hz Rumble"), wxT("f0=\"20.0\" v0=\"-80.0\" f1=\"49.237316986327\" v1=\"-33.107692718506\" f2=\"54.196034330446\" v2=\"-29.553844451904\" f3=\"88.033573501041\" v3=\"-6.923076629639\" f4=\"95.871851182279\" v4=\"-4.523078918457\" f5=\"108.957037410504\" v5=\"-1.938461303711\" f6=\"123.828171198057\" v6=\"-0.73846244812\" f7=\"149.228077614658\" v7=\"-0.092308044434\"") },
460 { kCURVE, XO("AM Radio"), wxT("f0=\"20.0\" v0=\"-63.67\" f1=\"31.0\" v1=\"-33.219\" f2=\"50.0\" v2=\"-3.01\" f3=\"63.0\" v3=\"-0.106\" f4=\"100.0\" v4=\"0.0\" f5=\"2500.0\" v5=\"0.0\" f6=\"4000.0\" v6=\"-0.614\" f7=\"5000.0\" v7=\"-8.059\" f8=\"8000.0\" v8=\"-39.981\" f9=\"20000.0\" v9=\"-103.651\" f10=\"48000.0\" v10=\"-164.485\"") },
461 { kBOTH, XO("Bass Boost"), wxT("f0=\"100.0\" v0=\"9.0\" f1=\"500.0\" v1=\"0.0\"") },
462 { kBOTH, XO("Bass Cut"), wxT("f0=\"150.0\" v0=\"-50.0\" f1=\"300.0\" v1=\"0.0\"") },
463 { kCURVE, XO("Low rolloff for speech"), wxT("f0=\"50.0\" v0=\"-120.0\" f1=\"60.0\" v1=\"-50.0\" f2=\"65.0\" v2=\"-24.0\" f3=\"70.0\" v3=\"-12.0\" f4=\"80.0\" v4=\"-4.0\" f5=\"90.0\" v5=\"-1.0\" f6=\"100.0\" v6=\"0.0\"") },
464 { kBOTH, XO("RIAA"), wxT("f0=\"20.0\" v0=\"19.274\" f1=\"25.0\" v1=\"18.954\" f2=\"31.0\" v2=\"18.516\" f3=\"40.0\" v3=\"17.792\" f4=\"50.0\" v4=\"16.946\" f5=\"63.0\" v5=\"15.852\" f6=\"80.0\" v6=\"14.506\" f7=\"100.0\" v7=\"13.088\" f8=\"125.0\" v8=\"11.563\" f9=\"160.0\" v9=\"9.809\" f10=\"200.0\" v10=\"8.219\" f11=\"250.0\" v11=\"6.677\" f12=\"315.0\" v12=\"5.179\" f13=\"400.0\" v13=\"3.784\" f14=\"500.0\" v14=\"2.648\" f15=\"630.0\" v15=\"1.642\" f16=\"800.0\" v16=\"0.751\" f17=\"1000.0\" v17=\"0.0\" f18=\"1250.0\" v18=\"-0.744\" f19=\"1600.0\" v19=\"-1.643\" f20=\"2000.0\" v20=\"-2.589\" f21=\"2500.0\" v21=\"-3.7\" f22=\"3150.0\" v22=\"-5.038\" f23=\"4000.0\" v23=\"-6.605\" f24=\"5000.0\" v24=\"-8.21\" f25=\"6300.0\" v25=\"-9.98\" f26=\"8000.0\" v26=\"-11.894\" f27=\"10000.0\" v27=\"-13.734\" f28=\"12500.0\" v28=\"-15.609\" f29=\"16000.0\" v29=\"-17.708\" f30=\"20000.0\" v30=\"-19.62\" f31=\"25000.0\" v31=\"-21.542\" f32=\"48000.0\" v32=\"-27.187\"") },
465 { kCURVE, XO("Telephone"), wxT("f0=\"20.0\" v0=\"-94.087\" f1=\"200.0\" v1=\"-14.254\" f2=\"250.0\" v2=\"-7.243\" f3=\"315.0\" v3=\"-2.245\" f4=\"400.0\" v4=\"-0.414\" f5=\"500.0\" v5=\"0.0\" f6=\"2500.0\" v6=\"0.0\" f7=\"3150.0\" v7=\"-0.874\" f8=\"4000.0\" v8=\"-3.992\" f9=\"5000.0\" v9=\"-9.993\" f10=\"48000.0\" v10=\"-88.117\"") },
466 { kBOTH, XO("Treble Boost"), wxT("f0=\"4000.0\" v0=\"0.0\" f1=\"5000.0\" v1=\"9.0\"") },
467 { kBOTH, XO("Treble Cut"), wxT("f0=\"6000.0\" v0=\"0.0\" f1=\"10000.0\" v1=\"-110.0\"") },
468 { kCURVE, XO("Walkie-talkie"), wxT("f0=\"100.0\" v0=\"-120.0\" f1=\"101.0\" v1=\"0.0\" f2=\"2000.0\" v2=\"0.0\" f3=\"2001.0\" v3=\"-120.0\"") },
470
471
472
473
475{
477
478 for (size_t i = 0; i < WXSIZEOF(FactoryPresets); i++)
479 {
480 if ((mOptions == kEqOptionGraphic) && (FactoryPresets[i].bForBoth == false))
481 continue;
482 names.push_back(FactoryPresets[i].name.Translation());
483 }
484
485 return names;
486}
487
489{
490 int index = -1;
491 for (size_t i = 0; i < WXSIZEOF(FactoryPresets); i++)
492 {
493 if ((mOptions == kEqOptionGraphic) && (FactoryPresets[i].bForBoth == false))
494 continue;
495 if (id-- == 0) {
496 index = i;
497 break;
498 }
499 }
500 if (index < 0)
501 return false;
502
503 // mParams =
504 wxString params = FactoryPresets[index].values;
505
508 S.SetForWriting( &eap );
509 // To do: externalize state so const_cast isn't needed
510 const_cast<EffectEqualization*>(this)->VisitSettings(S, settings);
511 return true;
512}
513
514
515
516// EffectUIClientInterface implementation
517
519{
520 // If editing a macro, we don't want to be using the unnamed curve so
521 // we offer to save it.
522
523 if (mDisallowCustom && mCurveName == wxT("unnamed"))
524 {
525 // PRL: This is unreachable. mDisallowCustom is always false.
526
528 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."),
529 wxOK | wxCENTRE,
530 XO("Filter Curve EQ needs a different name") );
531 return false;
532 }
533
534 // Update unnamed curve (so it's there for next time)
535 //(done in a hurry, may not be the neatest -MJS)
536 if (mDirty && !mDrawMode)
537 {
538 size_t numPoints = mLogEnvelope->GetNumberOfPoints();
539 Doubles when{ numPoints };
540 Doubles value{ numPoints };
541 mLogEnvelope->GetPoints(when.get(), value.get(), numPoints);
542 for (size_t i = 0, j = 0; j + 2 < numPoints; i++, j++)
543 {
544 if ((value[i] < value[i + 1] + .05) && (value[i] > value[i + 1] - .05) &&
545 (value[i + 1] < value[i + 2] + .05) && (value[i + 1] > value[i + 2] - .05))
546 { // within < 0.05 dB?
547 mLogEnvelope->Delete(j + 1);
548 numPoints--;
549 j--;
550 }
551 }
552 Select((int) mCurves.size() - 1);
553 }
554 SaveCurves();
555
556 // TODO: just visit these effect settings the default way
558 CurrentSettingsGroup(), wxT("dBMin"), mdBMin);
560 CurrentSettingsGroup(), wxT("dBMax"), mdBMax);
562 CurrentSettingsGroup(), wxT("DrawMode"), mDrawMode);
564 CurrentSettingsGroup(), wxT("DrawGrid"), mDrawGrid);
565
566 return true;
567}
568
569// Effect implementation
570
572{
573 wxString base = wxT("/Effects/Equalization/");
575 base = wxT("/Effects/GraphicEq/");
576 else if( mOptions == kEqOptionCurve )
577 base = wxT("/Effects/FilterCurve/");
578 return base;
579}
580
582{
583 int selcount = 0;
584 double rate = 0.0;
585
586 auto trackRange =
588 if (trackRange) {
589 rate = (*(trackRange.first++)) -> GetRate();
590 ++selcount;
591
592 for (auto track : trackRange) {
593 if (track->GetRate() != rate) {
595 XO(
596"To apply Equalization, all selected tracks must have the same sample rate.") );
597 return(false);
598 }
599 ++selcount;
600 }
601 }
602
603 mHiFreq = rate / 2.0;
604 // Unlikely, but better than crashing.
605 if (mHiFreq <= loFreqI) {
607 XO("Track sample rate is too low for this effect."),
608 wxOK | wxCENTRE,
609 XO("Effect Unavailable") );
610 return(false);
611 }
612
614
615 mBandsInUse = 0;
616 while (kThirdOct[mBandsInUse] <= mHiFreq) {
617 mBandsInUse++;
619 break;
620 }
621
623
625
626 CalcFilter();
627
628 return(true);
629}
630
632{
633#ifdef EXPERIMENTAL_EQ_SSE_THREADED
634 if(mEffectEqualization48x) {
635 if(mBench) {
636 mBench=false;
637 return mEffectEqualization48x->Benchmark(this);
638 }
639 else
640 return mEffectEqualization48x->Process(this);
641 }
642#endif
643 this->CopyInputTracks(); // Set up mOutputTracks.
644 CalcFilter();
645 bool bGoodResult = true;
646
647 int count = 0;
648 for( auto track : mOutputTracks->Selected< WaveTrack >() ) {
649 double trackStart = track->GetStartTime();
650 double trackEnd = track->GetEndTime();
651 double t0 = mT0 < trackStart? trackStart: mT0;
652 double t1 = mT1 > trackEnd? trackEnd: mT1;
653
654 if (t1 > t0) {
655 auto start = track->TimeToLongSamples(t0);
656 auto end = track->TimeToLongSamples(t1);
657 auto len = end - start;
658
659 if (!ProcessOne(count, track, start, len))
660 {
661 bGoodResult = false;
662 break;
663 }
664 }
665
666 count++;
667 }
668
669 this->ReplaceProcessedTracks(bGoodResult);
670 return bGoodResult;
671}
672
674{
675 mCurve = NULL;
676 mPanel = NULL;
677
678 return Effect::CloseUI();
679}
680
681std::unique_ptr<EffectUIValidator> EffectEqualization::PopulateOrExchange(
683{
684 S.SetBorder(0);
685
686 S.SetSizerProportion(1);
687 S.Prop(1).StartMultiColumn(1, wxEXPAND);
688 {
689 S.SetStretchyCol(0);
690 //S.SetStretchyRow(0); // The 5px Top border
691 S.SetStretchyRow(1); // The Graph
692 S.SetStretchyRow(2); // The EQ sliders
693 szrV = S.GetSizer();
694
695 // -------------------------------------------------------------------
696 // ROW 0: Top border
697 // -------------------------------------------------------------------
698 S.AddSpace(5);
699
700 // -------------------------------------------------------------------
701 // ROW 1: Equalization panel and sliders for vertical scale
702 // -------------------------------------------------------------------
703 S.SetSizerProportion(1);
704 S.Prop(1).StartMultiColumn(3, wxEXPAND);
705 {
706 S.SetStretchyCol(1);
707 S.SetStretchyRow(0);
708 szr1 = S.GetSizer();
709
710 S.StartVerticalLay(wxEXPAND, 1);
711 {
713 S.GetParent(), wxID_ANY, wxVERTICAL,
714 wxSize{ 100, 100 }, // Ruler can't handle small sizes
715 RulerPanel::Range{ 60.0, -120.0 },
717 XO("dB"),
719 .LabelEdges(true)
720 .TicksAtExtremes(true)
721 .TickColour( { 0, 0, 0 } )
722 );
723
724 S.Prop(0).AddSpace(0, 1);
725 S.Prop(1)
726 .Position(wxEXPAND)
727 .AddWindow(mdBRuler);
728 S.AddSpace(0, 1);
729 }
730 S.EndVerticalLay();
731
732 mPanel = safenew EqualizationPanel(S.GetParent(), wxID_ANY, this);
733 S.Prop(1)
734 .Position(wxEXPAND)
735 .MinSize( { wxDefaultCoord, wxDefaultCoord } )
736 .AddWindow(mPanel);
737
738 S.SetBorder(5);
739 S.StartVerticalLay();
740 {
741 S.AddVariableText(XO("+ dB"), false, wxCENTER);
743 .Name(XO("Max dB"))
744 .Style(wxSL_VERTICAL | wxSL_INVERSE)
745 .AddSlider( {}, 30, 60, 0);
746#if wxUSE_ACCESSIBILITY
747 mdBMaxSlider->SetAccessible(safenew SliderAx(mdBMaxSlider, XO("%d dB")));
748#endif
749
751 .Name(XO("Min dB"))
752 .Style(wxSL_VERTICAL | wxSL_INVERSE)
753 .AddSlider( {}, -30, -10, -120);
754 S.AddVariableText(XO("- dB"), false, wxCENTER);
755#if wxUSE_ACCESSIBILITY
756 mdBMinSlider->SetAccessible(safenew SliderAx(mdBMinSlider, XO("%d dB")));
757#endif
758 }
759 S.EndVerticalLay();
760 S.SetBorder(0);
761
762 // -------------------------------------------------------------------
763 // Frequency ruler below graph
764 // -------------------------------------------------------------------
765
766 // Column 1 is empty
767 S.AddSpace(1, 1);
768
770 S.GetParent(), wxID_ANY, wxHORIZONTAL,
771 wxSize{ 100, 100 }, // Ruler can't handle small sizes
772 RulerPanel::Range{ mLoFreq, mHiFreq },
774 XO("Hz"),
776 .Log(true)
777 .Flip(true)
778 .LabelEdges(true)
779 .TicksAtExtremes(true)
780 .TickColour( { 0, 0, 0 } )
781 );
782
783 S.SetBorder(1);
784 S.Prop(1)
785 .Position(wxEXPAND | wxALIGN_LEFT | wxALIGN_TOP | wxLEFT)
786 .AddWindow(mFreqRuler);
787 S.SetBorder(0);
788
789 // Column 3 is empty
790 S.AddSpace(1, 1);
791 }
792 S.EndMultiColumn();
793
794 // -------------------------------------------------------------------
795 // ROW 2: Graphic EQ
796 // -------------------------------------------------------------------
797 S.SetSizerProportion(1);
798 S.StartHorizontalLay(wxEXPAND, 1);
799 {
800 szrG = S.GetSizer();
801
802 // Panel used to host the sliders since they will be positioned manually.
803 //mGraphicPanel = S.Prop(1)
804 //.Position(wxEXPAND)
805 //.Size( { -1, 150 } )
806 //.StartPanel();
807 wxWindow *pParent = S.GetParent();
808 S.AddSpace(15,0);
809 {
810
811 // for (int i = 0; (i < NUMBER_OF_BANDS) && (kThirdOct[i] <= mHiFreq); ++i)
812 // May show more sliders than needed. Fixes Bug 2269
813 for (int i = 0; i < NUMBER_OF_BANDS; ++i)
814 {
815 TranslatableString freq = kThirdOct[i] < 1000.
816 ? XO("%d Hz").Format((int)kThirdOct[i])
817 : XO("%g kHz").Format(kThirdOct[i] / 1000.);
818 TranslatableString fNum = kThirdOct[i] < 1000.
819 ? Verbatim("%d").Format((int)kThirdOct[i])
820 /* i18n-hint k is SI abbreviation for x1,000. Usually unchanged in translation. */
821 : XO("%gk").Format(kThirdOct[i] / 1000.);
822 S.StartVerticalLay();
823 {
824 S.AddFixedText( fNum );
825 mSliders[i] = safenew wxSliderWrapper(pParent, ID_Slider + i, 0, -20, +20,
826 wxDefaultPosition, wxSize(-1,50), wxSL_VERTICAL | wxSL_INVERSE);
827
828#if wxUSE_ACCESSIBILITY
829 mSliders[i]->SetAccessible(safenew SliderAx(mSliders[i], XO("%d dB")));
830#endif
831
832 mSlidersOld[i] = 0;
833 mEQVals[i] = 0.;
834 S.Prop(1)
835 .Name(freq)
836 .ConnectRoot(
837 wxEVT_ERASE_BACKGROUND, &EffectEqualization::OnErase)
838 .Position(wxEXPAND)
839 .Size({ -1, 50 })
840 .AddWindow(mSliders[i]);
841 }
842 S.EndVerticalLay();
843 }
844 S.AddSpace(15,0);
845
846 } //S.EndPanel();
847 }
848 S.EndHorizontalLay();
849
850 // -------------------------------------------------------------------
851 // ROW 4: Various controls
852 // -------------------------------------------------------------------
853 S.SetSizerProportion(1);
854 S.Prop(1).StartMultiColumn(7, wxALIGN_CENTER_HORIZONTAL);
855 {
856 S.SetBorder(5);
857
858 S.AddSpace(5, 5);
859
860 if( mOptions == kEqLegacy )
861 {
862 S.StartHorizontalLay(wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
863 {
864 S.AddPrompt(XXO("&EQ Type:"));
865 }
866 S.EndHorizontalLay();
867
868 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 1);
869 {
870 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 1);
871 {
872 mDraw = S.Id(ID_Draw)
873 .Name(XO("Draw Curves"))
874 .AddRadioButton(XXO("&Draw"));
875
876 mGraphic = S.Id(ID_Graphic)
877 .Name(XO("Graphic EQ"))
878 .AddRadioButtonToGroup(XXO("&Graphic"));
879 }
880 S.EndHorizontalLay();
881 }
882 S.EndHorizontalLay();
883 }
884
885 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 0);
886 {
887 szrH = S.GetSizer();
888
889 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 1);
890 {
891 szrI = S.GetSizer();
892
894 .Name(XO("Interpolation type"))
895 .AddChoice( {},
897#if wxUSE_ACCESSIBILITY
898 // so that name can be set on a standard control
900#endif
901 }
902 S.EndHorizontalLay();
903
904 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 1);
905 {
906 szrL = S.GetSizer();
907
908 mLinFreq = S.Id(ID_Linear)
909 .Name(XO("Linear Frequency Scale"))
910 .AddCheckBox(XXO("Li&near Frequency Scale"), false);
911 }
912 S.EndHorizontalLay();
913 }
914 S.EndHorizontalLay();
915
916 // -------------------------------------------------------------------
917 // Filter length grouping
918 // -------------------------------------------------------------------
919
920 if( mOptions == kEqLegacy ){
921 S.StartHorizontalLay(wxEXPAND, 0);
922 {
923 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 0);
924 {
925 S.AddPrompt(XXO("Length of &Filter:"));
926 }
927 S.EndHorizontalLay();
928
929 S.StartHorizontalLay(wxEXPAND, 1);
930 {
931 mMSlider = S.Id(ID_Length)
932 .Name(XO("Length of Filter"))
933 .Style(wxSL_HORIZONTAL)
934 .AddSlider( {}, (mM - 1) / 2, 4095, 10);
935 }
936 S.EndHorizontalLay();
937
938 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 0);
939 {
940 wxString label;
941 label.Printf(wxT("%ld"), mM);
942 mMText = S.Name( Verbatim( label ) )
943 // fix for bug 577 (NVDA/Narrator screen readers do not
944 // read static text in dialogs)
945 .AddVariableText( Verbatim( label ) );
946 }
947 S.EndHorizontalLay();
948 }
949 S.EndHorizontalLay();
950
951 S.AddSpace(1, 1);
952 }
953
954 S.AddSpace(5, 5);
955
956 if( mOptions == kEqLegacy ){
957 S.AddSpace(5, 5);
958 S.StartHorizontalLay(wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
959 {
960 S.AddPrompt(XXO("&Select Curve:"));
961 }
962 S.EndHorizontalLay();
963
964 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 1);
965 {
966 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 1);
967 {
968 mCurve = S.Id(ID_Curve)
969 .Name(XO("Select Curve"))
970 .AddChoice( {},
971 [this]{
972 TranslatableStrings curves;
973 for (const auto &curve : mCurves)
974 curves.push_back( Verbatim( curve.Name ) );
975 return curves;
976 }()
977 );
978 }
979 S.EndHorizontalLay();
980 }
981 S.EndHorizontalLay();
982
983 S.Id(ID_Manage).AddButton(XXO("S&ave/Manage Curves..."));
984 }
985
986 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 1);
987 {
988 S.Id(ID_Clear).AddButton(XXO("Fla&tten"));
989 S.Id(ID_Invert).AddButton(XXO("&Invert"));
990
991 mGridOnOff = S.Id(ID_Grid)
992 .Name(XO("Show grid lines"))
993 .AddCheckBox(XXO("Show g&rid lines"), false);
994 }
995 S.EndHorizontalLay();
996
997 S.AddSpace(5, 5);
998 }
999 S.EndMultiColumn();
1000 }
1001 S.EndMultiColumn();
1002
1003#ifdef EXPERIMENTAL_EQ_SSE_THREADED
1004 if (mEffectEqualization48x)
1005 {
1006 // -------------------------------------------------------------------
1007 // ROW 6: Processing routine selection
1008 // -------------------------------------------------------------------
1009
1010 // Column 1 is blank
1011 S.AddSpace(1, 1);
1012
1013 S.StartHorizontalLay();
1014 {
1015 S.AddUnits(XO("&Processing: "));
1016
1017 // update the control state
1018 int mathPath = EffectEqualization48x::GetMathPath();
1019 int value =
1020 (mathPath & MATH_FUNCTION_SSE)
1021 ? (mathPath & MATH_FUNCTION_THREADED)
1022 ? 2
1023 : 1
1024 : false // (mathPath & MATH_FUNCTION_AVX) // not implemented
1025 ? (mathPath & MATH_FUNCTION_THREADED)
1026 ? 4
1027 : 3
1028 : 0;
1029
1030 mMathProcessingType[0] = S.Id(ID_DefaultMath)
1031 .AddRadioButton(XXO("D&efault"),
1032 0, value);
1033 mMathProcessingType[1] = S.Id(ID_SSE)
1034 .Disable(!EffectEqualization48x::GetMathCaps()->SSE)
1035 .AddRadioButtonToGroup(XXO("&SSE"),
1036 1, value);
1037 mMathProcessingType[2] = S.Id(ID_SSEThreaded)
1038 .Disable(!EffectEqualization48x::GetMathCaps()->SSE)
1039 .AddRadioButtonToGroup(XXO("SSE &Threaded"),
1040 2, value);
1041 mMathProcessingType[3] = S.Id(ID_AVX)
1042 // not implemented
1043 .Disable(true /* !EffectEqualization48x::GetMathCaps()->AVX */)
1044 .AddRadioButtonToGroup(XXO("A&VX"),
1045 3, value);
1046 mMathProcessingType[4] = S.Id(ID_AVXThreaded)
1047 // not implemented
1048 .Disable(true /* !EffectEqualization48x::GetMathCaps()->AVX */)
1049 .AddRadioButtonToGroup(XXO("AV&X Threaded"),
1050 4, value);
1051 S.Id(ID_Bench).AddButton(XXO("&Bench"));
1052 }
1053 S.EndHorizontalLay();
1054
1055 // Column 3 is blank
1056 S.AddSpace(1, 1);
1057 }
1058#endif
1059
1060 mUIParent->SetAutoLayout(false);
1062 mUIParent->Layout();
1063
1064 if( mOptions == kEqOptionCurve)
1065 mDrawMode = true;
1067 mDrawMode = false;
1068
1069 // "show" settings for graphics mode before setting the size of the dialog
1070 // as this needs more space than draw mode
1071 szrV->Show(szrG,!mDrawMode); // eq sliders
1072 szrH->Show(szrI,true); // interpolation choice
1073 szrH->Show(szrL,false); // linear freq checkbox
1074
1075 if( mOptions == kEqOptionGraphic){
1076 mPanel->Show( false );
1077 wxSize sz = szrV->GetMinSize();
1078 sz += wxSize( 30, 0);
1079 mUIParent->SetMinSize(sz);
1080 }
1081 else{
1082 mPanel->Show( true );
1083 szrV->Show(szr1, true);
1084 // This sizing calculation is hacky.
1085 // Rather than set the true minimum size we set a size we would
1086 // like to have.
1087 // This makes the default size of the dialog good, but has the
1088 // downside that the user can't adjust the dialog smaller.
1089 wxSize sz = szrV->GetMinSize();
1090 sz += wxSize( 400, 100);
1091 szrV->SetMinSize(sz);
1092 }
1093 ForceRecalc();
1094
1095 return nullptr;
1096}
1097
1098//
1099// Populate the window with relevant variables
1100//
1102{
1103 // Set log or lin freq scale (affects interpolation as well)
1104 mLinFreq->SetValue( mLin );
1105 wxCommandEvent dummyEvent;
1106 OnLinFreq(dummyEvent); // causes a CalcFilter
1107
1108 mGridOnOff->SetValue( mDrawGrid ); // checks/unchecks the box on the interface
1109
1110 if( mMSlider )
1111 mMSlider->SetValue((mM - 1) / 2);
1112
1113 mdBMinSlider->SetValue((int)mdBMin);
1114 mdBMaxSlider->SetValue((int)mdBMax);
1115
1116 // Reload the curve names
1117 UpdateCurves();
1118
1119 // Set graphic interpolation mode
1120 mInterpChoice->SetSelection(mInterp);
1121
1122 // Override draw mode, if we're not displaying the radio buttons.
1123 if( mOptions == kEqOptionCurve)
1124 mDrawMode = true;
1126 mDrawMode = false;
1127
1128 if( mDraw )
1129 mDraw->SetValue(mDrawMode);
1130 szrV->Show(szr1,mOptions != kEqOptionGraphic); // Graph
1131 szrV->Show(szrG,!mDrawMode); // eq sliders
1132 szrH->Show(szrI,mOptions == kEqLegacy ); // interpolation choice
1133 szrH->Show(szrL, mDrawMode); // linear freq checkbox
1134 if( mGraphic)
1135 mGraphic->SetValue(!mDrawMode);
1136 mGridOnOff->Show( mDrawMode );
1137
1138 // Set Graphic (Fader) or Draw mode
1139 if (!mDrawMode)
1140 UpdateGraphic();
1141
1142 UpdateRuler();
1143
1144 mUIParent->Layout();
1145 wxGetTopLevelParent(mUIParent)->Layout();
1146
1147 return true;
1148}
1149
1151{
1152 // Refresh ruler when values have changed
1153 int w1, w2, h;
1154 mdBRuler->ruler.GetMaxSize(&w1, &h);
1156 mdBRuler->ruler.GetMaxSize(&w2, &h);
1157 if( w1 != w2 ) // Reduces flicker
1158 {
1159 mdBRuler->SetSize(wxSize(w2,h));
1160 mFreqRuler->Refresh(false);
1161 }
1162 mdBRuler->Refresh(false);
1163
1164 mPanel->Refresh(false);
1165}
1166
1167// EffectEqualization implementation
1168
1170 sampleCount start, sampleCount len)
1171{
1172 // create a NEW WaveTrack to hold all of the output, including 'tails' each end
1173 auto output = t->EmptyCopy();
1175
1176 wxASSERT(mM - 1 < windowSize);
1177 size_t L = windowSize - (mM - 1); //Process L samples at a go
1178 auto s = start;
1179 auto idealBlockLen = t->GetMaxBlockSize() * 4;
1180 if (idealBlockLen % L != 0)
1181 idealBlockLen += (L - (idealBlockLen % L));
1182
1183 Floats buffer{ idealBlockLen };
1184
1185 Floats window1{ windowSize };
1186 Floats window2{ windowSize };
1187 float *thisWindow = window1.get();
1188 float *lastWindow = window2.get();
1189
1190 auto originalLen = len;
1191
1192 for(size_t i = 0; i < windowSize; i++)
1193 lastWindow[i] = 0;
1194
1195 TrackProgress(count, 0.);
1196 bool bLoopSuccess = true;
1197 size_t wcopy = 0;
1198 int offset = (mM - 1) / 2;
1199
1200 while (len != 0)
1201 {
1202 auto block = limitSampleBufferSize( idealBlockLen, len );
1203
1204 t->GetFloats(buffer.get(), s, block);
1205
1206 for(size_t i = 0; i < block; i += L) //go through block in lumps of length L
1207 {
1208 wcopy = std::min <size_t> (L, block - i);
1209 for(size_t j = 0; j < wcopy; j++)
1210 thisWindow[j] = buffer[i+j]; //copy the L (or remaining) samples
1211 for(auto j = wcopy; j < windowSize; j++)
1212 thisWindow[j] = 0; //this includes the padding
1213
1214 Filter(windowSize, thisWindow);
1215
1216 // Overlap - Add
1217 for(size_t j = 0; (j < mM - 1) && (j < wcopy); j++)
1218 buffer[i+j] = thisWindow[j] + lastWindow[L + j];
1219 for(size_t j = mM - 1; j < wcopy; j++)
1220 buffer[i+j] = thisWindow[j];
1221
1222 std::swap( thisWindow, lastWindow );
1223 } //next i, lump of this block
1224
1225 output->Append((samplePtr)buffer.get(), floatSample, block);
1226 len -= block;
1227 s += block;
1228
1229 if (TrackProgress(count, ( s - start ).as_double() /
1230 originalLen.as_double()))
1231 {
1232 bLoopSuccess = false;
1233 break;
1234 }
1235 }
1236
1237 if(bLoopSuccess)
1238 {
1239 // mM-1 samples of 'tail' left in lastWindow, get them now
1240 if(wcopy < (mM - 1)) {
1241 // Still have some overlap left to process
1242 // (note that lastWindow and thisWindow have been exchanged at this point
1243 // so that 'thisWindow' is really the window prior to 'lastWindow')
1244 size_t j = 0;
1245 for(; j < mM - 1 - wcopy; j++)
1246 buffer[j] = lastWindow[wcopy + j] + thisWindow[L + wcopy + j];
1247 // And fill in the remainder after the overlap
1248 for( ; j < mM - 1; j++)
1249 buffer[j] = lastWindow[wcopy + j];
1250 } else {
1251 for(size_t j = 0; j < mM - 1; j++)
1252 buffer[j] = lastWindow[wcopy + j];
1253 }
1254 output->Append((samplePtr)buffer.get(), floatSample, mM - 1);
1255 output->Flush();
1256
1257 // now move the appropriate bit of the output back to the track
1258 // (this could be enhanced in the future to use the tails)
1259 double offsetT0 = t->LongSamplesToTime(offset);
1260 double lenT = t->LongSamplesToTime(originalLen);
1261 // 'start' is the sample offset in 't', the passed in track
1262 // 'startT' is the equivalent time value
1263 // 'output' starts at zero
1264 double startT = t->LongSamplesToTime(start);
1265
1266 //output has one waveclip for the total length, even though
1267 //t might have whitespace separating multiple clips
1268 //we want to maintain the original clip structure, so
1269 //only paste the intersections of the NEW clip.
1270
1271 //Find the bits of clips that need replacing
1272 std::vector<std::pair<double, double> > clipStartEndTimes;
1273 std::vector<std::pair<double, double> > clipRealStartEndTimes; //the above may be truncated due to a clip being partially selected
1274 for (const auto &clip : t->GetClips())
1275 {
1276 double clipStartT;
1277 double clipEndT;
1278
1279 clipStartT = clip->GetPlayStartTime();
1280 clipEndT = clip->GetPlayEndTime();
1281 if( clipEndT <= startT )
1282 continue; // clip is not within selection
1283 if( clipStartT >= startT + lenT )
1284 continue; // clip is not within selection
1285
1286 //save the actual clip start/end so that we can rejoin them after we paste.
1287 clipRealStartEndTimes.push_back(std::pair<double,double>(clipStartT,clipEndT));
1288
1289 if( clipStartT < startT ) // does selection cover the whole clip?
1290 clipStartT = startT; // don't copy all the NEW clip
1291 if( clipEndT > startT + lenT ) // does selection cover the whole clip?
1292 clipEndT = startT + lenT; // don't copy all the NEW clip
1293
1294 //save them
1295 clipStartEndTimes.push_back(std::pair<double,double>(clipStartT,clipEndT));
1296 }
1297 //now go thru and replace the old clips with NEW
1298 for(unsigned int i = 0; i < clipStartEndTimes.size(); i++)
1299 {
1300 //remove the old audio and get the NEW
1301 t->Clear(clipStartEndTimes[i].first,clipStartEndTimes[i].second);
1302 auto toClipOutput = output->Copy(clipStartEndTimes[i].first-startT+offsetT0,clipStartEndTimes[i].second-startT+offsetT0);
1303 //put the processed audio in
1304 t->Paste(clipStartEndTimes[i].first, toClipOutput.get());
1305 //if the clip was only partially selected, the Paste will have created a split line. Join is needed to take care of this
1306 //This is not true when the selection is fully contained within one clip (second half of conditional)
1307 if( (clipRealStartEndTimes[i].first != clipStartEndTimes[i].first ||
1308 clipRealStartEndTimes[i].second != clipStartEndTimes[i].second) &&
1309 !(clipRealStartEndTimes[i].first <= startT &&
1310 clipRealStartEndTimes[i].second >= startT+lenT) )
1311 t->Join(clipRealStartEndTimes[i].first,clipRealStartEndTimes[i].second);
1312 }
1313 }
1314
1315 return bLoopSuccess;
1316}
1317
1319{
1320 double loLog = log10(mLoFreq);
1321 double hiLog = log10(mHiFreq);
1322 double denom = hiLog - loLog;
1323
1324 double delta = mHiFreq / ((double)(mWindowSize / 2.));
1325 double val0;
1326 double val1;
1327
1328 if( IsLinear() )
1329 {
1330 val0 = mLinEnvelope->GetValue(0.0); //no scaling required - saved as dB
1331 val1 = mLinEnvelope->GetValue(1.0);
1332 }
1333 else
1334 {
1335 val0 = mLogEnvelope->GetValue(0.0); //no scaling required - saved as dB
1336 val1 = mLogEnvelope->GetValue(1.0);
1337 }
1338 mFilterFuncR[0] = val0;
1339 double freq = delta;
1340
1341 for(size_t i = 1; i <= mWindowSize / 2; i++)
1342 {
1343 double when;
1344 if( IsLinear() )
1345 when = freq/mHiFreq;
1346 else
1347 when = (log10(freq) - loLog)/denom;
1348 if(when < 0.)
1349 {
1350 mFilterFuncR[i] = val0;
1351 }
1352 else if(when > 1.0)
1353 {
1354 mFilterFuncR[i] = val1;
1355 }
1356 else
1357 {
1358 if( IsLinear() )
1359 mFilterFuncR[i] = mLinEnvelope->GetValue(when);
1360 else
1361 mFilterFuncR[i] = mLogEnvelope->GetValue(when);
1362 }
1363 freq += delta;
1364 }
1365 mFilterFuncR[mWindowSize / 2] = val1;
1366
1368
1369 {
1370 size_t i = 1;
1371 for(; i < mWindowSize / 2; i++)
1372 {
1374 mFilterFuncR[mWindowSize - i] = mFilterFuncR[i]; //Fill entire array
1375 }
1376 mFilterFuncR[i] = DB_TO_LINEAR(mFilterFuncR[i]); //do last one
1377 }
1378
1379 //transfer to time domain to do the padding and windowing
1380 Floats outr{ mWindowSize };
1381 Floats outi{ mWindowSize };
1382 InverseRealFFT(mWindowSize, mFilterFuncR.get(), NULL, outr.get()); // To time domain
1383
1384 {
1385 size_t i = 0;
1386 for(; i <= (mM - 1) / 2; i++)
1387 { //Windowing - could give a choice, fixed for now - MJS
1388 // double mult=0.54-0.46*cos(2*M_PI*(i+(mM-1)/2.0)/(mM-1)); //Hamming
1389 //Blackman
1390 double mult =
1391 0.42 -
1392 0.5 * cos(2 * M_PI * (i + (mM - 1) / 2.0) / (mM - 1)) +
1393 .08 * cos(4 * M_PI * (i + (mM - 1) / 2.0) / (mM - 1));
1394 outr[i] *= mult;
1395 if(i != 0){
1396 outr[mWindowSize - i] *= mult;
1397 }
1398 }
1399 for(; i <= mWindowSize / 2; i++)
1400 { //Padding
1401 outr[i] = 0;
1402 outr[mWindowSize - i] = 0;
1403 }
1404 }
1405 Floats tempr{ mM };
1406 {
1407 size_t i = 0;
1408 for(; i < (mM - 1) / 2; i++)
1409 { //shift so that padding on right
1410 tempr[(mM - 1) / 2 + i] = outr[i];
1411 tempr[i] = outr[mWindowSize - (mM - 1) / 2 + i];
1412 }
1413 tempr[(mM - 1) / 2 + i] = outr[i];
1414 }
1415
1416 for (size_t i = 0; i < mM; i++)
1417 { //and copy useful values back
1418 outr[i] = tempr[i];
1419 }
1420 for (size_t i = mM; i < mWindowSize; i++)
1421 { //rest is padding
1422 outr[i]=0.;
1423 }
1424
1425 //Back to the frequency domain so we can use it
1426 RealFFT(mWindowSize, outr.get(), mFilterFuncR.get(), mFilterFuncI.get());
1427
1428 return TRUE;
1429}
1430
1431void EffectEqualization::Filter(size_t len, float *buffer)
1432{
1433 float re,im;
1434 // Apply FFT
1435 RealFFTf(buffer, hFFT.get());
1436 //FFT(len, false, inr, NULL, outr, outi);
1437
1438 // Apply filter
1439 // DC component is purely real
1440 mFFTBuffer[0] = buffer[0] * mFilterFuncR[0];
1441 for(size_t i = 1; i < (len / 2); i++)
1442 {
1443 re=buffer[hFFT->BitReversed[i] ];
1444 im=buffer[hFFT->BitReversed[i]+1];
1445 mFFTBuffer[2*i ] = re*mFilterFuncR[i] - im*mFilterFuncI[i];
1446 mFFTBuffer[2*i+1] = re*mFilterFuncI[i] + im*mFilterFuncR[i];
1447 }
1448 // Fs/2 component is purely real
1449 mFFTBuffer[1] = buffer[1] * mFilterFuncR[len/2];
1450
1451 // Inverse FFT and normalization
1452 InverseRealFFTf(mFFTBuffer.get(), hFFT.get());
1453 ReorderToTime(hFFT.get(), mFFTBuffer.get(), buffer);
1454}
1455
1456//
1457// Load external curves with fallback to default, then message
1458//
1459void EffectEqualization::LoadCurves(const wxString &fileName, bool append)
1460{
1461// We've disabled the XML management of curves.
1462// Just going via .cfg files now.
1463#if 1
1464 (void)fileName;
1465 (void)append;
1466 mCurves.clear();
1467 mCurves.push_back( wxT("unnamed") ); // we still need a default curve to use
1468#else
1469 // Construct normal curve filename
1470 //
1471 // LLL: Wouldn't you know that as of WX 2.6.2, there is a conflict
1472 // between wxStandardPaths and wxConfig under Linux. The latter
1473 // creates a normal file as "$HOME/.audacity", while the former
1474 // expects the ".audacity" portion to be a directory.
1475 // MJS: I don't know what the above means, or if I have broken it.
1476 wxFileName fn;
1477
1478 if(fileName.empty()) {
1479 // Check if presets are up to date.
1480 wxString eqCurvesCurrentVersion = wxString::Format(wxT("%d.%d"), EQCURVES_VERSION, EQCURVES_REVISION);
1481 wxString eqCurvesInstalledVersion;
1482 gPrefs->Read(GetPrefsPrefix() + "PresetVersion", &eqCurvesInstalledVersion, wxT(""));
1483
1484 bool needUpdate = (eqCurvesCurrentVersion != eqCurvesInstalledVersion);
1485
1486 // UpdateDefaultCurves allows us to import NEW factory presets only,
1487 // or update all factory preset curves.
1488 if (needUpdate)
1490 fn = wxFileName( FileNames::DataDir(), wxT("EQCurves.xml") );
1491 }
1492 else
1493 fn = fileName; // user is loading a specific set of curves
1494
1495 // If requested file doesn't exist...
1496 if( !fn.FileExists() && !GetDefaultFileName(fn) ) {
1497 mCurves.clear();
1498 /* i18n-hint: name of the 'unnamed' custom curve */
1499 mCurves.push_back( _("unnamed") ); // we still need a default curve to use
1500 return;
1501 }
1502
1503 EQCurve tempCustom(wxT("temp"));
1504 if( append == false ) // Start from scratch
1505 mCurves.clear();
1506 else // appending so copy and remove 'unnamed', to replace later
1507 {
1508 tempCustom.points = mCurves.back().points;
1509 mCurves.pop_back();
1510 }
1511
1512 // Load the curves
1513 XMLFileReader reader;
1514 const wxString fullPath{ fn.GetFullPath() };
1515 if( !reader.Parse( this, fullPath ) )
1516 {
1517 /* i18n-hint: EQ stands for 'Equalization'.*/
1518 auto msg = XO("Error Loading EQ Curves from file:\n%s\nError message says:\n%s")
1519 .Format( fullPath, reader.GetErrorStr() );
1520 // Inform user of load failure
1522 msg,
1523 wxOK | wxCENTRE,
1524 XO("Error Loading EQ Curves") );
1525 mCurves.push_back( _("unnamed") ); // we always need a default curve to use
1526 return;
1527 }
1528
1529 // Move "unnamed" to end, if it exists in current language.
1530 int numCurves = mCurves.size();
1531 int curve;
1532 EQCurve tempUnnamed(wxT("tempUnnamed"));
1533 for( curve = 0; curve < numCurves-1; curve++ )
1534 {
1535 if( mCurves[curve].Name == _("unnamed") )
1536 {
1537 tempUnnamed.points = mCurves[curve].points;
1538 mCurves.erase(mCurves.begin() + curve);
1539 mCurves.push_back( _("unnamed") ); // add 'unnamed' back at the end
1540 mCurves.back().points = tempUnnamed.points;
1541 }
1542 }
1543
1544 if( mCurves.back().Name != _("unnamed") )
1545 mCurves.push_back( _("unnamed") ); // we always need a default curve to use
1546 if( append == true )
1547 {
1548 mCurves.back().points = tempCustom.points;
1549 }
1550#endif
1551 return;
1552}
1553
1554//
1555// Update presets to match Audacity version.
1556//
1557void EffectEqualization::UpdateDefaultCurves(bool updateAll /* false */)
1558{
1559 if (mCurves.size() == 0)
1560 return;
1561
1562 wxString unnamed = wxT("unnamed");
1563
1564 // Save the "unnamed" curve and remove it so we can add it back as the final curve.
1565 EQCurve userUnnamed(wxT("temp"));
1566 userUnnamed = mCurves.back();
1567 mCurves.pop_back();
1568
1569 EQCurveArray userCurves = mCurves;
1570 mCurves.clear();
1571 // We only wamt to look for the shipped EQDefaultCurves.xml
1572 wxFileName fn = wxFileName(FileNames::ResourcesDir(), wxT("EQDefaultCurves.xml"));
1573 wxLogDebug(wxT("Attempting to load EQDefaultCurves.xml from %s"),fn.GetFullPath());
1574 XMLFileReader reader;
1575
1576 if(!reader.Parse(this, fn.GetFullPath())) {
1577 wxLogError(wxT("EQDefaultCurves.xml could not be read."));
1578 return;
1579 }
1580 else {
1581 wxLogDebug(wxT("Loading EQDefaultCurves.xml successful."));
1582 }
1583
1584 EQCurveArray defaultCurves = mCurves;
1585 mCurves.clear(); // clear now so that we can sort then add back.
1586
1587 // Remove "unnamed" if it exists.
1588 if (defaultCurves.back().Name == unnamed) {
1589 defaultCurves.pop_back();
1590 }
1591 else {
1592 wxLogError(wxT("Error in EQDefaultCurves.xml"));
1593 }
1594
1595 int numUserCurves = userCurves.size();
1596 int numDefaultCurves = defaultCurves.size();
1597 EQCurve tempCurve(wxT("test"));
1598
1599 if (updateAll) {
1600 // Update all factory preset curves.
1601 // Sort and add factory defaults first;
1602 mCurves = defaultCurves;
1603 std::sort(mCurves.begin(), mCurves.end());
1604 // then add remaining user curves:
1605 for (int curveCount = 0; curveCount < numUserCurves; curveCount++) {
1606 bool isCustom = true;
1607 tempCurve = userCurves[curveCount];
1608 // is the name in the default set?
1609 for (int defCurveCount = 0; defCurveCount < numDefaultCurves; defCurveCount++) {
1610 if (tempCurve.Name == mCurves[defCurveCount].Name) {
1611 isCustom = false;
1612 break;
1613 }
1614 }
1615 // if tempCurve is not in the default set, add it to mCurves.
1616 if (isCustom) {
1617 mCurves.push_back(tempCurve);
1618 }
1619 }
1620 }
1621 else {
1622 // Import NEW factory defaults but retain all user modified curves.
1623 for (int defCurveCount = 0; defCurveCount < numDefaultCurves; defCurveCount++) {
1624 bool isUserCurve = false;
1625 // Add if the curve is in the user's set (preserve user's copy)
1626 for (int userCurveCount = 0; userCurveCount < numUserCurves; userCurveCount++) {
1627 if (userCurves[userCurveCount].Name == defaultCurves[defCurveCount].Name) {
1628 isUserCurve = true;
1629 mCurves.push_back(userCurves[userCurveCount]);
1630 break;
1631 }
1632 }
1633 if (!isUserCurve) {
1634 mCurves.push_back(defaultCurves[defCurveCount]);
1635 }
1636 }
1637 std::sort(mCurves.begin(), mCurves.end());
1638 // now add the rest of the user's curves.
1639 for (int userCurveCount = 0; userCurveCount < numUserCurves; userCurveCount++) {
1640 bool isDefaultCurve = false;
1641 tempCurve = userCurves[userCurveCount];
1642 for (int defCurveCount = 0; defCurveCount < numDefaultCurves; defCurveCount++) {
1643 if (tempCurve.Name == defaultCurves[defCurveCount].Name) {
1644 isDefaultCurve = true;
1645 break;
1646 }
1647 }
1648 if (!isDefaultCurve) {
1649 mCurves.push_back(tempCurve);
1650 }
1651 }
1652 }
1653 defaultCurves.clear();
1654 userCurves.clear();
1655
1656 // Add back old "unnamed"
1657 if(userUnnamed.Name == unnamed) {
1658 mCurves.push_back( userUnnamed ); // we always need a default curve to use
1659 }
1660
1661 SaveCurves();
1662
1663 // Write current EqCurve version number
1664 // TODO: Probably better if we used pluginregistry.cfg
1665 wxString eqCurvesCurrentVersion = wxString::Format(wxT("%d.%d"), EQCURVES_VERSION, EQCURVES_REVISION);
1666 gPrefs->Write(GetPrefsPrefix()+"PresetVersion", eqCurvesCurrentVersion);
1667 gPrefs->Flush();
1668
1669 return;
1670}
1671
1672//
1673// Get fully qualified filename of EQDefaultCurves.xml
1674//
1676{
1677 // look in data dir first, in case the user has their own defaults (maybe downloaded ones)
1678 fileName = wxFileName( FileNames::DataDir(), wxT("EQDefaultCurves.xml") );
1679 if( !fileName.FileExists() )
1680 { // Default file not found in the data dir. Fall back to Resources dir.
1681 // See http://docs.wxwidgets.org/trunk/classwx_standard_paths.html#5514bf6288ee9f5a0acaf065762ad95d
1682 fileName = wxFileName( FileNames::ResourcesDir(), wxT("EQDefaultCurves.xml") );
1683 }
1684 if( !fileName.FileExists() )
1685 {
1686 // LLL: Is there really a need for an error message at all???
1687 //auto errorMessage = XO("EQCurves.xml and EQDefaultCurves.xml were not found on your system.\nPlease press 'help' to visit the download page.\n\nSave the curves at %s")
1688 // .Format( FileNames::DataDir() );
1689 //BasicUI::ShowErrorDialog( wxWidgetsWindowPlacement{ mUIParent },
1690 // XO("EQCurves.xml and EQDefaultCurves.xml missing"),
1691 // errorMessage, wxT("http://wiki.audacityteam.org/wiki/EQCurvesDownload"), false);
1692
1693 // Have another go at finding EQCurves.xml in the data dir, in case 'help' helped
1694 fileName = wxFileName( FileNames::DataDir(), wxT("EQDefaultCurves.xml") );
1695 }
1696 return (fileName.FileExists());
1697}
1698
1699
1700//
1701// Save curves to external file
1702//
1703void EffectEqualization::SaveCurves(const wxString &fileName)
1704{
1705 wxFileName fn;
1706 if( fileName.empty() )
1707 {
1708 // Construct default curve filename
1709 //
1710 // LLL: Wouldn't you know that as of WX 2.6.2, there is a conflict
1711 // between wxStandardPaths and wxConfig under Linux. The latter
1712 // creates a normal file as "$HOME/.audacity", while the former
1713 // expects the ".audacity" portion to be a directory.
1714 fn = wxFileName( FileNames::DataDir(), wxT("EQCurves.xml") );
1715
1716 // If the directory doesn't exist...
1717 if( !fn.DirExists() )
1718 {
1719 // Attempt to create it
1720 if( !fn.Mkdir( fn.GetPath(), 511, wxPATH_MKDIR_FULL ) )
1721 {
1722 // MkDir() will emit message
1723 return;
1724 }
1725 }
1726 }
1727 else
1728 fn = fileName;
1729
1730 GuardedCall( [&] {
1731 // Create/Open the file
1732 const wxString fullPath{ fn.GetFullPath() };
1733 XMLFileWriter eqFile{ fullPath, XO("Error Saving Equalization Curves") };
1734
1735 // Write the curves
1736 WriteXML( eqFile );
1737
1738 eqFile.Commit();
1739 } );
1740}
1741
1742//
1743// Make the passed curve index the active one
1744//
1745void EffectEqualization::setCurve(int currentCurve)
1746{
1747 // Set current choice
1748 wxASSERT( currentCurve < (int) mCurves.size() );
1749 Select(currentCurve);
1750
1751 Envelope *env;
1752 int numPoints = (int) mCurves[currentCurve].points.size();
1753
1754 if (mLin) { // linear freq mode
1755 env = mLinEnvelope.get();
1756 }
1757 else { // log freq mode
1758 env = mLogEnvelope.get();
1759 }
1760 env->Flatten(0.);
1761 env->SetTrackLen(1.0);
1762
1763 // Handle special case of no points.
1764 if (numPoints == 0) {
1765 ForceRecalc();
1766 return;
1767 }
1768
1769 double when, value;
1770
1771 // Handle special case 1 point.
1772 if (numPoints == 1) {
1773 // only one point, so ensure it is in range then return.
1774 when = mCurves[currentCurve].points[0].Freq;
1775 if (mLin) {
1776 when = when / mHiFreq;
1777 }
1778 else { // log scale
1779 // We don't go below loFreqI (20 Hz) in log view.
1780 double loLog = log10((double)loFreqI);
1781 double hiLog = log10(mHiFreq);
1782 double denom = hiLog - loLog;
1783 when = (log10(std::max((double) loFreqI, when)) - loLog)/denom;
1784 }
1785 value = mCurves[currentCurve].points[0].dB;
1786 env->Insert(std::min(1.0, std::max(0.0, when)), value);
1787 ForceRecalc();
1788 return;
1789 }
1790
1791 // We have at least two points, so ensure they are in frequency order.
1792 std::sort(mCurves[currentCurve].points.begin(),
1793 mCurves[currentCurve].points.end());
1794
1795 if (mCurves[currentCurve].points[0].Freq < 0) {
1796 // Corrupt or invalid curve, so bail.
1797 ForceRecalc();
1798 return;
1799 }
1800
1801 if(mLin) { // linear Hz scale
1802 for(int pointCount = 0; pointCount < numPoints; pointCount++) {
1803 when = mCurves[currentCurve].points[pointCount].Freq / mHiFreq;
1804 value = mCurves[currentCurve].points[pointCount].dB;
1805 if(when <= 1) {
1806 env->Insert(when, value);
1807 if (when == 1)
1808 break;
1809 }
1810 else {
1811 // There are more points at higher freqs,
1812 // so interpolate next one then stop.
1813 when = 1.0;
1814 double nextDB = mCurves[currentCurve].points[pointCount].dB;
1815 if (pointCount > 0) {
1816 double nextF = mCurves[currentCurve].points[pointCount].Freq;
1817 double lastF = mCurves[currentCurve].points[pointCount-1].Freq;
1818 double lastDB = mCurves[currentCurve].points[pointCount-1].dB;
1819 value = lastDB +
1820 ((nextDB - lastDB) *
1821 ((mHiFreq - lastF) / (nextF - lastF)));
1822 }
1823 else
1824 value = nextDB;
1825 env->Insert(when, value);
1826 break;
1827 }
1828 }
1829 }
1830 else { // log Hz scale
1831 double loLog = log10((double) loFreqI);
1832 double hiLog = log10(mHiFreq);
1833 double denom = hiLog - loLog;
1834 int firstAbove20Hz;
1835
1836 // log scale EQ starts at 20 Hz (threshold of hearing).
1837 // so find the first point (if any) above 20 Hz.
1838 for (firstAbove20Hz = 0; firstAbove20Hz < numPoints; firstAbove20Hz++) {
1839 if (mCurves[currentCurve].points[firstAbove20Hz].Freq > loFreqI)
1840 break;
1841 }
1842
1843 if (firstAbove20Hz == numPoints) {
1844 // All points below 20 Hz, so just use final point.
1845 when = 0.0;
1846 value = mCurves[currentCurve].points[numPoints-1].dB;
1847 env->Insert(when, value);
1848 ForceRecalc();
1849 return;
1850 }
1851
1852 if (firstAbove20Hz > 0) {
1853 // At least one point is before 20 Hz and there are more
1854 // beyond 20 Hz, so interpolate the first
1855 double prevF = mCurves[currentCurve].points[firstAbove20Hz-1].Freq;
1856 prevF = log10(std::max(1.0, prevF)); // log zero is bad.
1857 double prevDB = mCurves[currentCurve].points[firstAbove20Hz-1].dB;
1858 double nextF = log10(mCurves[currentCurve].points[firstAbove20Hz].Freq);
1859 double nextDB = mCurves[currentCurve].points[firstAbove20Hz].dB;
1860 when = 0.0;
1861 value = nextDB - ((nextDB - prevDB) * ((nextF - loLog) / (nextF - prevF)));
1862 env->Insert(when, value);
1863 }
1864
1865 // Now get the rest.
1866 for(int pointCount = firstAbove20Hz; pointCount < numPoints; pointCount++)
1867 {
1868 double flog = log10(mCurves[currentCurve].points[pointCount].Freq);
1869 wxASSERT(mCurves[currentCurve].points[pointCount].Freq >= loFreqI);
1870
1871 when = (flog - loLog)/denom;
1872 value = mCurves[currentCurve].points[pointCount].dB;
1873 if(when <= 1.0) {
1874 env->Insert(when, value);
1875 }
1876 else {
1877 // This looks weird when adjusting curve in Draw mode if
1878 // there is a point off-screen.
1879
1880 /*
1881 // we have a point beyond fs/2. Insert it so that env code can use it.
1882 // but just this one, we have no use for the rest
1883 env->SetTrackLen(when); // can't Insert if the envelope isn't long enough
1884 env->Insert(when, value);
1885 break;
1886 */
1887
1888 // interpolate the final point instead
1889 when = 1.0;
1890 if (pointCount > 0) {
1891 double lastDB = mCurves[currentCurve].points[pointCount-1].dB;
1892 double logLastF =
1893 log10(mCurves[currentCurve].points[pointCount-1].Freq);
1894 value = lastDB +
1895 ((value - lastDB) *
1896 ((log10(mHiFreq) - logLastF) / (flog - logLastF)));
1897 }
1898 env->Insert(when, value);
1899 break;
1900 }
1901 }
1902 }
1903 ForceRecalc();
1904}
1905
1907{
1908 setCurve((int) mCurves.size() - 1);
1909}
1910
1911void EffectEqualization::setCurve(const wxString &curveName)
1912{
1913 unsigned i = 0;
1914 for( i = 0; i < mCurves.size(); i++ )
1915 if( curveName == mCurves[ i ].Name )
1916 break;
1917 if( i == mCurves.size())
1918 {
1920 XO("Requested curve not found, using 'unnamed'"),
1921 wxOK|wxICON_ERROR,
1922 XO("Curve not found") );
1923 setCurve();
1924 }
1925 else
1926 setCurve( i );
1927}
1928
1929//
1930// Set NEW curve selection (safe to call outside of the UI)
1931//
1933{
1934 // Set current choice
1935 if (mCurve)
1936 {
1937 mCurve->SetSelection( curve );
1938 mCurveName = mCurves[ curve ].Name;
1939 }
1940}
1941
1942//
1943// Tell panel to recalc (safe to call outside of UI)
1944//
1946{
1947 if (mPanel)
1948 {
1950 }
1951}
1952
1953//
1954// Capture updated envelope
1955//
1957{
1958 if (IsLinear())
1959 {
1960 EnvelopeUpdated(mLinEnvelope.get(), true);
1961 }
1962 else
1963 {
1964 EnvelopeUpdated(mLogEnvelope.get(), false);
1965 }
1966}
1967
1969{
1970 // Allocate and populate point arrays
1971 size_t numPoints = env->GetNumberOfPoints();
1972 Doubles when{ numPoints };
1973 Doubles value{ numPoints };
1974 env->GetPoints( when.get(), value.get(), numPoints );
1975
1976 // Clear the unnamed curve
1977 int curve = mCurves.size() - 1;
1978 mCurves[ curve ].points.clear();
1979
1980 if(lin)
1981 {
1982 // Copy and convert points
1983 for (size_t point = 0; point < numPoints; point++)
1984 {
1985 double freq = when[ point ] * mHiFreq;
1986 double db = value[ point ];
1987
1988 // Add it to the curve
1989 mCurves[ curve ].points.push_back( EQPoint( freq, db ) );
1990 }
1991 }
1992 else
1993 {
1994 double loLog = log10( 20. );
1995 double hiLog = log10( mHiFreq );
1996 double denom = hiLog - loLog;
1997
1998 // Copy and convert points
1999 for (size_t point = 0; point < numPoints; point++)
2000 {
2001 double freq = pow( 10., ( ( when[ point ] * denom ) + loLog ));
2002 double db = value[ point ];
2003
2004 // Add it to the curve
2005 mCurves[ curve ].points.push_back( EQPoint( freq, db ) );
2006 }
2007 }
2008 // Remember that we've updated the unnamed curve
2009 mDirty = true;
2010
2011 // set 'unnamed' as the selected curve
2012 Select( (int) mCurves.size() - 1 );
2013}
2014
2015//
2016//
2017//
2019{
2020 return mDrawMode && mLin;
2021}
2022
2023//
2024// Flatten the curve
2025//
2027{
2028 mLogEnvelope->Flatten(0.);
2029 mLogEnvelope->SetTrackLen(1.0);
2030 mLinEnvelope->Flatten(0.);
2031 mLinEnvelope->SetTrackLen(1.0);
2032 ForceRecalc();
2033 if( !mDrawMode )
2034 {
2035 for( size_t i = 0; i < mBandsInUse; i++)
2036 {
2037 mSliders[i]->SetValue(0);
2038 mSlidersOld[i] = 0;
2039 mEQVals[i] = 0.;
2040
2041 wxString tip;
2042 if( kThirdOct[i] < 1000.)
2043 tip.Printf( wxT("%dHz\n%.1fdB"), (int)kThirdOct[i], 0. );
2044 else
2045 tip.Printf( wxT("%gkHz\n%.1fdB"), kThirdOct[i]/1000., 0. );
2046 mSliders[i]->SetToolTip(tip);
2047 }
2048 }
2050}
2051
2052//
2053// Process XML tags and handle the ones we recognize
2054//
2055bool EffectEqualization::HandleXMLTag(const std::string_view& tag, const AttributesList &attrs)
2056{
2057 // May want to add a version strings...
2058 if (tag == "equalizationeffect")
2059 {
2060 return true;
2061 }
2062
2063 // Located a NEW curve
2064 if (tag == "curve")
2065 {
2066 // Process the attributes
2067 for (auto pair : attrs)
2068 {
2069 auto attr = pair.first;
2070 auto value = pair.second;
2071
2072 // Create a NEW curve and name it
2073 if( attr == "name" )
2074 {
2075 const wxString strValue = value.ToWString();
2076 // check for a duplicate name and add (n) if there is one
2077 int n = 0;
2078 wxString strValueTemp = strValue;
2079 bool exists;
2080 do
2081 {
2082 exists = false;
2083 for(size_t i = 0; i < mCurves.size(); i++)
2084 {
2085 if(n>0)
2086 strValueTemp.Printf(wxT("%s (%d)"),strValue,n);
2087 if(mCurves[i].Name == strValueTemp)
2088 {
2089 exists = true;
2090 break;
2091 }
2092 }
2093 n++;
2094 }
2095 while(exists == true);
2096
2097 mCurves.push_back( EQCurve( strValueTemp ) );
2098 }
2099 }
2100
2101 // Tell caller it was processed
2102 return true;
2103 }
2104
2105 // Located a NEW point
2106 if(tag == "point")
2107 {
2108 // Set defaults in case attributes are missing
2109 double f = 0.0;
2110 double d = 0.0;
2111
2112 // Process the attributes
2113 double dblValue;
2114 for (auto pair : attrs)
2115 {
2116 auto attr = pair.first;
2117 auto value = pair.second;
2118
2119 // Get the frequency
2120 if( attr == "f" )
2121 {
2122 if (!value.TryGet(dblValue))
2123 return false;
2124 f = dblValue;
2125 }
2126 // Get the dB
2127 else if( attr == "d" )
2128 {
2129 if (!value.TryGet(dblValue))
2130 return false;
2131 d = dblValue;
2132 }
2133 }
2134
2135 // Create a NEW point
2136 mCurves[ mCurves.size() - 1 ].points.push_back( EQPoint( f, d ) );
2137
2138 // Tell caller it was processed
2139 return true;
2140 }
2141
2142 // Tell caller we didn't understand the tag
2143 return false;
2144}
2145
2146//
2147// Return handler for recognized tags
2148//
2150{
2151 if (tag == "equalizationeffect")
2152 {
2153 return this;
2154 }
2155
2156 if (tag == "curve")
2157 {
2158 return this;
2159 }
2160
2161 if (tag == "point")
2162 {
2163 return this;
2164 }
2165
2166 return NULL;
2167}
2168
2169//
2170// Write all of the curves to the XML file
2171//
2173// may throw
2174{
2175 // Start our hierarchy
2176 xmlFile.StartTag( wxT( "equalizationeffect" ) );
2177
2178 // Write all curves
2179 int numCurves = mCurves.size();
2180 int curve;
2181 for( curve = 0; curve < numCurves; curve++ )
2182 {
2183 // Start a NEW curve
2184 xmlFile.StartTag( wxT( "curve" ) );
2185 xmlFile.WriteAttr( wxT( "name" ), mCurves[ curve ].Name );
2186
2187 // Write all points
2188 int numPoints = mCurves[ curve ].points.size();
2189 int point;
2190 for( point = 0; point < numPoints; point++ )
2191 {
2192 // Write NEW point
2193 xmlFile.StartTag( wxT( "point" ) );
2194 xmlFile.WriteAttr( wxT( "f" ), mCurves[ curve ].points[ point ].Freq, 12 );
2195 xmlFile.WriteAttr( wxT( "d" ), mCurves[ curve ].points[ point ].dB, 12 );
2196 xmlFile.EndTag( wxT( "point" ) );
2197 }
2198
2199 // Terminate curve
2200 xmlFile.EndTag( wxT( "curve" ) );
2201 }
2202
2203 // Terminate our hierarchy
2204 xmlFile.EndTag( wxT( "equalizationeffect" ) );
2205}
2206
2208//
2209// All EffectEqualization methods beyond this point interact with the UI, so
2210// can't be called while the UI is not displayed.
2211//
2213
2215{
2216
2217 // Reload the curve names
2218 if( mCurve )
2219 mCurve->Clear();
2220 bool selectedCurveExists = false;
2221 for (size_t i = 0, cnt = mCurves.size(); i < cnt; i++)
2222 {
2223 if (mCurveName == mCurves[ i ].Name)
2224 selectedCurveExists = true;
2225 if( mCurve )
2226 mCurve->Append(mCurves[ i ].Name);
2227 }
2228 // In rare circumstances, mCurveName may not exist (bug 1891)
2229 if (!selectedCurveExists)
2230 mCurveName = mCurves[ (int)mCurves.size() - 1 ].Name;
2231 if( mCurve )
2232 mCurve->SetStringSelection(mCurveName);
2233
2234 // Allow the control to resize
2235 if( mCurve )
2236 mCurve->SetMinSize({-1, -1});
2237
2238 // Set initial curve
2240}
2241
2243{
2244 size_t numPoints = mLogEnvelope->GetNumberOfPoints();
2245 Doubles when{ numPoints };
2246 Doubles value{ numPoints };
2247 double deltadB = 0.1;
2248 double dx, dy, dx1, dy1, err;
2249
2250 mLogEnvelope->GetPoints( when.get(), value.get(), numPoints );
2251
2252 // set 'unnamed' as the selected curve
2254
2255 bool flag = true;
2256 while (flag)
2257 {
2258 flag = false;
2259 int numDeleted = 0;
2260 mLogEnvelope->GetPoints( when.get(), value.get(), numPoints );
2261 for (size_t j = 0; j + 2 < numPoints; j++)
2262 {
2263 dx = when[j+2+numDeleted] - when[j+numDeleted];
2264 dy = value[j+2+numDeleted] - value[j+numDeleted];
2265 dx1 = when[j+numDeleted+1] - when[j+numDeleted];
2266 dy1 = dy * dx1 / dx;
2267 err = fabs(value[j+numDeleted+1] - (value[j+numDeleted] + dy1));
2268 if( err < deltadB )
2269 { // within < deltadB dB?
2270 mLogEnvelope->Delete(j+1);
2271 numPoints--;
2272 numDeleted++;
2273 flag = true;
2274 }
2275 }
2276 }
2277
2278 if(mLin) // do not use IsLinear() here
2279 {
2280 EnvLogToLin();
2281 mEnvelope = mLinEnvelope.get();
2282 mFreqRuler->ruler.SetLog(false);
2284 }
2285
2286 szrV->Show(szrG,false);
2287 szrH->Show(szrI,false);
2288 szrH->Show(szrL,true);
2289
2290 mUIParent->Layout();
2291 wxGetTopLevelParent(mUIParent)->Layout();
2292 ForceRecalc(); // it may have changed slightly due to the deletion of points
2293}
2294
2296{
2297 double loLog = log10(mLoFreq);
2298 double hiLog = log10(mHiFreq);
2299 double denom = hiLog - loLog;
2300
2301 if(mLin) //going from lin to log freq scale - do not use IsLinear() here
2302 { // add some extra points to the linear envelope for the graphic to follow
2303 double step = pow(2., 1./12.); // twelve steps per octave
2304 double when,value;
2305 for(double freq=10.; freq<mHiFreq; freq*=step)
2306 {
2307 when = freq/mHiFreq;
2308 value = mLinEnvelope->GetValue(when);
2309 mLinEnvelope->Insert(when, value);
2310 }
2311
2312 EnvLinToLog();
2313 mEnvelope = mLogEnvelope.get();
2314 mFreqRuler->ruler.SetLog(true);
2316 }
2317
2318 for (size_t i = 0; i < mBandsInUse; i++)
2319 {
2320 if( kThirdOct[i] == mLoFreq )
2321 mWhenSliders[i] = 0.;
2322 else
2323 mWhenSliders[i] = (log10(kThirdOct[i])-loLog)/denom;
2324 mEQVals[i] = mLogEnvelope->GetValue(mWhenSliders[i]); //set initial values of sliders
2325 if( mEQVals[i] > 20.)
2326 mEQVals[i] = 20.;
2327 if( mEQVals[i] < -20.)
2328 mEQVals[i] = -20.;
2329 }
2330 ErrMin(); //move sliders to minimise error
2331 for (size_t i = 0; i < mBandsInUse; i++)
2332 {
2333 mSliders[i]->SetValue(lrint(mEQVals[i])); //actually set slider positions
2334 mSlidersOld[i] = mSliders[i]->GetValue();
2335 wxString tip;
2336 if( kThirdOct[i] < 1000.)
2337 tip.Printf( wxT("%dHz\n%.1fdB"), (int)kThirdOct[i], mEQVals[i] );
2338 else
2339 tip.Printf( wxT("%gkHz\n%.1fdB"), kThirdOct[i]/1000., mEQVals[i] );
2340 mSliders[i]->SetToolTip(tip);
2341 }
2342
2343 szrV->Show(szrG,true); // eq sliders
2344 szrH->Show(szrI,mOptions == kEqLegacy ); // interpolation choice
2345 szrH->Show(szrL,false); // linear freq checkbox
2346
2347 mUIParent->Layout();
2348 wxGetTopLevelParent(mUIParent)->Layout();
2349 mUIParent->Layout();
2350 wxGetTopLevelParent(mUIParent)->Layout();
2351
2352 GraphicEQ(mLogEnvelope.get());
2353 mDrawMode = false;
2354}
2355
2357{
2358 size_t numPoints = mLogEnvelope->GetNumberOfPoints();
2359 if( numPoints == 0 )
2360 {
2361 return;
2362 }
2363
2364 Doubles when{ numPoints };
2365 Doubles value{ numPoints };
2366
2367 mLinEnvelope->Flatten(0.);
2368 mLinEnvelope->SetTrackLen(1.0);
2369 mLogEnvelope->GetPoints( when.get(), value.get(), numPoints );
2370 mLinEnvelope->Reassign(0., value[0]);
2371 double loLog = log10(20.);
2372 double hiLog = log10(mHiFreq);
2373 double denom = hiLog - loLog;
2374
2375 for (size_t i = 0; i < numPoints; i++)
2376 mLinEnvelope->Insert(pow( 10., ((when[i] * denom) + loLog))/mHiFreq , value[i]);
2377 mLinEnvelope->Reassign(1., value[numPoints-1]);
2378}
2379
2381{
2382 size_t numPoints = mLinEnvelope->GetNumberOfPoints();
2383 if( numPoints == 0 )
2384 {
2385 return;
2386 }
2387
2388 Doubles when{ numPoints };
2389 Doubles value{ numPoints };
2390
2391 mLogEnvelope->Flatten(0.);
2392 mLogEnvelope->SetTrackLen(1.0);
2393 mLinEnvelope->GetPoints( when.get(), value.get(), numPoints );
2394 mLogEnvelope->Reassign(0., value[0]);
2395 double loLog = log10(20.);
2396 double hiLog = log10(mHiFreq);
2397 double denom = hiLog - loLog;
2398 bool changed = false;
2399
2400 for (size_t i = 0; i < numPoints; i++)
2401 {
2402 if( when[i]*mHiFreq >= 20 )
2403 {
2404 // Caution: on Linux, when when == 20, the log calculation rounds
2405 // to just under zero, which causes an assert error.
2406 double flog = (log10(when[i]*mHiFreq)-loLog)/denom;
2407 mLogEnvelope->Insert(std::max(0.0, flog) , value[i]);
2408 }
2409 else
2410 { //get the first point as close as we can to the last point requested
2411 changed = true;
2412 double v = value[i];
2413 mLogEnvelope->Insert(0., v);
2414 }
2415 }
2416 mLogEnvelope->Reassign(1., value[numPoints - 1]);
2417
2418 if(changed)
2419 EnvelopeUpdated(mLogEnvelope.get(), false);
2420}
2421
2423{
2424 double vals[NUM_PTS];
2425 double error = 0.0;
2426 double oldError = 0.0;
2427 double mEQValsOld = 0.0;
2428 double correction = 1.6;
2429 bool flag;
2430 size_t j=0;
2431 Envelope testEnvelope{ *mLogEnvelope };
2432
2433 for(size_t i = 0; i < NUM_PTS; i++)
2434 vals[i] = testEnvelope.GetValue(mWhens[i]);
2435
2436 // Do error minimisation
2437 error = 0.;
2438 GraphicEQ(&testEnvelope);
2439 for(size_t i = 0; i < NUM_PTS; i++) //calc initial error
2440 {
2441 double err = vals[i] - testEnvelope.GetValue(mWhens[i]);
2442 error += err*err;
2443 }
2444 oldError = error;
2445 while( j < mBandsInUse*12 ) //loop over the sliders a number of times
2446 {
2447 auto i = j % mBandsInUse; //use this slider
2448 if( (j > 0) & (i == 0) ) // if we've come back to the first slider again...
2449 {
2450 if( correction > 0 )
2451 correction = -correction; //go down
2452 else
2453 correction = -correction/2.; //go up half as much
2454 }
2455 flag = true; // check if we've hit the slider limit
2456 do
2457 {
2458 oldError = error;
2459 mEQValsOld = mEQVals[i];
2460 mEQVals[i] += correction; //move fader value
2461 if( mEQVals[i] > 20. )
2462 {
2463 mEQVals[i] = 20.;
2464 flag = false;
2465 }
2466 if( mEQVals[i] < -20. )
2467 {
2468 mEQVals[i] = -20.;
2469 flag = false;
2470 }
2471 GraphicEQ(&testEnvelope); //calculate envelope
2472 error = 0.;
2473 for(size_t k = 0; k < NUM_PTS; k++) //calculate error
2474 {
2475 double err = vals[k] - testEnvelope.GetValue(mWhens[k]);
2476 error += err*err;
2477 }
2478 }
2479 while( (error < oldError) && flag );
2480 if( error > oldError )
2481 {
2482 mEQVals[i] = mEQValsOld; //last one didn't work
2483 error = oldError;
2484 }
2485 else
2486 oldError = error;
2487 if( error < .0025 * mBandsInUse)
2488 break; // close enuff
2489 j++; //try next slider
2490 }
2491 if( error > .0025 * mBandsInUse ) // not within 0.05dB on each slider, on average
2492 {
2493 Select( (int) mCurves.size() - 1 );
2494 EnvelopeUpdated(&testEnvelope, false);
2495 }
2496}
2497
2499{
2500 // JKC: 'value' is for height of curve.
2501 // The 0.0 initial value would only get used if NUM_PTS were 0.
2502 double value = 0.0;
2503 double dist, span, s;
2504
2505 env->Flatten(0.);
2506 env->SetTrackLen(1.0);
2507
2508 switch( mInterp )
2509 {
2510 case kBspline: // B-spline
2511 {
2512 int minF = 0;
2513 for(size_t i = 0; i < NUM_PTS; i++)
2514 {
2515 while( (mWhenSliders[minF] <= mWhens[i]) & (minF < (int)mBandsInUse) )
2516 minF++;
2517 minF--;
2518 if( minF < 0 ) //before first slider
2519 {
2520 dist = mWhens[i] - mWhenSliders[0];
2521 span = mWhenSliders[1] - mWhenSliders[0];
2522 s = dist/span;
2523 if( s < -1.5 )
2524 value = 0.;
2525 else if( s < -.5 )
2526 value = mEQVals[0]*(s + 1.5)*(s + 1.5)/2.;
2527 else
2528 value = mEQVals[0]*(.75 - s*s) + mEQVals[1]*(s + .5)*(s + .5)/2.;
2529 }
2530 else
2531 {
2532 if( mWhens[i] > mWhenSliders[mBandsInUse-1] ) //after last fader
2533 {
2534 dist = mWhens[i] - mWhenSliders[mBandsInUse-1];
2536 s = dist/span;
2537 if( s > 1.5 )
2538 value = 0.;
2539 else if( s > .5 )
2540 value = mEQVals[mBandsInUse-1]*(s - 1.5)*(s - 1.5)/2.;
2541 else
2542 value = mEQVals[mBandsInUse-1]*(.75 - s*s) +
2543 mEQVals[mBandsInUse-2]*(s - .5)*(s - .5)/2.;
2544 }
2545 else //normal case
2546 {
2547 dist = mWhens[i] - mWhenSliders[minF];
2548 span = mWhenSliders[minF+1] - mWhenSliders[minF];
2549 s = dist/span;
2550 if(s < .5 )
2551 {
2552 value = mEQVals[minF]*(0.75 - s*s);
2553 if( minF+1 < (int)mBandsInUse )
2554 value += mEQVals[minF+1]*(s+.5)*(s+.5)/2.;
2555 if( minF-1 >= 0 )
2556 value += mEQVals[minF-1]*(s-.5)*(s-.5)/2.;
2557 }
2558 else
2559 {
2560 value = mEQVals[minF]*(s-1.5)*(s-1.5)/2.;
2561 if( minF+1 < (int)mBandsInUse )
2562 value += mEQVals[minF+1]*(.75-(1.-s)*(1.-s));
2563 if( minF+2 < (int)mBandsInUse )
2564 value += mEQVals[minF+2]*(s-.5)*(s-.5)/2.;
2565 }
2566 }
2567 }
2568 if(mWhens[i]<=0.)
2569 env->Reassign(0., value);
2570 env->Insert( mWhens[i], value );
2571 }
2572 env->Reassign( 1., value );
2573 break;
2574 }
2575
2576 case kCosine: // Cosine squared
2577 {
2578 int minF = 0;
2579 for(size_t i = 0; i < NUM_PTS; i++)
2580 {
2581 while( (mWhenSliders[minF] <= mWhens[i]) & (minF < (int)mBandsInUse) )
2582 minF++;
2583 minF--;
2584 if( minF < 0 ) //before first slider
2585 {
2586 dist = mWhenSliders[0] - mWhens[i];
2587 span = mWhenSliders[1] - mWhenSliders[0];
2588 if( dist < span )
2589 value = mEQVals[0]*(1. + cos(M_PI*dist/span))/2.;
2590 else
2591 value = 0.;
2592 }
2593 else
2594 {
2595 if( mWhens[i] > mWhenSliders[mBandsInUse-1] ) //after last fader
2596 {
2598 dist = mWhens[i] - mWhenSliders[mBandsInUse-1];
2599 if( dist < span )
2600 value = mEQVals[mBandsInUse-1]*(1. + cos(M_PI*dist/span))/2.;
2601 else
2602 value = 0.;
2603 }
2604 else //normal case
2605 {
2606 span = mWhenSliders[minF+1] - mWhenSliders[minF];
2607 dist = mWhenSliders[minF+1] - mWhens[i];
2608 value = mEQVals[minF]*(1. + cos(M_PI*(span-dist)/span))/2. +
2609 mEQVals[minF+1]*(1. + cos(M_PI*dist/span))/2.;
2610 }
2611 }
2612 if(mWhens[i]<=0.)
2613 env->Reassign(0., value);
2614 env->Insert( mWhens[i], value );
2615 }
2616 env->Reassign( 1., value );
2617 break;
2618 }
2619
2620 case kCubic: // Cubic Spline
2621 {
2622 double y2[NUMBER_OF_BANDS+1];
2625 for(double xf=0; xf<1.; xf+=1./NUM_PTS)
2626 {
2627 env->Insert(xf, splint(mWhenSliders, mEQVals, mBandsInUse+1, y2, xf));
2628 }
2629 break;
2630 }
2631 }
2632
2633 ForceRecalc();
2634}
2635
2636void EffectEqualization::spline(double x[], double y[], size_t n, double y2[])
2637{
2638 wxASSERT( n > 0 );
2639
2640 double p, sig;
2641 Doubles u{ n };
2642
2643 y2[0] = 0.; //
2644 u[0] = 0.; //'natural' boundary conditions
2645 for (size_t i = 1; i + 1 < n; i++)
2646 {
2647 sig = ( x[i] - x[i-1] ) / ( x[i+1] - x[i-1] );
2648 p = sig * y2[i-1] + 2.;
2649 y2[i] = (sig - 1.)/p;
2650 u[i] = ( y[i+1] - y[i] ) / ( x[i+1] - x[i] ) - ( y[i] - y[i-1] ) / ( x[i] - x[i-1] );
2651 u[i] = (6.*u[i]/( x[i+1] - x[i-1] ) - sig * u[i-1]) / p;
2652 }
2653 y2[n - 1] = 0.;
2654 for (size_t i = n - 1; i--;)
2655 y2[i] = y2[i]*y2[i+1] + u[i];
2656}
2657
2658double EffectEqualization::splint(double x[], double y[], size_t n, double y2[], double xr)
2659{
2660 wxASSERT( n > 1 );
2661
2662 double a, b, h;
2663 static double xlast = 0.; // remember last x value requested
2664 static size_t k = 0; // and which interval we were in
2665
2666 if( xr < xlast )
2667 k = 0; // gone back to start, (or somewhere to the left)
2668 xlast = xr;
2669 while( (x[k] <= xr) && (k + 1 < n) )
2670 k++;
2671 wxASSERT( k > 0 );
2672 k--;
2673 h = x[k+1] - x[k];
2674 a = ( x[k+1] - xr )/h;
2675 b = (xr - x[k])/h;
2676 return( a*y[k]+b*y[k+1]+((a*a*a-a)*y2[k]+(b*b*b-b)*y2[k+1])*h*h/6.);
2677}
2678
2680{
2681}
2682
2683void EffectEqualization::OnSize(wxSizeEvent & event)
2684{
2685 mUIParent->Layout();
2686 event.Skip();
2687}
2688
2689void EffectEqualization::OnSlider(wxCommandEvent & event)
2690{
2691 wxSlider *s = (wxSlider *)event.GetEventObject();
2692 for (size_t i = 0; i < mBandsInUse; i++)
2693 {
2694 if( s == mSliders[i])
2695 {
2696 int posn = mSliders[i]->GetValue();
2697 if( wxGetKeyState(WXK_SHIFT) )
2698 {
2699 if( posn > mSlidersOld[i] )
2700 mEQVals[i] += (float).1;
2701 else
2702 if( posn < mSlidersOld[i] )
2703 mEQVals[i] -= .1f;
2704 }
2705 else
2706 mEQVals[i] += (posn - mSlidersOld[i]);
2707 if( mEQVals[i] > 20. )
2708 mEQVals[i] = 20.;
2709 if( mEQVals[i] < -20. )
2710 mEQVals[i] = -20.;
2711 int newPosn = (int)mEQVals[i];
2712 mSliders[i]->SetValue( newPosn );
2713 mSlidersOld[i] = newPosn;
2714 wxString tip;
2715 if( kThirdOct[i] < 1000.)
2716 tip.Printf( wxT("%dHz\n%.1fdB"), (int)kThirdOct[i], mEQVals[i] );
2717 else
2718 tip.Printf( wxT("%gkHz\n%.1fdB"), kThirdOct[i]/1000., mEQVals[i] );
2719 s->SetToolTip(tip);
2720 break;
2721 }
2722 }
2723 GraphicEQ(mLogEnvelope.get());
2725}
2726
2727void EffectEqualization::OnInterp(wxCommandEvent & WXUNUSED(event))
2728{
2729 bool bIsGraphic = !mDrawMode;
2730 if (bIsGraphic)
2731 {
2732 GraphicEQ(mLogEnvelope.get());
2734 }
2735 mInterp = mInterpChoice->GetSelection();
2736}
2737
2738void EffectEqualization::OnDrawMode(wxCommandEvent & WXUNUSED(event))
2739{
2740 mDrawMode = true;
2741 UpdateDraw();
2742}
2743
2744void EffectEqualization::OnGraphicMode(wxCommandEvent & WXUNUSED(event))
2745{
2746 mDrawMode = false;
2747 UpdateGraphic();
2748}
2749
2750void EffectEqualization::OnSliderM(wxCommandEvent & WXUNUSED(event))
2751{
2752 size_t m = 2 * mMSlider->GetValue() + 1;
2753 // Must be odd
2754 wxASSERT( (m & 1) == 1 );
2755
2756 if (m != mM) {
2757 mM = m;
2758 wxString tip;
2759 tip.Printf(wxT("%d"), (int)mM);
2760 mMText->SetLabel(tip);
2761 mMText->SetName(mMText->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
2762 mMSlider->SetToolTip(tip);
2763
2764 ForceRecalc();
2765 }
2766}
2767
2768void EffectEqualization::OnSliderDBMIN(wxCommandEvent & WXUNUSED(event))
2769{
2770 float dB = mdBMinSlider->GetValue();
2771 if (dB != mdBMin) {
2772 mdBMin = dB;
2773 wxString tip;
2774 tip.Printf(_("%d dB"), (int)mdBMin);
2775 mdBMinSlider->SetToolTip(tip);
2776 UpdateRuler();
2777 }
2778}
2779
2780void EffectEqualization::OnSliderDBMAX(wxCommandEvent & WXUNUSED(event))
2781{
2782 float dB = mdBMaxSlider->GetValue();
2783 if (dB != mdBMax) {
2784 mdBMax = dB;
2785 wxString tip;
2786 tip.Printf(_("%d dB"), (int)mdBMax);
2787 mdBMaxSlider->SetToolTip(tip);
2788 UpdateRuler();
2789 }
2790}
2791
2792//
2793// New curve was selected
2794//
2795void EffectEqualization::OnCurve(wxCommandEvent & WXUNUSED(event))
2796{
2797 // Select NEW curve
2798 wxASSERT( mCurve != NULL );
2799 setCurve( mCurve->GetCurrentSelection() );
2800 if( !mDrawMode )
2801 UpdateGraphic();
2802}
2803
2804//
2805// User wants to modify the list in some way
2806//
2807void EffectEqualization::OnManage(wxCommandEvent & WXUNUSED(event))
2808{
2809 EditCurvesDialog d(mUIParent, this, mCurve->GetSelection());
2810 d.ShowModal();
2811
2812 // Reload the curve names
2813 UpdateCurves();
2814
2815 // Allow control to resize
2816 mUIParent->Layout();
2817}
2818
2819void EffectEqualization::OnClear(wxCommandEvent & WXUNUSED(event))
2820{
2821 Flatten();
2822}
2823
2824void EffectEqualization::OnInvert(wxCommandEvent & WXUNUSED(event)) // Inverts any curve
2825{
2826 if(!mDrawMode) // Graphic (Slider) mode. Invert the sliders.
2827 {
2828 for (size_t i = 0; i < mBandsInUse; i++)
2829 {
2830 mEQVals[i] = -mEQVals[i];
2831 int newPosn = (int)mEQVals[i];
2832 mSliders[i]->SetValue( newPosn );
2833 mSlidersOld[i] = newPosn;
2834
2835 wxString tip;
2836 if( kThirdOct[i] < 1000.)
2837 tip.Printf( wxT("%dHz\n%.1fdB"), (int)kThirdOct[i], mEQVals[i] );
2838 else
2839 tip.Printf( wxT("%gkHz\n%.1fdB"), kThirdOct[i]/1000., mEQVals[i] );
2840 mSliders[i]->SetToolTip(tip);
2841 }
2842 GraphicEQ(mLogEnvelope.get());
2843 }
2844 else // Draw mode. Invert the points.
2845 {
2846 bool lin = IsLinear(); // refers to the 'log' or 'lin' of the frequency scale, not the amplitude
2847 size_t numPoints; // number of points in the curve/envelope
2848
2849 // determine if log or lin curve is the current one
2850 // and find out how many points are in the curve
2851 if(lin) // lin freq scale and so envelope
2852 {
2853 numPoints = mLinEnvelope->GetNumberOfPoints();
2854 }
2855 else
2856 {
2857 numPoints = mLogEnvelope->GetNumberOfPoints();
2858 }
2859
2860 if( numPoints == 0 )
2861 return;
2862
2863 Doubles when{ numPoints };
2864 Doubles value{ numPoints };
2865
2866 if(lin)
2867 mLinEnvelope->GetPoints( when.get(), value.get(), numPoints );
2868 else
2869 mLogEnvelope->GetPoints( when.get(), value.get(), numPoints );
2870
2871 // invert the curve
2872 for (size_t i = 0; i < numPoints; i++)
2873 {
2874 if(lin)
2875 mLinEnvelope->Reassign(when[i] , -value[i]);
2876 else
2877 mLogEnvelope->Reassign(when[i] , -value[i]);
2878 }
2879
2880 // copy it back to the other one (just in case)
2881 if(lin)
2882 EnvLinToLog();
2883 else
2884 EnvLogToLin();
2885 }
2886
2887 // and update the display etc
2888 ForceRecalc();
2890}
2891
2892void EffectEqualization::OnGridOnOff(wxCommandEvent & WXUNUSED(event))
2893{
2894 mDrawGrid = mGridOnOff->IsChecked();
2895 mPanel->Refresh(false);
2896}
2897
2898void EffectEqualization::OnLinFreq(wxCommandEvent & WXUNUSED(event))
2899{
2900 mLin = mLinFreq->IsChecked();
2901 if(IsLinear()) //going from log to lin freq scale
2902 {
2903 mFreqRuler->ruler.SetLog(false);
2905 EnvLogToLin();
2906 mEnvelope = mLinEnvelope.get();
2907 mLin = true;
2908 }
2909 else //going from lin to log freq scale
2910 {
2911 mFreqRuler->ruler.SetLog(true);
2913 EnvLinToLog();
2914 mEnvelope = mLogEnvelope.get();
2915 mLin = false;
2916 }
2917 mFreqRuler->Refresh(false);
2918 ForceRecalc();
2919}
2920
2921#ifdef EXPERIMENTAL_EQ_SSE_THREADED
2922
2923void EffectEqualization::OnProcessingRadio(wxCommandEvent & event)
2924{
2925 int testEvent=event.GetId();
2926 switch(testEvent)
2927 {
2928 case ID_DefaultMath: EffectEqualization48x::SetMathPath(MATH_FUNCTION_ORIGINAL);
2929 break;
2930 case ID_SSE: EffectEqualization48x::SetMathPath(MATH_FUNCTION_SSE);
2931 break;
2932 case ID_SSEThreaded: EffectEqualization48x::SetMathPath(MATH_FUNCTION_THREADED | MATH_FUNCTION_SSE);
2933 break;
2934 case ID_AVX: testEvent = 2;
2935 break;
2936 case ID_AVXThreaded: testEvent = 2;
2937 break;
2938 }
2939
2940};
2941
2942void EffectEqualization::OnBench( wxCommandEvent & event)
2943{
2944 mBench=true;
2945 // OnOk(event);
2946}
2947
2948#endif
2949
2950//----------------------------------------------------------------------------
2951// EqualizationPanel
2952//----------------------------------------------------------------------------
2953
2954BEGIN_EVENT_TABLE(EqualizationPanel, wxPanelWrapper)
2956 EVT_MOUSE_EVENTS(EqualizationPanel::OnMouseEvent)
2957 EVT_MOUSE_CAPTURE_LOST(EqualizationPanel::OnCaptureLost)
2960
2962 wxWindow *parent, wxWindowID winid, EffectEqualization *effect)
2963: wxPanelWrapper(parent, winid)
2964{
2965 mParent = parent;
2966 mEffect = effect;
2967
2968 mBitmap = NULL;
2969 mWidth = 0;
2970 mHeight = 0;
2971
2972 mLinEditor = std::make_unique<EnvelopeEditor>(*mEffect->mLinEnvelope, false);
2973 mLogEditor = std::make_unique<EnvelopeEditor>(*mEffect->mLogEnvelope, false);
2974 mEffect->mEnvelope->Flatten(0.);
2975 mEffect->mEnvelope->SetTrackLen(1.0);
2976
2977 ForceRecalc();
2978}
2979
2981{
2982 if(HasCapture())
2983 ReleaseMouse();
2984}
2985
2987{
2988 mRecalcRequired = true;
2989 Refresh(false);
2990}
2991
2993{
2996
2997 mEffect->CalcFilter(); //to calculate the actual response
2999}
3000
3001void EqualizationPanel::OnSize(wxSizeEvent & WXUNUSED(event))
3002{
3003 Refresh( false );
3004}
3005
3006#include "../TrackPanelDrawingContext.h"
3007void EqualizationPanel::OnPaint(wxPaintEvent & WXUNUSED(event))
3008{
3009 wxPaintDC dc(this);
3010 if(mRecalcRequired) {
3011 Recalc();
3012 mRecalcRequired = false;
3013 }
3014 int width, height;
3015 GetSize(&width, &height);
3016
3017 if (!mBitmap || mWidth!=width || mHeight!=height)
3018 {
3019 mWidth = width;
3020 mHeight = height;
3021 mBitmap = std::make_unique<wxBitmap>(mWidth, mHeight,24);
3022 }
3023
3024 wxBrush bkgndBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE));
3025
3026 wxMemoryDC memDC;
3027 memDC.SelectObject(*mBitmap);
3028
3029 wxRect bkgndRect;
3030 bkgndRect.x = 0;
3031 bkgndRect.y = 0;
3032 bkgndRect.width = mWidth;
3033 bkgndRect.height = mHeight;
3034 memDC.SetBrush(bkgndBrush);
3035 memDC.SetPen(*wxTRANSPARENT_PEN);
3036 memDC.DrawRectangle(bkgndRect);
3037
3038 bkgndRect.y = mHeight;
3039 memDC.DrawRectangle(bkgndRect);
3040
3041 wxRect border;
3042 border.x = 0;
3043 border.y = 0;
3044 border.width = mWidth;
3045 border.height = mHeight;
3046
3047 memDC.SetBrush(*wxWHITE_BRUSH);
3048 memDC.SetPen(*wxBLACK_PEN);
3049 memDC.DrawRectangle(border);
3050
3051 mEnvRect = border;
3053
3054 // Pure blue x-axis line
3055 memDC.SetPen(wxPen(theTheme.Colour( clrGraphLines ), 1, wxPENSTYLE_SOLID));
3056 int center = (int) (mEnvRect.height * mEffect->mdBMax/(mEffect->mdBMax-mEffect->mdBMin) + .5);
3057 AColor::Line(memDC,
3058 mEnvRect.GetLeft(), mEnvRect.y + center,
3059 mEnvRect.GetRight(), mEnvRect.y + center);
3060
3061 // Draw the grid, if asked for. Do it now so it's underneath the main plots.
3062 if( mEffect->mDrawGrid )
3063 {
3064 mEffect->mFreqRuler->ruler.DrawGrid(memDC, mEnvRect.height, true, true, PANELBORDER, PANELBORDER);
3065 mEffect->mdBRuler->ruler.DrawGrid(memDC, mEnvRect.width, true, true, PANELBORDER, PANELBORDER);
3066 }
3067
3068 // Med-blue envelope line
3069 memDC.SetPen(wxPen(theTheme.Colour(clrGraphLines), 3, wxPENSTYLE_SOLID));
3070
3071 // Draw envelope
3072 int x, y, xlast = 0, ylast = 0;
3073 {
3074 Doubles values{ size_t(mEnvRect.width) };
3075 mEffect->mEnvelope->GetValues(values.get(), mEnvRect.width, 0.0, 1.0 / mEnvRect.width);
3076 bool off = false, off1 = false;
3077 for (int i = 0; i < mEnvRect.width; i++)
3078 {
3079 x = mEnvRect.x + i;
3080 y = lrint(mEnvRect.height*((mEffect->mdBMax - values[i]) / (mEffect->mdBMax - mEffect->mdBMin)) + .25); //needs more optimising, along with'what you get'?
3081 if (y >= mEnvRect.height)
3082 {
3083 y = mEnvRect.height - 1;
3084 off = true;
3085 }
3086 else
3087 {
3088 off = false;
3089 off1 = false;
3090 }
3091 if ((i != 0) & (!off1))
3092 {
3093 AColor::Line(memDC, xlast, ylast,
3094 x, mEnvRect.y + y);
3095 }
3096 off1 = off;
3097 xlast = x;
3098 ylast = mEnvRect.y + y;
3099 }
3100 }
3101
3102 //Now draw the actual response that you will get.
3103 //mFilterFunc has a linear scale, window has a log one so we have to fiddle about
3104 memDC.SetPen(wxPen(theTheme.Colour( clrResponseLines ), 1, wxPENSTYLE_SOLID));
3105 double scale = (double)mEnvRect.height/(mEffect->mdBMax-mEffect->mdBMin); //pixels per dB
3106 double yF; //gain at this freq
3107 double delta = mEffect->mHiFreq / (((double)mEffect->mWindowSize / 2.)); //size of each freq bin
3108
3109 bool lin = mEffect->IsLinear(); // log or lin scale?
3110
3111 double loLog = log10(mEffect->mLoFreq);
3112 double step = lin ? mEffect->mHiFreq : (log10(mEffect->mHiFreq) - loLog);
3113 step /= ((double)mEnvRect.width-1.);
3114 double freq; //actual freq corresponding to x position
3115 int halfM = (mEffect->mM - 1) / 2;
3116 int n; //index to mFreqFunc
3117 for(int i=0; i<mEnvRect.width; i++)
3118 {
3119 x = mEnvRect.x + i;
3120 freq = lin ? step*i : pow(10., loLog + i*step); //Hz
3121 if( ( lin ? step : (pow(10., loLog + (i+1)*step)-freq) ) < delta)
3122 { //not enough resolution in FFT
3123 // set up for calculating cos using recurrence - faster than calculating it directly each time
3124 double theta = M_PI*freq/mEffect->mHiFreq; //radians, normalized
3125 double wtemp = sin(0.5 * theta);
3126 double wpr = -2.0 * wtemp * wtemp;
3127 double wpi = -1.0 * sin(theta);
3128 double wr = cos(theta*halfM);
3129 double wi = sin(theta*halfM);
3130
3131 yF = 0.;
3132 for(int j=0;j<halfM;j++)
3133 {
3134 yF += 2. * mOutr[j] * wr; // This works for me, compared to the previous version. Compare wr to cos(theta*(halfM-j)). Works for me. Keep everything as doubles though.
3135 // do recurrence
3136 wr = (wtemp = wr) * wpr - wi * wpi + wr;
3137 wi = wi * wpr + wtemp * wpi + wi;
3138 }
3139 yF += mOutr[halfM];
3140 yF = fabs(yF);
3141 if(yF!=0.)
3142 yF = LINEAR_TO_DB(yF);
3143 else
3144 yF = mEffect->mdBMin;
3145 }
3146 else
3147 { //use FFT, it has enough resolution
3148 n = (int)(freq/delta + .5);
3149 if(pow(mEffect->mFilterFuncR[n],2)+pow(mEffect->mFilterFuncI[n],2)!=0.)
3150 yF = 10.0*log10(pow(mEffect->mFilterFuncR[n],2)+pow(mEffect->mFilterFuncI[n],2)); //10 here, a power
3151 else
3152 yF = mEffect->mdBMin;
3153 }
3154 if(yF < mEffect->mdBMin)
3155 yF = mEffect->mdBMin;
3156 yF = center-scale*yF;
3157 if(yF>mEnvRect.height)
3158 yF = mEnvRect.height - 1;
3159 if(yF<0.)
3160 yF=0.;
3161 y = (int)(yF+.5);
3162
3163 if (i != 0)
3164 {
3165 AColor::Line(memDC, xlast, ylast, x, mEnvRect.y + y);
3166 }
3167 xlast = x;
3168 ylast = mEnvRect.y + y;
3169 }
3170
3171 memDC.SetPen(*wxBLACK_PEN);
3172 if( mEffect->mDrawMode )
3173 {
3174 ZoomInfo zoomInfo( 0.0, mEnvRect.width-1 );
3175
3176 // Back pointer to TrackPanel won't be needed in the one drawing
3177 // function we use here
3178 TrackArtist artist( nullptr );
3179
3180 artist.pZoomInfo = &zoomInfo;
3181 TrackPanelDrawingContext context{ memDC, {}, {}, &artist };
3183 context, mEnvRect, false, 0.0,
3184 mEffect->mdBMin, mEffect->mdBMax, false);
3185 }
3186
3187 dc.Blit(0, 0, mWidth, mHeight, &memDC, 0, 0, wxCOPY, FALSE);
3188}
3189
3190void EqualizationPanel::OnMouseEvent(wxMouseEvent & event)
3191{
3192 if (!mEffect->mDrawMode)
3193 {
3194 return;
3195 }
3196
3197 if (event.ButtonDown() && !HasCapture())
3198 {
3199 CaptureMouse();
3200 }
3201
3202 auto &pEditor = (mEffect->mLin ? mLinEditor : mLogEditor);
3203 if (pEditor->MouseEvent(event, mEnvRect, ZoomInfo(0.0, mEnvRect.width),
3204 false, 0.0,
3206 {
3208 ForceRecalc();
3209 }
3210
3211 if (event.ButtonUp() && HasCapture())
3212 {
3213 ReleaseMouse();
3214 }
3215}
3216
3217void EqualizationPanel::OnCaptureLost(wxMouseCaptureLostEvent & WXUNUSED(event))
3218{
3219 if (HasCapture())
3220 {
3221 ReleaseMouse();
3222 }
3223}
3224
3225//----------------------------------------------------------------------------
3226// EditCurvesDialog
3227//----------------------------------------------------------------------------
3228// Note that the 'modified' curve used to be called 'custom' but is now called 'unnamed'
3229// Some things that deal with 'unnamed' curves still use, for example, 'mCustomBackup' as variable names.
3231
3232BEGIN_EVENT_TABLE(EditCurvesDialog, wxDialogWrapper)
3239 EVT_BUTTON(LibraryButtonID, EditCurvesDialog::OnLibrary)
3243 EditCurvesDialog::OnListSelectionChange)
3244 EVT_LIST_ITEM_DESELECTED(CurvesListID,
3245 EditCurvesDialog::OnListSelectionChange)
3247
3248EditCurvesDialog::EditCurvesDialog(wxWindow * parent, EffectEqualization * effect, int position):
3249wxDialogWrapper(parent, wxID_ANY, XO("Manage Curves List"),
3250 wxDefaultPosition, wxDefaultSize,
3251 wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
3252{
3253 SetLabel(XO("Manage Curves")); // Provide visual label
3254 SetName(XO("Manage Curves List")); // Provide audible label
3255 mParent = parent;
3256 mEffect = effect;
3257 mPosition = position;
3258 // make a copy of mEffect->mCurves here to muck about with.
3259 mEditCurves.clear();
3260 for (unsigned int i = 0; i < mEffect->mCurves.size(); i++)
3261 {
3262 mEditCurves.push_back(mEffect->mCurves[i].Name);
3263 mEditCurves[i].points = mEffect->mCurves[i].points;
3264 }
3265
3266 Populate();
3267 SetMinSize(GetSize());
3268}
3269
3271{
3272}
3273
3276{
3277 //------------------------- Main section --------------------
3278 ShuttleGui S(this, eIsCreating);
3280 // ----------------------- End of main section --------------
3281}
3282
3285{
3286 S.StartHorizontalLay(wxEXPAND);
3287 {
3288 S.StartStatic(XO("&Curves"), 1);
3289 {
3290 mList = S.Id(CurvesListID)
3291 .Style(wxSUNKEN_BORDER | wxLC_REPORT | wxLC_HRULES | wxLC_VRULES )
3292 .AddListControlReportMode({
3293 { XO("Curve Name"), wxLIST_FORMAT_RIGHT }
3294 });
3295 }
3296 S.EndStatic();
3297 S.StartVerticalLay(0);
3298 {
3299 S.Id(UpButtonID).AddButton(XXO("Move &Up"), wxALIGN_LEFT);
3300 S.Id(DownButtonID).AddButton(XXO("Move &Down"), wxALIGN_LEFT);
3301 S.Id(RenameButtonID).AddButton(XXO("&Rename..."), wxALIGN_LEFT);
3302 S.Id(DeleteButtonID).AddButton(XXO("D&elete..."), wxALIGN_LEFT);
3303 S.Id(ImportButtonID).AddButton(XXO("I&mport..."), wxALIGN_LEFT);
3304 S.Id(ExportButtonID).AddButton(XXO("E&xport..."), wxALIGN_LEFT);
3305 S.Id(LibraryButtonID).AddButton(XXO("&Get More..."), wxALIGN_LEFT);
3306 S.Id(DefaultsButtonID).AddButton(XXO("De&faults"), wxALIGN_LEFT);
3307 }
3308 S.EndVerticalLay();
3309 }
3310 S.EndHorizontalLay();
3311 S.AddStandardButtons();
3312 S.StartStatic(XO("Help"));
3313 S.AddConstTextBox( {}, XO("Rename 'unnamed' to save a new entry.\n'OK' saves all changes, 'Cancel' doesn't."));
3314 S.EndStatic();
3316 Fit();
3317
3318 return;
3319}
3320
3322{
3323 mList->DeleteAllItems();
3324 for (unsigned int i = 0; i < mEditCurves.size(); i++)
3325 mList->InsertItem(i, mEditCurves[i].Name);
3326 mList->SetColumnWidth(0, wxLIST_AUTOSIZE);
3327 int curvesWidth = mList->GetColumnWidth(0);
3328 mList->SetColumnWidth(0, wxLIST_AUTOSIZE_USEHEADER);
3329 int headerWidth = mList->GetColumnWidth(0);
3330 mList->SetColumnWidth(0, wxMax(headerWidth, curvesWidth));
3331 // use 'position' to set focus
3332 mList->EnsureVisible(position);
3333 mList->SetItemState(position, wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED, wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED);
3334}
3335
3336void EditCurvesDialog::OnUp(wxCommandEvent & WXUNUSED(event))
3337{
3338 long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3339 if ( item == -1 )
3340 return; // no items selected
3341 if( item == 0 )
3342 item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); // top item selected, can't move up
3343 int state;
3344 while( item != -1 )
3345 {
3346 if ( item == mList->GetItemCount()-1)
3347 { // 'unnamed' always stays at the bottom
3348 mEffect->Effect::MessageBox(
3349 XO("'unnamed' always stays at the bottom of the list"),
3351 XO("'unnamed' is special") ); // these could get tedious!
3352 return;
3353 }
3354 state = mList->GetItemState(item-1, wxLIST_STATE_SELECTED);
3355 if ( state != wxLIST_STATE_SELECTED )
3356 { // swap this with one above but only if it isn't selected
3357 EQCurve temp(wxT("temp"));
3358 temp.Name = mEditCurves[item].Name;
3359 temp.points = mEditCurves[item].points;
3360 mEditCurves[item].Name = mEditCurves[item-1].Name;
3361 mEditCurves[item].points = mEditCurves[item-1].points;
3362 mEditCurves[item-1].Name = temp.Name;
3363 mEditCurves[item-1].points = temp.points;
3364 wxString sTemp = mList->GetItemText(item);
3365 mList->SetItem(item, 0, mList->GetItemText(item-1));
3366 mList->SetItem(item-1, 0, sTemp);
3367 mList->SetItemState(item, 0, wxLIST_STATE_SELECTED);
3368 mList->SetItemState(item-1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
3369 }
3370 item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3371 }
3372}
3373
3374void EditCurvesDialog::OnDown(wxCommandEvent & WXUNUSED(event))
3375{ // looks harder than OnUp as we need to seek backwards up the list, hence GetPreviousItem
3376 long item = GetPreviousItem(mList->GetItemCount());
3377 if( item == -1 )
3378 return; // nothing selected
3379 int state;
3380 while( item != -1 )
3381 {
3382 if( (item != mList->GetItemCount()-1) && (item != mList->GetItemCount()-2) )
3383 { // can't move 'unnamed' down, or the one above it
3384 state = mList->GetItemState(item+1, wxLIST_STATE_SELECTED);
3385 if ( state != wxLIST_STATE_SELECTED )
3386 { // swap this with one below but only if it isn't selected
3387 EQCurve temp(wxT("temp"));
3388 temp.Name = mEditCurves[item].Name;
3389 temp.points = mEditCurves[item].points;
3390 mEditCurves[item].Name = mEditCurves[item+1].Name;
3391 mEditCurves[item].points = mEditCurves[item+1].points;
3392 mEditCurves[item+1].Name = temp.Name;
3393 mEditCurves[item+1].points = temp.points;
3394 wxString sTemp = mList->GetItemText(item);
3395 mList->SetItem(item, 0, mList->GetItemText(item+1));
3396 mList->SetItem(item+1, 0, sTemp);
3397 mList->SetItemState(item, 0, wxLIST_STATE_SELECTED);
3398 mList->SetItemState(item+1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
3399 }
3400 }
3401 item = GetPreviousItem(item);
3402 }
3403}
3404
3405long EditCurvesDialog::GetPreviousItem(long item) // wx doesn't have this
3406{
3407 long lastItem = -1;
3408 long itemTemp = mList->GetNextItem(-1, wxLIST_NEXT_ALL,
3409 wxLIST_STATE_SELECTED);
3410 while( (itemTemp != -1) && (itemTemp < item) )
3411 {
3412 lastItem = itemTemp;
3413 itemTemp = mList->GetNextItem(itemTemp, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3414 }
3415 return lastItem;
3416}
3417
3418// Rename curve/curves
3419void EditCurvesDialog::OnRename(wxCommandEvent & WXUNUSED(event))
3420{
3421 wxString name;
3422 int numCurves = mEditCurves.size();
3423 int curve = 0;
3424
3425 // Setup list of characters that aren't allowed
3426 wxArrayStringEx exclude{
3427 wxT("<") ,
3428 wxT(">") ,
3429 wxT("'") ,
3430 wxT("\"") ,
3431 };
3432
3433 // Get the first one to be renamed
3434 long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3435 long firstItem = item; // for reselection with PopulateList
3436 while(item >= 0)
3437 {
3438 // Prompt the user until a valid name is enter or cancelled
3439 bool overwrite = false;
3440 bool bad = true;
3441 while( bad ) // Check for an unacceptable duplicate
3442 { // Show the dialog and bail if the user cancels
3443 bad = false;
3444 // build the dialog
3445 AudacityTextEntryDialog dlg( this,
3446 XO("Rename '%s' to...").Format( mEditCurves[ item ].Name ),
3447 XO("Rename...") );
3448 dlg.SetTextValidator( wxFILTER_EXCLUDE_CHAR_LIST );
3449 dlg.SetName(
3450 wxString::Format( _("Rename '%s'"), mEditCurves[ item ].Name ) );
3451 wxTextValidator *tv = dlg.GetTextValidator();
3452 tv->SetExcludes( exclude ); // Tell the validator about excluded chars
3453 if( dlg.ShowModal() == wxID_CANCEL )
3454 {
3455 bad = true;
3456 break;
3457 }
3458
3459 // Extract the name from the dialog
3460 name = dlg.GetValue();
3461
3462 // Search list of curves for a duplicate name
3463 for( curve = 0; curve < numCurves; curve++ )
3464 {
3465 wxString temp = mEditCurves[ curve ].Name;
3466 if( name == mEditCurves[ curve ].Name ) // case sensitive
3467 {
3468 bad = true;
3469 if( curve == item ) // trying to rename a curve with the same name
3470 {
3471 mEffect->Effect::MessageBox(
3472 XO("Name is the same as the original one"),
3473 wxOK,
3474 XO("Same name") );
3475 break;
3476 }
3477 int answer = mEffect->Effect::MessageBox(
3478 XO("Overwrite existing curve '%s'?").Format( name ),
3479 wxYES_NO,
3480 XO("Curve exists") );
3481 if (answer == wxYES)
3482 {
3483 bad = false;
3484 overwrite = true; // we are going to overwrite the one with this name
3485 break;
3486 }
3487 }
3488 }
3489 if( name.empty() || name == wxT("unnamed") )
3490 bad = true;
3491 }
3492
3493 // if bad, we cancelled the rename dialog, so nothing to do.
3494 if( bad == true )
3495 ;
3496 else if(overwrite){
3497 // Overwrite another curve.
3498 // JKC: because 'overwrite' is true, 'curve' is the number of the curve that
3499 // we are about to overwrite.
3500 mEditCurves[ curve ].Name = name;
3501 mEditCurves[ curve ].points = mEditCurves[ item ].points;
3502 // if renaming the unnamed item, then select it,
3503 // otherwise get rid of the item we've renamed.
3504 if( item == (numCurves-1) )
3505 mList->SetItem(curve, 0, name);
3506 else
3507 {
3508 mEditCurves.erase( mEditCurves.begin() + item );
3509 numCurves--;
3510 }
3511 }
3512 else if( item == (numCurves-1) ) // renaming 'unnamed'
3513 { // Create a NEW entry
3514 mEditCurves.push_back( EQCurve( wxT("unnamed") ) );
3515 // Copy over the points
3516 mEditCurves[ numCurves ].points = mEditCurves[ numCurves - 1 ].points;
3517 // Give the original unnamed entry the NEW name
3518 mEditCurves[ numCurves - 1 ].Name = name;
3519 numCurves++;
3520 }
3521 else // just rename (the 'normal' case)
3522 {
3523 mEditCurves[ item ].Name = name;
3524 mList->SetItem(item, 0, name);
3525 }
3526 // get next selected item
3527 item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3528 }
3529
3530 PopulateList(firstItem); // Note: only saved to file when you OK out of the dialog
3531 return;
3532}
3533
3534// Delete curve/curves
3535void EditCurvesDialog::OnDelete(wxCommandEvent & WXUNUSED(event))
3536{
3537 // We could count them here
3538 // And then put in a 'Delete N items?' prompt.
3539
3540#if 0 // 'one at a time' prompt code
3541 // Get the first one to be deleted
3542 long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3543 // Take care, mList and mEditCurves will get out of sync as curves are deleted
3544 int deleted = 0;
3545 long highlight = -1;
3546
3547 while(item >= 0)
3548 {
3549 if(item == mList->GetItemCount()-1) //unnamed
3550 {
3551 mEffect->Effect::MessageBox(
3552 XO("You cannot delete the 'unnamed' curve."),
3553 wxOK | wxCENTRE,
3554 XO("Can't delete 'unnamed'") );
3555 }
3556 else
3557 {
3558 // Create the prompt
3559 auto quest = XO("Delete '%s'?")
3560 .Format(mEditCurves[ item-deleted ].Name));
3561
3562 // Ask for confirmation before removal
3563 int ans = mEffect->Effect::MessageBox(
3564 quest,
3565 wxYES_NO | wxCENTRE,
3566 XO("Confirm Deletion") );
3567 if( ans == wxYES )
3568 { // Remove the curve from the array
3569 mEditCurves.RemoveAt( item-deleted );
3570 deleted++;
3571 }
3572 else
3573 highlight = item-deleted; // if user presses 'No', select that curve
3574 }
3575 // get next selected item
3576 item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3577 }
3578
3579 if(highlight == -1)
3580 PopulateList(mEditCurves.size()-1); // set 'unnamed' as the selected curve
3581 else
3582 PopulateList(highlight); // user said 'No' to deletion
3583#else // 'DELETE all N' code
3584 int count = mList->GetSelectedItemCount();
3585 long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3586 // Create the prompt
3587 TranslatableString quest;
3588 if( count > 1 )
3589 quest = XO("Delete %d items?").Format( count );
3590 else
3591 if( count == 1 )
3592 quest = XO("Delete '%s'?").Format( mEditCurves[ item ].Name );
3593 else
3594 return;
3595 // Ask for confirmation before removal
3596 int ans = mEffect->Effect::MessageBox(
3597 quest,
3598 wxYES_NO | wxCENTRE,
3599 XO("Confirm Deletion") );
3600 if( ans == wxYES )
3601 { // Remove the curve(s) from the array
3602 // Take care, mList and mEditCurves will get out of sync as curves are deleted
3603 int deleted = 0;
3604 while(item >= 0)
3605 {
3606 // TODO: Migrate to the standard "Manage" dialog.
3607 if(item == mList->GetItemCount()-1) //unnamed
3608 {
3609 mEffect->Effect::MessageBox(
3610 XO("You cannot delete the 'unnamed' curve, it is special."),
3612 XO("Can't delete 'unnamed'") );
3613 }
3614 else
3615 {
3616 mEditCurves.erase( mEditCurves.begin() + item - deleted );
3617 deleted++;
3618 }
3619 item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3620 }
3621 PopulateList(mEditCurves.size() - 1); // set 'unnamed' as the selected curve
3622 }
3623#endif
3624}
3625
3627{
3628 static const FileNames::FileTypes results{
3630 };
3631 return results;
3632}
3633
3634void EditCurvesDialog::OnImport( wxCommandEvent & WXUNUSED(event))
3635{
3636 FileDialogWrapper filePicker(
3637 this,
3638 XO("Choose an EQ curve file"), FileNames::DataDir(), wxT(""),
3639 XMLtypes() );
3640 wxString fileName;
3641 if( filePicker.ShowModal() == wxID_CANCEL)
3642 return;
3643 else
3644 fileName = filePicker.GetPath();
3645 // Use EqualizationDialog::LoadCurves to read into (temporary) mEditCurves
3646 // This may not be the best OOP way of doing it, but I don't know better (MJS)
3647 EQCurveArray temp;
3648 temp = mEffect->mCurves; // temp copy of the main dialog curves
3649 mEffect->mCurves = mEditCurves; // copy EditCurvesDialog to main interface
3650 mEffect->LoadCurves(fileName, true); // use main interface to load imported curves
3651 mEditCurves = mEffect->mCurves; // copy back to this interface
3652 mEffect->mCurves = temp; // and reset the main interface how it was
3653 PopulateList(0); // update the EditCurvesDialog dialog
3654 return;
3655}
3656
3657void EditCurvesDialog::OnExport( wxCommandEvent & WXUNUSED(event))
3658{
3659 FileDialogWrapper filePicker(this, XO("Export EQ curves as..."),
3660 FileNames::DataDir(), wxT(""),
3661 XMLtypes(),
3662 wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER); // wxFD_CHANGE_DIR?
3663 wxString fileName;
3664 if( filePicker.ShowModal() == wxID_CANCEL)
3665 return;
3666 else
3667 fileName = filePicker.GetPath();
3668
3669 EQCurveArray temp;
3670 temp = mEffect->mCurves; // backup the parent's curves
3671 EQCurveArray exportCurves; // Copy selected curves to export
3672 exportCurves.clear();
3673 long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3674 int i=0;
3675 while(item >= 0)
3676 {
3677 if(item != mList->GetItemCount()-1) // not 'unnamed'
3678 {
3679 exportCurves.push_back(mEditCurves[item].Name);
3680 exportCurves[i].points = mEditCurves[item].points;
3681 i++;
3682 }
3683 else
3684 mEffect->Effect::MessageBox(
3685 XO("You cannot export 'unnamed' curve, it is special."),
3687 XO("Cannot Export 'unnamed'") );
3688 // get next selected item
3689 item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3690 }
3691 if(i>0)
3692 {
3693 mEffect->mCurves = exportCurves;
3694 mEffect->SaveCurves(fileName);
3695 mEffect->mCurves = temp;
3696 auto message = XO("%d curves exported to %s").Format( i, fileName );
3697 mEffect->Effect::MessageBox(
3698 message,
3700 XO("Curves exported") );
3701 }
3702 else
3703 mEffect->Effect::MessageBox(
3704 XO("No curves exported"),
3706 XO("No curves exported") );
3707}
3708
3709void EditCurvesDialog::OnLibrary( wxCommandEvent & WXUNUSED(event))
3710{
3711 // full path to wiki.
3712 wxLaunchDefaultBrowser(wxT("https://wiki.audacityteam.org/wiki/EQCurvesDownload"));
3713}
3714
3715void EditCurvesDialog::OnDefaults( wxCommandEvent & WXUNUSED(event))
3716{
3717 EQCurveArray temp;
3718 temp = mEffect->mCurves;
3719 // we expect this to fail in LoadCurves (due to a lack of path) and handle that there
3720 mEffect->LoadCurves( wxT("EQDefaultCurves.xml") );
3722 mEffect->mCurves = temp;
3723 PopulateList(0); // update the EditCurvesDialog dialog
3724}
3725
3726void EditCurvesDialog::OnOK(wxCommandEvent & WXUNUSED(event))
3727{
3728 // Make a backup of the current curves
3729 wxString backupPlace = wxFileName( FileNames::DataDir(), wxT("EQBackup.xml") ).GetFullPath();
3730 mEffect->SaveCurves(backupPlace);
3731 // Load back into the main dialog
3732 mEffect->mCurves.clear();
3733 for (unsigned int i = 0; i < mEditCurves.size(); i++)
3734 {
3735 mEffect->mCurves.push_back(mEditCurves[i].Name);
3736 mEffect->mCurves[i].points = mEditCurves[i].points;
3737 }
3740// mEffect->CreateChoice();
3741 wxGetTopLevelParent(mEffect->mUIParent)->Layout();
3742// mEffect->mUIParent->Layout();
3743
3744 // Select something sensible
3745 long item = mList->GetNextItem(-1,
3746 wxLIST_NEXT_ALL,
3747 wxLIST_STATE_SELECTED);
3748 if (item == -1)
3749 item = mList->GetItemCount()-1; // nothing selected, default to 'unnamed'
3750 mEffect->setCurve(item);
3751 EndModal(true);
3752}
3753
3755{
3756 const bool enable = mList->GetSelectedItemCount() > 0;
3757 static const int ids[] = {
3758 UpButtonID,
3762 };
3763 for (auto id : ids)
3764 FindWindowById(id, this)->Enable(enable);
3765}
3766
wxT("CloseDown"))
R GuardedCall(const F1 &body, const F2 &handler=F2::Default(), F3 delayedHandler=DefaultDelayedHandlerAction) noexcept(noexcept(handler(std::declval< AudacityException * >())) &&noexcept(handler(nullptr)) &&noexcept(std::function< void(AudacityException *)>{std::move(delayedHandler)}))
Execute some code on any thread; catch any AudacityException; enqueue error report on the main thread...
END_EVENT_TABLE()
@ UpButtonID
@ ImportButtonID
@ DownButtonID
@ RenameButtonID
@ DefaultsButtonID
@ DeleteButtonID
@ ExportButtonID
int min(int a, int b)
EVT_BUTTON(wxID_NO, DependencyDialog::OnNo) EVT_BUTTON(wxID_YES
EffectDistortion::Params params
Definition: Distortion.cpp:83
#define M_PI
Definition: Distortion.cpp:29
const RegistryPath & CurrentSettingsGroup()
Component of a configuration key path, for last-used destructive settings.
EffectType
@ EffectTypeProcess
const bool kBOTH
#define EQCURVES_REVISION
EVT_COMMAND_RANGE(ID_Slider, ID_Slider+NUMBER_OF_BANDS - 1, wxEVT_COMMAND_SLIDER_UPDATED, EffectEqualization::OnSlider) EffectEqualization
static const FileNames::FileTypes & XMLtypes()
#define UPDATE_ALL
const bool bForBoth
static const double kThirdOct[]
const wxChar * values
EVT_LIST_ITEM_SELECTED(CurvesListID, EditCurvesDialog::OnListSelectionChange) EVT_LIST_ITEM_DESELECTED(CurvesListID
Constructor.
static const struct @26 FactoryPresets[]
const TranslatableString name
@ ID_Curve
@ ID_Delete
@ ID_Graphic
@ ID_Mode
@ ID_dBMax
@ ID_Invert
@ ID_Interp
@ ID_Grid
@ ID_Length
@ ID_Linear
@ ID_Slider
@ ID_Manage
@ ID_Clear
@ ID_Draw
@ ID_dBMin
#define EQCURVES_VERSION
const bool kCURVE
const int kEqLegacy
Definition: Equalization.h:30
#define NUM_PTS
Definition: Equalization.h:15
#define PANELBORDER
Definition: Equalization.h:16
const int kEqOptionCurve
Definition: Equalization.h:28
const int kEqOptionGraphic
Definition: Equalization.h:27
#define NUMBER_OF_BANDS
Definition: Equalization.h:14
std::vector< EQCurve > EQCurveArray
Definition: Equalization.h:91
void InverseRealFFT(size_t NumSamples, const float *RealIn, const float *ImagIn, float *RealOut)
Definition: FFT.cpp:268
void RealFFT(size_t NumSamples, const float *RealIn, float *RealOut, float *ImagOut)
Definition: FFT.cpp:230
std::vector< RegistryPath > RegistryPaths
Definition: Identifier.h:219
#define XXO(s)
Definition: Internat.h:44
#define XO(s)
Definition: Internat.h:31
#define _(s)
Definition: Internat.h:75
#define safenew
Definition: MemoryX.h:10
#define LINEAR_TO_DB(x)
Definition: MemoryX.h:544
#define DB_TO_LINEAR(x)
Definition: MemoryX.h:543
FileConfig * gPrefs
Definition: Prefs.cpp:71
void RealFFTf(fft_type *buffer, const FFTParam *h)
Definition: RealFFTf.cpp:162
void InverseRealFFTf(fft_type *buffer, const FFTParam *h)
Definition: RealFFTf.cpp:264
void ReorderToTime(const FFTParam *hFFT, const fft_type *buffer, fft_type *TimeOut)
Definition: RealFFTf.cpp:361
HFFT GetFFT(size_t fftlen)
Definition: RealFFTf.cpp:105
size_t limitSampleBufferSize(size_t bufferSize, sampleCount limit)
Definition: SampleCount.cpp:22
@ floatSample
Definition: SampleFormat.h:34
char * samplePtr
Definition: SampleFormat.h:49
TranslatableStrings Msgids(const EnumValueSymbol strings[], size_t nStrings)
Convenience function often useful when adding choice controls.
@ eIsCreating
Definition: ShuttleGui.h:39
TranslatableString label
Definition: TagsEditor.cpp:163
static TranslatableStrings names
Definition: TagsEditor.cpp:151
THEME_API Theme theTheme
Definition: Theme.cpp:82
#define S(N)
Definition: ToChars.cpp:64
static Settings & settings()
Definition: TrackInfo.cpp:87
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
std::vector< TranslatableString > TranslatableStrings
static const auto fn
static std::once_flag flag
std::vector< Attribute > AttributesList
Definition: XMLTagHandler.h:40
static void Line(wxDC &dc, wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2)
Definition: AColor.cpp:187
Wrap wxTextEntryDialog so that caption IS translatable.
Generates EffectParameterMethods overrides from variadic template arguments.
CommandParameters, derived from wxFileConfig, is essentially doing the same things as the SettingsVis...
ComponentInterfaceSymbol pairs a persistent string identifier used internally with an optional,...
EQCurve is used with EffectEqualization.
Definition: Equalization.h:77
std::vector< EQPoint > points
Definition: Equalization.h:88
wxString Name
Definition: Equalization.h:87
EQPoint is used with EQCurve and hence EffectEqualization.
Definition: Equalization.h:55
void OnUp(wxCommandEvent &event)
void OnOK(wxCommandEvent &event)
void OnImport(wxCommandEvent &event)
EffectEqualization * mEffect
Definition: Equalization.h:412
void OnDown(wxCommandEvent &event)
long GetPreviousItem(long item)
void Populate()
Creates the dialog and its contents.
void OnDelete(wxCommandEvent &event)
void PopulateList(int position)
wxListCtrl * mList
Definition: Equalization.h:409
void OnLibrary(wxCommandEvent &event)
void OnRename(wxCommandEvent &event)
EQCurveArray mEditCurves
Definition: Equalization.h:410
void OnListSelectionChange(wxListEvent &event)
void OnDefaults(wxCommandEvent &event)
void OnExport(wxCommandEvent &event)
void PopulateOrExchange(ShuttleGui &S)
Defines the dialog and does data exchange with it.
double mT1
Definition: EffectBase.h:107
std::shared_ptr< TrackList > mOutputTracks
Definition: EffectBase.h:105
double mT0
Definition: EffectBase.h:106
void ReplaceProcessedTracks(const bool bGoodResult)
Definition: EffectBase.cpp:236
const AudacityProject * FindProject() const
Definition: EffectBase.cpp:315
static const ComponentInterfaceSymbol Symbol
Definition: Equalization.h:323
static const ComponentInterfaceSymbol Symbol
Definition: Equalization.h:331
An Effect that modifies volume in different frequency bands.
Definition: Equalization.h:99
RegistryPaths GetFactoryPresets() const override
Report names of factory presets.
double mWhens[NUM_PTS]
Definition: Equalization.h:226
void OnLinFreq(wxCommandEvent &event)
bool GetDefaultFileName(wxFileName &fileName)
wxChoice * mInterpChoice
Definition: Equalization.h:271
const EffectParameterMethods & Parameters() const override
static const size_t windowSize
Definition: Equalization.h:149
bool LoadFactoryDefaults(EffectSettings &settings) const override
Change settings back to "factory default".
wxCheckBox * mLinFreq
Definition: Equalization.h:269
void UpdateGraphic(void)
virtual ~EffectEqualization()
std::unique_ptr< Envelope > mLinEnvelope
Definition: Equalization.h:242
friend class EqualizationPanel
Definition: Equalization.h:289
wxCheckBox * mGridOnOff
Definition: Equalization.h:270
static constexpr EffectParameter DrawGrid
Definition: Equalization.h:312
wxStaticText * mMText
Definition: Equalization.h:274
EqualizationPanel * mPanel
Definition: Equalization.h:265
EffectType GetType() const override
Type determines how it behaves.
void OnSliderDBMIN(wxCommandEvent &event)
bool TransferDataToWindow(const EffectSettings &settings) override
Update controls for the settings.
static constexpr EffectParameter InterpLin
Definition: Equalization.h:306
std::unique_ptr< EffectUIValidator > PopulateOrExchange(ShuttleGui &S, EffectInstance &instance, EffectSettingsAccess &access) override
Add controls to effect panel; always succeeds.
void OnGraphicMode(wxCommandEvent &event)
bool VisitSettings(SettingsVisitor &visitor, EffectSettings &settings) override
void GraphicEQ(Envelope *env)
void OnGridOnOff(wxCommandEvent &event)
ManualPageID ManualPage() const override
Name of a page in the Audacity alpha manual, default is empty.
wxRadioButton * mDraw
Definition: Equalization.h:267
void OnErase(wxEvent &event)
static constexpr EffectParameter dBMin
Definition: Equalization.h:314
std::unique_ptr< Envelope > mLogEnvelope
Definition: Equalization.h:242
TranslatableString GetDescription() const override
void OnManage(wxCommandEvent &event)
void Filter(size_t len, float *buffer)
void OnClear(wxCommandEvent &event)
wxSlider * mdBMinSlider
Definition: Equalization.h:276
void setCurve(int currentCurve)
double splint(double x[], double y[], size_t n, double y2[], double xr)
Envelope * mEnvelope
Definition: Equalization.h:243
XMLTagHandler * HandleXMLChild(const std::string_view &tag) override
void OnInvert(wxCommandEvent &event)
void spline(double x[], double y[], size_t n, double y2[])
wxString GetPrefsPrefix()
void OnCurve(wxCommandEvent &event)
void Select(int sel)
void OnSliderDBMAX(wxCommandEvent &event)
bool Process(EffectInstance &instance, EffectSettings &settings) override
Actually do the effect here.
bool DoLoadFactoryDefaults(EffectSettings &settings)
RulerPanel * mdBRuler
Definition: Equalization.h:229
wxRadioButton * mGraphic
Definition: Equalization.h:268
static const EnumValueSymbol kInterpStrings[nInterpolations]
Definition: Equalization.h:299
wxSlider * mMSlider
Definition: Equalization.h:275
wxSlider * mdBMaxSlider
Definition: Equalization.h:277
bool HandleXMLTag(const std::string_view &tag, const AttributesList &attrs) override
RulerPanel * mFreqRuler
Definition: Equalization.h:230
bool CloseUI() override
int mSlidersOld[NUMBER_OF_BANDS]
Definition: Equalization.h:237
void WriteXML(XMLWriter &xmlFile) const
EffectEqualization(int Options=kEqLegacy)
static constexpr EffectParameter FilterLength
Definition: Equalization.h:302
void LoadCurves(const wxString &fileName={}, bool append=false)
static constexpr EffectParameter DrawMode
Definition: Equalization.h:310
wxSlider * mSliders[NUMBER_OF_BANDS]
Definition: Equalization.h:278
static const ComponentInterfaceSymbol Symbol
Definition: Equalization.h:103
ComponentInterfaceSymbol GetSymbol() const override
bool ValidateUI(EffectSettings &) override
double mEQVals[NUMBER_OF_BANDS+1]
Definition: Equalization.h:238
bool LoadFactoryPreset(int id, EffectSettings &settings) const override
Change settings to the preset whose name is GetFactoryPresets()[id]
void SaveCurves(const wxString &fileName={})
void OnDrawMode(wxCommandEvent &event)
void OnSize(wxSizeEvent &event)
void OnSliderM(wxCommandEvent &event)
void OnSlider(wxCommandEvent &event)
double mWhenSliders[NUMBER_OF_BANDS+1]
Definition: Equalization.h:227
static constexpr EnumParameter InterpMeth
Definition: Equalization.h:308
void OnInterp(wxCommandEvent &event)
EQCurveArray mCurves
Definition: Equalization.h:240
bool Init() override
Call once to set up state for whole list of tracks to be processed.
void UpdateDefaultCurves(bool updateAll=false)
static constexpr EffectParameter dBMax
Definition: Equalization.h:316
bool ProcessOne(int count, WaveTrack *t, sampleCount start, sampleCount len)
const EffectSettingsManager & GetDefinition() const override
Definition: Effect.cpp:443
bool VisitSettings(SettingsVisitor &visitor, EffectSettings &settings) override
Definition: Effect.cpp:210
void CopyInputTracks(bool allSyncLockSelected=false)
Definition: Effect.cpp:724
bool LoadFactoryDefaults(EffectSettings &settings) const override
Change settings back to "factory default".
Definition: Effect.cpp:273
int MessageBox(const TranslatableString &message, long style=DefaultMessageBoxStyle, const TranslatableString &titleStr={}) const
Definition: Effect.cpp:854
wxWindow * mUIParent
Definition: Effect.h:304
@ DefaultMessageBoxStyle
Definition: Effect.h:174
bool TrackProgress(int whichTrack, double frac, const TranslatableString &={}) const
Definition: Effect.cpp:674
bool CloseUI() override
Definition: Effect.cpp:312
Performs effect computation.
Interface for manipulations of an Effect's settings.
static void DrawPoints(const Envelope &env, TrackPanelDrawingContext &context, const wxRect &r, bool dB, double dBRange, float zoomMin, float zoomMax, bool mirrored, int origin=0)
Piecewise linear or piecewise exponential function from double to double.
Definition: Envelope.h:72
void SetTrackLen(double trackLen, double sampleDur=0.0)
Definition: Envelope.cpp:787
void GetValues(double *buffer, int len, double t0, double tstep) const
Get many envelope points at once.
Definition: Envelope.cpp:923
size_t GetNumberOfPoints() const
Return number of points.
Definition: Envelope.cpp:689
int Reassign(double when, double value)
Move a point at when to value.
Definition: Envelope.cpp:669
void GetPoints(double *bufferWhen, double *bufferValue, int bufferLen) const
Returns the sets of when and value pairs.
Definition: Envelope.cpp:694
void Insert(int point, const EnvPoint &p)
insert a point
Definition: Envelope.cpp:361
void Flatten(double value)
Definition: Envelope.cpp:132
EqualizationPanel is used with EqualizationDialog and controls a graph for EffectEqualization....
Definition: Equalization.h:337
void OnSize(wxSizeEvent &event)
EffectEqualization * mEffect
Definition: Equalization.h:366
void OnCaptureLost(wxMouseCaptureLostEvent &event)
std::unique_ptr< wxBitmap > mBitmap
Definition: Equalization.h:371
std::unique_ptr< EnvelopeEditor > mLinEditor
Definition: Equalization.h:367
void OnMouseEvent(wxMouseEvent &event)
void OnPaint(wxPaintEvent &event)
std::unique_ptr< EnvelopeEditor > mLogEditor
Definition: Equalization.h:367
virtual bool Flush(bool bCurrentOnly=false) wxOVERRIDE
Definition: FileConfig.cpp:143
virtual wxString GetPath() const
virtual int ShowModal()
FILES_API const FileType XMLFiles
Definition: FileNames.h:74
std::vector< FileType > FileTypes
Definition: FileNames.h:76
Abstract base class used in importing a file.
void DrawGrid(wxDC &dc, int length, bool minor=true, bool major=true, int xOffset=0, int yOffset=0) const
Definition: Ruler.cpp:1521
void SetLog(bool log)
Definition: Ruler.cpp:142
void GetMaxSize(wxCoord *width, wxCoord *height)
Definition: Ruler.cpp:1603
void SetRange(double min, double max)
Definition: Ruler.cpp:188
@ LinearDBFormat
Definition: Ruler.h:34
@ IntFormat
Definition: Ruler.h:30
RulerPanel class allows you to work with a Ruler like any other wxWindow.
Definition: Ruler.h:228
Ruler ruler
Definition: Ruler.h:288
std::pair< double, double > Range
Definition: Ruler.h:232
bool GetFloats(float *buffer, sampleCount start, size_t len, fillFormat fill=fillZero, bool mayThrow=true, sampleCount *pNumWithinClips=nullptr) const
Retrieve samples from a track in floating-point format, regardless of the storage format.
Definition: SampleTrack.h:67
double LongSamplesToTime(sampleCount pos) const
Convert correctly between a number of samples and an (absolute) time in seconds.
Definition: SampleTrack.cpp:47
Visitor of effect or command parameters. This is a base class with lots of virtual functions that do ...
Definition: Shuttle.h:115
virtual void Define(Arg< bool > var, const wxChar *key, bool vdefault, bool vmin=false, bool vmax=false, bool vscl=false)
Definition: Shuttle.cpp:359
SettingsVisitor that gets parameter values into a string.
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:628
SettingsVisitor that sets parameters to a value (from a string)
wxColour & Colour(int iIndex)
This class handles the actual rendering of WaveTracks (both waveforms and spectra),...
Definition: TrackArtist.h:39
ZoomInfo * pZoomInfo
Definition: TrackArtist.h:116
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:486
auto Selected() -> TrackIterRange< TrackType >
Definition: Track.h:1454
Holds a msgid for the translation catalog; may also bind format arguments.
wxString Translation() const
TranslatableString & Format(Args &&...args) &
Capture variadic format arguments (by copy) when there is no plural.
A Track that contains audio waveform data.
Definition: WaveTrack.h:57
void Paste(double t0, const Track *src) override
Definition: WaveTrack.cpp:1573
void ConvertToSampleFormat(sampleFormat format, const std::function< void(size_t)> &progressReport={})
Definition: WaveTrack.cpp:596
size_t GetMaxBlockSize() const override
This returns a nonnegative number of samples meant to size a memory buffer.
Definition: WaveTrack.cpp:1816
void Join(double t0, double t1)
Definition: WaveTrack.cpp:1718
void Clear(double t0, double t1) override
Definition: WaveTrack.cpp:788
WaveClipHolders & GetClips()
Definition: WaveTrack.h:329
Holder EmptyCopy(const SampleBlockFactoryPtr &pFactory={}, bool keepLink=true) const
Definition: WaveTrack.cpp:705
An alternative to using wxWindowAccessible, which in wxWidgets 3.1.1 contained GetParent() which was ...
Reads a file and passes the results through an XMLTagHandler.
Definition: XMLFileReader.h:19
const TranslatableString & GetErrorStr() const
bool Parse(XMLTagHandler *baseHandler, const FilePath &fname)
Wrapper to output XML data to files.
Definition: XMLWriter.h:85
This class is an interface which should be implemented by classes which wish to be able to load and s...
Definition: XMLTagHandler.h:42
Base class for XMLFileWriter and XMLStringWriter that provides the general functionality for creating...
Definition: XMLWriter.h:26
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
Extend wxArrayString with move operations and construction and insertion fromstd::initializer_list.
#define lrint(dbl)
Definition: float_cast.h:169
FILES_API FilePath ResourcesDir()
FILES_API FilePath DataDir()
Audacity user data directory.
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
bool SetConfig(const EffectDefinitionInterface &ident, ConfigurationType type, const RegistryPath &group, const RegistryPath &key, const Value &value)
bool GetConfig(const EffectDefinitionInterface &ident, ConfigurationType type, const RegistryPath &group, const RegistryPath &key, Value &var, const Value &defval)
BuiltinEffectsModule::Registration< EffectEqualizationCurve > reg2
BuiltinEffectsModule::Registration< EffectEqualizationGraphic > reg3
void OnClear(wxCommandEvent &e)
std::vector< CommandFlagOptions > & Options()
Definition: Menus.cpp:535
void swap(std::unique_ptr< Alg_seq > &a, std::unique_ptr< Alg_seq > &b)
Definition: NoteTrack.cpp:753
const Type def
Default value.
Definition: Shuttle.h:29
Externalized state of a plug-in.
Options & LabelEdges(bool l)
Definition: Ruler.h:250
Options & Log(bool l)
Definition: Ruler.h:244