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((int) mCurves.size() - 1);
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  for (size_t i = 0, cnt = mCurves.size(); i < cnt; i++)
2121  {
2122  mCurve->Append(mCurves[ i ].Name);
2123  }
2124  mCurve->SetStringSelection(mCurveName);
2125 
2126  // Allow the control to resize
2127  mCurve->SetSizeHints(-1, -1);
2128 
2129  // Set initial curve
2130  setCurve( mCurveName );
2131 }
2132 
2134 {
2135  size_t numPoints = mLogEnvelope->GetNumberOfPoints();
2136  Doubles when{ numPoints };
2137  Doubles value{ numPoints };
2138  double deltadB = 0.1;
2139  double dx, dy, dx1, dy1, err;
2140 
2141  mLogEnvelope->GetPoints( when.get(), value.get(), numPoints );
2142 
2143  // set 'unnamed' as the selected curve
2144  EnvelopeUpdated();
2145 
2146  bool flag = true;
2147  while (flag)
2148  {
2149  flag = false;
2150  int numDeleted = 0;
2151  mLogEnvelope->GetPoints( when.get(), value.get(), numPoints );
2152  for (size_t j = 0; j + 2 < numPoints; j++)
2153  {
2154  dx = when[j+2+numDeleted] - when[j+numDeleted];
2155  dy = value[j+2+numDeleted] - value[j+numDeleted];
2156  dx1 = when[j+numDeleted+1] - when[j+numDeleted];
2157  dy1 = dy * dx1 / dx;
2158  err = fabs(value[j+numDeleted+1] - (value[j+numDeleted] + dy1));
2159  if( err < deltadB )
2160  { // within < deltadB dB?
2161  mLogEnvelope->Delete(j+1);
2162  numPoints--;
2163  numDeleted++;
2164  flag = true;
2165  }
2166  }
2167  }
2168 
2169  if(mLin) // do not use IsLinear() here
2170  {
2171  EnvLogToLin();
2172  mEnvelope = mLinEnvelope.get();
2173  mFreqRuler->ruler.SetLog(false);
2175  }
2176 
2177  szrV->Show(szrG,false);
2178  szrH->Show(szrI,false);
2179  szrH->Show(szrL,true);
2180 
2181  mUIParent->Layout();
2182  wxGetTopLevelParent(mUIParent)->Layout();
2183  ForceRecalc(); // it may have changed slightly due to the deletion of points
2184 }
2185 
2187 {
2188  double loLog = log10(mLoFreq);
2189  double hiLog = log10(mHiFreq);
2190  double denom = hiLog - loLog;
2191 
2192  if(mLin) //going from lin to log freq scale - do not use IsLinear() here
2193  { // add some extra points to the linear envelope for the graphic to follow
2194  double step = pow(2., 1./12.); // twelve steps per octave
2195  double when,value;
2196  for(double freq=10.; freq<mHiFreq; freq*=step)
2197  {
2198  when = freq/mHiFreq;
2199  value = mLinEnvelope->GetValue(when);
2200  mLinEnvelope->InsertOrReplace(when, value);
2201  }
2202 
2203  EnvLinToLog();
2204  mEnvelope = mLogEnvelope.get();
2205  mFreqRuler->ruler.SetLog(true);
2206  mFreqRuler->ruler.SetRange(mLoFreq, mHiFreq);
2207  }
2208 
2209  for (size_t i = 0; i < mBandsInUse; i++)
2210  {
2211  if( kThirdOct[i] == mLoFreq )
2212  mWhenSliders[i] = 0.;
2213  else
2214  mWhenSliders[i] = (log10(kThirdOct[i])-loLog)/denom;
2215  mEQVals[i] = mLogEnvelope->GetValue(mWhenSliders[i]); //set initial values of sliders
2216  if( mEQVals[i] > 20.)
2217  mEQVals[i] = 20.;
2218  if( mEQVals[i] < -20.)
2219  mEQVals[i] = -20.;
2220  }
2221  ErrMin(); //move sliders to minimise error
2222  for (size_t i = 0; i < mBandsInUse; i++)
2223  {
2224  mSliders[i]->SetValue(lrint(mEQVals[i])); //actually set slider positions
2225  mSlidersOld[i] = mSliders[i]->GetValue();
2226  wxString tip;
2227  if( kThirdOct[i] < 1000.)
2228  tip.Printf( wxT("%dHz\n%.1fdB"), (int)kThirdOct[i], mEQVals[i] );
2229  else
2230  tip.Printf( wxT("%gkHz\n%.1fdB"), kThirdOct[i]/1000., mEQVals[i] );
2231  mSliders[i]->SetToolTip(tip);
2232  }
2233 
2234  szrV->Show(szrG,true); // eq sliders
2235  szrH->Show(szrI,true); // interpolation choice
2236  szrH->Show(szrL,false); // linear freq checkbox
2237 
2238  mUIParent->Layout();
2239  wxGetTopLevelParent(mUIParent)->Layout();
2240 // mUIParent->Layout(); // Make all sizers get resized first
2241  LayoutEQSliders(); // Then layout sliders
2242  mUIParent->Layout();
2243  wxGetTopLevelParent(mUIParent)->Layout();
2244 // mUIParent->Layout(); // And layout again to resize dialog
2245 
2246 #if 0
2247  wxSize wsz = mUIParent->GetSize();
2248  wxSize ssz = szrV->GetSize();
2249  if (ssz.x > wsz.x || ssz.y > wsz.y)
2250  {
2251  mUIParent->Fit();
2252  }
2253 #endif
2254  GraphicEQ(mLogEnvelope.get());
2255  mDrawMode = false;
2256 }
2257 
2259 {
2260  size_t numPoints = mLogEnvelope->GetNumberOfPoints();
2261  if( numPoints == 0 )
2262  {
2263  return;
2264  }
2265 
2266  Doubles when{ numPoints };
2267  Doubles value{ numPoints };
2268 
2269  mLinEnvelope->Flatten(0.);
2270  mLinEnvelope->SetTrackLen(1.0);
2271  mLogEnvelope->GetPoints( when.get(), value.get(), numPoints );
2272  mLinEnvelope->Reassign(0., value[0]);
2273  double loLog = log10(20.);
2274  double hiLog = log10(mHiFreq);
2275  double denom = hiLog - loLog;
2276 
2277  for (size_t i = 0; i < numPoints; i++)
2278  mLinEnvelope->InsertOrReplace(pow( 10., ((when[i] * denom) + loLog))/mHiFreq , value[i]);
2279  mLinEnvelope->Reassign(1., value[numPoints-1]);
2280 }
2281 
2283 {
2284  size_t numPoints = mLinEnvelope->GetNumberOfPoints();
2285  if( numPoints == 0 )
2286  {
2287  return;
2288  }
2289 
2290  Doubles when{ numPoints };
2291  Doubles value{ numPoints };
2292 
2293  mLogEnvelope->Flatten(0.);
2294  mLogEnvelope->SetTrackLen(1.0);
2295  mLinEnvelope->GetPoints( when.get(), value.get(), numPoints );
2296  mLogEnvelope->Reassign(0., value[0]);
2297  double loLog = log10(20.);
2298  double hiLog = log10(mHiFreq);
2299  double denom = hiLog - loLog;
2300  bool changed = false;
2301 
2302  for (size_t i = 0; i < numPoints; i++)
2303  {
2304  if( when[i]*mHiFreq >= 20 )
2305  {
2306  // Caution: on Linux, when when == 20, the log calulation rounds
2307  // to just under zero, which causes an assert error.
2308  double flog = (log10(when[i]*mHiFreq)-loLog)/denom;
2309  mLogEnvelope->InsertOrReplace(std::max(0.0, flog) , value[i]);
2310  }
2311  else
2312  { //get the first point as close as we can to the last point requested
2313  changed = true;
2314  double v = value[i];
2315  mLogEnvelope->InsertOrReplace(0., v);
2316  }
2317  }
2318  mLogEnvelope->Reassign(1., value[numPoints - 1]);
2319 
2320  if(changed)
2321  EnvelopeUpdated(mLogEnvelope.get(), false);
2322 }
2323 
2325 {
2326  double vals[NUM_PTS];
2327  double error = 0.0;
2328  double oldError = 0.0;
2329  double mEQValsOld = 0.0;
2330  double correction = 1.6;
2331  bool flag;
2332  size_t j=0;
2333  Envelope testEnvelope{ *mLogEnvelope };
2334 
2335  for(size_t i = 0; i < NUM_PTS; i++)
2336  vals[i] = testEnvelope.GetValue(mWhens[i]);
2337 
2338  // Do error minimisation
2339  error = 0.;
2340  GraphicEQ(&testEnvelope);
2341  for(size_t i = 0; i < NUM_PTS; i++) //calc initial error
2342  {
2343  double err = vals[i] - testEnvelope.GetValue(mWhens[i]);
2344  error += err*err;
2345  }
2346  oldError = error;
2347  while( j < mBandsInUse*12 ) //loop over the sliders a number of times
2348  {
2349  auto i = j % mBandsInUse; //use this slider
2350  if( (j > 0) & (i == 0) ) // if we've come back to the first slider again...
2351  {
2352  if( correction > 0 )
2353  correction = -correction; //go down
2354  else
2355  correction = -correction/2.; //go up half as much
2356  }
2357  flag = true; // check if we've hit the slider limit
2358  do
2359  {
2360  oldError = error;
2361  mEQValsOld = mEQVals[i];
2362  mEQVals[i] += correction; //move fader value
2363  if( mEQVals[i] > 20. )
2364  {
2365  mEQVals[i] = 20.;
2366  flag = false;
2367  }
2368  if( mEQVals[i] < -20. )
2369  {
2370  mEQVals[i] = -20.;
2371  flag = false;
2372  }
2373  GraphicEQ(&testEnvelope); //calculate envelope
2374  error = 0.;
2375  for(size_t k = 0; k < NUM_PTS; k++) //calculate error
2376  {
2377  double err = vals[k] - testEnvelope.GetValue(mWhens[k]);
2378  error += err*err;
2379  }
2380  }
2381  while( (error < oldError) && flag );
2382  if( error > oldError )
2383  {
2384  mEQVals[i] = mEQValsOld; //last one didn't work
2385  error = oldError;
2386  }
2387  else
2388  oldError = error;
2389  if( error < .0025 * mBandsInUse)
2390  break; // close enuff
2391  j++; //try next slider
2392  }
2393  if( error > .0025 * mBandsInUse ) // not within 0.05dB on each slider, on average
2394  {
2395  Select( (int) mCurves.size() - 1 );
2396  EnvelopeUpdated(&testEnvelope, false);
2397  }
2398 }
2399 
2401 {
2402  // JKC: 'value' is for height of curve.
2403  // The 0.0 initial value would only get used if NUM_PTS were 0.
2404  double value = 0.0;
2405  double dist, span, s;
2406 
2407  env->Flatten(0.);
2408  env->SetTrackLen(1.0);
2409 
2410  switch( mInterp )
2411  {
2412  case kBspline: // B-spline
2413  {
2414  int minF = 0;
2415  for(size_t i = 0; i < NUM_PTS; i++)
2416  {
2417  while( (mWhenSliders[minF] <= mWhens[i]) & (minF < (int)mBandsInUse) )
2418  minF++;
2419  minF--;
2420  if( minF < 0 ) //before first slider
2421  {
2422  dist = mWhens[i] - mWhenSliders[0];
2423  span = mWhenSliders[1] - mWhenSliders[0];
2424  s = dist/span;
2425  if( s < -1.5 )
2426  value = 0.;
2427  else if( s < -.5 )
2428  value = mEQVals[0]*(s + 1.5)*(s + 1.5)/2.;
2429  else
2430  value = mEQVals[0]*(.75 - s*s) + mEQVals[1]*(s + .5)*(s + .5)/2.;
2431  }
2432  else
2433  {
2434  if( mWhens[i] > mWhenSliders[mBandsInUse-1] ) //after last fader
2435  {
2436  dist = mWhens[i] - mWhenSliders[mBandsInUse-1];
2437  span = mWhenSliders[mBandsInUse-1] - mWhenSliders[mBandsInUse-2];
2438  s = dist/span;
2439  if( s > 1.5 )
2440  value = 0.;
2441  else if( s > .5 )
2442  value = mEQVals[mBandsInUse-1]*(s - 1.5)*(s - 1.5)/2.;
2443  else
2444  value = mEQVals[mBandsInUse-1]*(.75 - s*s) +
2445  mEQVals[mBandsInUse-2]*(s - .5)*(s - .5)/2.;
2446  }
2447  else //normal case
2448  {
2449  dist = mWhens[i] - mWhenSliders[minF];
2450  span = mWhenSliders[minF+1] - mWhenSliders[minF];
2451  s = dist/span;
2452  if(s < .5 )
2453  {
2454  value = mEQVals[minF]*(0.75 - s*s);
2455  if( minF+1 < (int)mBandsInUse )
2456  value += mEQVals[minF+1]*(s+.5)*(s+.5)/2.;
2457  if( minF-1 >= 0 )
2458  value += mEQVals[minF-1]*(s-.5)*(s-.5)/2.;
2459  }
2460  else
2461  {
2462  value = mEQVals[minF]*(s-1.5)*(s-1.5)/2.;
2463  if( minF+1 < (int)mBandsInUse )
2464  value += mEQVals[minF+1]*(.75-(1.-s)*(1.-s));
2465  if( minF+2 < (int)mBandsInUse )
2466  value += mEQVals[minF+2]*(s-.5)*(s-.5)/2.;
2467  }
2468  }
2469  }
2470  if(mWhens[i]<=0.)
2471  env->Reassign(0., value);
2472  env->InsertOrReplace( mWhens[i], value );
2473  }
2474  env->Reassign( 1., value );
2475  break;
2476  }
2477 
2478  case kCosine: // Cosine squared
2479  {
2480  int minF = 0;
2481  for(size_t i = 0; i < NUM_PTS; i++)
2482  {
2483  while( (mWhenSliders[minF] <= mWhens[i]) & (minF < (int)mBandsInUse) )
2484  minF++;
2485  minF--;
2486  if( minF < 0 ) //before first slider
2487  {
2488  dist = mWhenSliders[0] - mWhens[i];
2489  span = mWhenSliders[1] - mWhenSliders[0];
2490  if( dist < span )
2491  value = mEQVals[0]*(1. + cos(M_PI*dist/span))/2.;
2492  else
2493  value = 0.;
2494  }
2495  else
2496  {
2497  if( mWhens[i] > mWhenSliders[mBandsInUse-1] ) //after last fader
2498  {
2499  span = mWhenSliders[mBandsInUse-1] - mWhenSliders[mBandsInUse-2];
2500  dist = mWhens[i] - mWhenSliders[mBandsInUse-1];
2501  if( dist < span )
2502  value = mEQVals[mBandsInUse-1]*(1. + cos(M_PI*dist/span))/2.;
2503  else
2504  value = 0.;
2505  }
2506  else //normal case
2507  {
2508  span = mWhenSliders[minF+1] - mWhenSliders[minF];
2509  dist = mWhenSliders[minF+1] - mWhens[i];
2510  value = mEQVals[minF]*(1. + cos(M_PI*(span-dist)/span))/2. +
2511  mEQVals[minF+1]*(1. + cos(M_PI*dist/span))/2.;
2512  }
2513  }
2514  if(mWhens[i]<=0.)
2515  env->Reassign(0., value);
2516  env->InsertOrReplace( mWhens[i], value );
2517  }
2518  env->Reassign( 1., value );
2519  break;
2520  }
2521 
2522  case kCubic: // Cubic Spline
2523  {
2524  double y2[NUMBER_OF_BANDS+1];
2527  for(double xf=0; xf<1.; xf+=1./NUM_PTS)
2528  {
2529  env->InsertOrReplace(xf, splint(mWhenSliders, mEQVals, mBandsInUse+1, y2, xf));
2530  }
2531  break;
2532  }
2533  }
2534 
2535  ForceRecalc();
2536 }
2537 
2538 void EffectEqualization::spline(double x[], double y[], size_t n, double y2[])
2539 {
2540  wxASSERT( n > 0 );
2541 
2542  double p, sig;
2543  Doubles u{ n };
2544 
2545  y2[0] = 0.; //
2546  u[0] = 0.; //'natural' boundary conditions
2547  for (size_t i = 1; i + 1 < n; i++)
2548  {
2549  sig = ( x[i] - x[i-1] ) / ( x[i+1] - x[i-1] );
2550  p = sig * y2[i-1] + 2.;
2551  y2[i] = (sig - 1.)/p;
2552  u[i] = ( y[i+1] - y[i] ) / ( x[i+1] - x[i] ) - ( y[i] - y[i-1] ) / ( x[i] - x[i-1] );
2553  u[i] = (6.*u[i]/( x[i+1] - x[i-1] ) - sig * u[i-1]) / p;
2554  }
2555  y2[n - 1] = 0.;
2556  for (size_t i = n - 1; i--;)
2557  y2[i] = y2[i]*y2[i+1] + u[i];
2558 }
2559 
2560 double EffectEqualization::splint(double x[], double y[], size_t n, double y2[], double xr)
2561 {
2562  wxASSERT( n > 1 );
2563 
2564  double a, b, h;
2565  static double xlast = 0.; // remember last x value requested
2566  static size_t k = 0; // and which interval we were in
2567 
2568  if( xr < xlast )
2569  k = 0; // gone back to start, (or somewhere to the left)
2570  xlast = xr;
2571  while( (x[k] <= xr) && (k + 1 < n) )
2572  k++;
2573  wxASSERT( k > 0 );
2574  k--;
2575  h = x[k+1] - x[k];
2576  a = ( x[k+1] - xr )/h;
2577  b = (xr - x[k])/h;
2578  return( a*y[k]+b*y[k+1]+((a*a*a-a)*y2[k]+(b*b*b-b)*y2[k+1])*h*h/6.);
2579 }
2580 
2581 void EffectEqualization::OnSize(wxSizeEvent & event)
2582 {
2583  mUIParent->Layout();
2584 
2585  if (!mDrawMode)
2586  {
2587  LayoutEQSliders();
2588  }
2589 
2590  event.Skip();
2591 }
2592 
2593 void EffectEqualization::OnSlider(wxCommandEvent & event)
2594 {
2595  wxSlider *s = (wxSlider *)event.GetEventObject();
2596  for (size_t i = 0; i < mBandsInUse; i++)
2597  {
2598  if( s == mSliders[i])
2599  {
2600  int posn = mSliders[i]->GetValue();
2601  if( wxGetKeyState(WXK_SHIFT) )
2602  {
2603  if( posn > mSlidersOld[i] )
2604  mEQVals[i] += (float).1;
2605  else
2606  if( posn < mSlidersOld[i] )
2607  mEQVals[i] -= .1f;
2608  }
2609  else
2610  mEQVals[i] += (posn - mSlidersOld[i]);
2611  if( mEQVals[i] > 20. )
2612  mEQVals[i] = 20.;
2613  if( mEQVals[i] < -20. )
2614  mEQVals[i] = -20.;
2615  int newPosn = (int)mEQVals[i];
2616  mSliders[i]->SetValue( newPosn );
2617  mSlidersOld[i] = newPosn;
2618  wxString tip;
2619  if( kThirdOct[i] < 1000.)
2620  tip.Printf( wxT("%dHz\n%.1fdB"), (int)kThirdOct[i], mEQVals[i] );
2621  else
2622  tip.Printf( wxT("%gkHz\n%.1fdB"), kThirdOct[i]/1000., mEQVals[i] );
2623  s->SetToolTip(tip);
2624  break;
2625  }
2626  }
2627  GraphicEQ(mLogEnvelope.get());
2628  EnvelopeUpdated();
2629 }
2630 
2631 void EffectEqualization::OnInterp(wxCommandEvent & WXUNUSED(event))
2632 {
2633  if (mGraphic->GetValue())
2634  {
2635  GraphicEQ(mLogEnvelope.get());
2636  EnvelopeUpdated();
2637  }
2638  mInterp = mInterpChoice->GetSelection();
2639 }
2640 
2641 void EffectEqualization::OnDrawMode(wxCommandEvent & WXUNUSED(event))
2642 {
2643  UpdateDraw();
2644 
2645  mDrawMode = true;
2646 }
2647 
2648 void EffectEqualization::OnGraphicMode(wxCommandEvent & WXUNUSED(event))
2649 {
2650  UpdateGraphic();
2651 
2652  mDrawMode = false;
2653 }
2654 
2655 void EffectEqualization::OnSliderM(wxCommandEvent & WXUNUSED(event))
2656 {
2658  ForceRecalc();
2659 }
2660 
2661 void EffectEqualization::OnSliderDBMIN(wxCommandEvent & WXUNUSED(event))
2662 {
2664 }
2665 
2666 void EffectEqualization::OnSliderDBMAX(wxCommandEvent & WXUNUSED(event))
2667 {
2669 }
2670 
2671 //
2672 // New curve was selected
2673 //
2674 void EffectEqualization::OnCurve(wxCommandEvent & WXUNUSED(event))
2675 {
2676  // Select NEW curve
2677  wxASSERT( mCurve != NULL );
2678  setCurve( mCurve->GetCurrentSelection() );
2679  if( !mDrawMode )
2680  UpdateGraphic();
2681 }
2682 
2683 //
2684 // User wants to modify the list in some way
2685 //
2686 void EffectEqualization::OnManage(wxCommandEvent & WXUNUSED(event))
2687 {
2688  EditCurvesDialog d(mUIParent, this, mCurve->GetSelection());
2689  d.ShowModal();
2690 
2691  // Reload the curve names
2692  UpdateCurves();
2693 
2694  // Allow control to resize
2695  mUIParent->Layout();
2696 }
2697 
2698 void EffectEqualization::OnClear(wxCommandEvent & WXUNUSED(event))
2699 {
2700  Flatten();
2701 }
2702 
2703 void EffectEqualization::OnInvert(wxCommandEvent & WXUNUSED(event)) // Inverts any curve
2704 {
2705  if(!mDrawMode) // Graphic (Slider) mode. Invert the sliders.
2706  {
2707  for (size_t i = 0; i < mBandsInUse; i++)
2708  {
2709  mEQVals[i] = -mEQVals[i];
2710  int newPosn = (int)mEQVals[i];
2711  mSliders[i]->SetValue( newPosn );
2712  mSlidersOld[i] = newPosn;
2713 
2714  wxString tip;
2715  if( kThirdOct[i] < 1000.)
2716  tip.Printf( wxT("%dHz\n%.1fdB"), (int)kThirdOct[i], mEQVals[i] );
2717  else
2718  tip.Printf( wxT("%gkHz\n%.1fdB"), kThirdOct[i]/1000., mEQVals[i] );
2719  mSliders[i]->SetToolTip(tip);
2720  }
2721  GraphicEQ(mLogEnvelope.get());
2722  }
2723  else // Draw mode. Invert the points.
2724  {
2725  bool lin = IsLinear(); // refers to the 'log' or 'lin' of the frequency scale, not the amplitude
2726  size_t numPoints; // number of points in the curve/envelope
2727 
2728  // determine if log or lin curve is the current one
2729  // and find out how many points are in the curve
2730  if(lin) // lin freq scale and so envelope
2731  {
2732  numPoints = mLinEnvelope->GetNumberOfPoints();
2733  }
2734  else
2735  {
2736  numPoints = mLogEnvelope->GetNumberOfPoints();
2737  }
2738 
2739  if( numPoints == 0 )
2740  return;
2741 
2742  Doubles when{ numPoints };
2743  Doubles value{ numPoints };
2744 
2745  if(lin)
2746  mLinEnvelope->GetPoints( when.get(), value.get(), numPoints );
2747  else
2748  mLogEnvelope->GetPoints( when.get(), value.get(), numPoints );
2749 
2750  // invert the curve
2751  for (size_t i = 0; i < numPoints; i++)
2752  {
2753  if(lin)
2754  mLinEnvelope->Reassign(when[i] , -value[i]);
2755  else
2756  mLogEnvelope->Reassign(when[i] , -value[i]);
2757  }
2758 
2759  // copy it back to the other one (just in case)
2760  if(lin)
2761  EnvLinToLog();
2762  else
2763  EnvLogToLin();
2764  }
2765 
2766  // and update the display etc
2767  ForceRecalc();
2768  EnvelopeUpdated();
2769 }
2770 
2771 void EffectEqualization::OnGridOnOff(wxCommandEvent & WXUNUSED(event))
2772 {
2773  mDrawGrid = mGridOnOff->IsChecked();
2774  mPanel->Refresh(false);
2775 }
2776 
2777 void EffectEqualization::OnLinFreq(wxCommandEvent & WXUNUSED(event))
2778 {
2779  mLin = mLinFreq->IsChecked();
2780  if(IsLinear()) //going from log to lin freq scale
2781  {
2782  mFreqRuler->ruler.SetLog(false);
2784  EnvLogToLin();
2785  mEnvelope = mLinEnvelope.get();
2786  mLin = true;
2787  }
2788  else //going from lin to log freq scale
2789  {
2790  mFreqRuler->ruler.SetLog(true);
2792  EnvLinToLog();
2793  mEnvelope = mLogEnvelope.get();
2794  mLin = false;
2795  }
2796  mFreqRuler->Refresh(false);
2797  ForceRecalc();
2798 }
2799 
2800 #ifdef EXPERIMENTAL_EQ_SSE_THREADED
2801 
2802 void EffectEqualization::OnProcessingRadio(wxCommandEvent & event)
2803 {
2804  int testEvent=event.GetId();
2805  switch(testEvent)
2806  {
2807  case ID_DefaultMath: EffectEqualization48x::SetMathPath(MATH_FUNCTION_ORIGINAL);
2808  break;
2809  case ID_SSE: EffectEqualization48x::SetMathPath(MATH_FUNCTION_SSE);
2810  break;
2811  case ID_SSEThreaded: EffectEqualization48x::SetMathPath(MATH_FUNCTION_THREADED | MATH_FUNCTION_SSE);
2812  break;
2813  case ID_AVX: testEvent = 2;
2814  break;
2815  case ID_AVXThreaded: testEvent = 2;
2816  break;
2817  }
2818 
2819 };
2820 
2821 void EffectEqualization::OnBench( wxCommandEvent & event)
2822 {
2823  mBench=true;
2824  // OnOk(event);
2825 }
2826 
2827 #endif
2828 
2829 //----------------------------------------------------------------------------
2830 // EqualizationPanel
2831 //----------------------------------------------------------------------------
2832 
2833 BEGIN_EVENT_TABLE(EqualizationPanel, wxPanelWrapper)
2834  EVT_PAINT(EqualizationPanel::OnPaint)
2835  EVT_MOUSE_EVENTS(EqualizationPanel::OnMouseEvent)
2836  EVT_MOUSE_CAPTURE_LOST(EqualizationPanel::OnCaptureLost)
2837  EVT_SIZE(EqualizationPanel::OnSize)
2839 
2841  wxWindow *parent, wxWindowID winid, EffectEqualization *effect)
2842 : wxPanelWrapper(parent, winid)
2843 {
2844  mParent = parent;
2845  mEffect = effect;
2846 
2847  mBitmap = NULL;
2848  mWidth = 0;
2849  mHeight = 0;
2850 
2851  mLinEditor = std::make_unique<EnvelopeEditor>(*mEffect->mLinEnvelope, false);
2852  mLogEditor = std::make_unique<EnvelopeEditor>(*mEffect->mLogEnvelope, false);
2853  mEffect->mEnvelope->Flatten(0.);
2854  mEffect->mEnvelope->SetTrackLen(1.0);
2855 
2856  ForceRecalc();
2857 }
2858 
2860 {
2861  if(HasCapture())
2862  ReleaseMouse();
2863 }
2864 
2866 {
2867  mRecalcRequired = true;
2868  Refresh(false);
2869 }
2870 
2872 {
2875 
2876  mEffect->CalcFilter(); //to calculate the actual response
2878 }
2879 
2880 void EqualizationPanel::OnSize(wxSizeEvent & WXUNUSED(event))
2881 {
2882  Refresh( false );
2883 }
2884 
2885 #include "../TrackPanelDrawingContext.h"
2886 void EqualizationPanel::OnPaint(wxPaintEvent & WXUNUSED(event))
2887 {
2888  wxPaintDC dc(this);
2889  if(mRecalcRequired) {
2890  Recalc();
2891  mRecalcRequired = false;
2892  }
2893  int width, height;
2894  GetSize(&width, &height);
2895 
2896  if (!mBitmap || mWidth!=width || mHeight!=height)
2897  {
2898  mWidth = width;
2899  mHeight = height;
2900  mBitmap = std::make_unique<wxBitmap>(mWidth, mHeight);
2901  }
2902 
2903  wxBrush bkgndBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE));
2904 
2905  wxMemoryDC memDC;
2906  memDC.SelectObject(*mBitmap);
2907 
2908  wxRect bkgndRect;
2909  bkgndRect.x = 0;
2910  bkgndRect.y = 0;
2911  bkgndRect.width = mWidth;
2912  bkgndRect.height = mHeight;
2913  memDC.SetBrush(bkgndBrush);
2914  memDC.SetPen(*wxTRANSPARENT_PEN);
2915  memDC.DrawRectangle(bkgndRect);
2916 
2917  bkgndRect.y = mHeight;
2918  memDC.DrawRectangle(bkgndRect);
2919 
2920  wxRect border;
2921  border.x = 0;
2922  border.y = 0;
2923  border.width = mWidth;
2924  border.height = mHeight;
2925 
2926  memDC.SetBrush(*wxWHITE_BRUSH);
2927  memDC.SetPen(*wxBLACK_PEN);
2928  memDC.DrawRectangle(border);
2929 
2930  mEnvRect = border;
2931  mEnvRect.Deflate(PANELBORDER, PANELBORDER);
2932 
2933  // Pure blue x-axis line
2934  memDC.SetPen(wxPen(theTheme.Colour( clrGraphLines ), 1, wxSOLID));
2935  int center = (int) (mEnvRect.height * mEffect->mdBMax/(mEffect->mdBMax-mEffect->mdBMin) + .5);
2936  AColor::Line(memDC,
2937  mEnvRect.GetLeft(), mEnvRect.y + center,
2938  mEnvRect.GetRight(), mEnvRect.y + center);
2939 
2940  // Draw the grid, if asked for. Do it now so it's underneath the main plots.
2941  if( mEffect->mDrawGrid )
2942  {
2943  mEffect->mFreqRuler->ruler.DrawGrid(memDC, mEnvRect.height, true, true, PANELBORDER, PANELBORDER);
2944  mEffect->mdBRuler->ruler.DrawGrid(memDC, mEnvRect.width, true, true, PANELBORDER, PANELBORDER);
2945  }
2946 
2947  // Med-blue envelope line
2948  memDC.SetPen(wxPen(theTheme.Colour(clrGraphLines), 3, wxSOLID));
2949 
2950  // Draw envelope
2951  int x, y, xlast = 0, ylast = 0;
2952  {
2953  Doubles values{ size_t(mEnvRect.width) };
2954  mEffect->mEnvelope->GetValues(values.get(), mEnvRect.width, 0.0, 1.0 / mEnvRect.width);
2955  bool off = false, off1 = false;
2956  for (int i = 0; i < mEnvRect.width; i++)
2957  {
2958  x = mEnvRect.x + i;
2959  y = lrint(mEnvRect.height*((mEffect->mdBMax - values[i]) / (mEffect->mdBMax - mEffect->mdBMin)) + .25); //needs more optimising, along with'what you get'?
2960  if (y >= mEnvRect.height)
2961  {
2962  y = mEnvRect.height - 1;
2963  off = true;
2964  }
2965  else
2966  {
2967  off = false;
2968  off1 = false;
2969  }
2970  if ((i != 0) & (!off1))
2971  {
2972  AColor::Line(memDC, xlast, ylast,
2973  x, mEnvRect.y + y);
2974  }
2975  off1 = off;
2976  xlast = x;
2977  ylast = mEnvRect.y + y;
2978  }
2979  }
2980 
2981  //Now draw the actual response that you will get.
2982  //mFilterFunc has a linear scale, window has a log one so we have to fiddle about
2983  memDC.SetPen(wxPen(theTheme.Colour( clrResponseLines ), 1, wxSOLID));
2984  double scale = (double)mEnvRect.height/(mEffect->mdBMax-mEffect->mdBMin); //pixels per dB
2985  double yF; //gain at this freq
2986  double delta = mEffect->mHiFreq / (((double)mEffect->mWindowSize / 2.)); //size of each freq bin
2987 
2988  bool lin = mEffect->IsLinear(); // log or lin scale?
2989 
2990  double loLog = log10(mEffect->mLoFreq);
2991  double step = lin ? mEffect->mHiFreq : (log10(mEffect->mHiFreq) - loLog);
2992  step /= ((double)mEnvRect.width-1.);
2993  double freq; //actual freq corresponding to x position
2994  int halfM = (mEffect->mM - 1) / 2;
2995  int n; //index to mFreqFunc
2996  for(int i=0; i<mEnvRect.width; i++)
2997  {
2998  x = mEnvRect.x + i;
2999  freq = lin ? step*i : pow(10., loLog + i*step); //Hz
3000  if( ( lin ? step : (pow(10., loLog + (i+1)*step)-freq) ) < delta)
3001  { //not enough resolution in FFT
3002  // set up for calculating cos using recurrance - faster than calculating it directly each time
3003  double theta = M_PI*freq/mEffect->mHiFreq; //radians, normalized
3004  double wtemp = sin(0.5 * theta);
3005  double wpr = -2.0 * wtemp * wtemp;
3006  double wpi = -1.0 * sin(theta);
3007  double wr = cos(theta*halfM);
3008  double wi = sin(theta*halfM);
3009 
3010  yF = 0.;
3011  for(int j=0;j<halfM;j++)
3012  {
3013  yF += 2. * mOutr[j] * wr; // This works for me, compared to the previous version. Compare wr to cos(theta*(halfM-j)). Works for me. Keep everything as doubles though.
3014  // do recurrance
3015  wr = (wtemp = wr) * wpr - wi * wpi + wr;
3016  wi = wi * wpr + wtemp * wpi + wi;
3017  }
3018  yF += mOutr[halfM];
3019  yF = fabs(yF);
3020  if(yF!=0.)
3021  yF = LINEAR_TO_DB(yF);
3022  else
3023  yF = mEffect->mdBMin;
3024  }
3025  else
3026  { //use FFT, it has enough resolution
3027  n = (int)(freq/delta + .5);
3028  if(pow(mEffect->mFilterFuncR[n],2)+pow(mEffect->mFilterFuncI[n],2)!=0.)
3029  yF = 10.0*log10(pow(mEffect->mFilterFuncR[n],2)+pow(mEffect->mFilterFuncI[n],2)); //10 here, a power
3030  else
3031  yF = mEffect->mdBMin;
3032  }
3033  if(yF < mEffect->mdBMin)
3034  yF = mEffect->mdBMin;
3035  yF = center-scale*yF;
3036  if(yF>mEnvRect.height)
3037  yF = mEnvRect.height - 1;
3038  if(yF<0.)
3039  yF=0.;
3040  y = (int)(yF+.5);
3041 
3042  if (i != 0)
3043  {
3044  AColor::Line(memDC, xlast, ylast, x, mEnvRect.y + y);
3045  }
3046  xlast = x;
3047  ylast = mEnvRect.y + y;
3048  }
3049 
3050  memDC.SetPen(*wxBLACK_PEN);
3051  if( mEffect->mDraw->GetValue() )
3052  {
3053  TrackPanelDrawingContext context{ memDC, {}, {} };
3055  context, mEnvRect, ZoomInfo(0.0, mEnvRect.width-1), false, 0.0,
3056  mEffect->mdBMin, mEffect->mdBMax, false);
3057  }
3058 
3059  dc.Blit(0, 0, mWidth, mHeight, &memDC, 0, 0, wxCOPY, FALSE);
3060 }
3061 
3062 void EqualizationPanel::OnMouseEvent(wxMouseEvent & event)
3063 {
3064  if (!mEffect->mDrawMode)
3065  {
3066  return;
3067  }
3068 
3069  if (event.ButtonDown() && !HasCapture())
3070  {
3071  CaptureMouse();
3072  }
3073 
3074  auto &pEditor = (mEffect->mLin ? mLinEditor : mLogEditor);
3075  if (pEditor->MouseEvent(event, mEnvRect, ZoomInfo(0.0, mEnvRect.width),
3076  false, 0.0,
3078  {
3080  ForceRecalc();
3081  }
3082 
3083  if (event.ButtonUp() && HasCapture())
3084  {
3085  ReleaseMouse();
3086  }
3087 }
3088 
3089 void EqualizationPanel::OnCaptureLost(wxMouseCaptureLostEvent & WXUNUSED(event))
3090 {
3091  if (HasCapture())
3092  {
3093  ReleaseMouse();
3094  }
3095 }
3096 
3097 //----------------------------------------------------------------------------
3098 // EditCurvesDialog
3099 //----------------------------------------------------------------------------
3100 // Note that the 'modified' curve used to be called 'custom' but is now called 'unnamed'
3101 // Some things that deal with 'unnamed' curves still use, for example, 'mCustomBackup' as variable names.
3103 
3104 BEGIN_EVENT_TABLE(EditCurvesDialog, wxDialogWrapper)
3111  EVT_BUTTON(LibraryButtonID, EditCurvesDialog::OnLibrary)
3113  EVT_BUTTON(wxID_OK, EditCurvesDialog::OnOK)
3114  EVT_LIST_ITEM_SELECTED(CurvesListID,
3115  EditCurvesDialog::OnListSelectionChange)
3116  EVT_LIST_ITEM_DESELECTED(CurvesListID,
3117  EditCurvesDialog::OnListSelectionChange)
3119 
3120 EditCurvesDialog::EditCurvesDialog(wxWindow * parent, EffectEqualization * effect, int position):
3121 wxDialogWrapper(parent, wxID_ANY, _("Manage Curves List"),
3122  wxDefaultPosition, wxDefaultSize,
3123  wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
3124 {
3125  SetLabel(_("Manage Curves")); // Provide visual label
3126  SetName(_("Manage Curves List")); // Provide audible label
3127  mParent = parent;
3128  mEffect = effect;
3129  mPosition = position;
3130  // make a copy of mEffect->mCurves here to muck about with.
3131  mEditCurves.clear();
3132  for (unsigned int i = 0; i < mEffect->mCurves.size(); i++)
3133  {
3134  mEditCurves.push_back(mEffect->mCurves[i].Name);
3135  mEditCurves[i].points = mEffect->mCurves[i].points;
3136  }
3137 
3138  Populate();
3139  SetMinSize(GetSize());
3140 }
3141 
3143 {
3144 }
3145 
3148 {
3149  //------------------------- Main section --------------------
3150  ShuttleGui S(this, eIsCreating);
3151  PopulateOrExchange(S);
3152  // ----------------------- End of main section --------------
3153 }
3154 
3157 {
3158  S.StartHorizontalLay(wxEXPAND);
3159  {
3160  S.StartStatic(_("&Curves"), 1);
3161  {
3162  S.SetStyle(wxSUNKEN_BORDER | wxLC_REPORT | wxLC_HRULES | wxLC_VRULES );
3164  mList->InsertColumn(0, _("Curve Name"), wxLIST_FORMAT_RIGHT);
3165  }
3166  S.EndStatic();
3167  S.StartVerticalLay(0);
3168  {
3169  S.Id(UpButtonID).AddButton(_("Move &Up"), wxALIGN_LEFT);
3170  S.Id(DownButtonID).AddButton(_("Move &Down"), wxALIGN_LEFT);
3171  S.Id(RenameButtonID).AddButton(_("&Rename..."), wxALIGN_LEFT);
3172  S.Id(DeleteButtonID).AddButton(_("D&elete..."), wxALIGN_LEFT);
3173  S.Id(ImportButtonID).AddButton(_("I&mport..."), wxALIGN_LEFT);
3174  S.Id(ExportButtonID).AddButton(_("E&xport..."), wxALIGN_LEFT);
3175  S.Id(LibraryButtonID).AddButton(_("&Get More..."), wxALIGN_LEFT);
3176  S.Id(DefaultsButtonID).AddButton(_("De&faults"), wxALIGN_LEFT);
3177  }
3178  S.EndVerticalLay();
3179  }
3180  S.EndHorizontalLay();
3181  S.AddStandardButtons();
3182  S.StartStatic(_("Help"));
3183  S.AddConstTextBox( {}, _("Rename 'unnamed' to save a new entry.\n'OK' saves all changes, 'Cancel' doesn't."));
3184  S.EndStatic();
3186  Fit();
3187 
3188  return;
3189 }
3190 
3192 {
3193  mList->DeleteAllItems();
3194  for (unsigned int i = 0; i < mEditCurves.size(); i++)
3195  mList->InsertItem(i, mEditCurves[i].Name);
3196  mList->SetColumnWidth(0, wxLIST_AUTOSIZE);
3197  int curvesWidth = mList->GetColumnWidth(0);
3198  mList->SetColumnWidth(0, wxLIST_AUTOSIZE_USEHEADER);
3199  int headerWidth = mList->GetColumnWidth(0);
3200  mList->SetColumnWidth(0, wxMax(headerWidth, curvesWidth));
3201  // use 'position' to set focus
3202  mList->EnsureVisible(position);
3203  mList->SetItemState(position, wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED, wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED);
3204 }
3205 
3206 void EditCurvesDialog::OnUp(wxCommandEvent & WXUNUSED(event))
3207 {
3208  long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3209  if ( item == -1 )
3210  return; // no items selected
3211  if( item == 0 )
3212  item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); // top item selected, can't move up
3213  int state;
3214  while( item != -1 )
3215  {
3216  if ( item == mList->GetItemCount()-1)
3217  { // 'unnamed' always stays at the bottom
3218  mEffect->Effect::MessageBox(_("'unnamed' always stays at the bottom of the list"),
3220  _("'unnamed' is special")); // these could get tedious!
3221  return;
3222  }
3223  state = mList->GetItemState(item-1, wxLIST_STATE_SELECTED);
3224  if ( state != wxLIST_STATE_SELECTED )
3225  { // swap this with one above but only if it isn't selected
3226  EQCurve temp(wxT("temp"));
3227  temp.Name = mEditCurves[item].Name;
3228  temp.points = mEditCurves[item].points;
3229  mEditCurves[item].Name = mEditCurves[item-1].Name;
3230  mEditCurves[item].points = mEditCurves[item-1].points;
3231  mEditCurves[item-1].Name = temp.Name;
3232  mEditCurves[item-1].points = temp.points;
3233  wxString sTemp = mList->GetItemText(item);
3234  mList->SetItem(item, 0, mList->GetItemText(item-1));
3235  mList->SetItem(item-1, 0, sTemp);
3236  mList->SetItemState(item, 0, wxLIST_STATE_SELECTED);
3237  mList->SetItemState(item-1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
3238  }
3239  item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3240  }
3241 }
3242 
3243 void EditCurvesDialog::OnDown(wxCommandEvent & WXUNUSED(event))
3244 { // looks harder than OnUp as we need to seek backwards up the list, hence GetPreviousItem
3245  long item = GetPreviousItem(mList->GetItemCount());
3246  if( item == -1 )
3247  return; // nothing selected
3248  int state;
3249  while( item != -1 )
3250  {
3251  if( (item != mList->GetItemCount()-1) && (item != mList->GetItemCount()-2) )
3252  { // can't move 'unnamed' down, or the one above it
3253  state = mList->GetItemState(item+1, wxLIST_STATE_SELECTED);
3254  if ( state != wxLIST_STATE_SELECTED )
3255  { // swap this with one below but only if it isn't selected
3256  EQCurve temp(wxT("temp"));
3257  temp.Name = mEditCurves[item].Name;
3258  temp.points = mEditCurves[item].points;
3259  mEditCurves[item].Name = mEditCurves[item+1].Name;
3260  mEditCurves[item].points = mEditCurves[item+1].points;
3261  mEditCurves[item+1].Name = temp.Name;
3262  mEditCurves[item+1].points = temp.points;
3263  wxString sTemp = mList->GetItemText(item);
3264  mList->SetItem(item, 0, mList->GetItemText(item+1));
3265  mList->SetItem(item+1, 0, sTemp);
3266  mList->SetItemState(item, 0, wxLIST_STATE_SELECTED);
3267  mList->SetItemState(item+1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
3268  }
3269  }
3270  item = GetPreviousItem(item);
3271  }
3272 }
3273 
3274 long EditCurvesDialog::GetPreviousItem(long item) // wx doesn't have this
3275 {
3276  long lastItem = -1;
3277  long itemTemp = mList->GetNextItem(-1, wxLIST_NEXT_ALL,
3278  wxLIST_STATE_SELECTED);
3279  while( (itemTemp != -1) && (itemTemp < item) )
3280  {
3281  lastItem = itemTemp;
3282  itemTemp = mList->GetNextItem(itemTemp, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3283  }
3284  return lastItem;
3285 }
3286 
3287 // Rename curve/curves
3288 void EditCurvesDialog::OnRename(wxCommandEvent & WXUNUSED(event))
3289 {
3290  wxString name;
3291  int numCurves = mEditCurves.size();
3292  int curve = 0;
3293 
3294  // Setup list of characters that aren't allowed
3295  wxArrayString exclude;
3296  exclude.Add( wxT("<") );
3297  exclude.Add( wxT(">") );
3298  exclude.Add( wxT("'") );
3299  exclude.Add( wxT("\"") );
3300 
3301  // Get the first one to be renamed
3302  long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3303  long firstItem = item; // for reselection with PopulateList
3304  while(item >= 0)
3305  {
3306  // Prompt the user until a valid name is enter or cancelled
3307  bool overwrite = false;
3308  bool bad = true;
3309  while( bad ) // Check for an unacceptable duplicate
3310  { // Show the dialog and bail if the user cancels
3311  bad = false;
3312  // build the dialog
3313  AudacityTextEntryDialog dlg( this,
3314  wxString::Format( _("Rename '%s' to..."), mEditCurves[ item ].Name ),
3315  _("Rename...") );
3316  dlg.SetTextValidator( wxFILTER_EXCLUDE_CHAR_LIST );
3317  dlg.SetName(
3318  wxString::Format( _("Rename '%s'"), mEditCurves[ item ].Name ) );
3319  wxTextValidator *tv = dlg.GetTextValidator();
3320  tv->SetExcludes( exclude ); // Tell the validator about excluded chars
3321  if( dlg.ShowModal() == wxID_CANCEL )
3322  {
3323  bad = true;
3324  break;
3325  }
3326 
3327  // Extract the name from the dialog
3328  name = dlg.GetValue();
3329 
3330  // Search list of curves for a duplicate name
3331  for( curve = 0; curve < numCurves; curve++ )
3332  {
3333  wxString temp = mEditCurves[ curve ].Name;
3334  if( name.IsSameAs( mEditCurves[ curve ].Name )) // case sensitive
3335  {
3336  bad = true;
3337  if( curve == item ) // trying to rename a curve with the same name
3338  {
3339  mEffect->Effect::MessageBox( _("Name is the same as the original one"), wxOK, _("Same name") );
3340  break;
3341  }
3342  int answer = mEffect->Effect::MessageBox(
3343  wxString::Format( _("Overwrite existing curve '%s'?"), name ),
3344  wxYES_NO, _("Curve exists") );
3345  if (answer == wxYES)
3346  {
3347  bad = false;
3348  overwrite = true; // we are going to overwrite the one with this name
3349  break;
3350  }
3351  }
3352  }
3353  if( name == wxT("") || name == wxT("unnamed") )
3354  bad = true;
3355  }
3356 
3357  // if bad, we cancelled the rename dialog, so nothing to do.
3358  if( bad == true )
3359  ;
3360  else if(overwrite){
3361  // Overwrite another curve.
3362  // JKC: because 'overwrite' is true, 'curve' is the number of the curve that
3363  // we are about to overwrite.
3364  mEditCurves[ curve ].Name = name;
3365  mEditCurves[ curve ].points = mEditCurves[ item ].points;
3366  // if renaming the unnamed item, then select it,
3367  // otherwise get rid of the item we've renamed.
3368  if( item == (numCurves-1) )
3369  mList->SetItem(curve, 0, name);
3370  else
3371  {
3372  mEditCurves.erase( mEditCurves.begin() + item );
3373  numCurves--;
3374  }
3375  }
3376  else if( item == (numCurves-1) ) // renaming 'unnamed'
3377  { // Create a NEW entry
3378  mEditCurves.push_back( EQCurve( wxT("unnamed") ) );
3379  // Copy over the points
3380  mEditCurves[ numCurves ].points = mEditCurves[ numCurves - 1 ].points;
3381  // Give the original unnamed entry the NEW name
3382  mEditCurves[ numCurves - 1 ].Name = name;
3383  numCurves++;
3384  }
3385  else // just rename (the 'normal' case)
3386  {
3387  mEditCurves[ item ].Name = name;
3388  mList->SetItem(item, 0, name);
3389  }
3390  // get next selected item
3391  item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3392  }
3393 
3394  PopulateList(firstItem); // Note: only saved to file when you OK out of the dialog
3395  return;
3396 }
3397 
3398 // Delete curve/curves
3399 void EditCurvesDialog::OnDelete(wxCommandEvent & WXUNUSED(event))
3400 {
3401  // We could could count them here
3402  // And then put in a 'Delete N items?' prompt.
3403 
3404 #if 0 // 'one at a time' prompt code
3405  // Get the first one to be deleted
3406  long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3407  // Take care, mList and mEditCurves will get out of sync as curves are deleted
3408  int deleted = 0;
3409  long highlight = -1;
3410 
3411  while(item >= 0)
3412  {
3413  if(item == mList->GetItemCount()-1) //unnamed
3414  {
3415  mEffect->Effect::MessageBox(_("You cannot delete the 'unnamed' curve."),
3416  wxOK | wxCENTRE, _("Can't delete 'unnamed'"));
3417  }
3418  else
3419  {
3420  // Create the prompt
3421  wxString quest;
3422  quest = wxString::Format(_("Delete '%s'?"),
3423  mEditCurves[ item-deleted ].Name);
3424 
3425  // Ask for confirmation before removal
3426  int ans = mEffect->Effect::MessageBox( quest, wxYES_NO | wxCENTRE, _("Confirm Deletion") );
3427  if( ans == wxYES )
3428  { // Remove the curve from the array
3429  mEditCurves.RemoveAt( item-deleted );
3430  deleted++;
3431  }
3432  else
3433  highlight = item-deleted; // if user presses 'No', select that curve
3434  }
3435  // get next selected item
3436  item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3437  }
3438 
3439  if(highlight == -1)
3440  PopulateList(mEditCurves.GetCount()-1); // set 'unnamed' as the selected curve
3441  else
3442  PopulateList(highlight); // user said 'No' to deletion
3443 #else // 'DELETE all N' code
3444  int count = mList->GetSelectedItemCount();
3445  long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3446  // Create the prompt
3447  wxString quest;
3448  if( count > 1 )
3449  quest = wxString::Format(_("Delete %d items?"), count);
3450  else
3451  if( count == 1 )
3452  quest = wxString::Format(_("Delete '%s'?"), mEditCurves[ item ].Name);
3453  else
3454  return;
3455  // Ask for confirmation before removal
3456  int ans = mEffect->Effect::MessageBox( quest, wxYES_NO | wxCENTRE, _("Confirm Deletion") );
3457  if( ans == wxYES )
3458  { // Remove the curve(s) from the array
3459  // Take care, mList and mEditCurves will get out of sync as curves are deleted
3460  int deleted = 0;
3461  while(item >= 0)
3462  {
3463  if(item == mList->GetItemCount()-1) //unnamed
3464  {
3465  mEffect->Effect::MessageBox(_("You cannot delete the 'unnamed' curve, it is special."),
3467  _("Can't delete 'unnamed'"));
3468  }
3469  else
3470  {
3471  mEditCurves.erase( mEditCurves.begin() + item - deleted );
3472  deleted++;
3473  }
3474  item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3475  }
3476  PopulateList(mEditCurves.size() - 1); // set 'unnamed' as the selected curve
3477  }
3478 #endif
3479 }
3480 
3481 void EditCurvesDialog::OnImport( wxCommandEvent & WXUNUSED(event))
3482 {
3483  FileDialogWrapper filePicker(this, _("Choose an EQ curve file"), FileNames::DataDir(), wxT(""), _("xml files (*.xml;*.XML)|*.xml;*.XML"));
3484  wxString fileName = wxT("");
3485  if( filePicker.ShowModal() == wxID_CANCEL)
3486  return;
3487  else
3488  fileName = filePicker.GetPath();
3489  // Use EqualizationDialog::LoadCurves to read into (temporary) mEditCurves
3490  // This may not be the best OOP way of doing it, but I don't know better (MJS)
3491  EQCurveArray temp;
3492  temp = mEffect->mCurves; // temp copy of the main dialog curves
3493  mEffect->mCurves = mEditCurves; // copy EditCurvesDialog to main interface
3494  mEffect->LoadCurves(fileName, true); // use main interface to load imported curves
3495  mEditCurves = mEffect->mCurves; // copy back to this interface
3496  mEffect->mCurves = temp; // and reset the main interface how it was
3497  PopulateList(0); // update the EditCurvesDialog dialog
3498  return;
3499 }
3500 
3501 void EditCurvesDialog::OnExport( wxCommandEvent & WXUNUSED(event))
3502 {
3503  FileDialogWrapper filePicker(this, _("Export EQ curves as..."), FileNames::DataDir(), wxT(""), wxT("*.XML"), wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER); // wxFD_CHANGE_DIR?
3504  wxString fileName = wxT("");
3505  if( filePicker.ShowModal() == wxID_CANCEL)
3506  return;
3507  else
3508  fileName = filePicker.GetPath();
3509 
3510  EQCurveArray temp;
3511  temp = mEffect->mCurves; // backup the parent's curves
3512  EQCurveArray exportCurves; // Copy selected curves to export
3513  exportCurves.clear();
3514  long item = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3515  int i=0;
3516  while(item >= 0)
3517  {
3518  if(item != mList->GetItemCount()-1) // not 'unnamed'
3519  {
3520  exportCurves.push_back(mEditCurves[item].Name);
3521  exportCurves[i].points = mEditCurves[item].points;
3522  i++;
3523  }
3524  else
3525  mEffect->Effect::MessageBox(_("You cannot export 'unnamed' curve, it is special."),
3527  _("Cannot Export 'unnamed'"));
3528  // get next selected item
3529  item = mList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3530  }
3531  if(i>0)
3532  {
3533  mEffect->mCurves = exportCurves;
3534  mEffect->SaveCurves(fileName);
3535  mEffect->mCurves = temp;
3536  wxString message;
3537  message.Printf(_("%d curves exported to %s"), i, fileName);
3538  mEffect->Effect::MessageBox(message,
3540  _("Curves exported"));
3541  }
3542  else
3543  mEffect->Effect::MessageBox(_("No curves exported"),
3545  _("No curves exported"));
3546 }
3547 
3548 void EditCurvesDialog::OnLibrary( wxCommandEvent & WXUNUSED(event))
3549 {
3550  // full path to wiki.
3551  wxLaunchDefaultBrowser(wxT("https://wiki.audacityteam.org/wiki/EQCurvesDownload"));
3552 }
3553 
3554 void EditCurvesDialog::OnDefaults( wxCommandEvent & WXUNUSED(event))
3555 {
3556  EQCurveArray temp;
3557  temp = mEffect->mCurves;
3558  // we expect this to fail in LoadCurves (due to a lack of path) and handle that there
3559  mEffect->LoadCurves( wxT("EQDefaultCurves.xml") );
3561  mEffect->mCurves = temp;
3562  PopulateList(0); // update the EditCurvesDialog dialog
3563 }
3564 
3565 void EditCurvesDialog::OnOK(wxCommandEvent & WXUNUSED(event))
3566 {
3567  // Make a backup of the current curves
3568  wxString backupPlace = wxFileName( FileNames::DataDir(), wxT("EQBackup.xml") ).GetFullPath();
3569  mEffect->SaveCurves(backupPlace);
3570  // Load back into the main dialog
3571  mEffect->mCurves.clear();
3572  for (unsigned int i = 0; i < mEditCurves.size(); i++)
3573  {
3574  mEffect->mCurves.push_back(mEditCurves[i].Name);
3575  mEffect->mCurves[i].points = mEditCurves[i].points;
3576  }
3577  mEffect->SaveCurves();
3578  mEffect->LoadCurves();
3579 // mEffect->CreateChoice();
3580  wxGetTopLevelParent(mEffect->mUIParent)->Layout();
3581 // mEffect->mUIParent->Layout();
3582 
3583  // Select something sensible
3584  long item = mList->GetNextItem(-1,
3585  wxLIST_NEXT_ALL,
3586  wxLIST_STATE_SELECTED);
3587  if (item == -1)
3588  item = mList->GetItemCount()-1; // nothing selected, default to 'unnamed'
3589  mEffect->setCurve(item);
3590  EndModal(true);
3591 }
3592 
3594 {
3595  const bool enable = mList->GetSelectedItemCount() > 0;
3596  static const int ids[] = {
3597  UpButtonID,
3598  DownButtonID,
3601  };
3602  for (auto id : ids)
3603  FindWindowById(id, this)->Enable(enable);
3604 }
3605 
3606 #if wxUSE_ACCESSIBILITY
3607 
3608 SliderAx::SliderAx(wxWindow * window, const wxString &fmt) :
3609 WindowAccessible( window )
3610 {
3611  mParent = window;
3612  mFmt = fmt;
3613 }
3614 
3615 SliderAx::~SliderAx()
3616 {
3617 }
3618 
3619 // Retrieves the address of an IDispatch interface for the specified child.
3620 // All objects must support this property.
3621 wxAccStatus SliderAx::GetChild( int childId, wxAccessible** child )
3622 {
3623  if( childId == wxACC_SELF )
3624  {
3625  *child = this;
3626  }
3627  else
3628  {
3629  *child = NULL;
3630  }
3631 
3632  return wxACC_OK;
3633 }
3634 
3635 // Gets the number of children.
3636 wxAccStatus SliderAx::GetChildCount(int* childCount)
3637 {
3638  *childCount = 3;
3639 
3640  return wxACC_OK;
3641 }
3642 
3643 // Gets the default action for this object (0) or > 0 (the action for a child).
3644 // Return wxACC_OK even if there is no action. actionName is the action, or the empty
3645 // string if there is no action.
3646 // The retrieved string describes the action that is performed on an object,
3647 // not what the object does as a result. For example, a toolbar button that prints
3648 // a document has a default action of "Press" rather than "Prints the current document."
3649 wxAccStatus SliderAx::GetDefaultAction( int WXUNUSED(childId), wxString *actionName )
3650 {
3651  actionName->Clear();
3652 
3653  return wxACC_OK;
3654 }
3655 
3656 // Returns the description for this object or a child.
3657 wxAccStatus SliderAx::GetDescription( int WXUNUSED(childId), wxString *description )
3658 {
3659  description->Clear();
3660 
3661  return wxACC_OK;
3662 }
3663 
3664 // Gets the window with the keyboard focus.
3665 // If childId is 0 and child is NULL, no object in
3666 // this subhierarchy has the focus.
3667 // If this object has the focus, child should be 'this'.
3668 wxAccStatus SliderAx::GetFocus(int* childId, wxAccessible** child)
3669 {
3670  *childId = 0;
3671  *child = this;
3672 
3673  return wxACC_OK;
3674 }
3675 
3676 // Returns help text for this object or a child, similar to tooltip text.
3677 wxAccStatus SliderAx::GetHelpText( int WXUNUSED(childId), wxString *helpText )
3678 {
3679  helpText->Clear();
3680 
3681  return wxACC_OK;
3682 }
3683 
3684 // Returns the keyboard shortcut for this object or child.
3685 // Return e.g. ALT+K
3686 wxAccStatus SliderAx::GetKeyboardShortcut( int WXUNUSED(childId), wxString *shortcut )
3687 {
3688  shortcut->Clear();
3689 
3690  return wxACC_OK;
3691 }
3692 
3693 // Returns the rectangle for this object (id = 0) or a child element (id > 0).
3694 // rect is in screen coordinates.
3695 wxAccStatus SliderAx::GetLocation( wxRect& rect, int WXUNUSED(elementId) )
3696 {
3697  wxSlider *s = wxDynamicCast( GetWindow(), wxSlider );
3698 
3699  rect = s->GetRect();
3700  rect.SetPosition( s->GetParent()->ClientToScreen( rect.GetPosition() ) );
3701 
3702  return wxACC_OK;
3703 }
3704 
3705 // Gets the name of the specified object.
3706 wxAccStatus SliderAx::GetName(int WXUNUSED(childId), wxString* name)
3707 {
3708  wxSlider *s = wxDynamicCast( GetWindow(), wxSlider );
3709 
3710  *name = s->GetName();
3711 
3712  return wxACC_OK;
3713 }
3714 
3715 // Returns a role constant.
3716 wxAccStatus SliderAx::GetRole(int childId, wxAccRole* role)
3717 {
3718  switch( childId )
3719  {
3720  case 0:
3721  *role = wxROLE_SYSTEM_SLIDER;
3722  break;
3723 
3724  case 1:
3725  case 3:
3726  *role = wxROLE_SYSTEM_PUSHBUTTON;
3727  break;
3728 
3729  case 2:
3730  *role = wxROLE_SYSTEM_INDICATOR;
3731  break;
3732  }
3733 
3734  return wxACC_OK;
3735 }
3736 
3737 // Gets a variant representing the selected children
3738 // of this object.
3739 // Acceptable values:
3740 // - a null variant (IsNull() returns TRUE)
3741 // - a list variant (GetType() == wxT("list"))
3742 // - an integer representing the selected child element,
3743 // or 0 if this object is selected (GetType() == wxT("long"))
3744 // - a "void*" pointer to a wxAccessible child object
3745 wxAccStatus SliderAx::GetSelections( wxVariant * WXUNUSED(selections) )
3746 {
3747  return wxACC_NOT_IMPLEMENTED;
3748 }
3749 
3750 // Returns a state constant.
3751 wxAccStatus SliderAx::GetState(int childId, long* state)
3752 {
3753  wxSlider *s = wxDynamicCast( GetWindow(), wxSlider );
3754 
3755  switch( childId )
3756  {
3757  case 0:
3758  *state = wxACC_STATE_SYSTEM_FOCUSABLE;
3759  break;
3760 
3761  case 1:
3762  if( s->GetValue() == s->GetMin() )
3763  {
3764  *state = wxACC_STATE_SYSTEM_INVISIBLE;
3765  }
3766  break;
3767 
3768  case 3:
3769  if( s->GetValue() == s->GetMax() )
3770  {
3771  *state = wxACC_STATE_SYSTEM_INVISIBLE;
3772  }
3773  break;
3774  }
3775 
3776  // Do not use mSliderIsFocused is not set until after this method
3777  // is called.
3778  *state |= ( s == wxWindow::FindFocus() ? wxACC_STATE_SYSTEM_FOCUSED : 0 );
3779 
3780  return wxACC_OK;
3781 }
3782 
3783 // Returns a localized string representing the value for the object
3784 // or child.
3785 wxAccStatus SliderAx::GetValue(int childId, wxString* strValue)
3786 {
3787  wxSlider *s = wxDynamicCast( GetWindow(), wxSlider );
3788 
3789  if( childId == 0 )
3790  {
3791  strValue->Printf( mFmt, s->GetValue() );
3792 
3793  return wxACC_OK;
3794  }
3795 
3796  return wxACC_NOT_SUPPORTED;
3797 }
3798 
3799 #endif
3800 
void UpdateGraphic(void)
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:199
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:461
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:600
int MessageBox(const wxString &message, long style=DefaultMessageBoxStyle, const wxString &titleStr=wxString{})
Definition: Effect.cpp:2660
bool ValidateUI() override
friend class EqualizationPanel
Definition: Equalization.h:279
bool TrackProgress(int whichTrack, double frac, const wxString &=wxEmptyString)
Definition: Effect.cpp:1985
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI...
Definition: ShuttleGui.h:409
virtual ~EffectEqualization()
wxRadioButton * mGraphic
Definition: Equalization.h:260
void Flatten(double value)
Definition: Envelope.cpp:137
size_t GetNumberOfPoints() const
Return number of points.
Definition: Envelope.cpp:980
wxString GetCurrentSettingsGroup() override
Definition: Effect.cpp:801
void CopyInputTracks()
Definition: Effect.cpp:2036
bool GetSelected() const
Definition: Track.h:275
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:2162
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:1549
void EndMultiColumn()
HFFT GetFFT(size_t fftlen)
Definition: RealFFTf.cpp:110
bool TransferDataToWindow() override
wxString label
Definition: Tags.cpp:727
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:960
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:985
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:922
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:584
void SetSizerProportion(int iProp)
Definition: ShuttleGui.h:289
TrackList * inputTracks() const
Definition: Effect.h:458
#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:329
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:786
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:797
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:194
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:1077
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:101
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:70
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:472
EQCurveArray mCurves
Definition: Equalization.h:232
XMLTagHandler * HandleXMLChild(const wxChar *tag) override
An iterator for a TrackList.
Definition: Track.h:401
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:1465
#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:236
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:308
#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:801
wxStaticText * AddVariableText(const wxString &Str, bool bCenter=false, int PositionFlags=0)
Definition: ShuttleGui.cpp:414
bool LoadFactoryDefaults() override
Definition: Effect.cpp:636
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:800
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:682
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:1214
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:459
EffectEqualization * mEffect
Definition: Equalization.h:313
TrackFactory * GetTrackFactory()
Definition: Project.cpp:1427
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:460
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)