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 if ( (S.GetMode() == eIsCreating ) && !IsBatchProcessing() )
687 });
688
689 //LoadCurves();
690
691
692
693 S.SetBorder(0);
694
695 S.SetSizerProportion(1);
696 S.Prop(1).StartMultiColumn(1, wxEXPAND);
697 {
698 S.SetStretchyCol(0);
699 //S.SetStretchyRow(0); // The 5px Top border
700 S.SetStretchyRow(1); // The Graph
701 S.SetStretchyRow(2); // The EQ sliders
702 szrV = S.GetSizer();
703
704 // -------------------------------------------------------------------
705 // ROW 0: Top border
706 // -------------------------------------------------------------------
707 S.AddSpace(5);
708
709 // -------------------------------------------------------------------
710 // ROW 1: Equalization panel and sliders for vertical scale
711 // -------------------------------------------------------------------
712 S.SetSizerProportion(1);
713 S.Prop(1).StartMultiColumn(3, wxEXPAND);
714 {
715 S.SetStretchyCol(1);
716 S.SetStretchyRow(0);
717 szr1 = S.GetSizer();
718
719 S.StartVerticalLay(wxEXPAND, 1);
720 {
722 S.GetParent(), wxID_ANY, wxVERTICAL,
723 wxSize{ 100, 100 }, // Ruler can't handle small sizes
724 RulerPanel::Range{ 60.0, -120.0 },
726 XO("dB"),
728 .LabelEdges(true)
729 .TicksAtExtremes(true)
730 .TickColour( { 0, 0, 0 } )
731 );
732
733 S.Prop(0).AddSpace(0, 1);
734 S.Prop(1)
735 .Position(wxEXPAND)
736 .AddWindow(mdBRuler);
737 S.AddSpace(0, 1);
738 }
739 S.EndVerticalLay();
740
741 mPanel = safenew EqualizationPanel(S.GetParent(), wxID_ANY, this);
742 S.Prop(1)
743 .Position(wxEXPAND)
744 .MinSize( { wxDefaultCoord, wxDefaultCoord } )
745 .AddWindow(mPanel);
746
747 S.SetBorder(5);
748 S.StartVerticalLay();
749 {
750 S.AddVariableText(XO("+ dB"), false, wxCENTER);
752 .Name(XO("Max dB"))
753 .Style(wxSL_VERTICAL | wxSL_INVERSE)
754 .AddSlider( {}, 30, 60, 0);
755#if wxUSE_ACCESSIBILITY
756 mdBMaxSlider->SetAccessible(safenew SliderAx(mdBMaxSlider, XO("%d dB")));
757#endif
758
760 .Name(XO("Min dB"))
761 .Style(wxSL_VERTICAL | wxSL_INVERSE)
762 .AddSlider( {}, -30, -10, -120);
763 S.AddVariableText(XO("- dB"), false, wxCENTER);
764#if wxUSE_ACCESSIBILITY
765 mdBMinSlider->SetAccessible(safenew SliderAx(mdBMinSlider, XO("%d dB")));
766#endif
767 }
768 S.EndVerticalLay();
769 S.SetBorder(0);
770
771 // -------------------------------------------------------------------
772 // Frequency ruler below graph
773 // -------------------------------------------------------------------
774
775 // Column 1 is empty
776 S.AddSpace(1, 1);
777
779 S.GetParent(), wxID_ANY, wxHORIZONTAL,
780 wxSize{ 100, 100 }, // Ruler can't handle small sizes
781 RulerPanel::Range{ mLoFreq, mHiFreq },
783 XO("Hz"),
785 .Log(true)
786 .Flip(true)
787 .LabelEdges(true)
788 .TicksAtExtremes(true)
789 .TickColour( { 0, 0, 0 } )
790 );
791
792 S.SetBorder(1);
793 S.Prop(1)
794 .Position(wxEXPAND | wxALIGN_LEFT | wxALIGN_TOP | wxLEFT)
795 .AddWindow(mFreqRuler);
796 S.SetBorder(0);
797
798 // Column 3 is empty
799 S.AddSpace(1, 1);
800 }
801 S.EndMultiColumn();
802
803 // -------------------------------------------------------------------
804 // ROW 2: Graphic EQ
805 // -------------------------------------------------------------------
806 S.SetSizerProportion(1);
807 S.StartHorizontalLay(wxEXPAND, 1);
808 {
809 szrG = S.GetSizer();
810
811 // Panel used to host the sliders since they will be positioned manually.
812 //mGraphicPanel = S.Prop(1)
813 //.Position(wxEXPAND)
814 //.Size( { -1, 150 } )
815 //.StartPanel();
816 wxWindow *pParent = S.GetParent();
817 S.AddSpace(15,0);
818 {
819
820 // for (int i = 0; (i < NUMBER_OF_BANDS) && (kThirdOct[i] <= mHiFreq); ++i)
821 // May show more sliders than needed. Fixes Bug 2269
822 for (int i = 0; i < NUMBER_OF_BANDS; ++i)
823 {
824 TranslatableString freq = kThirdOct[i] < 1000.
825 ? XO("%d Hz").Format((int)kThirdOct[i])
826 : XO("%g kHz").Format(kThirdOct[i] / 1000.);
827 TranslatableString fNum = kThirdOct[i] < 1000.
828 ? Verbatim("%d").Format((int)kThirdOct[i])
829 /* i18n-hint k is SI abbreviation for x1,000. Usually unchanged in translation. */
830 : XO("%gk").Format(kThirdOct[i] / 1000.);
831 S.StartVerticalLay();
832 {
833 S.AddFixedText( fNum );
834 mSliders[i] = safenew wxSliderWrapper(pParent, ID_Slider + i, 0, -20, +20,
835 wxDefaultPosition, wxSize(-1,50), wxSL_VERTICAL | wxSL_INVERSE);
836
837#if wxUSE_ACCESSIBILITY
838 mSliders[i]->SetAccessible(safenew SliderAx(mSliders[i], XO("%d dB")));
839#endif
840
841 mSlidersOld[i] = 0;
842 mEQVals[i] = 0.;
843 S.Prop(1)
844 .Name(freq)
845 .ConnectRoot(
846 wxEVT_ERASE_BACKGROUND, &EffectEqualization::OnErase)
847 .Position(wxEXPAND)
848 .Size({ -1, 50 })
849 .AddWindow(mSliders[i]);
850 }
851 S.EndVerticalLay();
852 }
853 S.AddSpace(15,0);
854
855 } //S.EndPanel();
856 }
857 S.EndHorizontalLay();
858
859 // -------------------------------------------------------------------
860 // ROW 4: Various controls
861 // -------------------------------------------------------------------
862 S.SetSizerProportion(1);
863 S.Prop(1).StartMultiColumn(7, wxALIGN_CENTER_HORIZONTAL);
864 {
865 S.SetBorder(5);
866
867 S.AddSpace(5, 5);
868
869 if( mOptions == kEqLegacy )
870 {
871 S.StartHorizontalLay(wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
872 {
873 S.AddPrompt(XXO("&EQ Type:"));
874 }
875 S.EndHorizontalLay();
876
877 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 1);
878 {
879 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 1);
880 {
881 mDraw = S.Id(ID_Draw)
882 .Name(XO("Draw Curves"))
883 .AddRadioButton(XXO("&Draw"));
884
885 mGraphic = S.Id(ID_Graphic)
886 .Name(XO("Graphic EQ"))
887 .AddRadioButtonToGroup(XXO("&Graphic"));
888 }
889 S.EndHorizontalLay();
890 }
891 S.EndHorizontalLay();
892 }
893
894 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 0);
895 {
896 szrH = S.GetSizer();
897
898 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 1);
899 {
900 szrI = S.GetSizer();
901
903 .Name(XO("Interpolation type"))
904 .AddChoice( {},
906#if wxUSE_ACCESSIBILITY
907 // so that name can be set on a standard control
909#endif
910 }
911 S.EndHorizontalLay();
912
913 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 1);
914 {
915 szrL = S.GetSizer();
916
917 mLinFreq = S.Id(ID_Linear)
918 .Name(XO("Linear Frequency Scale"))
919 .AddCheckBox(XXO("Li&near Frequency Scale"), false);
920 }
921 S.EndHorizontalLay();
922 }
923 S.EndHorizontalLay();
924
925 // -------------------------------------------------------------------
926 // Filter length grouping
927 // -------------------------------------------------------------------
928
929 if( mOptions == kEqLegacy ){
930 S.StartHorizontalLay(wxEXPAND, 0);
931 {
932 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 0);
933 {
934 S.AddPrompt(XXO("Length of &Filter:"));
935 }
936 S.EndHorizontalLay();
937
938 S.StartHorizontalLay(wxEXPAND, 1);
939 {
940 mMSlider = S.Id(ID_Length)
941 .Name(XO("Length of Filter"))
942 .Style(wxSL_HORIZONTAL)
943 .AddSlider( {}, (mM - 1) / 2, 4095, 10);
944 }
945 S.EndHorizontalLay();
946
947 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 0);
948 {
949 wxString label;
950 label.Printf(wxT("%ld"), mM);
951 mMText = S.Name( Verbatim( label ) )
952 // fix for bug 577 (NVDA/Narrator screen readers do not
953 // read static text in dialogs)
954 .AddVariableText( Verbatim( label ) );
955 }
956 S.EndHorizontalLay();
957 }
958 S.EndHorizontalLay();
959
960 S.AddSpace(1, 1);
961 }
962
963 S.AddSpace(5, 5);
964
965 if( mOptions == kEqLegacy ){
966 S.AddSpace(5, 5);
967 S.StartHorizontalLay(wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
968 {
969 S.AddPrompt(XXO("&Select Curve:"));
970 }
971 S.EndHorizontalLay();
972
973 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 1);
974 {
975 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 1);
976 {
977 mCurve = S.Id(ID_Curve)
978 .Name(XO("Select Curve"))
979 .AddChoice( {},
980 [this]{
981 TranslatableStrings curves;
982 for (const auto &curve : mCurves)
983 curves.push_back( Verbatim( curve.Name ) );
984 return curves;
985 }()
986 );
987 }
988 S.EndHorizontalLay();
989 }
990 S.EndHorizontalLay();
991
992 S.Id(ID_Manage).AddButton(XXO("S&ave/Manage Curves..."));
993 }
994
995 S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 1);
996 {
997 S.Id(ID_Clear).AddButton(XXO("Fla&tten"));
998 S.Id(ID_Invert).AddButton(XXO("&Invert"));
999
1000 mGridOnOff = S.Id(ID_Grid)
1001 .Name(XO("Show grid lines"))
1002 .AddCheckBox(XXO("Show g&rid lines"), false);
1003 }
1004 S.EndHorizontalLay();
1005
1006 S.AddSpace(5, 5);
1007 }
1008 S.EndMultiColumn();
1009 }
1010 S.EndMultiColumn();
1011
1012#ifdef EXPERIMENTAL_EQ_SSE_THREADED
1013 if (mEffectEqualization48x)
1014 {
1015 // -------------------------------------------------------------------
1016 // ROW 6: Processing routine selection
1017 // -------------------------------------------------------------------
1018
1019 // Column 1 is blank
1020 S.AddSpace(1, 1);
1021
1022 S.StartHorizontalLay();
1023 {
1024 S.AddUnits(XO("&Processing: "));
1025
1026 // update the control state
1027 int mathPath = EffectEqualization48x::GetMathPath();
1028 int value =
1029 (mathPath & MATH_FUNCTION_SSE)
1030 ? (mathPath & MATH_FUNCTION_THREADED)
1031 ? 2
1032 : 1
1033 : false // (mathPath & MATH_FUNCTION_AVX) // not implemented
1034 ? (mathPath & MATH_FUNCTION_THREADED)
1035 ? 4
1036 : 3
1037 : 0;
1038
1039 mMathProcessingType[0] = S.Id(ID_DefaultMath)
1040 .AddRadioButton(XXO("D&efault"),
1041 0, value);
1042 mMathProcessingType[1] = S.Id(ID_SSE)
1043 .Disable(!EffectEqualization48x::GetMathCaps()->SSE)
1044 .AddRadioButtonToGroup(XXO("&SSE"),
1045 1, value);
1046 mMathProcessingType[2] = S.Id(ID_SSEThreaded)
1047 .Disable(!EffectEqualization48x::GetMathCaps()->SSE)
1048 .AddRadioButtonToGroup(XXO("SSE &Threaded"),
1049 2, value);
1050 mMathProcessingType[3] = S.Id(ID_AVX)
1051 // not implemented
1052 .Disable(true /* !EffectEqualization48x::GetMathCaps()->AVX */)
1053 .AddRadioButtonToGroup(XXO("A&VX"),
1054 3, value);
1055 mMathProcessingType[4] = S.Id(ID_AVXThreaded)
1056 // not implemented
1057 .Disable(true /* !EffectEqualization48x::GetMathCaps()->AVX */)
1058 .AddRadioButtonToGroup(XXO("AV&X Threaded"),
1059 4, value);
1060 S.Id(ID_Bench).AddButton(XXO("&Bench"));
1061 }
1062 S.EndHorizontalLay();
1063
1064 // Column 3 is blank
1065 S.AddSpace(1, 1);
1066 }
1067#endif
1068
1069 mUIParent->SetAutoLayout(false);
1071 mUIParent->Layout();
1072
1073 if( mOptions == kEqOptionCurve)
1074 mDrawMode = true;
1076 mDrawMode = false;
1077
1078 // "show" settings for graphics mode before setting the size of the dialog
1079 // as this needs more space than draw mode
1080 szrV->Show(szrG,!mDrawMode); // eq sliders
1081 szrH->Show(szrI,true); // interpolation choice
1082 szrH->Show(szrL,false); // linear freq checkbox
1083
1084 if( mOptions == kEqOptionGraphic){
1085 mPanel->Show( false );
1086 wxSize sz = szrV->GetMinSize();
1087 sz += wxSize( 30, 0);
1088 mUIParent->SetMinSize(sz);
1089 }
1090 else{
1091 mPanel->Show( true );
1092 szrV->Show(szr1, true);
1093 // This sizing calculation is hacky.
1094 // Rather than set the true minimum size we set a size we would
1095 // like to have.
1096 // This makes the default size of the dialog good, but has the
1097 // downside that the user can't adjust the dialog smaller.
1098 wxSize sz = szrV->GetMinSize();
1099 sz += wxSize( 400, 100);
1100 szrV->SetMinSize(sz);
1101 }
1102 ForceRecalc();
1103
1104 return nullptr;
1105}
1106
1107//
1108// Populate the window with relevant variables
1109//
1111{
1112 // Set log or lin freq scale (affects interpolation as well)
1113 mLinFreq->SetValue( mLin );
1114 wxCommandEvent dummyEvent;
1115 OnLinFreq(dummyEvent); // causes a CalcFilter
1116
1117 mGridOnOff->SetValue( mDrawGrid ); // checks/unchecks the box on the interface
1118
1119 if( mMSlider )
1120 mMSlider->SetValue((mM - 1) / 2);
1121
1122 mdBMinSlider->SetValue((int)mdBMin);
1123 mdBMaxSlider->SetValue((int)mdBMax);
1124
1125 // Reload the curve names
1126 UpdateCurves();
1127
1128 // Set graphic interpolation mode
1129 mInterpChoice->SetSelection(mInterp);
1130
1131 // Override draw mode, if we're not displaying the radio buttons.
1132 if( mOptions == kEqOptionCurve)
1133 mDrawMode = true;
1135 mDrawMode = false;
1136
1137 if( mDraw )
1138 mDraw->SetValue(mDrawMode);
1139 szrV->Show(szr1,mOptions != kEqOptionGraphic); // Graph
1140 szrV->Show(szrG,!mDrawMode); // eq sliders
1141 szrH->Show(szrI,mOptions == kEqLegacy ); // interpolation choice
1142 szrH->Show(szrL, mDrawMode); // linear freq checkbox
1143 if( mGraphic)
1144 mGraphic->SetValue(!mDrawMode);
1145 mGridOnOff->Show( mDrawMode );
1146
1147 // Set Graphic (Fader) or Draw mode
1148 if (!mDrawMode)
1149 UpdateGraphic();
1150
1151 mUIParent->Layout();
1152 wxGetTopLevelParent(mUIParent)->Layout();
1153
1154 return true;
1155}
1156
1158{
1159 // Refresh ruler when values have changed
1160 int w1, w2, h;
1161 mdBRuler->ruler.GetMaxSize(&w1, &h);
1163 mdBRuler->ruler.GetMaxSize(&w2, &h);
1164 if( w1 != w2 ) // Reduces flicker
1165 {
1166 mdBRuler->SetSize(wxSize(w2,h));
1167 mFreqRuler->Refresh(false);
1168 }
1169 mdBRuler->Refresh(false);
1170
1171 mPanel->Refresh(false);
1172}
1173
1174// EffectEqualization implementation
1175
1177 sampleCount start, sampleCount len)
1178{
1179 // create a NEW WaveTrack to hold all of the output, including 'tails' each end
1180 auto output = t->EmptyCopy();
1182
1183 wxASSERT(mM - 1 < windowSize);
1184 size_t L = windowSize - (mM - 1); //Process L samples at a go
1185 auto s = start;
1186 auto idealBlockLen = t->GetMaxBlockSize() * 4;
1187 if (idealBlockLen % L != 0)
1188 idealBlockLen += (L - (idealBlockLen % L));
1189
1190 Floats buffer{ idealBlockLen };
1191
1192 Floats window1{ windowSize };
1193 Floats window2{ windowSize };
1194 float *thisWindow = window1.get();
1195 float *lastWindow = window2.get();
1196
1197 auto originalLen = len;
1198
1199 for(size_t i = 0; i < windowSize; i++)
1200 lastWindow[i] = 0;
1201
1202 TrackProgress(count, 0.);
1203 bool bLoopSuccess = true;
1204 size_t wcopy = 0;
1205 int offset = (mM - 1) / 2;
1206
1207 while (len != 0)
1208 {
1209 auto block = limitSampleBufferSize( idealBlockLen, len );
1210
1211 t->GetFloats(buffer.get(), s, block);
1212
1213 for(size_t i = 0; i < block; i += L) //go through block in lumps of length L
1214 {
1215 wcopy = std::min <size_t> (L, block - i);
1216 for(size_t j = 0; j < wcopy; j++)
1217 thisWindow[j] = buffer[i+j]; //copy the L (or remaining) samples
1218 for(auto j = wcopy; j < windowSize; j++)
1219 thisWindow[j] = 0; //this includes the padding
1220
1221 Filter(windowSize, thisWindow);
1222
1223 // Overlap - Add
1224 for(size_t j = 0; (j < mM - 1) && (j < wcopy); j++)
1225 buffer[i+j] = thisWindow[j] + lastWindow[L + j];
1226 for(size_t j = mM - 1; j < wcopy; j++)
1227 buffer[i+j] = thisWindow[j];
1228
1229 std::swap( thisWindow, lastWindow );
1230 } //next i, lump of this block
1231
1232 output->Append((samplePtr)buffer.get(), floatSample, block);
1233 len -= block;
1234 s += block;
1235
1236 if (TrackProgress(count, ( s - start ).as_double() /
1237 originalLen.as_double()))
1238 {
1239 bLoopSuccess = false;
1240 break;
1241 }
1242 }
1243
1244 if(bLoopSuccess)
1245 {
1246 // mM-1 samples of 'tail' left in lastWindow, get them now
1247 if(wcopy < (mM - 1)) {
1248 // Still have some overlap left to process
1249 // (note that lastWindow and thisWindow have been exchanged at this point
1250 // so that 'thisWindow' is really the window prior to 'lastWindow')
1251 size_t j = 0;
1252 for(; j < mM - 1 - wcopy; j++)
1253 buffer[j] = lastWindow[wcopy + j] + thisWindow[L + wcopy + j];
1254 // And fill in the remainder after the overlap
1255 for( ; j < mM - 1; j++)
1256 buffer[j] = lastWindow[wcopy + j];
1257 } else {
1258 for(size_t j = 0; j < mM - 1; j++)
1259 buffer[j] = lastWindow[wcopy + j];
1260 }
1261 output->Append((samplePtr)buffer.get(), floatSample, mM - 1);
1262 output->Flush();
1263
1264 // now move the appropriate bit of the output back to the track
1265 // (this could be enhanced in the future to use the tails)
1266 double offsetT0 = t->LongSamplesToTime(offset);
1267 double lenT = t->LongSamplesToTime(originalLen);
1268 // 'start' is the sample offset in 't', the passed in track
1269 // 'startT' is the equivalent time value
1270 // 'output' starts at zero
1271 double startT = t->LongSamplesToTime(start);
1272
1273 //output has one waveclip for the total length, even though
1274 //t might have whitespace separating multiple clips
1275 //we want to maintain the original clip structure, so
1276 //only paste the intersections of the NEW clip.
1277
1278 //Find the bits of clips that need replacing
1279 std::vector<std::pair<double, double> > clipStartEndTimes;
1280 std::vector<std::pair<double, double> > clipRealStartEndTimes; //the above may be truncated due to a clip being partially selected
1281 for (const auto &clip : t->GetClips())
1282 {
1283 double clipStartT;
1284 double clipEndT;
1285
1286 clipStartT = clip->GetPlayStartTime();
1287 clipEndT = clip->GetPlayEndTime();
1288 if( clipEndT <= startT )
1289 continue; // clip is not within selection
1290 if( clipStartT >= startT + lenT )
1291 continue; // clip is not within selection
1292
1293 //save the actual clip start/end so that we can rejoin them after we paste.
1294 clipRealStartEndTimes.push_back(std::pair<double,double>(clipStartT,clipEndT));
1295
1296 if( clipStartT < startT ) // does selection cover the whole clip?
1297 clipStartT = startT; // don't copy all the NEW clip
1298 if( clipEndT > startT + lenT ) // does selection cover the whole clip?
1299 clipEndT = startT + lenT; // don't copy all the NEW clip
1300
1301 //save them
1302 clipStartEndTimes.push_back(std::pair<double,double>(clipStartT,clipEndT));
1303 }
1304 //now go thru and replace the old clips with NEW
1305 for(unsigned int i = 0; i < clipStartEndTimes.size(); i++)
1306 {
1307 //remove the old audio and get the NEW
1308 t->Clear(clipStartEndTimes[i].first,clipStartEndTimes[i].second);
1309 auto toClipOutput = output->Copy(clipStartEndTimes[i].first-startT+offsetT0,clipStartEndTimes[i].second-startT+offsetT0);
1310 //put the processed audio in
1311 t->Paste(clipStartEndTimes[i].first, toClipOutput.get());
1312 //if the clip was only partially selected, the Paste will have created a split line. Join is needed to take care of this
1313 //This is not true when the selection is fully contained within one clip (second half of conditional)
1314 if( (clipRealStartEndTimes[i].first != clipStartEndTimes[i].first ||
1315 clipRealStartEndTimes[i].second != clipStartEndTimes[i].second) &&
1316 !(clipRealStartEndTimes[i].first <= startT &&
1317 clipRealStartEndTimes[i].second >= startT+lenT) )
1318 t->Join(clipRealStartEndTimes[i].first,clipRealStartEndTimes[i].second);
1319 }
1320 }
1321
1322 return bLoopSuccess;
1323}
1324
1326{
1327 double loLog = log10(mLoFreq);
1328 double hiLog = log10(mHiFreq);
1329 double denom = hiLog - loLog;
1330
1331 double delta = mHiFreq / ((double)(mWindowSize / 2.));
1332 double val0;
1333 double val1;
1334
1335 if( IsLinear() )
1336 {
1337 val0 = mLinEnvelope->GetValue(0.0); //no scaling required - saved as dB
1338 val1 = mLinEnvelope->GetValue(1.0);
1339 }
1340 else
1341 {
1342 val0 = mLogEnvelope->GetValue(0.0); //no scaling required - saved as dB
1343 val1 = mLogEnvelope->GetValue(1.0);
1344 }
1345 mFilterFuncR[0] = val0;
1346 double freq = delta;
1347
1348 for(size_t i = 1; i <= mWindowSize / 2; i++)
1349 {
1350 double when;
1351 if( IsLinear() )
1352 when = freq/mHiFreq;
1353 else
1354 when = (log10(freq) - loLog)/denom;
1355 if(when < 0.)
1356 {
1357 mFilterFuncR[i] = val0;
1358 }
1359 else if(when > 1.0)
1360 {
1361 mFilterFuncR[i] = val1;
1362 }
1363 else
1364 {
1365 if( IsLinear() )
1366 mFilterFuncR[i] = mLinEnvelope->GetValue(when);
1367 else
1368 mFilterFuncR[i] = mLogEnvelope->GetValue(when);
1369 }
1370 freq += delta;
1371 }
1372 mFilterFuncR[mWindowSize / 2] = val1;
1373
1375
1376 {
1377 size_t i = 1;
1378 for(; i < mWindowSize / 2; i++)
1379 {
1381 mFilterFuncR[mWindowSize - i] = mFilterFuncR[i]; //Fill entire array
1382 }
1383 mFilterFuncR[i] = DB_TO_LINEAR(mFilterFuncR[i]); //do last one
1384 }
1385
1386 //transfer to time domain to do the padding and windowing
1387 Floats outr{ mWindowSize };
1388 Floats outi{ mWindowSize };
1389 InverseRealFFT(mWindowSize, mFilterFuncR.get(), NULL, outr.get()); // To time domain
1390
1391 {
1392 size_t i = 0;
1393 for(; i <= (mM - 1) / 2; i++)
1394 { //Windowing - could give a choice, fixed for now - MJS
1395 // double mult=0.54-0.46*cos(2*M_PI*(i+(mM-1)/2.0)/(mM-1)); //Hamming
1396 //Blackman
1397 double mult =
1398 0.42 -
1399 0.5 * cos(2 * M_PI * (i + (mM - 1) / 2.0) / (mM - 1)) +
1400 .08 * cos(4 * M_PI * (i + (mM - 1) / 2.0) / (mM - 1));
1401 outr[i] *= mult;
1402 if(i != 0){
1403 outr[mWindowSize - i] *= mult;
1404 }
1405 }
1406 for(; i <= mWindowSize / 2; i++)
1407 { //Padding
1408 outr[i] = 0;
1409 outr[mWindowSize - i] = 0;
1410 }
1411 }
1412 Floats tempr{ mM };
1413 {
1414 size_t i = 0;
1415 for(; i < (mM - 1) / 2; i++)
1416 { //shift so that padding on right
1417 tempr[(mM - 1) / 2 + i] = outr[i];
1418 tempr[i] = outr[mWindowSize - (mM - 1) / 2 + i];
1419 }
1420 tempr[(mM - 1) / 2 + i] = outr[i];
1421 }
1422
1423 for (size_t i = 0; i < mM; i++)
1424 { //and copy useful values back
1425 outr[i] = tempr[i];
1426 }
1427 for (size_t i = mM; i < mWindowSize; i++)
1428 { //rest is padding
1429 outr[i]=0.;
1430 }
1431
1432 //Back to the frequency domain so we can use it
1433 RealFFT(mWindowSize, outr.get(), mFilterFuncR.get(), mFilterFuncI.get());
1434
1435 return TRUE;
1436}
1437
1438void EffectEqualization::Filter(size_t len, float *buffer)
1439{
1440 float re,im;
1441 // Apply FFT
1442 RealFFTf(buffer, hFFT.get());
1443 //FFT(len, false, inr, NULL, outr, outi);
1444
1445 // Apply filter
1446 // DC component is purely real
1447 mFFTBuffer[0] = buffer[0] * mFilterFuncR[0];
1448 for(size_t i = 1; i < (len / 2); i++)
1449 {
1450 re=buffer[hFFT->BitReversed[i] ];
1451 im=buffer[hFFT->BitReversed[i]+1];
1452 mFFTBuffer[2*i ] = re*mFilterFuncR[i] - im*mFilterFuncI[i];
1453 mFFTBuffer[2*i+1] = re*mFilterFuncI[i] + im*mFilterFuncR[i];
1454 }
1455 // Fs/2 component is purely real
1456 mFFTBuffer[1] = buffer[1] * mFilterFuncR[len/2];
1457
1458 // Inverse FFT and normalization
1459 InverseRealFFTf(mFFTBuffer.get(), hFFT.get());
1460 ReorderToTime(hFFT.get(), mFFTBuffer.get(), buffer);
1461}
1462
1463//
1464// Load external curves with fallback to default, then message
1465//
1466void EffectEqualization::LoadCurves(const wxString &fileName, bool append)
1467{
1468// We've disabled the XML management of curves.
1469// Just going via .cfg files now.
1470#if 1
1471 (void)fileName;
1472 (void)append;
1473 mCurves.clear();
1474 mCurves.push_back( wxT("unnamed") ); // we still need a default curve to use
1475#else
1476 // Construct normal curve filename
1477 //
1478 // LLL: Wouldn't you know that as of WX 2.6.2, there is a conflict
1479 // between wxStandardPaths and wxConfig under Linux. The latter
1480 // creates a normal file as "$HOME/.audacity", while the former
1481 // expects the ".audacity" portion to be a directory.
1482 // MJS: I don't know what the above means, or if I have broken it.
1483 wxFileName fn;
1484
1485 if(fileName.empty()) {
1486 // Check if presets are up to date.
1487 wxString eqCurvesCurrentVersion = wxString::Format(wxT("%d.%d"), EQCURVES_VERSION, EQCURVES_REVISION);
1488 wxString eqCurvesInstalledVersion;
1489 gPrefs->Read(GetPrefsPrefix() + "PresetVersion", &eqCurvesInstalledVersion, wxT(""));
1490
1491 bool needUpdate = (eqCurvesCurrentVersion != eqCurvesInstalledVersion);
1492
1493 // UpdateDefaultCurves allows us to import NEW factory presets only,
1494 // or update all factory preset curves.
1495 if (needUpdate)
1497 fn = wxFileName( FileNames::DataDir(), wxT("EQCurves.xml") );
1498 }
1499 else
1500 fn = fileName; // user is loading a specific set of curves
1501
1502 // If requested file doesn't exist...
1503 if( !fn.FileExists() && !GetDefaultFileName(fn) ) {
1504 mCurves.clear();
1505 /* i18n-hint: name of the 'unnamed' custom curve */
1506 mCurves.push_back( _("unnamed") ); // we still need a default curve to use
1507 return;
1508 }
1509
1510 EQCurve tempCustom(wxT("temp"));
1511 if( append == false ) // Start from scratch
1512 mCurves.clear();
1513 else // appending so copy and remove 'unnamed', to replace later
1514 {
1515 tempCustom.points = mCurves.back().points;
1516 mCurves.pop_back();
1517 }
1518
1519 // Load the curves
1520 XMLFileReader reader;
1521 const wxString fullPath{ fn.GetFullPath() };
1522 if( !reader.Parse( this, fullPath ) )
1523 {
1524 /* i18n-hint: EQ stands for 'Equalization'.*/
1525 auto msg = XO("Error Loading EQ Curves from file:\n%s\nError message says:\n%s")
1526 .Format( fullPath, reader.GetErrorStr() );
1527 // Inform user of load failure
1529 msg,
1530 wxOK | wxCENTRE,
1531 XO("Error Loading EQ Curves") );
1532 mCurves.push_back( _("unnamed") ); // we always need a default curve to use
1533 return;
1534 }
1535
1536 // Move "unnamed" to end, if it exists in current language.
1537 int numCurves = mCurves.size();
1538 int curve;
1539 EQCurve tempUnnamed(wxT("tempUnnamed"));
1540 for( curve = 0; curve < numCurves-1; curve++ )
1541 {
1542 if( mCurves[curve].Name == _("unnamed") )
1543 {
1544 tempUnnamed.points = mCurves[curve].points;
1545 mCurves.erase(mCurves.begin() + curve);
1546 mCurves.push_back( _("unnamed") ); // add 'unnamed' back at the end
1547 mCurves.back().points = tempUnnamed.points;
1548 }
1549 }
1550
1551 if( mCurves.back().Name != _("unnamed") )
1552 mCurves.push_back( _("unnamed") ); // we always need a default curve to use
1553 if( append == true )
1554 {
1555 mCurves.back().points = tempCustom.points;
1556 }
1557#endif
1558 return;
1559}
1560
1561//
1562// Update presets to match Audacity version.
1563//
1564void EffectEqualization::UpdateDefaultCurves(bool updateAll /* false */)
1565{
1566 if (mCurves.size() == 0)
1567 return;
1568
1569 wxString unnamed = wxT("unnamed");
1570
1571 // Save the "unnamed" curve and remove it so we can add it back as the final curve.
1572 EQCurve userUnnamed(wxT("temp"));
1573 userUnnamed = mCurves.back();
1574 mCurves.pop_back();
1575
1576 EQCurveArray userCurves = mCurves;
1577 mCurves.clear();
1578 // We only wamt to look for the shipped EQDefaultCurves.xml
1579 wxFileName fn = wxFileName(FileNames::ResourcesDir(), wxT("EQDefaultCurves.xml"));
1580 wxLogDebug(wxT("Attempting to load EQDefaultCurves.xml from %s"),fn.GetFullPath());
1581 XMLFileReader reader;
1582
1583 if(!reader.Parse(this, fn.GetFullPath())) {
1584 wxLogError(wxT("EQDefaultCurves.xml could not be read."));
1585 return;
1586 }
1587 else {
1588 wxLogDebug(wxT("Loading EQDefaultCurves.xml successful."));
1589 }
1590
1591 EQCurveArray defaultCurves = mCurves;
1592 mCurves.clear(); // clear now so that we can sort then add back.
1593
1594 // Remove "unnamed" if it exists.
1595 if (defaultCurves.back().Name == unnamed) {
1596 defaultCurves.pop_back();
1597 }
1598 else {
1599 wxLogError(wxT("Error in EQDefaultCurves.xml"));
1600 }
1601
1602 int numUserCurves = userCurves.size();
1603 int numDefaultCurves = defaultCurves.size();
1604 EQCurve tempCurve(wxT("test"));
1605
1606 if (updateAll) {
1607 // Update all factory preset curves.
1608 // Sort and add factory defaults first;
1609 mCurves = defaultCurves;
1610 std::sort(mCurves.begin(), mCurves.end());
1611 // then add remaining user curves:
1612 for (int curveCount = 0; curveCount < numUserCurves; curveCount++) {
1613 bool isCustom = true;
1614 tempCurve = userCurves[curveCount];
1615 // is the name in the default set?
1616 for (int defCurveCount = 0; defCurveCount < numDefaultCurves; defCurveCount++) {
1617 if (tempCurve.Name == mCurves[defCurveCount].Name) {
1618 isCustom = false;
1619 break;
1620 }
1621 }
1622 // if tempCurve is not in the default set, add it to mCurves.
1623 if (isCustom) {
1624 mCurves.push_back(tempCurve);
1625 }
1626 }
1627 }
1628 else {
1629 // Import NEW factory defaults but retain all user modified curves.
1630 for (int defCurveCount = 0; defCurveCount < numDefaultCurves; defCurveCount++) {
1631 bool isUserCurve = false;
1632 // Add if the curve is in the user's set (preserve user's copy)
1633 for (int userCurveCount = 0; userCurveCount < numUserCurves; userCurveCount++) {
1634 if (userCurves[userCurveCount].Name == defaultCurves[defCurveCount].Name) {
1635 isUserCurve = true;
1636 mCurves.push_back(userCurves[userCurveCount]);
1637 break;
1638 }
1639 }
1640 if (!isUserCurve) {
1641 mCurves.push_back(defaultCurves[defCurveCount]);
1642 }
1643 }
1644 std::sort(mCurves.begin(), mCurves.end());
1645 // now add the rest of the user's curves.
1646 for (int userCurveCount = 0; userCurveCount < numUserCurves; userCurveCount++) {
1647 bool isDefaultCurve = false;
1648 tempCurve = userCurves[userCurveCount];
1649 for (int defCurveCount = 0; defCurveCount < numDefaultCurves; defCurveCount++) {
1650 if (tempCurve.Name == defaultCurves[defCurveCount].Name) {
1651 isDefaultCurve = true;
1652 break;
1653 }
1654 }
1655 if (!isDefaultCurve) {
1656 mCurves.push_back(tempCurve);
1657 }
1658 }
1659 }
1660 defaultCurves.clear();
1661 userCurves.clear();
1662
1663 // Add back old "unnamed"
1664 if(userUnnamed.Name == unnamed) {
1665 mCurves.push_back( userUnnamed ); // we always need a default curve to use
1666 }
1667
1668 SaveCurves();
1669
1670 // Write current EqCurve version number
1671 // TODO: Probably better if we used pluginregistry.cfg
1672 wxString eqCurvesCurrentVersion = wxString::Format(wxT("%d.%d"), EQCURVES_VERSION, EQCURVES_REVISION);
1673 gPrefs->Write(GetPrefsPrefix()+"PresetVersion", eqCurvesCurrentVersion);
1674 gPrefs->Flush();
1675
1676 return;
1677}
1678
1679//
1680// Get fully qualified filename of EQDefaultCurves.xml
1681//
1683{
1684 // look in data dir first, in case the user has their own defaults (maybe downloaded ones)
1685 fileName = wxFileName( FileNames::DataDir(), wxT("EQDefaultCurves.xml") );
1686 if( !fileName.FileExists() )
1687 { // Default file not found in the data dir. Fall back to Resources dir.
1688 // See http://docs.wxwidgets.org/trunk/classwx_standard_paths.html#5514bf6288ee9f5a0acaf065762ad95d
1689 fileName = wxFileName( FileNames::ResourcesDir(), wxT("EQDefaultCurves.xml") );
1690 }
1691 if( !fileName.FileExists() )
1692 {
1693 // LLL: Is there really a need for an error message at all???
1694 //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")
1695 // .Format( FileNames::DataDir() );
1696 //BasicUI::ShowErrorDialog( wxWidgetsWindowPlacement{ mUIParent },
1697 // XO("EQCurves.xml and EQDefaultCurves.xml missing"),
1698 // errorMessage, wxT("http://wiki.audacityteam.org/wiki/EQCurvesDownload"), false);
1699
1700 // Have another go at finding EQCurves.xml in the data dir, in case 'help' helped
1701 fileName = wxFileName( FileNames::DataDir(), wxT("EQDefaultCurves.xml") );
1702 }
1703 return (fileName.FileExists());
1704}
1705
1706
1707//
1708// Save curves to external file
1709//
1710void EffectEqualization::SaveCurves(const wxString &fileName)
1711{
1712 wxFileName fn;
1713 if( fileName.empty() )
1714 {
1715 // Construct default curve filename
1716 //
1717 // LLL: Wouldn't you know that as of WX 2.6.2, there is a conflict
1718 // between wxStandardPaths and wxConfig under Linux. The latter
1719 // creates a normal file as "$HOME/.audacity", while the former
1720 // expects the ".audacity" portion to be a directory.
1721 fn = wxFileName( FileNames::DataDir(), wxT("EQCurves.xml") );
1722
1723 // If the directory doesn't exist...
1724 if( !fn.DirExists() )
1725 {
1726 // Attempt to create it
1727 if( !fn.Mkdir( fn.GetPath(), 511, wxPATH_MKDIR_FULL ) )
1728 {
1729 // MkDir() will emit message
1730 return;
1731 }
1732 }
1733 }
1734 else
1735 fn = fileName;
1736
1737 GuardedCall( [&] {
1738 // Create/Open the file
1739 const wxString fullPath{ fn.GetFullPath() };
1740 XMLFileWriter eqFile{ fullPath, XO("Error Saving Equalization Curves") };
1741
1742 // Write the curves
1743 WriteXML( eqFile );
1744
1745 eqFile.Commit();
1746 } );
1747}
1748
1749//
1750// Make the passed curve index the active one
1751//
1752void EffectEqualization::setCurve(int currentCurve)
1753{
1754 // Set current choice
1755 wxASSERT( currentCurve < (int) mCurves.size() );
1756 Select(currentCurve);
1757
1758 Envelope *env;
1759 int numPoints = (int) mCurves[currentCurve].points.size();
1760
1761 if (mLin) { // linear freq mode
1762 env = mLinEnvelope.get();
1763 }
1764 else { // log freq mode
1765 env = mLogEnvelope.get();
1766 }
1767 env->Flatten(0.);
1768 env->SetTrackLen(1.0);
1769
1770 // Handle special case of no points.
1771 if (numPoints == 0) {
1772 ForceRecalc();
1773 return;
1774 }
1775
1776 double when, value;
1777
1778 // Handle special case 1 point.
1779 if (numPoints == 1) {
1780 // only one point, so ensure it is in range then return.
1781 when = mCurves[currentCurve].points[0].Freq;
1782 if (mLin) {
1783 when = when / mHiFreq;
1784 }
1785 else { // log scale
1786 // We don't go below loFreqI (20 Hz) in log view.
1787 double loLog = log10((double)loFreqI);
1788 double hiLog = log10(mHiFreq);
1789 double denom = hiLog - loLog;
1790 when = (log10(std::max((double) loFreqI, when)) - loLog)/denom;
1791 }
1792 value = mCurves[currentCurve].points[0].dB;
1793 env->Insert(std::min(1.0, std::max(0.0, when)), value);
1794 ForceRecalc();
1795 return;
1796 }
1797
1798 // We have at least two points, so ensure they are in frequency order.
1799 std::sort(mCurves[currentCurve].points.begin(),
1800 mCurves[currentCurve].points.end());
1801
1802 if (mCurves[currentCurve].points[0].Freq < 0) {
1803 // Corrupt or invalid curve, so bail.
1804 ForceRecalc();
1805 return;
1806 }
1807
1808 if(mLin) { // linear Hz scale
1809 for(int pointCount = 0; pointCount < numPoints; pointCount++) {
1810 when = mCurves[currentCurve].points[pointCount].Freq / mHiFreq;
1811 value = mCurves[currentCurve].points[pointCount].dB;
1812 if(when <= 1) {
1813 env->Insert(when, value);
1814 if (when == 1)
1815 break;
1816 }
1817 else {
1818 // There are more points at higher freqs,
1819 // so interpolate next one then stop.
1820 when = 1.0;
1821 double nextDB = mCurves[currentCurve].points[pointCount].dB;
1822 if (pointCount > 0) {
1823 double nextF = mCurves[currentCurve].points[pointCount].Freq;
1824 double lastF = mCurves[currentCurve].points[pointCount-1].Freq;
1825 double lastDB = mCurves[currentCurve].points[pointCount-1].dB;
1826 value = lastDB +
1827 ((nextDB - lastDB) *
1828 ((mHiFreq - lastF) / (nextF - lastF)));
1829 }
1830 else
1831 value = nextDB;
1832 env->Insert(when, value);
1833 break;
1834 }
1835 }
1836 }
1837 else { // log Hz scale
1838 double loLog = log10((double) loFreqI);
1839 double hiLog = log10(mHiFreq);
1840 double denom = hiLog - loLog;
1841 int firstAbove20Hz;
1842
1843 // log scale EQ starts at 20 Hz (threshold of hearing).
1844 // so find the first point (if any) above 20 Hz.
1845 for (firstAbove20Hz = 0; firstAbove20Hz < numPoints; firstAbove20Hz++) {
1846 if (mCurves[currentCurve].points[firstAbove20Hz].Freq > loFreqI)
1847 break;
1848 }
1849
1850 if (firstAbove20Hz == numPoints) {
1851 // All points below 20 Hz, so just use final point.
1852 when = 0.0;
1853 value = mCurves[currentCurve].points[numPoints-1].dB;
1854 env->Insert(when, value);
1855 ForceRecalc();
1856 return;
1857 }
1858
1859 if (firstAbove20Hz > 0) {
1860 // At least one point is before 20 Hz and there are more
1861 // beyond 20 Hz, so interpolate the first
1862 double prevF = mCurves[currentCurve].points[firstAbove20Hz-1].Freq;
1863 prevF = log10(std::max(1.0, prevF)); // log zero is bad.
1864 double prevDB = mCurves[currentCurve].points[firstAbove20Hz-1].dB;
1865 double nextF = log10(mCurves[currentCurve].points[firstAbove20Hz].Freq);
1866 double nextDB = mCurves[currentCurve].points[firstAbove20Hz].dB;
1867 when = 0.0;
1868 value = nextDB - ((nextDB - prevDB) * ((nextF - loLog) / (nextF - prevF)));
1869 env->Insert(when, value);
1870 }
1871
1872 // Now get the rest.
1873 for(int pointCount = firstAbove20Hz; pointCount < numPoints; pointCount++)
1874 {
1875 double flog = log10(mCurves[currentCurve].points[pointCount].Freq);
1876 wxASSERT(mCurves[currentCurve].points[pointCount].Freq >= loFreqI);
1877
1878 when = (flog - loLog)/denom;
1879 value = mCurves[currentCurve].points[pointCount].dB;
1880 if(when <= 1.0) {
1881 env->Insert(when, value);
1882 }
1883 else {
1884 // This looks weird when adjusting curve in Draw mode if
1885 // there is a point off-screen.
1886
1887 /*
1888 // we have a point beyond fs/2. Insert it so that env code can use it.
1889 // but just this one, we have no use for the rest
1890 env->SetTrackLen(when); // can't Insert if the envelope isn't long enough
1891 env->Insert(when, value);
1892 break;
1893 */
1894
1895 // interpolate the final point instead
1896 when = 1.0;
1897 if (pointCount > 0) {
1898 double lastDB = mCurves[currentCurve].points[pointCount-1].dB;
1899 double logLastF =
1900 log10(mCurves[currentCurve].points[pointCount-1].Freq);
1901 value = lastDB +
1902 ((value - lastDB) *
1903 ((log10(mHiFreq) - logLastF) / (flog - logLastF)));
1904 }
1905 env->Insert(when, value);
1906 break;
1907 }
1908 }
1909 }
1910 ForceRecalc();
1911}
1912
1914{
1915 setCurve((int) mCurves.size() - 1);
1916}
1917
1918void EffectEqualization::setCurve(const wxString &curveName)
1919{
1920 unsigned i = 0;
1921 for( i = 0; i < mCurves.size(); i++ )
1922 if( curveName == mCurves[ i ].Name )
1923 break;
1924 if( i == mCurves.size())
1925 {
1927 XO("Requested curve not found, using 'unnamed'"),
1928 wxOK|wxICON_ERROR,
1929 XO("Curve not found") );
1930 setCurve();
1931 }
1932 else
1933 setCurve( i );
1934}
1935
1936//
1937// Set NEW curve selection (safe to call outside of the UI)
1938//
1940{
1941 // Set current choice
1942 if (mCurve)
1943 {
1944 mCurve->SetSelection( curve );
1945 mCurveName = mCurves[ curve ].Name;
1946 }
1947}
1948
1949//
1950// Tell panel to recalc (safe to call outside of UI)
1951//
1953{
1954 if (mPanel)
1955 {
1957 }
1958}
1959
1960//
1961// Capture updated envelope
1962//
1964{
1965 if (IsLinear())
1966 {
1967 EnvelopeUpdated(mLinEnvelope.get(), true);
1968 }
1969 else
1970 {
1971 EnvelopeUpdated(mLogEnvelope.get(), false);
1972 }
1973}
1974
1976{
1977 // Allocate and populate point arrays
1978 size_t numPoints = env->GetNumberOfPoints();
1979 Doubles when{ numPoints };
1980 Doubles value{ numPoints };
1981 env->GetPoints( when.get(), value.get(), numPoints );
1982
1983 // Clear the unnamed curve
1984 int curve = mCurves.size() - 1;
1985 mCurves[ curve ].points.clear();
1986
1987 if(lin)
1988 {
1989 // Copy and convert points
1990 for (size_t point = 0; point < numPoints; point++)
1991 {
1992 double freq = when[ point ] * mHiFreq;
1993 double db = value[ point ];
1994
1995 // Add it to the curve
1996 mCurves[ curve ].points.push_back( EQPoint( freq, db ) );
1997 }
1998 }
1999 else
2000 {
2001 double loLog = log10( 20. );
2002 double hiLog = log10( mHiFreq );
2003 double denom = hiLog - loLog;
2004
2005 // Copy and convert points
2006 for (size_t point = 0; point < numPoints; point++)
2007 {
2008 double freq = pow( 10., ( ( when[ point ] * denom ) + loLog ));
2009 double db = value[ point ];
2010
2011 // Add it to the curve
2012 mCurves[ curve ].points.push_back( EQPoint( freq, db ) );
2013 }
2014 }
2015 // Remember that we've updated the unnamed curve
2016 mDirty = true;
2017
2018 // set 'unnamed' as the selected curve
2019 Select( (int) mCurves.size() - 1 );
2020}
2021
2022//
2023//
2024//
2026{
2027 return mDrawMode && mLin;
2028}
2029
2030//
2031// Flatten the curve
2032//
2034{
2035 mLogEnvelope->Flatten(0.);
2036 mLogEnvelope->SetTrackLen(1.0);
2037 mLinEnvelope->Flatten(0.);
2038 mLinEnvelope->SetTrackLen(1.0);
2039 ForceRecalc();
2040 if( !mDrawMode )
2041 {
2042 for( size_t i = 0; i < mBandsInUse; i++)
2043 {
2044 mSliders[i]->SetValue(0);
2045 mSlidersOld[i] = 0;
2046 mEQVals[i] = 0.;
2047
2048 wxString tip;
2049 if( kThirdOct[i] < 1000.)
2050 tip.Printf( wxT("%dHz\n%.1fdB"), (int)kThirdOct[i], 0. );
2051 else
2052 tip.Printf( wxT("%gkHz\n%.1fdB"), kThirdOct[i]/1000., 0. );
2053 mSliders[i]->SetToolTip(tip);
2054 }
2055 }
2057}
2058
2059//
2060// Process XML tags and handle the ones we recognize
2061//
2062bool EffectEqualization::HandleXMLTag(const std::string_view& tag, const AttributesList &attrs)
2063{
2064 // May want to add a version strings...
2065 if (tag == "equalizationeffect")
2066 {
2067 return true;
2068 }
2069
2070 // Located a NEW curve
2071 if (tag == "curve")
2072 {
2073 // Process the attributes
2074 for (auto pair : attrs)
2075 {
2076 auto attr = pair.first;
2077 auto value = pair.second;
2078
2079 // Create a NEW curve and name it
2080 if( attr == "name" )
2081 {
2082 const wxString strValue = value.ToWString();
2083 // check for a duplicate name and add (n) if there is one
2084 int n = 0;
2085 wxString strValueTemp = strValue;
2086 bool exists;
2087 do
2088 {
2089 exists = false;
2090 for(size_t i = 0; i < mCurves.size(); i++)
2091 {
2092 if(n>0)
2093 strValueTemp.Printf(wxT("%s (%d)"),strValue,n);
2094 if(mCurves[i].Name == strValueTemp)
2095 {
2096 exists = true;
2097 break;
2098 }
2099 }
2100 n++;
2101 }
2102 while(exists == true);
2103
2104 mCurves.push_back( EQCurve( strValueTemp ) );
2105 }
2106 }
2107
2108 // Tell caller it was processed
2109 return true;
2110 }
2111
2112 // Located a NEW point
2113 if(tag == "point")
2114 {
2115 // Set defaults in case attributes are missing
2116 double f = 0.0;
2117 double d = 0.0;
2118
2119 // Process the attributes
2120 double dblValue;
2121 for (auto pair : attrs)
2122 {
2123 auto attr = pair.first;
2124 auto value = pair.second;
2125
2126 // Get the frequency
2127 if( attr == "f" )
2128 {
2129 if (!value.TryGet(dblValue))
2130 return false;
2131 f = dblValue;
2132 }
2133 // Get the dB
2134 else if( attr == "d" )
2135 {
2136 if (!value.TryGet(dblValue))
2137 return false;
2138 d = dblValue;
2139 }
2140 }
2141
2142 // Create a NEW point
2143 mCurves[ mCurves.size() - 1 ].points.push_back( EQPoint( f, d ) );
2144
2145 // Tell caller it was processed
2146 return true;
2147 }
2148
2149 // Tell caller we didn't understand the tag
2150 return false;
2151}
2152
2153//
2154// Return handler for recognized tags
2155//
2157{
2158 if (tag == "equalizationeffect")
2159 {
2160 return this;
2161 }
2162
2163 if (tag == "curve")
2164 {
2165 return this;
2166 }
2167
2168 if (tag == "point")
2169 {
2170 return this;
2171 }
2172
2173 return NULL;
2174}
2175
2176//
2177// Write all of the curves to the XML file
2178//
2180// may throw
2181{
2182 // Start our hierarchy
2183 xmlFile.StartTag( wxT( "equalizationeffect" ) );
2184
2185 // Write all curves
2186 int numCurves = mCurves.size();
2187 int curve;
2188 for( curve = 0; curve < numCurves; curve++ )
2189 {
2190 // Start a NEW curve
2191 xmlFile.StartTag( wxT( "curve" ) );
2192 xmlFile.WriteAttr( wxT( "name" ), mCurves[ curve ].Name );
2193
2194 // Write all points
2195 int numPoints = mCurves[ curve ].points.size();
2196 int point;
2197 for( point = 0; point < numPoints; point++ )
2198 {
2199 // Write NEW point
2200 xmlFile.StartTag( wxT( "point" ) );
2201 xmlFile.WriteAttr( wxT( "f" ), mCurves[ curve ].points[ point ].Freq, 12 );
2202 xmlFile.WriteAttr( wxT( "d" ), mCurves[ curve ].points[ point ].dB, 12 );
2203 xmlFile.EndTag( wxT( "point" ) );
2204 }
2205
2206 // Terminate curve
2207 xmlFile.EndTag( wxT( "curve" ) );
2208 }
2209
2210 // Terminate our hierarchy
2211 xmlFile.EndTag( wxT( "equalizationeffect" ) );
2212}
2213
2215//
2216// All EffectEqualization methods beyond this point interact with the UI, so
2217// can't be called while the UI is not displayed.
2218//
2220
2222{
2223
2224 // Reload the curve names
2225 if( mCurve )
2226 mCurve->Clear();
2227 bool selectedCurveExists = false;
2228 for (size_t i = 0, cnt = mCurves.size(); i < cnt; i++)
2229 {
2230 if (mCurveName == mCurves[ i ].Name)
2231 selectedCurveExists = true;
2232 if( mCurve )
2233 mCurve->Append(mCurves[ i ].Name);
2234 }
2235 // In rare circumstances, mCurveName may not exist (bug 1891)
2236 if (!selectedCurveExists)
2237 mCurveName = mCurves[ (int)mCurves.size() - 1 ].Name;
2238 if( mCurve )
2239 mCurve->SetStringSelection(mCurveName);
2240
2241 // Allow the control to resize
2242 if( mCurve )
2243 mCurve->SetMinSize({-1, -1});
2244
2245 // Set initial curve
2247}
2248
2250{
2251 size_t numPoints = mLogEnvelope->GetNumberOfPoints();
2252 Doubles when{ numPoints };
2253 Doubles value{ numPoints };
2254 double deltadB = 0.1;
2255 double dx, dy, dx1, dy1, err;
2256
2257 mLogEnvelope->GetPoints( when.get(), value.get(), numPoints );
2258
2259 // set 'unnamed' as the selected curve
2261
2262 bool flag = true;
2263 while (flag)
2264 {
2265 flag = false;
2266 int numDeleted = 0;
2267 mLogEnvelope->GetPoints( when.get(), value.get(), numPoints );
2268 for (size_t j = 0; j + 2 < numPoints; j++)
2269 {
2270 dx = when[j+2+numDeleted] - when[j+numDeleted];
2271 dy = value[j+2+numDeleted] - value[j+numDeleted];
2272 dx1 = when[j+numDeleted+1] - when[j+numDeleted];
2273 dy1 = dy * dx1 / dx;
2274 err = fabs(value[j+numDeleted+1] - (value[j+numDeleted] + dy1));
2275 if( err < deltadB )
2276 { // within < deltadB dB?
2277 mLogEnvelope->Delete(j+1);
2278 numPoints--;
2279 numDeleted++;
2280 flag = true;
2281 }
2282 }
2283 }
2284
2285 if(mLin) // do not use IsLinear() here
2286 {
2287 EnvLogToLin();
2288 mEnvelope = mLinEnvelope.get();
2289 mFreqRuler->ruler.SetLog(false);
2291 }
2292
2293 szrV->Show(szrG,false);
2294 szrH->Show(szrI,false);
2295 szrH->Show(szrL,true);
2296
2297 mUIParent->Layout();
2298 wxGetTopLevelParent(mUIParent)->Layout();
2299 ForceRecalc(); // it may have changed slightly due to the deletion of points
2300}
2301
2303{
2304 double loLog = log10(mLoFreq);
2305 double hiLog = log10(mHiFreq);
2306 double denom = hiLog - loLog;
2307
2308 if(mLin) //going from lin to log freq scale - do not use IsLinear() here
2309 { // add some extra points to the linear envelope for the graphic to follow
2310 double step = pow(2., 1./12.); // twelve steps per octave
2311 double when,value;
2312 for(double freq=10.; freq<mHiFreq; freq*=step)
2313 {
2314 when = freq/mHiFreq;
2315 value = mLinEnvelope->GetValue(when);
2316 mLinEnvelope->Insert(when, value);
2317 }
2318
2319 EnvLinToLog();
2320 mEnvelope = mLogEnvelope.get();
2321 mFreqRuler->ruler.SetLog(true);
2323 }
2324
2325 for (size_t i = 0; i < mBandsInUse; i++)
2326 {
2327 if( kThirdOct[i] == mLoFreq )
2328 mWhenSliders[i] = 0.;
2329 else
2330 mWhenSliders[i] = (log10(kThirdOct[i])-loLog)/denom;
2331 mEQVals[i] = mLogEnvelope->GetValue(mWhenSliders[i]); //set initial values of sliders
2332 if( mEQVals[i] > 20.)
2333 mEQVals[i] = 20.;
2334 if( mEQVals[i] < -20.)
2335 mEQVals[i] = -20.;
2336 }
2337 ErrMin(); //move sliders to minimise error
2338 for (size_t i = 0; i < mBandsInUse; i++)
2339 {
2340 mSliders[i]->SetValue(lrint(mEQVals[i])); //actually set slider positions
2341 mSlidersOld[i] = mSliders[i]->GetValue();
2342 wxString tip;
2343 if( kThirdOct[i] < 1000.)
2344 tip.Printf( wxT("%dHz\n%.1fdB"), (int)kThirdOct[i], mEQVals[i] );
2345 else
2346 tip.Printf( wxT("%gkHz\n%.1fdB"), kThirdOct[i]/1000., mEQVals[i] );
2347 mSliders[i]->SetToolTip(tip);
2348 }
2349
2350 szrV->Show(szrG,true); // eq sliders
2351 szrH->Show(szrI,mOptions == kEqLegacy ); // interpolation choice
2352 szrH->Show(szrL,false); // linear freq checkbox
2353
2354 mUIParent->Layout();
2355 wxGetTopLevelParent(mUIParent)->Layout();
2356 mUIParent->Layout();
2357 wxGetTopLevelParent(mUIParent)->Layout();
2358
2359 GraphicEQ(mLogEnvelope.get());
2360 mDrawMode = false;
2361}
2362
2364{
2365 size_t numPoints = mLogEnvelope->GetNumberOfPoints();
2366 if( numPoints == 0 )
2367 {
2368 return;
2369 }
2370
2371 Doubles when{ numPoints };
2372 Doubles value{ numPoints };
2373
2374 mLinEnvelope->Flatten(0.);
2375 mLinEnvelope->SetTrackLen(1.0);
2376 mLogEnvelope->GetPoints( when.get(), value.get(), numPoints );
2377 mLinEnvelope->Reassign(0., value[0]);
2378 double loLog = log10(20.);
2379 double hiLog = log10(mHiFreq);
2380 double denom = hiLog - loLog;
2381
2382 for (size_t i = 0; i < numPoints; i++)
2383 mLinEnvelope->Insert(pow( 10., ((when[i] * denom) + loLog))/mHiFreq , value[i]);
2384 mLinEnvelope->Reassign(1., value[numPoints-1]);
2385}
2386
2388{
2389 size_t numPoints = mLinEnvelope->GetNumberOfPoints();
2390 if( numPoints == 0 )
2391 {
2392 return;
2393 }
2394
2395 Doubles when{ numPoints };
2396 Doubles value{ numPoints };
2397
2398 mLogEnvelope->Flatten(0.);
2399 mLogEnvelope->SetTrackLen(1.0);
2400 mLinEnvelope->GetPoints( when.get(), value.get(), numPoints );
2401 mLogEnvelope->Reassign(0., value[0]);
2402 double loLog = log10(20.);
2403 double hiLog = log10(mHiFreq);
2404 double denom = hiLog - loLog;
2405 bool changed = false;
2406
2407 for (size_t i = 0; i < numPoints; i++)
2408 {
2409 if( when[i]*mHiFreq >= 20 )
2410 {
2411 // Caution: on Linux, when when == 20, the log calculation rounds
2412 // to just under zero, which causes an assert error.
2413 double flog = (log10(when[i]*mHiFreq)-loLog)/denom;
2414 mLogEnvelope->Insert(std::max(0.0, flog) , value[i]);
2415 }
2416 else
2417 { //get the first point as close as we can to the last point requested
2418 changed = true;
2419 double v = value[i];
2420 mLogEnvelope->Insert(0., v);
2421 }
2422 }
2423 mLogEnvelope->Reassign(1., value[numPoints - 1]);
2424
2425 if(changed)
2426 EnvelopeUpdated(mLogEnvelope.get(), false);
2427}
2428
2430{
2431 double vals[NUM_PTS];
2432 double error = 0.0;
2433 double oldError = 0.0;
2434 double mEQValsOld = 0.0;
2435 double correction = 1.6;
2436 bool flag;
2437 size_t j=0;
2438 Envelope testEnvelope{ *mLogEnvelope };
2439
2440 for(size_t i = 0; i < NUM_PTS; i++)
2441 vals[i] = testEnvelope.GetValue(mWhens[i]);
2442
2443 // Do error minimisation
2444 error = 0.;
2445 GraphicEQ(&testEnvelope);
2446 for(size_t i = 0; i < NUM_PTS; i++) //calc initial error
2447 {
2448 double err = vals[i] - testEnvelope.GetValue(mWhens[i]);
2449 error += err*err;
2450 }
2451 oldError = error;
2452 while( j < mBandsInUse*12 ) //loop over the sliders a number of times
2453 {
2454 auto i = j % mBandsInUse; //use this slider
2455 if( (j > 0) & (i == 0) ) // if we've come back to the first slider again...
2456 {
2457 if( correction > 0 )
2458 correction = -correction; //go down
2459 else
2460 correction = -correction/2.; //go up half as much
2461 }
2462 flag = true; // check if we've hit the slider limit
2463 do
2464 {
2465 oldError = error;
2466 mEQValsOld = mEQVals[i];
2467 mEQVals[i] += correction; //move fader value
2468 if( mEQVals[i] > 20. )
2469 {
2470 mEQVals[i] = 20.;
2471 flag = false;
2472 }
2473 if( mEQVals[i] < -20. )
2474 {
2475 mEQVals[i] = -20.;
2476 flag = false;
2477 }
2478 GraphicEQ(&testEnvelope); //calculate envelope
2479 error = 0.;
2480 for(size_t k = 0; k < NUM_PTS; k++) //calculate error
2481 {
2482 double err = vals[k] - testEnvelope.GetValue(mWhens[k]);
2483 error += err*err;
2484 }
2485 }
2486 while( (error < oldError) && flag );
2487 if( error > oldError )
2488 {
2489 mEQVals[i] = mEQValsOld; //last one didn't work
2490 error = oldError;
2491 }
2492 else
2493 oldError = error;
2494 if( error < .0025 * mBandsInUse)
2495 break; // close enuff
2496 j++; //try next slider
2497 }
2498 if( error > .0025 * mBandsInUse ) // not within 0.05dB on each slider, on average
2499 {
2500 Select( (int) mCurves.size() - 1 );
2501 EnvelopeUpdated(&testEnvelope, false);
2502 }
2503}
2504
2506{
2507 // JKC: 'value' is for height of curve.
2508 // The 0.0 initial value would only get used if NUM_PTS were 0.
2509 double value = 0.0;
2510 double dist, span, s;
2511
2512 env->Flatten(0.);
2513 env->SetTrackLen(1.0);
2514
2515 switch( mInterp )
2516 {
2517 case kBspline: // B-spline
2518 {
2519 int minF = 0;
2520 for(size_t i = 0; i < NUM_PTS; i++)
2521 {
2522 while( (mWhenSliders[minF] <= mWhens[i]) & (minF < (int)mBandsInUse) )
2523 minF++;
2524 minF--;
2525 if( minF < 0 ) //before first slider
2526 {
2527 dist = mWhens[i] - mWhenSliders[0];
2528 span = mWhenSliders[1] - mWhenSliders[0];
2529 s = dist/span;
2530 if( s < -1.5 )
2531 value = 0.;
2532 else if( s < -.5 )
2533 value = mEQVals[0]*(s + 1.5)*(s + 1.5)/2.;
2534 else
2535 value = mEQVals[0]*(.75 - s*s) + mEQVals[1]*(s + .5)*(s + .5)/2.;
2536 }
2537 else
2538 {
2539 if( mWhens[i] > mWhenSliders[mBandsInUse-1] ) //after last fader
2540 {
2541 dist = mWhens[i] - mWhenSliders[mBandsInUse-1];
2543 s = dist/span;
2544 if( s > 1.5 )
2545 value = 0.;
2546 else if( s > .5 )
2547 value = mEQVals[mBandsInUse-1]*(s - 1.5)*(s - 1.5)/2.;
2548 else
2549 value = mEQVals[mBandsInUse-1]*(.75 - s*s) +
2550 mEQVals[mBandsInUse-2]*(s - .5)*(s - .5)/2.;
2551 }
2552 else //normal case
2553 {
2554 dist = mWhens[i] - mWhenSliders[minF];
2555 span = mWhenSliders[minF+1] - mWhenSliders[minF];
2556 s = dist/span;
2557 if(s < .5 )
2558 {
2559 value = mEQVals[minF]*(0.75 - s*s);
2560 if( minF+1 < (int)mBandsInUse )
2561 value += mEQVals[minF+1]*(s+.5)*(s+.5)/2.;
2562 if( minF-1 >= 0 )
2563 value += mEQVals[minF-1]*(s-.5)*(s-.5)/2.;
2564 }
2565 else
2566 {
2567 value = mEQVals[minF]*(s-1.5)*(s-1.5)/2.;
2568 if( minF+1 < (int)mBandsInUse )
2569 value += mEQVals[minF+1]*(.75-(1.-s)*(1.-s));
2570 if( minF+2 < (int)mBandsInUse )
2571 value += mEQVals[minF+2]*(s-.5)*(s-.5)/2.;
2572 }
2573 }
2574 }
2575 if(mWhens[i]<=0.)
2576 env->Reassign(0., value);
2577 env->Insert( mWhens[i], value );
2578 }
2579 env->Reassign( 1., value );
2580 break;
2581 }
2582
2583 case kCosine: // Cosine squared
2584 {
2585 int minF = 0;
2586 for(size_t i = 0; i < NUM_PTS; i++)
2587 {
2588 while( (mWhenSliders[minF] <= mWhens[i]) & (minF < (int)mBandsInUse) )
2589 minF++;
2590 minF--;
2591 if( minF < 0 ) //before first slider
2592 {
2593 dist = mWhenSliders[0] - mWhens[i];
2594 span = mWhenSliders[1] - mWhenSliders[0];
2595 if( dist < span )
2596 value = mEQVals[0]*(1. + cos(M_PI*dist/span))/2.;
2597 else
2598 value = 0.;
2599 }
2600 else
2601 {
2602 if( mWhens[i] > mWhenSliders[mBandsInUse-1] ) //after last fader
2603 {
2605 dist = mWhens[i] - mWhenSliders[mBandsInUse-1];
2606 if( dist < span )
2607 value = mEQVals[mBandsInUse-1]*(1. + cos(M_PI*dist/span))/2.;
2608 else
2609 value = 0.;
2610 }
2611 else //normal case
2612 {
2613 span = mWhenSliders[minF+1] - mWhenSliders[minF];
2614 dist = mWhenSliders[minF+1] - mWhens[i];
2615 value = mEQVals[minF]*(1. + cos(M_PI*(span-dist)/span))/2. +
2616 mEQVals[minF+1]*(1. + cos(M_PI*dist/span))/2.;
2617 }
2618 }
2619 if(mWhens[i]<=0.)
2620 env->Reassign(0., value);
2621 env->Insert( mWhens[i], value );
2622 }
2623 env->Reassign( 1., value );
2624 break;
2625 }
2626
2627 case kCubic: // Cubic Spline
2628 {
2629 double y2[NUMBER_OF_BANDS+1];
2632 for(double xf=0; xf<1.; xf+=1./NUM_PTS)
2633 {
2634 env->Insert(xf, splint(mWhenSliders, mEQVals, mBandsInUse+1, y2, xf));
2635 }
2636 break;
2637 }
2638 }
2639
2640 ForceRecalc();
2641}
2642
2643void EffectEqualization::spline(double x[], double y[], size_t n, double y2[])
2644{
2645 wxASSERT( n > 0 );
2646
2647 double p, sig;
2648 Doubles u{ n };
2649
2650 y2[0] = 0.; //
2651 u[0] = 0.; //'natural' boundary conditions
2652 for (size_t i = 1; i + 1 < n; i++)
2653 {
2654 sig = ( x[i] - x[i-1] ) / ( x[i+1] - x[i-1] );
2655 p = sig * y2[i-1] + 2.;
2656 y2[i] = (sig - 1.)/p;
2657 u[i] = ( y[i+1] - y[i] ) / ( x[i+1] - x[i] ) - ( y[i] - y[i-1] ) / ( x[i] - x[i-1] );
2658 u[i] = (6.*u[i]/( x[i+1] - x[i-1] ) - sig * u[i-1]) / p;
2659 }
2660 y2[n - 1] = 0.;
2661 for (size_t i = n - 1; i--;)
2662 y2[i] = y2[i]*y2[i+1] + u[i];
2663}
2664
2665double EffectEqualization::splint(double x[], double y[], size_t n, double y2[], double xr)
2666{
2667 wxASSERT( n > 1 );
2668
2669 double a, b, h;
2670 static double xlast = 0.; // remember last x value requested
2671 static size_t k = 0; // and which interval we were in
2672
2673 if( xr < xlast )
2674 k = 0; // gone back to start, (or somewhere to the left)
2675 xlast = xr;
2676 while( (x[k] <= xr) && (k + 1 < n) )
2677 k++;
2678 wxASSERT( k > 0 );
2679 k--;
2680 h = x[k+1] - x[k];
2681 a = ( x[k+1] - xr )/h;
2682 b = (xr - x[k])/h;
2683 return( a*y[k]+b*y[k+1]+((a*a*a-a)*y2[k]+(b*b*b-b)*y2[k+1])*h*h/6.);
2684}
2685
2687{
2688}
2689
2690void EffectEqualization::OnSize(wxSizeEvent & event)
2691{
2692 mUIParent->Layout();
2693 event.Skip();
2694}
2695
2696void EffectEqualization::OnSlider(wxCommandEvent & event)
2697{
2698 wxSlider *s = (wxSlider *)event.GetEventObject();
2699 for (size_t i = 0; i < mBandsInUse; i++)
2700 {
2701 if( s == mSliders[i])
2702 {
2703 int posn = mSliders[i]->GetValue();
2704 if( wxGetKeyState(WXK_SHIFT) )
2705 {
2706 if( posn > mSlidersOld[i] )
2707 mEQVals[i] += (float).1;
2708 else
2709 if( posn < mSlidersOld[i] )
2710 mEQVals[i] -= .1f;
2711 }
2712 else
2713 mEQVals[i] += (posn - mSlidersOld[i]);
2714 if( mEQVals[i] > 20. )
2715 mEQVals[i] = 20.;
2716 if( mEQVals[i] < -20. )
2717 mEQVals[i] = -20.;
2718 int newPosn = (int)mEQVals[i];
2719 mSliders[i]->SetValue( newPosn );
2720 mSlidersOld[i] = newPosn;
2721 wxString tip;
2722 if( kThirdOct[i] < 1000.)
2723 tip.Printf( wxT("%dHz\n%.1fdB"), (int)kThirdOct[i], mEQVals[i] );
2724 else
2725 tip.Printf( wxT("%gkHz\n%.1fdB"), kThirdOct[i]/1000., mEQVals[i] );
2726 s->SetToolTip(tip);
2727 break;
2728 }
2729 }
2730 GraphicEQ(mLogEnvelope.get());
2732}
2733
2734void EffectEqualization::OnInterp(wxCommandEvent & WXUNUSED(event))
2735{
2736 bool bIsGraphic = !mDrawMode;
2737 if (bIsGraphic)
2738 {
2739 GraphicEQ(mLogEnvelope.get());
2741 }
2742 mInterp = mInterpChoice->GetSelection();
2743}
2744
2745void EffectEqualization::OnDrawMode(wxCommandEvent & WXUNUSED(event))
2746{
2747 mDrawMode = true;
2748 UpdateDraw();
2749}
2750
2751void EffectEqualization::OnGraphicMode(wxCommandEvent & WXUNUSED(event))
2752{
2753 mDrawMode = false;
2754 UpdateGraphic();
2755}
2756
2757void EffectEqualization::OnSliderM(wxCommandEvent & WXUNUSED(event))
2758{
2759 size_t m = 2 * mMSlider->GetValue() + 1;
2760 // Must be odd
2761 wxASSERT( (m & 1) == 1 );
2762
2763 if (m != mM) {
2764 mM = m;
2765 wxString tip;
2766 tip.Printf(wxT("%d"), (int)mM);
2767 mMText->SetLabel(tip);
2768 mMText->SetName(mMText->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
2769 mMSlider->SetToolTip(tip);
2770
2771 ForceRecalc();
2772 }
2773}
2774
2775void EffectEqualization::OnSliderDBMIN(wxCommandEvent & WXUNUSED(event))
2776{
2777 float dB = mdBMinSlider->GetValue();
2778 if (dB != mdBMin) {
2779 mdBMin = dB;
2780 wxString tip;
2781 tip.Printf(_("%d dB"), (int)mdBMin);
2782 mdBMinSlider->SetToolTip(tip);
2783 UpdateRuler();
2784 }
2785}
2786
2787void EffectEqualization::OnSliderDBMAX(wxCommandEvent & WXUNUSED(event))
2788{
2789 float dB = mdBMaxSlider->GetValue();
2790 if (dB != mdBMax) {
2791 mdBMax = dB;
2792 wxString tip;
2793 tip.Printf(_("%d dB"), (int)mdBMax);
2794 mdBMaxSlider->SetToolTip(tip);
2795 UpdateRuler();
2796 }
2797}
2798
2799//
2800// New curve was selected
2801//
2802void EffectEqualization::OnCurve(wxCommandEvent & WXUNUSED(event))
2803{
2804 // Select NEW curve
2805 wxASSERT( mCurve != NULL );
2806 setCurve( mCurve->GetCurrentSelection() );
2807 if( !mDrawMode )
2808 UpdateGraphic();
2809}
2810
2811//
2812// User wants to modify the list in some way
2813//
2814void EffectEqualization::OnManage(wxCommandEvent & WXUNUSED(event))
2815{
2816 EditCurvesDialog d(mUIParent, this, mCurve->GetSelection());
2817 d.ShowModal();
2818
2819 // Reload the curve names
2820 UpdateCurves();
2821
2822 // Allow control to resize
2823 mUIParent->Layout();
2824}
2825
2826void EffectEqualization::OnClear(wxCommandEvent & WXUNUSED(event))
2827{
2828 Flatten();
2829}
2830
2831void EffectEqualization::OnInvert(wxCommandEvent & WXUNUSED(event)) // Inverts any curve
2832{
2833 if(!mDrawMode) // Graphic (Slider) mode. Invert the sliders.
2834 {
2835 for (size_t i = 0; i < mBandsInUse; i++)
2836 {
2837 mEQVals[i] = -mEQVals[i];
2838 int newPosn = (int)mEQVals[i];
2839 mSliders[i]->SetValue( newPosn );
2840 mSlidersOld[i] = newPosn;
2841
2842 wxString tip;
2843 if( kThirdOct[i] < 1000.)
2844 tip.Printf( wxT("%dHz\n%.1fdB"), (int)kThirdOct[i], mEQVals[i] );
2845 else
2846 tip.Printf( wxT("%gkHz\n%.1fdB"), kThirdOct[i]/1000., mEQVals[i] );
2847 mSliders[i]->SetToolTip(tip);
2848 }
2849 GraphicEQ(mLogEnvelope.get());
2850 }
2851 else // Draw mode. Invert the points.
2852 {
2853 bool lin = IsLinear(); // refers to the 'log' or 'lin' of the frequency scale, not the amplitude
2854 size_t numPoints; // number of points in the curve/envelope
2855
2856 // determine if log or lin curve is the current one
2857 // and find out how many points are in the curve
2858 if(lin) // lin freq scale and so envelope
2859 {
2860 numPoints = mLinEnvelope->GetNumberOfPoints();
2861 }
2862 else
2863 {
2864 numPoints = mLogEnvelope->GetNumberOfPoints();
2865 }
2866
2867 if( numPoints == 0 )
2868 return;
2869
2870 Doubles when{ numPoints };
2871 Doubles value{ numPoints };
2872
2873 if(lin)
2874 mLinEnvelope->GetPoints( when.get(), value.get(), numPoints );
2875 else
2876 mLogEnvelope->GetPoints( when.get(), value.get(), numPoints );
2877
2878 // invert the curve
2879 for (size_t i = 0; i < numPoints; i++)
2880 {
2881 if(lin)
2882 mLinEnvelope->Reassign(when[i] , -value[i]);
2883 else
2884 mLogEnvelope->Reassign(when[i] , -value[i]);
2885 }
2886
2887 // copy it back to the other one (just in case)
2888 if(lin)
2889 EnvLinToLog();
2890 else
2891 EnvLogToLin();
2892 }
2893
2894 // and update the display etc
2895 ForceRecalc();
2897}
2898
2899void EffectEqualization::OnGridOnOff(wxCommandEvent & WXUNUSED(event))
2900{
2901 mDrawGrid = mGridOnOff->IsChecked();
2902 mPanel->Refresh(false);
2903}
2904
2905void EffectEqualization::OnLinFreq(wxCommandEvent & WXUNUSED(event))
2906{
2907 mLin = mLinFreq->IsChecked();
2908 if(IsLinear()) //going from log to lin freq scale
2909 {
2910 mFreqRuler->ruler.SetLog(false);
2912 EnvLogToLin();
2913 mEnvelope = mLinEnvelope.get();
2914 mLin = true;
2915 }
2916 else //going from lin to log freq scale
2917 {
2918 mFreqRuler->ruler.SetLog(true);
2920 EnvLinToLog();
2921 mEnvelope = mLogEnvelope.get();
2922 mLin = false;
2923 }
2924 mFreqRuler->Refresh(false);
2925 ForceRecalc();
2926}
2927
2928#ifdef EXPERIMENTAL_EQ_SSE_THREADED
2929
2930void EffectEqualization::OnProcessingRadio(wxCommandEvent & event)
2931{
2932 int testEvent=event.GetId();
2933 switch(testEvent)
2934 {
2935 case ID_DefaultMath: EffectEqualization48x::SetMathPath(MATH_FUNCTION_ORIGINAL);
2936 break;
2937 case ID_SSE: EffectEqualization48x::SetMathPath(MATH_FUNCTION_SSE);
2938 break;
2939 case ID_SSEThreaded: EffectEqualization48x::SetMathPath(MATH_FUNCTION_THREADED | MATH_FUNCTION_SSE);
2940 break;
2941 case ID_AVX: testEvent = 2;
2942 break;
2943 case ID_AVXThreaded: testEvent = 2;
2944 break;
2945 }
2946
2947};
2948
2949void EffectEqualization::OnBench( wxCommandEvent & event)
2950{
2951 mBench=true;
2952 // OnOk(event);
2953}
2954
2955#endif
2956
2957//----------------------------------------------------------------------------
2958// EqualizationPanel
2959//----------------------------------------------------------------------------
2960
2961BEGIN_EVENT_TABLE(EqualizationPanel, wxPanelWrapper)
2963 EVT_MOUSE_EVENTS(EqualizationPanel::OnMouseEvent)
2964 EVT_MOUSE_CAPTURE_LOST(EqualizationPanel::OnCaptureLost)
2967
2969 wxWindow *parent, wxWindowID winid, EffectEqualization *effect)
2970: wxPanelWrapper(parent, winid)
2971{
2972 mParent = parent;
2973 mEffect = effect;
2974
2975 mBitmap = NULL;
2976 mWidth = 0;
2977 mHeight = 0;
2978
2979 mLinEditor = std::make_unique<EnvelopeEditor>(*mEffect->mLinEnvelope, false);
2980 mLogEditor = std::make_unique<EnvelopeEditor>(*mEffect->mLogEnvelope, false);
2981 mEffect->mEnvelope->Flatten(0.);
2982 mEffect->mEnvelope->SetTrackLen(1.0);
2983
2984 ForceRecalc();
2985}
2986
2988{
2989 if(HasCapture())
2990 ReleaseMouse();
2991}
2992
2994{
2995 mRecalcRequired = true;
2996 Refresh(false);
2997}
2998
3000{
3003
3004 mEffect->CalcFilter(); //to calculate the actual response
3006}
3007
3008void EqualizationPanel::OnSize(wxSizeEvent & WXUNUSED(event))
3009{
3010 Refresh( false );
3011}
3012
3013#include "../TrackPanelDrawingContext.h"
3014void EqualizationPanel::OnPaint(wxPaintEvent & WXUNUSED(event))
3015{
3016 wxPaintDC dc(this);
3017 if(mRecalcRequired) {
3018 Recalc();
3019 mRecalcRequired = false;
3020 }
3021 int width, height;
3022 GetSize(&width, &height);
3023
3024 if (!mBitmap || mWidth!=width || mHeight!=height)
3025 {
3026 mWidth = width;
3027 mHeight = height;
3028 mBitmap = std::make_unique<wxBitmap>(mWidth, mHeight,24);
3029 }
3030
3031 wxBrush bkgndBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE));
3032
3033 wxMemoryDC memDC;
3034 memDC.SelectObject(*mBitmap);
3035
3036 wxRect bkgndRect;
3037 bkgndRect.x = 0;
3038 bkgndRect.y = 0;
3039 bkgndRect.width = mWidth;
3040 bkgndRect.height = mHeight;
3041 memDC.SetBrush(bkgndBrush);
3042 memDC.SetPen(*wxTRANSPARENT_PEN);
3043 memDC.DrawRectangle(bkgndRect);
3044
3045 bkgndRect.y = mHeight;
3046 memDC.DrawRectangle(bkgndRect);
3047
3048 wxRect border;
3049 border.x = 0;
3050 border.y = 0;
3051 border.width = mWidth;
3052 border.height = mHeight;
3053
3054 memDC.SetBrush(*wxWHITE_BRUSH);
3055 memDC.SetPen(*wxBLACK_PEN);
3056 memDC.DrawRectangle(border);
3057
3058 mEnvRect = border;
3060
3061 // Pure blue x-axis line
3062 memDC.SetPen(wxPen(theTheme.Colour( clrGraphLines ), 1, wxPENSTYLE_SOLID));
3063 int center = (int) (mEnvRect.height * mEffect->mdBMax/(mEffect->mdBMax-mEffect->mdBMin) + .5);
3064 AColor::Line(memDC,
3065 mEnvRect.GetLeft(), mEnvRect.y + center,
3066 mEnvRect.GetRight(), mEnvRect.y + center);
3067
3068 // Draw the grid, if asked for. Do it now so it's underneath the main plots.
3069 if( mEffect->mDrawGrid )
3070 {
3071 mEffect->mFreqRuler->ruler.DrawGrid(memDC, mEnvRect.height, true, true, PANELBORDER, PANELBORDER);
3072 mEffect->mdBRuler->ruler.DrawGrid(memDC, mEnvRect.width, true, true, PANELBORDER, PANELBORDER);
3073 }
3074
3075 // Med-blue envelope line
3076 memDC.SetPen(wxPen(theTheme.Colour(clrGraphLines), 3, wxPENSTYLE_SOLID));
3077
3078 // Draw envelope
3079 int x, y, xlast = 0, ylast = 0;
3080 {
3081 Doubles values{ size_t(mEnvRect.width) };
3082 mEffect->mEnvelope->GetValues(values.get(), mEnvRect.width, 0.0, 1.0 / mEnvRect.width);
3083 bool off = false, off1 = false;
3084 for (int i = 0; i < mEnvRect.width; i++)
3085 {
3086 x = mEnvRect.x + i;
3087 y = lrint(mEnvRect.height*((mEffect->mdBMax - values[i]) / (mEffect->mdBMax - mEffect->mdBMin)) + .25); //needs more optimising, along with'what you get'?
3088 if (y >= mEnvRect.height)
3089 {
3090 y = mEnvRect.height - 1;
3091 off = true;
3092 }
3093 else
3094 {
3095 off = false;
3096 off1 = false;
3097 }
3098 if ((i != 0) & (!off1))
3099 {
3100 AColor::Line(memDC, xlast, ylast,
3101 x, mEnvRect.y + y);
3102 }
3103 off1 = off;
3104 xlast = x;
3105 ylast = mEnvRect.y + y;
3106 }
3107 }
3108
3109 //Now draw the actual response that you will get.
3110 //mFilterFunc has a linear scale, window has a log one so we have to fiddle about
3111 memDC.SetPen(wxPen(theTheme.Colour( clrResponseLines ), 1, wxPENSTYLE_SOLID));
3112 double scale = (double)mEnvRect.height/(mEffect->mdBMax-mEffect->mdBMin); //pixels per dB
3113 double yF; //gain at this freq
3114 double delta = mEffect->mHiFreq / (((double)mEffect->mWindowSize / 2.)); //size of each freq bin
3115
3116 bool lin = mEffect->IsLinear(); // log or lin scale?
3117
3118 double loLog = log10(mEffect->mLoFreq);
3119 double step = lin ? mEffect->mHiFreq : (log10(mEffect->mHiFreq) - loLog);
3120 step /= ((double)mEnvRect.width-1.);
3121 double freq; //actual freq corresponding to x position
3122 int halfM = (mEffect->mM - 1) / 2;
3123 int n; //index to mFreqFunc
3124 for(int i=0; i<mEnvRect.width; i++)
3125 {
3126 x = mEnvRect.x + i;
3127 freq = lin ? step*i : pow(10., loLog + i*step); //Hz
3128 if( ( lin ? step : (pow(10., loLog + (i+1)*step)-freq) ) < delta)
3129 { //not enough resolution in FFT
3130 // set up for calculating cos using recurrence - faster than calculating it directly each time
3131 double theta = M_PI*freq/mEffect->mHiFreq; //radians, normalized
3132 double wtemp = sin(0.5 * theta);
3133 double wpr = -2.0 * wtemp * wtemp;
3134 double wpi = -1.0 * sin(theta);
3135 double wr = cos(theta*halfM);
3136 double wi = sin(theta*halfM);
3137
3138 yF = 0.;
3139 for(int j=0;j<halfM;j++)
3140 {
3141 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.
3142 // do recurrence
3143 wr = (wtemp = wr) * wpr - wi * wpi + wr;
3144 wi = wi * wpr + wtemp * wpi + wi;
3145 }
3146 yF += mOutr[halfM];
3147 yF = fabs(yF);
3148 if(yF!=0.)
3149 yF = LINEAR_TO_DB(yF);
3150 else
3151 yF = mEffect->mdBMin;
3152 }
3153 else
3154 { //use FFT, it has enough resolution
3155 n = (int)(freq/delta + .5);
3156 if(pow(mEffect->mFilterFuncR[n],2)+pow(mEffect->mFilterFuncI[n],2)!=0.)
3157 yF = 10.0*log10(pow(mEffect->mFilterFuncR[n],2)+pow(mEffect->mFilterFuncI[n],2)); //10 here, a power
3158 else
3159 yF = mEffect->mdBMin;
3160 }
3161 if(yF < mEffect->mdBMin)
3162 yF = mEffect->mdBMin;
3163 yF = center-scale*yF;
3164 if(yF>mEnvRect.height)
3165 yF = mEnvRect.height - 1;
3166 if(yF<0.)
3167 yF=0.;
3168 y = (int)(yF+.5);
3169
3170 if (i != 0)
3171 {
3172 AColor::Line(memDC, xlast, ylast, x, mEnvRect.y + y);
3173 }
3174 xlast = x;
3175 ylast = mEnvRect.y + y;
3176 }
3177
3178 memDC.SetPen(*wxBLACK_PEN);
3179 if( mEffect->mDrawMode )
3180 {
3181 ZoomInfo zoomInfo( 0.0, mEnvRect.width-1 );
3182
3183 // Back pointer to TrackPanel won't be needed in the one drawing
3184 // function we use here
3185 TrackArtist artist( nullptr );
3186
3187 artist.pZoomInfo = &zoomInfo;
3188 TrackPanelDrawingContext context{ memDC, {}, {}, &artist };
3190 context, mEnvRect, false, 0.0,
3191 mEffect->mdBMin, mEffect->mdBMax, false);
3192 }
3193
3194 dc.Blit(0, 0, mWidth, mHeight, &memDC, 0, 0, wxCOPY, FALSE);
3195}
3196
3197void EqualizationPanel::OnMouseEvent(wxMouseEvent & event)
3198{
3199 if (!mEffect->mDrawMode)
3200 {
3201 return;
3202 }
3203
3204 if (event.ButtonDown() && !HasCapture())
3205 {
3206 CaptureMouse();
3207 }
3208
3209 auto &pEditor = (mEffect->mLin ? mLinEditor : mLogEditor);
3210 if (pEditor->MouseEvent(event, mEnvRect, ZoomInfo(0.0, mEnvRect.width),
3211 false, 0.0,
3213 {
3215 ForceRecalc();
3216 }
3217
3218 if (event.ButtonUp() && HasCapture())
3219 {
3220 ReleaseMouse();
3221 }
3222}
3223
3224void EqualizationPanel::OnCaptureLost(wxMouseCaptureLostEvent & WXUNUSED(event))
3225{
3226 if (HasCapture())
3227 {
3228 ReleaseMouse();
3229 }
3230}
3231
3232//----------------------------------------------------------------------------
3233// EditCurvesDialog
3234//----------------------------------------------------------------------------
3235// Note that the 'modified' curve used to be called 'custom' but is now called 'unnamed'
3236// Some things that deal with 'unnamed' curves still use, for example, 'mCustomBackup' as variable names.
3238
3239BEGIN_EVENT_TABLE(EditCurvesDialog, wxDialogWrapper)
3246 EVT_BUTTON(LibraryButtonID, EditCurvesDialog::OnLibrary)
3250 EditCurvesDialog::OnListSelectionChange)
3251 EVT_LIST_ITEM_DESELECTED(CurvesListID,
3252 EditCurvesDialog::OnListSelectionChange)
3254
3255EditCurvesDialog::EditCurvesDialog(wxWindow * parent, EffectEqualization * effect, int position):
3256wxDialogWrapper(parent, wxID_ANY, XO("Manage Curves List"),
3257 wxDefaultPosition, wxDefaultSize,
3258 wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
3259{
3260 SetLabel(XO("Manage Curves")); // Provide visual label
3261 SetName(XO("Manage Curves List")); // Provide audible label
3262 mParent = parent;
3263 mEffect = effect;
3264 mPosition = position;
3265 // make a copy of mEffect->mCurves here to muck about with.
3266 mEditCurves.clear();
3267 for (unsigned int i = 0; i < mEffect->mCurves.size(); i++)
3268 {
3269 mEditCurves.push_back(mEffect->mCurves[i].Name);
3270 mEditCurves[i].points = mEffect->mCurves[i].points;
3271 }
3272
3273 Populate();
3274 SetMinSize(GetSize());
3275}
3276
3278{
3279}
3280
3283{
3284 //------------------------- Main section --------------------
3285 ShuttleGui S(this, eIsCreating);
3287 // ----------------------- End of main section --------------
3288}
3289
3292{
3293 S.StartHorizontalLay(wxEXPAND);
3294 {
3295 S.StartStatic(XO("&Curves"), 1);
3296 {
3297 mList = S.Id(CurvesListID)
3298 .Style(wxSUNKEN_BORDER | wxLC_REPORT | wxLC_HRULES | wxLC_VRULES )
3299 .AddListControlReportMode({
3300 { XO("Curve Name"), wxLIST_FORMAT_RIGHT }
3301 });
3302 }
3303 S.EndStatic();
3304 S.StartVerticalLay(0);
3305 {
3306 S.Id(UpButtonID).AddButton(XXO("Move &Up"), wxALIGN_LEFT);
3307 S.Id(DownButtonID).AddButton(XXO("Move &Down"), wxALIGN_LEFT);
3308 S.Id(RenameButtonID).AddButton(XXO("&Rename..."), wxALIGN_LEFT);
3309 S.Id(DeleteButtonID).AddButton(XXO("D&elete..."), wxALIGN_LEFT);
3310 S.Id(ImportButtonID).AddButton(XXO("I&mport..."), wxALIGN_LEFT);
3311 S.Id(ExportButtonID).AddButton(XXO("E&xport..."), wxALIGN_LEFT);
3312 S.Id(LibraryButtonID).AddButton(XXO("&Get More..."), wxALIGN_LEFT);
3313 S.Id(DefaultsButtonID).AddButton(XXO("De&faults"), wxALIGN_LEFT);
3314 }
3315 S.EndVerticalLay();
3316 }
3317 S.EndHorizontalLay();
3318 S.AddStandardButtons();
3319 S.StartStatic(XO("Help"));
3320 S.AddConstTextBox( {}, XO("Rename 'unnamed' to save a new entry.\n'OK' saves all changes, 'Cancel' doesn't."));
3321 S.EndStatic();
3323 Fit();
3324
3325 return;
3326}
3327
3329{
3330 mList->DeleteAllItems();
3331 for (unsigned int i = 0; i < mEditCurves.size(); i++)
3332 mList->InsertItem(i, mEditCurves[i].Name);
3333 mList->SetColumnWidth(0, wxLIST_AUTOSIZE);
3334 int curvesWidth = mList->GetColumnWidth(0);
3335 mList->SetColumnWidth(0, wxLIST_AUTOSIZE_USEHEADER);
3336 int headerWidth = mList->GetColumnWidth(0);
3337 mList->SetColumnWidth(0, wxMax(headerWidth, curvesWidth));
3338 // use 'position' to set focus
3339 mList->EnsureVisible(position);
3340 mList->SetItemState(position, wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED, wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED);
3341}
3342
3343void EditCurvesDialog::OnUp(wxCommandEvent & WXUNUSED(event))
3344{
3345 long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3346 if ( item == -1 )
3347 return; // no items selected
3348 if( item == 0 )
3349 item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); // top item selected, can't move up
3350 int state;
3351 while( item != -1 )
3352 {
3353 if ( item == mList->GetItemCount()-1)
3354 { // 'unnamed' always stays at the bottom
3355 mEffect->Effect::MessageBox(
3356 XO("'unnamed' always stays at the bottom of the list"),
3358 XO("'unnamed' is special") ); // these could get tedious!
3359 return;
3360 }
3361 state = mList->GetItemState(item-1, wxLIST_STATE_SELECTED);
3362 if ( state != wxLIST_STATE_SELECTED )
3363 { // swap this with one above but only if it isn't selected
3364 EQCurve temp(wxT("temp"));
3365 temp.Name = mEditCurves[item].Name;
3366 temp.points = mEditCurves[item].points;
3367 mEditCurves[item].Name = mEditCurves[item-1].Name;
3368 mEditCurves[item].points = mEditCurves[item-1].points;
3369 mEditCurves[item-1].Name = temp.Name;
3370 mEditCurves[item-1].points = temp.points;
3371 wxString sTemp = mList->GetItemText(item);
3372 mList->SetItem(item, 0, mList->GetItemText(item-1));
3373 mList->SetItem(item-1, 0, sTemp);
3374 mList->SetItemState(item, 0, wxLIST_STATE_SELECTED);
3375 mList->SetItemState(item-1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
3376 }
3377 item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3378 }
3379}
3380
3381void EditCurvesDialog::OnDown(wxCommandEvent & WXUNUSED(event))
3382{ // looks harder than OnUp as we need to seek backwards up the list, hence GetPreviousItem
3383 long item = GetPreviousItem(mList->GetItemCount());
3384 if( item == -1 )
3385 return; // nothing selected
3386 int state;
3387 while( item != -1 )
3388 {
3389 if( (item != mList->GetItemCount()-1) && (item != mList->GetItemCount()-2) )
3390 { // can't move 'unnamed' down, or the one above it
3391 state = mList->GetItemState(item+1, wxLIST_STATE_SELECTED);
3392 if ( state != wxLIST_STATE_SELECTED )
3393 { // swap this with one below but only if it isn't selected
3394 EQCurve temp(wxT("temp"));
3395 temp.Name = mEditCurves[item].Name;
3396 temp.points = mEditCurves[item].points;
3397 mEditCurves[item].Name = mEditCurves[item+1].Name;
3398 mEditCurves[item].points = mEditCurves[item+1].points;
3399 mEditCurves[item+1].Name = temp.Name;
3400 mEditCurves[item+1].points = temp.points;
3401 wxString sTemp = mList->GetItemText(item);
3402 mList->SetItem(item, 0, mList->GetItemText(item+1));
3403 mList->SetItem(item+1, 0, sTemp);
3404 mList->SetItemState(item, 0, wxLIST_STATE_SELECTED);
3405 mList->SetItemState(item+1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
3406 }
3407 }
3408 item = GetPreviousItem(item);
3409 }
3410}
3411
3412long EditCurvesDialog::GetPreviousItem(long item) // wx doesn't have this
3413{
3414 long lastItem = -1;
3415 long itemTemp = mList->GetNextItem(-1, wxLIST_NEXT_ALL,
3416 wxLIST_STATE_SELECTED);
3417 while( (itemTemp != -1) && (itemTemp < item) )
3418 {
3419 lastItem = itemTemp;
3420 itemTemp = mList->GetNextItem(itemTemp, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3421 }
3422 return lastItem;
3423}
3424
3425// Rename curve/curves
3426void EditCurvesDialog::OnRename(wxCommandEvent & WXUNUSED(event))
3427{
3428 wxString name;
3429 int numCurves = mEditCurves.size();
3430 int curve = 0;
3431
3432 // Setup list of characters that aren't allowed
3433 wxArrayStringEx exclude{
3434 wxT("<") ,
3435 wxT(">") ,
3436 wxT("'") ,
3437 wxT("\"") ,
3438 };
3439
3440 // Get the first one to be renamed
3441 long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3442 long firstItem = item; // for reselection with PopulateList
3443 while(item >= 0)
3444 {
3445 // Prompt the user until a valid name is enter or cancelled
3446 bool overwrite = false;
3447 bool bad = true;
3448 while( bad ) // Check for an unacceptable duplicate
3449 { // Show the dialog and bail if the user cancels
3450 bad = false;
3451 // build the dialog
3452 AudacityTextEntryDialog dlg( this,
3453 XO("Rename '%s' to...").Format( mEditCurves[ item ].Name ),
3454 XO("Rename...") );
3455 dlg.SetTextValidator( wxFILTER_EXCLUDE_CHAR_LIST );
3456 dlg.SetName(
3457 wxString::Format( _("Rename '%s'"), mEditCurves[ item ].Name ) );
3458 wxTextValidator *tv = dlg.GetTextValidator();
3459 tv->SetExcludes( exclude ); // Tell the validator about excluded chars
3460 if( dlg.ShowModal() == wxID_CANCEL )
3461 {
3462 bad = true;
3463 break;
3464 }
3465
3466 // Extract the name from the dialog
3467 name = dlg.GetValue();
3468
3469 // Search list of curves for a duplicate name
3470 for( curve = 0; curve < numCurves; curve++ )
3471 {
3472 wxString temp = mEditCurves[ curve ].Name;
3473 if( name == mEditCurves[ curve ].Name ) // case sensitive
3474 {
3475 bad = true;
3476 if( curve == item ) // trying to rename a curve with the same name
3477 {
3478 mEffect->Effect::MessageBox(
3479 XO("Name is the same as the original one"),
3480 wxOK,
3481 XO("Same name") );
3482 break;
3483 }
3484 int answer = mEffect->Effect::MessageBox(
3485 XO("Overwrite existing curve '%s'?").Format( name ),
3486 wxYES_NO,
3487 XO("Curve exists") );
3488 if (answer == wxYES)
3489 {
3490 bad = false;
3491 overwrite = true; // we are going to overwrite the one with this name
3492 break;
3493 }
3494 }
3495 }
3496 if( name.empty() || name == wxT("unnamed") )
3497 bad = true;
3498 }
3499
3500 // if bad, we cancelled the rename dialog, so nothing to do.
3501 if( bad == true )
3502 ;
3503 else if(overwrite){
3504 // Overwrite another curve.
3505 // JKC: because 'overwrite' is true, 'curve' is the number of the curve that
3506 // we are about to overwrite.
3507 mEditCurves[ curve ].Name = name;
3508 mEditCurves[ curve ].points = mEditCurves[ item ].points;
3509 // if renaming the unnamed item, then select it,
3510 // otherwise get rid of the item we've renamed.
3511 if( item == (numCurves-1) )
3512 mList->SetItem(curve, 0, name);
3513 else
3514 {
3515 mEditCurves.erase( mEditCurves.begin() + item );
3516 numCurves--;
3517 }
3518 }
3519 else if( item == (numCurves-1) ) // renaming 'unnamed'
3520 { // Create a NEW entry
3521 mEditCurves.push_back( EQCurve( wxT("unnamed") ) );
3522 // Copy over the points
3523 mEditCurves[ numCurves ].points = mEditCurves[ numCurves - 1 ].points;
3524 // Give the original unnamed entry the NEW name
3525 mEditCurves[ numCurves - 1 ].Name = name;
3526 numCurves++;
3527 }
3528 else // just rename (the 'normal' case)
3529 {
3530 mEditCurves[ item ].Name = name;
3531 mList->SetItem(item, 0, name);
3532 }
3533 // get next selected item
3534 item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3535 }
3536
3537 PopulateList(firstItem); // Note: only saved to file when you OK out of the dialog
3538 return;
3539}
3540
3541// Delete curve/curves
3542void EditCurvesDialog::OnDelete(wxCommandEvent & WXUNUSED(event))
3543{
3544 // We could count them here
3545 // And then put in a 'Delete N items?' prompt.
3546
3547#if 0 // 'one at a time' prompt code
3548 // Get the first one to be deleted
3549 long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3550 // Take care, mList and mEditCurves will get out of sync as curves are deleted
3551 int deleted = 0;
3552 long highlight = -1;
3553
3554 while(item >= 0)
3555 {
3556 if(item == mList->GetItemCount()-1) //unnamed
3557 {
3558 mEffect->Effect::MessageBox(
3559 XO("You cannot delete the 'unnamed' curve."),
3560 wxOK | wxCENTRE,
3561 XO("Can't delete 'unnamed'") );
3562 }
3563 else
3564 {
3565 // Create the prompt
3566 auto quest = XO("Delete '%s'?")
3567 .Format(mEditCurves[ item-deleted ].Name));
3568
3569 // Ask for confirmation before removal
3570 int ans = mEffect->Effect::MessageBox(
3571 quest,
3572 wxYES_NO | wxCENTRE,
3573 XO("Confirm Deletion") );
3574 if( ans == wxYES )
3575 { // Remove the curve from the array
3576 mEditCurves.RemoveAt( item-deleted );
3577 deleted++;
3578 }
3579 else
3580 highlight = item-deleted; // if user presses 'No', select that curve
3581 }
3582 // get next selected item
3583 item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3584 }
3585
3586 if(highlight == -1)
3587 PopulateList(mEditCurves.size()-1); // set 'unnamed' as the selected curve
3588 else
3589 PopulateList(highlight); // user said 'No' to deletion
3590#else // 'DELETE all N' code
3591 int count = mList->GetSelectedItemCount();
3592 long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3593 // Create the prompt
3594 TranslatableString quest;
3595 if( count > 1 )
3596 quest = XO("Delete %d items?").Format( count );
3597 else
3598 if( count == 1 )
3599 quest = XO("Delete '%s'?").Format( mEditCurves[ item ].Name );
3600 else
3601 return;
3602 // Ask for confirmation before removal
3603 int ans = mEffect->Effect::MessageBox(
3604 quest,
3605 wxYES_NO | wxCENTRE,
3606 XO("Confirm Deletion") );
3607 if( ans == wxYES )
3608 { // Remove the curve(s) from the array
3609 // Take care, mList and mEditCurves will get out of sync as curves are deleted
3610 int deleted = 0;
3611 while(item >= 0)
3612 {
3613 // TODO: Migrate to the standard "Manage" dialog.
3614 if(item == mList->GetItemCount()-1) //unnamed
3615 {
3616 mEffect->Effect::MessageBox(
3617 XO("You cannot delete the 'unnamed' curve, it is special."),
3619 XO("Can't delete 'unnamed'") );
3620 }
3621 else
3622 {
3623 mEditCurves.erase( mEditCurves.begin() + item - deleted );
3624 deleted++;
3625 }
3626 item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3627 }
3628 PopulateList(mEditCurves.size() - 1); // set 'unnamed' as the selected curve
3629 }
3630#endif
3631}
3632
3634{
3635 static const FileNames::FileTypes results{
3637 };
3638 return results;
3639}
3640
3641void EditCurvesDialog::OnImport( wxCommandEvent & WXUNUSED(event))
3642{
3643 FileDialogWrapper filePicker(
3644 this,
3645 XO("Choose an EQ curve file"), FileNames::DataDir(), wxT(""),
3646 XMLtypes() );
3647 wxString fileName;
3648 if( filePicker.ShowModal() == wxID_CANCEL)
3649 return;
3650 else
3651 fileName = filePicker.GetPath();
3652 // Use EqualizationDialog::LoadCurves to read into (temporary) mEditCurves
3653 // This may not be the best OOP way of doing it, but I don't know better (MJS)
3654 EQCurveArray temp;
3655 temp = mEffect->mCurves; // temp copy of the main dialog curves
3656 mEffect->mCurves = mEditCurves; // copy EditCurvesDialog to main interface
3657 mEffect->LoadCurves(fileName, true); // use main interface to load imported curves
3658 mEditCurves = mEffect->mCurves; // copy back to this interface
3659 mEffect->mCurves = temp; // and reset the main interface how it was
3660 PopulateList(0); // update the EditCurvesDialog dialog
3661 return;
3662}
3663
3664void EditCurvesDialog::OnExport( wxCommandEvent & WXUNUSED(event))
3665{
3666 FileDialogWrapper filePicker(this, XO("Export EQ curves as..."),
3667 FileNames::DataDir(), wxT(""),
3668 XMLtypes(),
3669 wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER); // wxFD_CHANGE_DIR?
3670 wxString fileName;
3671 if( filePicker.ShowModal() == wxID_CANCEL)
3672 return;
3673 else
3674 fileName = filePicker.GetPath();
3675
3676 EQCurveArray temp;
3677 temp = mEffect->mCurves; // backup the parent's curves
3678 EQCurveArray exportCurves; // Copy selected curves to export
3679 exportCurves.clear();
3680 long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3681 int i=0;
3682 while(item >= 0)
3683 {
3684 if(item != mList->GetItemCount()-1) // not 'unnamed'
3685 {
3686 exportCurves.push_back(mEditCurves[item].Name);
3687 exportCurves[i].points = mEditCurves[item].points;
3688 i++;
3689 }
3690 else
3691 mEffect->Effect::MessageBox(
3692 XO("You cannot export 'unnamed' curve, it is special."),
3694 XO("Cannot Export 'unnamed'") );
3695 // get next selected item
3696 item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3697 }
3698 if(i>0)
3699 {
3700 mEffect->mCurves = exportCurves;
3701 mEffect->SaveCurves(fileName);
3702 mEffect->mCurves = temp;
3703 auto message = XO("%d curves exported to %s").Format( i, fileName );
3704 mEffect->Effect::MessageBox(
3705 message,
3707 XO("Curves exported") );
3708 }
3709 else
3710 mEffect->Effect::MessageBox(
3711 XO("No curves exported"),
3713 XO("No curves exported") );
3714}
3715
3716void EditCurvesDialog::OnLibrary( wxCommandEvent & WXUNUSED(event))
3717{
3718 // full path to wiki.
3719 wxLaunchDefaultBrowser(wxT("https://wiki.audacityteam.org/wiki/EQCurvesDownload"));
3720}
3721
3722void EditCurvesDialog::OnDefaults( wxCommandEvent & WXUNUSED(event))
3723{
3724 EQCurveArray temp;
3725 temp = mEffect->mCurves;
3726 // we expect this to fail in LoadCurves (due to a lack of path) and handle that there
3727 mEffect->LoadCurves( wxT("EQDefaultCurves.xml") );
3729 mEffect->mCurves = temp;
3730 PopulateList(0); // update the EditCurvesDialog dialog
3731}
3732
3733void EditCurvesDialog::OnOK(wxCommandEvent & WXUNUSED(event))
3734{
3735 // Make a backup of the current curves
3736 wxString backupPlace = wxFileName( FileNames::DataDir(), wxT("EQBackup.xml") ).GetFullPath();
3737 mEffect->SaveCurves(backupPlace);
3738 // Load back into the main dialog
3739 mEffect->mCurves.clear();
3740 for (unsigned int i = 0; i < mEditCurves.size(); i++)
3741 {
3742 mEffect->mCurves.push_back(mEditCurves[i].Name);
3743 mEffect->mCurves[i].points = mEditCurves[i].points;
3744 }
3747// mEffect->CreateChoice();
3748 wxGetTopLevelParent(mEffect->mUIParent)->Layout();
3749// mEffect->mUIParent->Layout();
3750
3751 // Select something sensible
3752 long item = mList->GetNextItem(-1,
3753 wxLIST_NEXT_ALL,
3754 wxLIST_STATE_SELECTED);
3755 if (item == -1)
3756 item = mList->GetItemCount()-1; // nothing selected, default to 'unnamed'
3757 mEffect->setCurve(item);
3758 EndModal(true);
3759}
3760
3762{
3763 const bool enable = mList->GetSelectedItemCount() > 0;
3764 static const int ids[] = {
3765 UpButtonID,
3769 };
3770 for (auto id : ids)
3771 FindWindowById(id, this)->Enable(enable);
3772}
3773
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
@ 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
static const double kThirdOct[]
const wxChar * values
EVT_LIST_ITEM_SELECTED(CurvesListID, EditCurvesDialog::OnListSelectionChange) EVT_LIST_ITEM_DESELECTED(CurvesListID
Constructor.
const TranslatableString name
#define EQCURVES_VERSION
const bool kCURVE
static const struct @25 FactoryPresets[]
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:503
#define DB_TO_LINEAR(x)
Definition: MemoryX.h:502
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:23
@ 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:224
const AudacityProject * FindProject() const
Definition: EffectBase.cpp:303
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:453
bool VisitSettings(SettingsVisitor &visitor, EffectSettings &settings) override
Definition: Effect.cpp:217
@ DefaultMessageBoxStyle
Definition: Effect.h:259
void CopyInputTracks(bool allSyncLockSelected=false)
Definition: Effect.cpp:739
bool LoadFactoryDefaults(EffectSettings &settings) const override
Change settings back to "factory default".
Definition: Effect.cpp:280
int MessageBox(const TranslatableString &message, long style=DefaultMessageBoxStyle, const TranslatableString &titleStr={}) const
Definition: Effect.cpp:869
wxWindow * mUIParent
Definition: Effect.h:389
bool TrackProgress(int whichTrack, double frac, const TranslatableString &={}) const
Definition: Effect.cpp:691
bool IsBatchProcessing() const override
Definition: Effect.cpp:562
bool CloseUI() override
Definition: Effect.cpp:318
bool LoadUserPreset(const RegistryPath &name, EffectSettings &settings) const override
Change settings to a user-named preset.
Definition: Effect.cpp:245
Performs effect computation.
Interface for manipulations of an Effect's settings.
void ModifySettings(Function &&function)
Do a correct read-modify-write of 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:65
double LongSamplesToTime(sampleCount pos) const
Convert correctly between a number of samples and an (absolute) time in seconds.
Definition: SampleTrack.cpp:40
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:631
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:467
auto Selected() -> TrackIterRange< TrackType >
Definition: Track.h:1446
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:1563
void ConvertToSampleFormat(sampleFormat format, const std::function< void(size_t)> &progressReport={})
Definition: WaveTrack.cpp:598
size_t GetMaxBlockSize() const override
This returns a nonnegative number of samples meant to size a memory buffer.
Definition: WaveTrack.cpp:1806
void Join(double t0, double t1)
Definition: WaveTrack.cpp:1708
void Clear(double t0, double t1) override
Definition: WaveTrack.cpp:788
Holder EmptyCopy(const SampleBlockFactoryPtr &pFactory={}) const
Definition: WaveTrack.cpp:707
WaveClipHolders & GetClips()
Definition: WaveTrack.h:322
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:84
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:18
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, if Traits<Type>::iterated_type is defined.
Definition: PackedArray.h:126
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