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