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