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