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