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