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