Audacity  2.3.1
FreqWindow.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3  Audacity: A Digital Audio Editor
4 
5  FreqWindow.cpp
6 
7  Dominic Mazzoni
8 
9 *******************************************************************//****************************************************************//****************************************************************//*******************************************************************/
36 
37 /*
38  Salvo Ventura - November 2006
39  Extended range check for additional FFT windows
40 */
41 
42 
43 #include "Audacity.h"
44 #include "FreqWindow.h"
45 
46 #include <algorithm>
47 
48 #include <wx/brush.h>
49 #include <wx/button.h>
50 #include <wx/choice.h>
51 #include <wx/font.h>
52 #include <wx/image.h>
53 #include <wx/dcmemory.h>
54 #include <wx/file.h>
55 #include <wx/filedlg.h>
56 #include <wx/intl.h>
57 #include <wx/sizer.h>
58 #include <wx/statbmp.h>
59 #include <wx/stattext.h>
60 #include <wx/statusbr.h>
61 
62 #include <wx/textfile.h>
63 
64 #include <math.h>
65 
66 #include "ShuttleGui.h"
67 #include "AColor.h"
68 #include "FFT.h"
69 #include "Internat.h"
70 #include "PitchName.h"
71 #include "prefs/GUISettings.h"
72 #include "Prefs.h"
73 #include "Project.h"
74 #include "WaveClip.h"
75 #include "Theme.h"
76 #include "AllThemeResources.h"
77 
78 #include "FileNames.h"
79 
80 #include "WaveTrack.h"
81 
82 #include "Experimental.h"
83 
85 #include "./widgets/HelpSystem.h"
86 #include "widgets/ErrorDialog.h"
87 #include "widgets/Ruler.h"
88 
89 #if wxUSE_ACCESSIBILITY
91 #endif
92 
93 DEFINE_EVENT_TYPE(EVT_FREQWINDOW_RECALC);
94 
95 enum {
96  FirstID = 7000,
97 
107 };
108 
109 // These specify the minimum plot window width
110 
111 #define FREQ_WINDOW_WIDTH 480
112 #define FREQ_WINDOW_HEIGHT 330
113 
114 
115 static const char * ZoomIn[] = {
116 "16 16 6 1",
117 " c None",
118 "+ c #1C1C1C",
119 "@ c #AEAEAE",
120 "# c #F7F7F7",
121 "$ c #CFCECC",
122 "* c #1C1CA0",
123 " ++++ ",
124 " @+# @[email protected] ",
125 " + @** [email protected] ",
126 " +#@ ** #+ ",
128 " + ****** [email protected]",
129 " +# ** #[email protected]",
130 " + ** [email protected]@",
131 " +++# #[email protected]@ ",
133 " [email protected]@ @@@@ ",
134 " [email protected]@ ",
135 " [email protected]@ ",
137 "@[email protected]@ ",
138 " @@ "};
139 
140 
141 static const char * ZoomOut[] = {
142 "16 16 6 1",
143 " c None",
144 "+ c #1C1C1C",
145 "@ c #AEAEAE",
146 "# c #F7F7F7",
147 "$ c #CFCECC",
148 "* c #1C1CA0",
149 " ++++ ",
150 " @+# [email protected] ",
151 " + @@ [email protected] ",
152 " +# @ #+ ",
154 " + ****** [email protected]",
155 " +# #[email protected]",
156 " + [email protected]@",
157 " +++# #[email protected]@ ",
159 " [email protected]@ @@@@ ",
160 " [email protected]@ ",
161 " [email protected]@ ",
163 "@[email protected]@ ",
164 " @@ "};
165 
166 // FreqWindow
167 
168 BEGIN_EVENT_TABLE(FreqWindow, wxDialogWrapper)
169  EVT_CLOSE(FreqWindow::OnCloseWindow)
170  EVT_SIZE(FreqWindow::OnSize)
171  EVT_SLIDER(FreqZoomSliderID, FreqWindow::OnZoomSlider)
172  EVT_COMMAND_SCROLL(FreqPanScrollerID, FreqWindow::OnPanScroller)
173  EVT_CHOICE(FreqAlgChoiceID, FreqWindow::OnAlgChoice)
174  EVT_CHOICE(FreqSizeChoiceID, FreqWindow::OnSizeChoice)
175  EVT_CHOICE(FreqFuncChoiceID, FreqWindow::OnFuncChoice)
176  EVT_CHOICE(FreqAxisChoiceID, FreqWindow::OnAxisChoice)
179  EVT_BUTTON(wxID_CANCEL, FreqWindow::OnCloseButton)
180  EVT_BUTTON(wxID_HELP, FreqWindow::OnGetURL)
181  EVT_CHECKBOX(GridOnOffID, FreqWindow::OnGridOnOff)
182  EVT_COMMAND(wxID_ANY, EVT_FREQWINDOW_RECALC, FreqWindow::OnRecalc)
184 
186 : mAlg(Spectrum)
187 , mRate(0.0)
188 , mWindowSize(0)
189 {
190 }
191 
193 {
194 }
195 
196 FreqWindow::FreqWindow(wxWindow * parent, wxWindowID id,
197  const wxString & title,
198  const wxPoint & pos)
199 : wxDialogWrapper(parent, id, title, pos, wxDefaultSize,
200  wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMAXIMIZE_BOX),
201  mAnalyst(std::make_unique<SpectrumAnalyst>())
202 {
203  SetName(GetTitle());
204 
205  mMouseX = 0;
206  mMouseY = 0;
207  mRate = 0;
208  mDataLen = 0;
209 
210  p = GetActiveProject();
211  if (!p)
212  return;
213 
214  wxArrayString algChoices;
215  algChoices.Add(_("Spectrum"));
216  algChoices.Add(_("Standard Autocorrelation"));
217  algChoices.Add(_("Cuberoot Autocorrelation"));
218  algChoices.Add(_("Enhanced Autocorrelation"));
219  /* i18n-hint: This is a technical term, derived from the word
220  * "spectrum". Do not translate it unless you are sure you
221  * know the correct technical word in your language. */
222  algChoices.Add(_("Cepstrum"));
223 
224  wxArrayString sizeChoices;
225  sizeChoices.Add(wxT("128"));
226  sizeChoices.Add(wxT("256"));
227  sizeChoices.Add(wxT("512"));
228  sizeChoices.Add(wxT("1024"));
229  sizeChoices.Add(wxT("2048"));
230  sizeChoices.Add(wxT("4096"));
231  sizeChoices.Add(wxT("8192"));
232  sizeChoices.Add(wxT("16384"));
233  sizeChoices.Add(wxT("32768"));
234  sizeChoices.Add(wxT("65536"));
235 
236  wxArrayString funcChoices;
237  for (int i = 0, cnt = NumWindowFuncs(); i < cnt; i++)
238  {
239  /* i18n-hint: This refers to a "window function",
240  * such as Hann or Rectangular, used in the
241  * Frequency analyze dialog box. */
242  funcChoices.Add(wxString::Format("%s window", WindowFuncName(i) ) );
243  }
244 
245  wxArrayString axisChoices;
246  axisChoices.Add(_("Linear frequency"));
247  axisChoices.Add(_("Log frequency"));
248 
249  mFreqFont = wxFont(fontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
250  mArrowCursor = std::make_unique<wxCursor>(wxCURSOR_ARROW);
251  mCrossCursor = std::make_unique<wxCursor>(wxCURSOR_CROSS);
252 
253  gPrefs->Read(wxT("/FreqWindow/DrawGrid"), &mDrawGrid, true);
254 
255  long size;
256  gPrefs->Read(wxT("/FreqWindow/SizeChoice"), &mSize, 3);
257  sizeChoices[mSize].ToLong(&size);
258  mWindowSize = size;
259 
260  int alg;
261  gPrefs->Read(wxT("/FreqWindow/AlgChoice"), &alg, 0);
262  mAlg = static_cast<SpectrumAnalyst::Algorithm>(alg);
263 
264  gPrefs->Read(wxT("/FreqWindow/FuncChoice"), &mFunc, 3);
265  gPrefs->Read(wxT("/FreqWindow/AxisChoice"), &mAxis, 1);
267  if(dBRange < 90.)
268  dBRange = 90.;
269 
270  ShuttleGui S(this, eIsCreating);
271 
272  S.SetBorder(0);
273 
274  S.AddSpace(5);
275 
276  S.SetSizerProportion(1);
277  S.StartMultiColumn(3, wxEXPAND);
278  {
279  S.SetStretchyCol(1);
280  S.SetStretchyRow(0);
281 
282  // -------------------------------------------------------------------
283  // ROW 1: Freq response panel and sliders for vertical scale
284  // -------------------------------------------------------------------
285 
286  S.StartVerticalLay(2);
287  {
289  this, wxID_ANY, wxVERTICAL,
290  wxSize{ 100, 100 }, // Ruler can't handle small sizes
291  RulerPanel::Range{ 0.0, -dBRange },
293  _("dB"),
295  .LabelEdges(true)
296  .TickColour( theTheme.Colour( clrGraphLabels ) )
297  );
298 
299  S.AddSpace(wxDefaultCoord, 1);
300  S.Prop(1);
301  S.AddWindow(vRuler, wxALIGN_RIGHT | wxALIGN_TOP);
302  S.AddSpace(wxDefaultCoord, 1);
303  }
304  S.EndVerticalLay();
305 
306  mFreqPlot = safenew FreqPlot(this, wxID_ANY);
307  mFreqPlot->SetMinSize(wxSize(wxDefaultCoord, FREQ_WINDOW_HEIGHT));
308  S.Prop(1);
309  S.AddWindow(mFreqPlot, wxEXPAND);
310 
311  S.StartHorizontalLay(wxEXPAND, 0);
312  {
313  S.StartVerticalLay();
314  {
315  mPanScroller = safenew wxScrollBar(this, FreqPanScrollerID,
316  wxDefaultPosition, wxDefaultSize, wxSB_VERTICAL);
317 #if wxUSE_ACCESSIBILITY
318  // so that name can be set on a standard control
320 #endif
321  mPanScroller->SetName(_("Scroll"));
322  S.Prop(1);
323  S.AddWindow(mPanScroller, wxALIGN_LEFT | wxTOP);
324  }
325  S.EndVerticalLay();
326 
327  S.StartVerticalLay();
328  {
329  wxStaticBitmap *zi = safenew wxStaticBitmap(this, wxID_ANY, wxBitmap(ZoomIn));
330  S.AddWindow((wxWindow *) zi, wxALIGN_CENTER);
331 
332  S.AddSpace(5);
333 
334  mZoomSlider = safenew wxSlider(this, FreqZoomSliderID, 100, 1, 100,
335  wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL);
336  S.Prop(1);
337  S.AddWindow(mZoomSlider, wxALIGN_CENTER_HORIZONTAL);
338 #if wxUSE_ACCESSIBILITY
339  // so that name can be set on a standard control
341 #endif
342  mZoomSlider->SetName(_("Zoom"));
343 
344  S.AddSpace(5);
345 
346  wxStaticBitmap *zo = safenew wxStaticBitmap(this, wxID_ANY, wxBitmap(ZoomOut));
347  S.AddWindow((wxWindow *) zo, wxALIGN_CENTER);
348  }
349  S.EndVerticalLay();
350 
351  S.AddSpace(5, wxDefaultCoord);
352  }
353  S.EndHorizontalLay();
354 
355  // -------------------------------------------------------------------
356  // ROW 2: Frequency ruler
357  // -------------------------------------------------------------------
358 
359  S.AddSpace(1);
360 
361  S.StartHorizontalLay(wxEXPAND, 0);
362  {
364  this, wxID_ANY, wxHORIZONTAL,
365  wxSize{ 100, 100 }, // Ruler can't handle small sizes
366  RulerPanel::Range{ 10, 20000 },
368  _("Hz"),
370  .Log(true)
371  .Flip(true)
372  .LabelEdges(true)
373  .TickColour( theTheme.Colour( clrGraphLabels ) )
374  );
375 
376  S.AddSpace(1, wxDefaultCoord);
377  S.Prop(1);
378  S.AddWindow(hRuler, wxALIGN_LEFT | wxALIGN_TOP);
379  S.AddSpace(1, wxDefaultCoord);
380  }
381  S.EndHorizontalLay();
382 
383  S.AddSpace(1);
384 
385  // -------------------------------------------------------------------
386  // ROW 3: Spacer
387  // -------------------------------------------------------------------
388  S.AddSpace(5);
389  S.AddSpace(5);
390  S.AddSpace(5);
391 
392  // -------------------------------------------------------------------
393  // ROW 4: Info
394  // -------------------------------------------------------------------
395 
396  S.AddSpace(1);
397 
398  S.StartHorizontalLay(wxEXPAND);
399  {
400  S.SetSizerProportion(1);
401  S.StartMultiColumn(6);
402  S.SetStretchyCol(1);
403  S.SetStretchyCol(3);
404  {
405  S.AddPrompt(_("Cursor:"));
406 
407  S.SetStyle(wxTE_READONLY);
408  mCursorText = S.AddTextBox( {}, wxT(""), 10);
409 
410  S.AddPrompt(_("Peak:"));
411 
412  S.SetStyle(wxTE_READONLY);
413  mPeakText = S.AddTextBox( {}, wxT(""), 10);
414  S.AddSpace(5);
415 
416  mGridOnOff = S.Id(GridOnOffID).AddCheckBox(_("&Grids"), wxT("false"));
417  mGridOnOff->SetValue(mDrawGrid);
418  }
419  S.EndMultiColumn();
420  }
421  S.EndHorizontalLay();
422 
423  S.AddSpace(1);
424  }
425  S.EndMultiColumn();
426 
427  // -------------------------------------------------------------------
428  // ROW 5: Spacer
429  // -------------------------------------------------------------------
430 
431  S.AddSpace(5);
432 
433  S.SetBorder(2);
434  S.SetSizerProportion(0);
435  S.StartMultiColumn(9, wxALIGN_CENTER);
436  {
437  // ----------------------------------------------------------------
438  // ROW 6: Algorithm, Size, Export, Replot
439  // ----------------------------------------------------------------
440 
441  S.AddSpace(5);
442 
443  mAlgChoice = S.Id(FreqAlgChoiceID).AddChoice(_("&Algorithm:"), wxT(""), &algChoices);
444  mAlgChoice->SetSelection(mAlg);
445  S.SetSizeHints(wxDefaultCoord, wxDefaultCoord);
446 
447  S.AddSpace(5);
448 
449  mSizeChoice = S.Id(FreqSizeChoiceID).AddChoice(_("&Size:"), wxT(""), &sizeChoices);
450  mSizeChoice->SetSelection(mSize);
451  S.SetSizeHints(wxDefaultCoord, wxDefaultCoord);
452 
453  S.AddSpace(5);
454 
455  mExportButton = S.Id(FreqExportButtonID).AddButton(_("&Export..."));
456 
457  S.AddSpace(5);
458 
459 
460  // ----------------------------------------------------------------
461  // ROW 7: Function, Axix, Grids, Close
462  // ----------------------------------------------------------------
463 
464  S.AddSpace(5);
465 
466  mFuncChoice = S.Id(FreqFuncChoiceID).AddChoice(_("&Function:"), wxT(""), &funcChoices);
467  mFuncChoice->SetSelection(mFunc);
468  S.SetSizeHints(wxDefaultCoord, wxDefaultCoord);
469  mFuncChoice->MoveAfterInTabOrder(mSizeChoice);
470 
471  S.AddSpace(5);
472 
473  mAxisChoice = S.Id(FreqAxisChoiceID).AddChoice(_("&Axis:"), wxT(""), &axisChoices);
474  mAxisChoice->SetSelection(mAxis);
475  S.SetSizeHints(wxDefaultCoord, wxDefaultCoord);
476  mAxisChoice->MoveAfterInTabOrder(mFuncChoice);
477 
478  S.AddSpace(5);
479 
480  mReplotButton = S.Id(ReplotButtonID).AddButton(_("&Replot..."));
481 
482  S.AddSpace(5);
483 
484  //mCloseButton = S.Id(wxID_CANCEL).AddButton(_("&Close"));
485 
486  //S.AddSpace(5);
487  }
488  S.EndMultiColumn();
490 
491  // -------------------------------------------------------------------
492  // ROW 8: Spacer
493  // -------------------------------------------------------------------
494 
495  S.AddSpace(5);
496 
497  mProgress = safenew FreqGauge(this, wxID_ANY); //, wxST_SIZEGRIP);
498  S.AddWindow(mProgress, wxEXPAND);
499 
500  // Log-frequency axis works for spectrum plots only.
502  {
503  mAxis = 0;
504  mAxisChoice->Disable();
505  }
506  mLogAxis = mAxis != 0;
507 
508  mCloseButton = static_cast<wxButton*>(FindWindowById( wxID_CANCEL ));
509  mCloseButton->SetDefault();
510  mCloseButton->SetFocus();
511 
512  Layout();
513  Fit();
514  // Bug 1607:
515  Center();
516 
517  SetMinSize(GetSize());
518  mAlgChoice->SetFocus();
519 
520 #if defined(__WXGTK__)
521  // This should be rechecked with wx3.
522  //
523  // The scrollbar (focus some reason) doesn't allow tabbing past it
524  // because it can't receive focus. So, convince it otherwise.
525  //
526  // Unfortunately, this still doesn't let you adjust the scrollbar
527  // from the keyboard. Near as I can tell, wxWGTK is capturing the
528  // keyboard input, so the GTK widget doesn't see it, preventing
529  // the normal scroll events from being generated.
530  //
531  // I guess the only way round it would be to handle key actions
532  // ourselves, but we'll leave that for a future date.
533 // gtk_widget_set_can_focus(mPanScroller->m_widget, true);
534 #endif
535 }
536 
538 {
539 }
540 
541 void FreqWindow::OnGetURL(wxCommandEvent & WXUNUSED(event))
542 {
543  // Original help page is back on-line (March 2016), but the manual should be more reliable.
544  // http://www.eramp.com/WCAG_2_audio_contrast_tool_help.htm
545  HelpSystem::ShowHelp(this, wxT("Plot Spectrum"));
546 }
547 
548 bool FreqWindow::Show(bool show)
549 {
550  if (!show)
551  {
552  mFreqPlot->SetCursor(*mArrowCursor);
553  }
554 
555  bool shown = IsShown();
556 
557  if (show && !shown)
558  {
560  if(dBRange < 90.)
561  dBRange = 90.;
562  GetAudio();
563  // Don't send an event. We need the recalc right away.
564  // so that mAnalyst is valid when we paint.
565  //SendRecalcEvent();
566  Recalc();
567  }
568 
569  bool res = wxDialogWrapper::Show(show);
570 
571  return res;
572 }
573 
575 {
576  mData.reset();
577  mDataLen = 0;
578 
579  int selcount = 0;
580  bool warning = false;
581  for (auto track : p->GetTracks()->Selected< const WaveTrack >()) {
582  if (selcount==0) {
583  mRate = track->GetRate();
584  auto start = track->TimeToLongSamples(p->mViewInfo.selectedRegion.t0());
585  auto end = track->TimeToLongSamples(p->mViewInfo.selectedRegion.t1());
586  auto dataLen = end - start;
587  if (dataLen > 10485760) {
588  warning = true;
589  mDataLen = 10485760;
590  }
591  else
592  // dataLen is not more than 10 * 2 ^ 20
593  mDataLen = dataLen.as_size_t();
594  mData = Floats{ mDataLen };
595  // Don't allow throw for bad reads
596  track->Get((samplePtr)mData.get(), floatSample, start, mDataLen,
597  fillZero, false);
598  }
599  else {
600  if (track->GetRate() != mRate) {
601  AudacityMessageBox(_("To plot the spectrum, all selected tracks must be the same sample rate."));
602  mData.reset();
603  mDataLen = 0;
604  return;
605  }
606  auto start = track->TimeToLongSamples(p->mViewInfo.selectedRegion.t0());
607  Floats buffer2{ mDataLen };
608  // Again, stop exceptions
609  track->Get((samplePtr)buffer2.get(), floatSample, start, mDataLen,
610  fillZero, false);
611  for (size_t i = 0; i < mDataLen; i++)
612  mData[i] += buffer2[i];
613  }
614  selcount++;
615  }
616 
617  if (selcount == 0)
618  return;
619 
620  if (warning) {
621  wxString msg;
622  msg.Printf(_("Too much audio was selected. Only the first %.1f seconds of audio will be analyzed."),
623  (mDataLen / mRate));
624  AudacityMessageBox(msg);
625  }
626 }
627 
628 void FreqWindow::OnSize(wxSizeEvent & WXUNUSED(event))
629 {
630  Layout();
631 
632  DrawPlot();
633 
634  Refresh(true);
635 }
636 
637 void FreqWindow::DrawBackground(wxMemoryDC & dc)
638 {
639  Layout();
640 
641  mBitmap.reset();
642 
643  mPlotRect = mFreqPlot->GetClientRect();
644 
645  mBitmap = std::make_unique<wxBitmap>(mPlotRect.width, mPlotRect.height,24);
646 
647  dc.SelectObject(*mBitmap);
648 
649  dc.SetBackground(wxBrush(wxColour(254, 254, 254)));// DONT-THEME Mask colour.
650  dc.Clear();
651 
652  dc.SetPen(*wxBLACK_PEN);
653  dc.SetBrush(*wxWHITE_BRUSH);
654  dc.DrawRectangle(mPlotRect);
655 
656  dc.SetFont(mFreqFont);
657 }
658 
660 {
661  if (!mData || mDataLen < mWindowSize || mAnalyst->GetProcessedSize() == 0) {
662  wxMemoryDC memDC;
663 
664  vRuler->ruler.SetLog(false);
665  vRuler->ruler.SetRange(0.0, -dBRange);
666 
667  hRuler->ruler.SetLog(false);
668  hRuler->ruler.SetRange(0, 1);
669 
670  DrawBackground(memDC);
671 
672  if (mDataLen < mWindowSize) {
673  wxString msg = _("Not enough data selected.");
674  wxSize sz = memDC.GetTextExtent(msg);
675  memDC.DrawText(msg,
676  (mPlotRect.GetWidth() - sz.GetWidth()) / 2,
677  (mPlotRect.GetHeight() - sz.GetHeight()) / 2);
678  }
679 
680  memDC.SelectObject(wxNullBitmap);
681 
682  mFreqPlot->Refresh();
683 
684  Refresh();
685 
686  return;
687  }
688 
689  float yRange = mYMax - mYMin;
690  float yTotal = yRange * ((float) mZoomSlider->GetValue() / 100.0f);
691 
692  int sTotal = yTotal * 100;
693  int sRange = yRange * 100;
694  int sPos = mPanScroller->GetThumbPosition() + ((mPanScroller->GetThumbSize() - sTotal) / 2);
695  mPanScroller->SetScrollbar(sPos, sTotal, sRange, sTotal);
696 
697  float yMax = mYMax - ((float)sPos / 100);
698  float yMin = yMax - yTotal;
699 
700  // Set up y axis ruler
701 
703  vRuler->ruler.SetUnits(_("dB"));
705  } else {
706  vRuler->ruler.SetUnits(wxT(""));
708  }
709  int w1, w2, h;
710  vRuler->ruler.GetMaxSize(&w1, &h);
711  vRuler->ruler.SetRange(yMax, yMin); // Note inversion for vertical.
712  vRuler->ruler.GetMaxSize(&w2, &h);
713  if( w1 != w2 ) // Reduces flicker
714  {
715  vRuler->SetMinSize(wxSize(w2,h));
716  Layout();
717  }
718  vRuler->Refresh(false);
719 
720  wxMemoryDC memDC;
721  DrawBackground(memDC);
722 
723  // Get the plot dimensions
724  //
725  // Must be done after setting the vertical ruler above since the
726  // the width could change.
727  wxRect r = mPlotRect;
728 
729  // Set up x axis ruler
730 
731  int width = r.width - 2;
732 
733  float xMin, xMax, xRatio, xStep;
734 
736  xMin = mRate / mWindowSize;
737  xMax = mRate / 2;
738  xRatio = xMax / xMin;
739  if (mLogAxis)
740  {
741  xStep = pow(2.0f, (log(xRatio) / log(2.0f)) / width);
742  hRuler->ruler.SetLog(true);
743  }
744  else
745  {
746  xStep = (xMax - xMin) / width;
747  hRuler->ruler.SetLog(false);
748  }
749  hRuler->ruler.SetUnits(_("Hz"));
750  } else {
751  xMin = 0;
752  xMax = mAnalyst->GetProcessedSize() / mRate;
753  xStep = (xMax - xMin) / width;
754  hRuler->ruler.SetLog(false);
755  hRuler->ruler.SetUnits(_("s"));
756  }
757  hRuler->ruler.SetRange(xMin, xMax-xStep);
758  hRuler->Refresh(false);
759 
760  // Draw the plot
762  memDC.SetPen(wxPen(theTheme.Colour( clrHzPlot ), 1, wxPENSTYLE_SOLID));
763  else
764  memDC.SetPen(wxPen(theTheme.Colour( clrWavelengthPlot), 1, wxPENSTYLE_SOLID));
765 
766  float xPos = xMin;
767 
768  for (int i = 0; i < width; i++) {
769  float y;
770 
771  if (mLogAxis)
772  y = mAnalyst->GetProcessedValue(xPos, xPos * xStep);
773  else
774  y = mAnalyst->GetProcessedValue(xPos, xPos + xStep);
775 
776  float ynorm = (y - yMin) / yTotal;
777 
778  int lineheight = (int)(ynorm * (r.height - 1));
779 
780  if (lineheight > r.height - 2)
781  lineheight = r.height - 2;
782 
783  if (ynorm > 0.0)
784  AColor::Line(memDC, r.x + 1 + i, r.y + r.height - 1 - lineheight,
785  r.x + 1 + i, r.y + r.height - 1);
786 
787  if (mLogAxis)
788  xPos *= xStep;
789  else
790  xPos += xStep;
791  }
792 
793  // Outline the graph
794  memDC.SetPen(*wxBLACK_PEN);
795  memDC.SetBrush(*wxTRANSPARENT_BRUSH);
796  memDC.DrawRectangle(r);
797 
798  if(mDrawGrid)
799  {
800  hRuler->ruler.DrawGrid(memDC, r.height, true, true, 1, 1);
801  vRuler->ruler.DrawGrid(memDC, r.width, true, true, 1, 1);
802  }
803 
804  memDC.SelectObject( wxNullBitmap );
805 
806  mFreqPlot->Refresh();
807 }
808 
809 
810 void FreqWindow::PlotMouseEvent(wxMouseEvent & event)
811 {
812  if (event.Moving() && (event.m_x != mMouseX || event.m_y != mMouseY)) {
813  mMouseX = event.m_x;
814  mMouseY = event.m_y;
815 
816  if (mPlotRect.Contains(mMouseX, mMouseY))
817  mFreqPlot->SetCursor(*mCrossCursor);
818  else
819  mFreqPlot->SetCursor(*mArrowCursor);
820 
821  mFreqPlot->Refresh(false);
822  }
823 }
824 
825 void FreqWindow::OnPanScroller(wxScrollEvent & WXUNUSED(event))
826 {
827  DrawPlot();
828 }
829 
830 void FreqWindow::OnZoomSlider(wxCommandEvent & WXUNUSED(event))
831 {
832  DrawPlot();
833 }
834 
835 void FreqWindow::OnAlgChoice(wxCommandEvent & WXUNUSED(event))
836 {
837  mAlg = SpectrumAnalyst::Algorithm(mAlgChoice->GetSelection());
838 
839  // Log-frequency axis works for spectrum plots only.
841  mAxisChoice->Enable(true);
842  mLogAxis = mAxisChoice->GetSelection() ? true : false;
843  }
844  else {
845  mAxisChoice->Disable();
846  mLogAxis = false;
847  }
848 
849  SendRecalcEvent();
850 }
851 
852 void FreqWindow::OnSizeChoice(wxCommandEvent & WXUNUSED(event))
853 {
854  long windowSize = 0;
855  mSizeChoice->GetStringSelection().ToLong(&windowSize);
856  mWindowSize = windowSize;
857 
858  SendRecalcEvent();
859 }
860 
861 void FreqWindow::OnFuncChoice(wxCommandEvent & WXUNUSED(event))
862 {
863  SendRecalcEvent();
864 }
865 
866 void FreqWindow::OnAxisChoice(wxCommandEvent & WXUNUSED(event))
867 {
868  mLogAxis = mAxisChoice->GetSelection() ? true : false;
869  DrawPlot();
870 }
871 
872 void FreqWindow::PlotPaint(wxPaintEvent & event)
873 {
874  wxPaintDC dc( (wxWindow *) event.GetEventObject() );
875 
876  dc.DrawBitmap( *mBitmap, 0, 0, true );
877  // Fix for Bug 1226 "Plot Spectrum freezes... if insufficient samples selected"
878  if (!mData || mDataLen < mWindowSize)
879  return;
880 
881  dc.SetFont(mFreqFont);
882 
883  wxRect r = mPlotRect;
884 
885  int width = r.width - 2;
886 
887  float xMin, xMax, xRatio, xStep;
888 
890  xMin = mRate / mWindowSize;
891  xMax = mRate / 2;
892  xRatio = xMax / xMin;
893  if (mLogAxis)
894  xStep = pow(2.0f, (log(xRatio) / log(2.0f)) / width);
895  else
896  xStep = (xMax - xMin) / width;
897  } else {
898  xMin = 0;
899  xMax = mAnalyst->GetProcessedSize() / mRate;
900  xStep = (xMax - xMin) / width;
901  }
902 
903  float xPos = xMin;
904 
905  // Find the peak nearest the cursor and plot it
906  if ( r.Contains(mMouseX, mMouseY) & (mMouseX!=0) & (mMouseX!=r.width-1) ) {
907  if (mLogAxis)
908  xPos = xMin * pow(xStep, mMouseX - (r.x + 1));
909  else
910  xPos = xMin + xStep * (mMouseX - (r.x + 1));
911 
912  float bestValue = 0;
913  float bestpeak = mAnalyst->FindPeak(xPos, &bestValue);
914 
915  int px;
916  if (mLogAxis)
917  px = (int)(log(bestpeak / xMin) / log(xStep));
918  else
919  px = (int)((bestpeak - xMin) * width / (xMax - xMin));
920 
921  dc.SetPen(wxPen(wxColour(160,160,160), 1, wxPENSTYLE_SOLID));
922  AColor::Line(dc, r.x + 1 + px, r.y, r.x + 1 + px, r.y + r.height);
923 
924  // print out info about the cursor location
925 
926  float value;
927 
928  if (mLogAxis) {
929  xPos = xMin * pow(xStep, mMouseX - (r.x + 1));
930  value = mAnalyst->GetProcessedValue(xPos, xPos * xStep);
931  } else {
932  xPos = xMin + xStep * (mMouseX - (r.x + 1));
933  value = mAnalyst->GetProcessedValue(xPos, xPos + xStep);
934  }
935 
936  wxString cursor;
937  wxString peak;
938  wxString xpitch;
939  wxString peakpitch;
940  const wxChar *xp;
941  const wxChar *pp;
942 
944  xpitch = PitchName_Absolute(FreqToMIDInote(xPos));
945  peakpitch = PitchName_Absolute(FreqToMIDInote(bestpeak));
946  xp = xpitch;
947  pp = peakpitch;
948  /* i18n-hint: The %d's are replaced by numbers, the %s by musical notes, e.g. A#*/
949  cursor.Printf(_("%d Hz (%s) = %d dB"), (int)(xPos + 0.5), xp, (int)(value + 0.5));
950  peak.Printf(_("%d Hz (%s) = %.1f dB"), (int)(bestpeak + 0.5), pp, bestValue);
951  } else if (xPos > 0.0 && bestpeak > 0.0) {
952  xpitch = PitchName_Absolute(FreqToMIDInote(1.0 / xPos));
953  peakpitch = PitchName_Absolute(FreqToMIDInote(1.0 / bestpeak));
954  xp = xpitch;
955  pp = peakpitch;
956  /* i18n-hint: The %d's are replaced by numbers, the %s by musical notes, e.g. A#
957  * the %.4f are numbers, and 'sec' should be an abbreviation for seconds */
958  cursor.Printf(_("%.4f sec (%d Hz) (%s) = %f"),
959  xPos, (int)(1.0 / xPos + 0.5), xp, value);
960  peak.Printf(_("%.4f sec (%d Hz) (%s) = %.3f"),
961  bestpeak, (int)(1.0 / bestpeak + 0.5), pp, bestValue);
962  }
963  mCursorText->SetValue(cursor);
964  mPeakText->SetValue(peak);
965  }
966  else {
967  mCursorText->SetValue(wxT(""));
968  mPeakText->SetValue(wxT(""));
969  }
970 
971 
972  // Outline the graph
973  dc.SetPen(*wxBLACK_PEN);
974  dc.SetBrush(*wxTRANSPARENT_BRUSH);
975  dc.DrawRectangle(r);
976 }
977 
978 void FreqWindow::OnCloseWindow(wxCloseEvent & WXUNUSED(event))
979 {
980  Show(false);
981 }
982 
983 void FreqWindow::OnCloseButton(wxCommandEvent & WXUNUSED(event))
984 {
985  gPrefs->Write(wxT("/FreqWindow/DrawGrid"), mDrawGrid);
986  gPrefs->Write(wxT("/FreqWindow/SizeChoice"), mSizeChoice->GetSelection());
987  gPrefs->Write(wxT("/FreqWindow/AlgChoice"), mAlgChoice->GetSelection());
988  gPrefs->Write(wxT("/FreqWindow/FuncChoice"), mFuncChoice->GetSelection());
989  gPrefs->Write(wxT("/FreqWindow/AxisChoice"), mAxisChoice->GetSelection());
990  gPrefs->Flush();
991  Show(false);
992 }
993 
995 {
996  wxCommandEvent e(EVT_FREQWINDOW_RECALC, wxID_ANY);
997  GetEventHandler()->AddPendingEvent(e);
998 }
999 
1001 {
1002  if (!mData || mDataLen < mWindowSize) {
1003  DrawPlot();
1004  return;
1005  }
1006 
1008  SpectrumAnalyst::Algorithm(mAlgChoice->GetSelection());
1009  int windowFunc = mFuncChoice->GetSelection();
1010 
1011  wxWindow *hadFocus = FindFocus();
1012  // In wxMac, the skipped window MUST be a top level window. I'd originally made it
1013  // just the mProgress window with the idea of preventing user interaction with the
1014  // controls while the plot was being recalculated. This doesn't appear to be necessary
1015  // so just use the the top level window instead.
1016  {
1017  Maybe<wxWindowDisabler> blocker;
1018  if (IsShown())
1019  blocker.create(this);
1020  wxYieldIfNeeded();
1021 
1022  mAnalyst->Calculate(alg, windowFunc, mWindowSize, mRate,
1023  mData.get(), mDataLen,
1024  &mYMin, &mYMax, mProgress);
1025  }
1026  if (hadFocus) {
1027  hadFocus->SetFocus();
1028  }
1029 
1030  if (alg == SpectrumAnalyst::Spectrum) {
1031  if(mYMin < -dBRange)
1032  mYMin = -dBRange;
1033  if(mYMax <= -dBRange)
1034  mYMax = -dBRange + 10.; // it's all out of range, but show a scale.
1035  else
1036  mYMax += .5;
1037  }
1038 
1039  // Prime the scrollbar
1040  mPanScroller->SetScrollbar(0, (mYMax - mYMin) * 100, (mYMax - mYMin) * 100, 1);
1041 
1042  DrawPlot();
1043 }
1044 
1045 void FreqWindow::OnExport(wxCommandEvent & WXUNUSED(event))
1046 {
1047  wxString fName = _("spectrum.txt");
1048 
1050  _("Export Spectral Data As:"),
1051  wxEmptyString, fName, wxT("txt"), wxT("*.txt"), wxFD_SAVE | wxRESIZE_BORDER, this);
1052 
1053  if (fName == wxT(""))
1054  return;
1055 
1056  wxTextFile f(fName);
1057 #ifdef __WXMAC__
1058  wxFile{}.Create(fName);
1059 #else
1060  f.Create();
1061 #endif
1062  f.Open();
1063  if (!f.IsOpened()) {
1064  AudacityMessageBox( wxString::Format(
1065  _("Couldn't write to file: %s"), fName ) );
1066  return;
1067  }
1068 
1069  const int processedSize = mAnalyst->GetProcessedSize();
1070  const float *const processed = mAnalyst->GetProcessed();
1071  if (mAlgChoice->GetSelection() == 0) {
1072  f.AddLine(_("Frequency (Hz)\tLevel (dB)"));
1073  for (int i = 1; i < processedSize; i++)
1074  f.AddLine(wxString::
1075  Format(wxT("%f\t%f"), i * mRate / mWindowSize,
1076  processed[i]));
1077  } else {
1078  f.AddLine(_("Lag (seconds)\tFrequency (Hz)\tLevel"));
1079  for (int i = 1; i < processedSize; i++)
1080  f.AddLine(wxString::Format(wxT("%f\t%f\t%f"),
1081  i / mRate, mRate / i, processed[i]));
1082  }
1083 
1084 #ifdef __WXMAC__
1085  f.Write(wxTextFileType_Mac);
1086 #else
1087  f.Write();
1088 #endif
1089  f.Close();
1090 }
1091 
1092 void FreqWindow::OnReplot(wxCommandEvent & WXUNUSED(event))
1093 {
1095  if(dBRange < 90.)
1096  dBRange = 90.;
1097  GetAudio();
1098  SendRecalcEvent();
1099 }
1100 
1101 void FreqWindow::OnGridOnOff(wxCommandEvent & WXUNUSED(event))
1102 {
1103  mDrawGrid = mGridOnOff->IsChecked();
1104 
1105  DrawPlot();
1106 }
1107 
1108 void FreqWindow::OnRecalc(wxCommandEvent & WXUNUSED(event))
1109 {
1110  Recalc();
1111 }
1112 
1113 BEGIN_EVENT_TABLE(FreqPlot, wxWindow)
1114  EVT_ERASE_BACKGROUND(FreqPlot::OnErase)
1115  EVT_PAINT(FreqPlot::OnPaint)
1116  EVT_MOUSE_EVENTS(FreqPlot::OnMouseEvent)
1118 
1119 FreqPlot::FreqPlot(wxWindow *parent, wxWindowID winid)
1120 : wxWindow(parent, winid)
1121 {
1122  freqWindow = (FreqWindow *) parent;
1123 }
1124 
1126 {
1127  return false;
1128 }
1129 
1130 void FreqPlot::OnErase(wxEraseEvent & WXUNUSED(event))
1131 {
1132  // Ignore it to prevent flashing
1133 }
1134 
1135 void FreqPlot::OnPaint(wxPaintEvent & evt)
1136 {
1137  freqWindow->PlotPaint(evt);
1138 }
1139 
1140 void FreqPlot::OnMouseEvent(wxMouseEvent & event)
1141 {
1142  freqWindow->PlotMouseEvent(event);
1143 }
1144 
1145 FreqGauge::FreqGauge(wxWindow * parent, wxWindowID winid)
1146 : wxStatusBar(parent, winid, wxST_SIZEGRIP)
1147 {
1148  mRange = 0;
1149 }
1150 
1151 void FreqGauge::SetRange(int range, int bar, int gap)
1152 {
1153  mRange = range;
1154  mBar = bar;
1155  mGap = gap;
1156 
1157  GetFieldRect(0, mRect);
1158  mRect.Inflate(-1);
1159 
1160  mInterval = mRange / (mRect.width / (mBar + mGap));
1161  mRect.width = mBar;
1162  mMargin = mRect.x;
1163  mLast = -1;
1164 
1165  Update();
1166 }
1167 
1168 void FreqGauge::SetValue(int value)
1169 {
1170  mCur = value / mInterval;
1171 
1172  if (mCur != mLast)
1173  {
1174  wxClientDC dc(this);
1175  dc.SetPen(*wxTRANSPARENT_PEN);
1176  dc.SetBrush(wxColour(100, 100, 220));
1177 
1178  while (mLast < mCur)
1179  {
1180  mLast++;
1181  mRect.x = mMargin + mLast * (mBar + mGap);
1182  dc.DrawRectangle(mRect);
1183  }
1184  Update();
1185  }
1186 }
1187 
1189 {
1190  mRange = 0;
1191  Refresh(true);
1192 }
1193 
1194 bool SpectrumAnalyst::Calculate(Algorithm alg, int windowFunc,
1195  size_t windowSize, double rate,
1196  const float *data, size_t dataLen,
1197  float *pYMin, float *pYMax,
1198  FreqGauge *progress)
1199 {
1200  // Wipe old data
1201  mProcessed.resize(0);
1202  mRate = 0.0;
1203  mWindowSize = 0;
1204 
1205  // Validate inputs
1206  int f = NumWindowFuncs();
1207 
1208  if (!(windowSize >= 32 && windowSize <= 65536 &&
1209  alg >= SpectrumAnalyst::Spectrum &&
1211  windowFunc >= 0 && windowFunc < f)) {
1212  return false;
1213  }
1214 
1215  if (dataLen < windowSize) {
1216  return false;
1217  }
1218 
1219  // Now repopulate
1220  mRate = rate;
1221  mWindowSize = windowSize;
1222  mAlg = alg;
1223 
1224  auto half = mWindowSize / 2;
1225  mProcessed.resize(mWindowSize);
1226 
1227  Floats in{ mWindowSize };
1228  Floats out{ mWindowSize };
1229  Floats out2{ mWindowSize };
1230  Floats win{ mWindowSize };
1231 
1232  for (size_t i = 0; i < mWindowSize; i++) {
1233  mProcessed[i] = 0.0f;
1234  win[i] = 1.0f;
1235  }
1236 
1237  WindowFunc(windowFunc, mWindowSize, win.get());
1238 
1239  // Scale window such that an amplitude of 1.0 in the time domain
1240  // shows an amplitude of 0dB in the frequency domain
1241  double wss = 0;
1242  for (size_t i = 0; i<mWindowSize; i++)
1243  wss += win[i];
1244  if(wss > 0)
1245  wss = 4.0 / (wss*wss);
1246  else
1247  wss = 1.0;
1248 
1249  if (progress) {
1250  progress->SetRange(dataLen);
1251  }
1252 
1253  size_t start = 0;
1254  int windows = 0;
1255  while (start + mWindowSize <= dataLen) {
1256  for (size_t i = 0; i < mWindowSize; i++)
1257  in[i] = win[i] * data[start + i];
1258 
1259  switch (alg) {
1260  case Spectrum:
1261  PowerSpectrum(mWindowSize, in.get(), out.get());
1262 
1263  for (size_t i = 0; i < half; i++)
1264  mProcessed[i] += out[i];
1265  break;
1266 
1267  case Autocorrelation:
1270 
1271  // Take FFT
1272  RealFFT(mWindowSize, in.get(), out.get(), out2.get());
1273  // Compute power
1274  for (size_t i = 0; i < mWindowSize; i++)
1275  in[i] = (out[i] * out[i]) + (out2[i] * out2[i]);
1276 
1277  if (alg == Autocorrelation) {
1278  for (size_t i = 0; i < mWindowSize; i++)
1279  in[i] = sqrt(in[i]);
1280  }
1281  if (alg == CubeRootAutocorrelation ||
1282  alg == EnhancedAutocorrelation) {
1283  // Tolonen and Karjalainen recommend taking the cube root
1284  // of the power, instead of the square root
1285 
1286  for (size_t i = 0; i < mWindowSize; i++)
1287  in[i] = pow(in[i], 1.0f / 3.0f);
1288  }
1289  // Take FFT
1290  RealFFT(mWindowSize, in.get(), out.get(), out2.get());
1291 
1292  // Take real part of result
1293  for (size_t i = 0; i < half; i++)
1294  mProcessed[i] += out[i];
1295  break;
1296 
1297  case Cepstrum:
1298  RealFFT(mWindowSize, in.get(), out.get(), out2.get());
1299 
1300  // Compute log power
1301  // Set a sane lower limit assuming maximum time amplitude of 1.0
1302  {
1303  float power;
1304  float minpower = 1e-20*mWindowSize*mWindowSize;
1305  for (size_t i = 0; i < mWindowSize; i++)
1306  {
1307  power = (out[i] * out[i]) + (out2[i] * out2[i]);
1308  if(power < minpower)
1309  in[i] = log(minpower);
1310  else
1311  in[i] = log(power);
1312  }
1313  // Take IFFT
1314  InverseRealFFT(mWindowSize, in.get(), NULL, out.get());
1315 
1316  // Take real part of result
1317  for (size_t i = 0; i < half; i++)
1318  mProcessed[i] += out[i];
1319  }
1320 
1321  break;
1322 
1323  default:
1324  wxASSERT(false);
1325  break;
1326  } //switch
1327 
1328  // Update the progress bar
1329  if (progress) {
1330  progress->SetValue(start);
1331  }
1332 
1333  start += half;
1334  windows++;
1335  }
1336 
1337  if (progress) {
1338  // Reset for next time
1339  progress->Reset();
1340  }
1341 
1342  float mYMin = 1000000, mYMax = -1000000;
1343  double scale;
1344  switch (alg) {
1345  case Spectrum:
1346  // Convert to decibels
1347  mYMin = 1000000.;
1348  mYMax = -1000000.;
1349  scale = wss / (double)windows;
1350  for (size_t i = 0; i < half; i++)
1351  {
1352  mProcessed[i] = 10 * log10(mProcessed[i] * scale);
1353  if(mProcessed[i] > mYMax)
1354  mYMax = mProcessed[i];
1355  else if(mProcessed[i] < mYMin)
1356  mYMin = mProcessed[i];
1357  }
1358  break;
1359 
1360  case Autocorrelation:
1362  for (size_t i = 0; i < half; i++)
1363  mProcessed[i] = mProcessed[i] / windows;
1364 
1365  // Find min/max
1366  mYMin = mProcessed[0];
1367  mYMax = mProcessed[0];
1368  for (size_t i = 1; i < half; i++)
1369  if (mProcessed[i] > mYMax)
1370  mYMax = mProcessed[i];
1371  else if (mProcessed[i] < mYMin)
1372  mYMin = mProcessed[i];
1373  break;
1374 
1376  for (size_t i = 0; i < half; i++)
1377  mProcessed[i] = mProcessed[i] / windows;
1378 
1379  // Peak Pruning as described by Tolonen and Karjalainen, 2000
1380 
1381  // Clip at zero, copy to temp array
1382  for (size_t i = 0; i < half; i++) {
1383  if (mProcessed[i] < 0.0)
1384  mProcessed[i] = float(0.0);
1385  out[i] = mProcessed[i];
1386  }
1387 
1388  // Subtract a time-doubled signal (linearly interp.) from the original
1389  // (clipped) signal
1390  for (size_t i = 0; i < half; i++)
1391  if ((i % 2) == 0)
1392  mProcessed[i] -= out[i / 2];
1393  else
1394  mProcessed[i] -= ((out[i / 2] + out[i / 2 + 1]) / 2);
1395 
1396  // Clip at zero again
1397  for (size_t i = 0; i < half; i++)
1398  if (mProcessed[i] < 0.0)
1399  mProcessed[i] = float(0.0);
1400 
1401  // Find NEW min/max
1402  mYMin = mProcessed[0];
1403  mYMax = mProcessed[0];
1404  for (size_t i = 1; i < half; i++)
1405  if (mProcessed[i] > mYMax)
1406  mYMax = mProcessed[i];
1407  else if (mProcessed[i] < mYMin)
1408  mYMin = mProcessed[i];
1409  break;
1410 
1411  case Cepstrum:
1412  for (size_t i = 0; i < half; i++)
1413  mProcessed[i] = mProcessed[i] / windows;
1414 
1415  // Find min/max, ignoring first and last few values
1416  {
1417  size_t ignore = 4;
1418  mYMin = mProcessed[ignore];
1419  mYMax = mProcessed[ignore];
1420  for (size_t i = ignore + 1; i + ignore < half; i++)
1421  if (mProcessed[i] > mYMax)
1422  mYMax = mProcessed[i];
1423  else if (mProcessed[i] < mYMin)
1424  mYMin = mProcessed[i];
1425  }
1426  break;
1427 
1428  default:
1429  wxASSERT(false);
1430  break;
1431  }
1432 
1433  if (pYMin)
1434  *pYMin = mYMin;
1435  if (pYMax)
1436  *pYMax = mYMax;
1437 
1438  return true;
1439 }
1440 
1441 const float *SpectrumAnalyst::GetProcessed() const
1442 {
1443  return &mProcessed[0];
1444 }
1445 
1447 {
1448  return mProcessed.size() / 2;
1449 }
1450 
1451 float SpectrumAnalyst::GetProcessedValue(float freq0, float freq1) const
1452 {
1453  float bin0, bin1, binwidth;
1454 
1455  if (mAlg == Spectrum) {
1456  bin0 = freq0 * mWindowSize / mRate;
1457  bin1 = freq1 * mWindowSize / mRate;
1458  } else {
1459  bin0 = freq0 * mRate;
1460  bin1 = freq1 * mRate;
1461  }
1462  binwidth = bin1 - bin0;
1463 
1464  float value = float(0.0);
1465 
1466  if (binwidth < 1.0) {
1467  float binmid = (bin0 + bin1) / 2.0;
1468  int ibin = (int)(binmid) - 1;
1469  if (ibin < 1)
1470  ibin = 1;
1471  if (ibin >= GetProcessedSize() - 3)
1472  ibin = std::max(0, GetProcessedSize() - 4);
1473 
1474  value = CubicInterpolate(mProcessed[ibin],
1475  mProcessed[ibin + 1],
1476  mProcessed[ibin + 2],
1477  mProcessed[ibin + 3], binmid - ibin);
1478 
1479  } else {
1480  if (bin0 < 0)
1481  bin0 = 0;
1482  if (bin1 >= GetProcessedSize())
1483  bin1 = GetProcessedSize() - 1;
1484 
1485  if ((int)(bin1) > (int)(bin0))
1486  value += mProcessed[(int)(bin0)] * ((int)(bin0) + 1 - bin0);
1487  bin0 = 1 + (int)(bin0);
1488  while (bin0 < (int)(bin1)) {
1489  value += mProcessed[(int)(bin0)];
1490  bin0 += 1.0;
1491  }
1492  value += mProcessed[(int)(bin1)] * (bin1 - (int)(bin1));
1493 
1494  value /= binwidth;
1495  }
1496 
1497  return value;
1498 }
1499 
1500 float SpectrumAnalyst::FindPeak(float xPos, float *pY) const
1501 {
1502  float bestpeak = 0.0f;
1503  float bestValue = 0.0;
1504  if (GetProcessedSize() > 1) {
1505  bool up = (mProcessed[1] > mProcessed[0]);
1506  float bestdist = 1000000;
1507  for (int bin = 3; bin < GetProcessedSize() - 1; bin++) {
1508  bool nowUp = mProcessed[bin] > mProcessed[bin - 1];
1509  if (!nowUp && up) {
1510  // Local maximum. Find actual value by cubic interpolation
1511  int leftbin = bin - 2;
1512  /*
1513  if (leftbin < 1)
1514  leftbin = 1;
1515  */
1516  float valueAtMax = 0.0;
1517  float max = leftbin + CubicMaximize(mProcessed[leftbin],
1518  mProcessed[leftbin + 1],
1519  mProcessed[leftbin + 2],
1520  mProcessed[leftbin + 3],
1521  &valueAtMax);
1522 
1523  float thispeak;
1524  if (mAlg == Spectrum)
1525  thispeak = max * mRate / mWindowSize;
1526  else
1527  thispeak = max / mRate;
1528 
1529  if (fabs(thispeak - xPos) < bestdist) {
1530  bestpeak = thispeak;
1531  bestdist = fabs(thispeak - xPos);
1532  bestValue = valueAtMax;
1533  // Should this test come after the enclosing if?
1534  if (thispeak > xPos)
1535  break;
1536  }
1537  }
1538  up = nowUp;
1539  }
1540  }
1541 
1542  if (pY)
1543  *pY = bestValue;
1544  return bestpeak;
1545 }
1546 
1547 // If f(0)=y0, f(1)=y1, f(2)=y2, and f(3)=y3, this function finds
1548 // the degree-three polynomial which best fits these points and
1549 // returns the value of this polynomial at a value x. Usually
1550 // 0 < x < 3
1551 
1552 float SpectrumAnalyst::CubicInterpolate(float y0, float y1, float y2, float y3, float x) const
1553 {
1554  float a, b, c, d;
1555 
1556  a = y0 / -6.0 + y1 / 2.0 - y2 / 2.0 + y3 / 6.0;
1557  b = y0 - 5.0 * y1 / 2.0 + 2.0 * y2 - y3 / 2.0;
1558  c = -11.0 * y0 / 6.0 + 3.0 * y1 - 3.0 * y2 / 2.0 + y3 / 3.0;
1559  d = y0;
1560 
1561  float xx = x * x;
1562  float xxx = xx * x;
1563 
1564  return (a * xxx + b * xx + c * x + d);
1565 }
1566 
1567 float SpectrumAnalyst::CubicMaximize(float y0, float y1, float y2, float y3, float * max) const
1568 {
1569  // Find coefficients of cubic
1570 
1571  float a, b, c, d;
1572 
1573  a = y0 / -6.0 + y1 / 2.0 - y2 / 2.0 + y3 / 6.0;
1574  b = y0 - 5.0 * y1 / 2.0 + 2.0 * y2 - y3 / 2.0;
1575  c = -11.0 * y0 / 6.0 + 3.0 * y1 - 3.0 * y2 / 2.0 + y3 / 3.0;
1576  d = y0;
1577 
1578  // Take derivative
1579 
1580  float da, db, dc;
1581 
1582  da = 3 * a;
1583  db = 2 * b;
1584  dc = c;
1585 
1586  // Find zeroes of derivative using quadratic equation
1587 
1588  float discriminant = db * db - 4 * da * dc;
1589  if (discriminant < 0.0)
1590  return float(-1.0); // error
1591 
1592  float x1 = (-db + sqrt(discriminant)) / (2 * da);
1593  float x2 = (-db - sqrt(discriminant)) / (2 * da);
1594 
1595  // The one which corresponds to a local _maximum_ in the
1596  // cubic is the one we want - the one with a negative
1597  // second derivative
1598 
1599  float dda = 2 * da;
1600  float ddb = db;
1601 
1602  if (dda * x1 + ddb < 0)
1603  {
1604  *max = a*x1*x1*x1+b*x1*x1+c*x1+d;
1605  return x1;
1606  }
1607  else
1608  {
1609  *max = a*x2*x2*x2+b*x2*x2+c*x2+d;
1610  return x2;
1611  }
1612 }
const float * GetProcessed() const
wxCheckBox * mGridOnOff
Definition: FreqWindow.h:192
float mYMin
Definition: FreqWindow.h:209
void OnPaint(wxPaintEvent &event)
void SetLog(bool log)
Definition: Ruler.cpp:155
void OnAlgChoice(wxCommandEvent &event)
Definition: FreqWindow.cpp:835
void SetSizeHints(int minX, int minY)
Used to modify an already placed Window.
Definition: ShuttleGui.cpp:194
AudacityPrefs * gPrefs
Definition: Prefs.cpp:73
wxButton * mExportButton
Definition: FreqWindow.h:190
double mRate
Definition: FreqWindow.h:203
AUDACITY_DLL_API Theme theTheme
Definition: Theme.cpp:209
wxRect mPlotRect
Definition: FreqWindow.h:182
void OnErase(wxEraseEvent &event)
double t0() const
void OnAxisChoice(wxCommandEvent &event)
Definition: FreqWindow.cpp:866
float FindPeak(float xPos, float *pY) const
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI...
Definition: ShuttleGui.h:409
void OnRecalc(wxCommandEvent &event)
Abstract base class used in importing a file.
Definition: Import.h:32
wxButton * mCloseButton
Definition: FreqWindow.h:189
SelectedRegion selectedRegion
Definition: ViewInfo.h:162
#define ENV_DB_KEY
Definition: GUISettings.h:15
int mRange
Definition: FreqWindow.h:99
wxWindow * AddWindow(wxWindow *pWindow, int Flags=wxALIGN_CENTRE|wxALL)
Definition: ShuttleGui.cpp:288
void OnGridOnOff(wxCommandEvent &event)
float scale(mad_fixed_t sample)
Definition: ImportMP3.cpp:176
std::unique_ptr< SpectrumAnalyst > mAnalyst
Definition: FreqWindow.h:218
void OnFuncChoice(wxCommandEvent &event)
Definition: FreqWindow.cpp:861
void OnSizeChoice(wxCommandEvent &event)
Definition: FreqWindow.cpp:852
int mMargin
Definition: FreqWindow.h:105
int mInterval
Definition: FreqWindow.h:102
Options & LabelEdges(bool l)
Definition: Ruler.h:266
void GetMaxSize(wxCoord *width, wxCoord *height)
Definition: Ruler.cpp:1511
void EndMultiColumn()
bool mDrawGrid
Definition: FreqWindow.h:163
wxRect mRect
Definition: FreqWindow.h:98
std::unique_ptr< wxCursor > mArrowCursor
Definition: FreqWindow.h:186
void OnSize(wxSizeEvent &event)
Definition: FreqWindow.cpp:628
FreqGauge(wxWindow *parent, wxWindowID winid)
int AudacityMessageBox(const wxString &message, const wxString &caption=AudacityMessageBoxCaptionStr(), long style=wxOK|wxCENTRE, wxWindow *parent=NULL, int x=wxDefaultCoord, int y=wxDefaultCoord)
Definition: ErrorDialog.h:92
Like a smart pointer, allows for object to not exist (nullptr)
Definition: MemoryX.h:209
wxChoice * mAlgChoice
Definition: FreqWindow.h:193
Works with FreqWindow to dsplay a spectrum plot of the waveform. This class actually does the graph d...
Definition: FreqWindow.h:108
std::unique_ptr< wxBitmap > mBitmap
Definition: FreqWindow.h:213
double t1() const
RulerPanel class allows you to work with a Ruler like any other wxWindow.
Definition: Ruler.h:244
Floats mData
Definition: FreqWindow.h:205
void PowerSpectrum(size_t NumSamples, const float *In, float *Out)
Definition: FFT.cpp:305
void SetSizerProportion(int iProp)
Definition: ShuttleGui.h:289
auto Selected() -> TrackIterRange< TrackType >
Definition: Track.h:1263
#define safenew
Definition: Audacity.h:230
size_t mWindowSize
Definition: FreqWindow.h:84
RulerPanel * hRuler
Definition: FreqWindow.h:178
double FreqToMIDInote(const double freq)
Definition: PitchName.cpp:28
const wxChar * WindowFuncName(int whichFunction)
Definition: FFT.cpp:335
void Reset()
void OnCloseButton(wxCommandEvent &event)
Definition: FreqWindow.cpp:983
void EndHorizontalLay()
void Recalc()
FreqWindow * freqWindow
Definition: FreqWindow.h:122
void AddPrompt(const wxString &Prompt)
Right aligned text string.
Definition: ShuttleGui.cpp:239
An alternative to using wxWindowAccessible, which in wxWidgets 3.1.1 contained GetParent() which was ...
#define ENV_DB_RANGE
Definition: GUISettings.h:16
bool mLogAxis
Definition: FreqWindow.h:208
int GetProcessedSize() const
void EndVerticalLay()
wxTextCtrl * mCursorText
Definition: FreqWindow.h:199
void SetValue(int value)
Options & Flip(bool f)
Definition: Ruler.h:263
void SetRange(int range, int bar=12, int gap=3)
void InverseRealFFT(size_t NumSamples, const float *RealIn, const float *ImagIn, float *RealOut)
Definition: FFT.cpp:269
wxTextCtrl * AddTextBox(const wxString &Caption, const wxString &Value, const int nChars)
Definition: ShuttleGui.cpp:548
float CubicMaximize(float y0, float y1, float y2, float y3, float *max) const
wxButton * mReplotButton
Definition: FreqWindow.h:191
wxCheckBox * AddCheckBox(const wxString &Prompt, const wxString &Selected)
Definition: ShuttleGui.cpp:298
size_t mWindowSize
Definition: FreqWindow.h:206
wxScrollBar * mPanScroller
Definition: FreqWindow.h:197
bool Calculate(Algorithm alg, int windowFunc, size_t windowSize, double rate, const float *data, size_t dataLen, float *pYMin=NULL, float *pYMax=NULL, FreqGauge *progress=NULL)
void OnMouseEvent(wxMouseEvent &event)
void DrawBackground(wxMemoryDC &dc)
Definition: FreqWindow.cpp:637
void OnReplot(wxCommandEvent &event)
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)
void WindowFunc(int whichFunction, size_t NumSamples, float *in)
Definition: FFT.cpp:507
static const int gap
Definition: Meter.cpp:176
void StartMultiColumn(int nCols, int PositionFlags=wxALIGN_LEFT)
void OnCloseWindow(wxCloseEvent &event)
Definition: FreqWindow.cpp:978
wxChoice * AddChoice(const wxString &Prompt, const wxString &Selected, const wxArrayString *pChoices)
Definition: ShuttleGui.cpp:379
char * samplePtr
Definition: Types.h:203
A Track that contains audio waveform data.
Definition: WaveTrack.h:60
ShuttleGui & Id(int id)
float mYMax
Definition: FreqWindow.h:210
void SetStyle(int Style)
Definition: ShuttleGui.h:287
void GetAudio()
Definition: FreqWindow.cpp:574
FreqWindow(wxWindow *parent, wxWindowID id, const wxString &title, const wxPoint &pos)
Definition: FreqWindow.cpp:196
Options & Log(bool l)
Definition: Ruler.h:260
static const int fontSize
Definition: FreqWindow.h:174
std::vector< float > mProcessed
Definition: FreqWindow.h:85
DEFINE_EVENT_TYPE(EVT_FREQWINDOW_RECALC)
ViewInfo mViewInfo
Definition: Project.h:555
void SetUnits(const wxString &units)
Definition: Ruler.cpp:166
static void ShowHelp(wxWindow *parent, const wxString &localFileName, const wxString &remoteURL, bool bModal=false, bool alwaysDefaultBrowser=false)
Definition: HelpSystem.cpp:194
FreqPlot * mFreqPlot
Definition: FreqWindow.h:179
wxString PitchName_Absolute(const double dMIDInote, const PitchNameChoice choice)
Definition: PitchName.cpp:153
SpectrumAnalyst::Algorithm mAlg
Definition: FreqWindow.h:165
static void Line(wxDC &dc, wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2)
Definition: AColor.cpp:122
int NumWindowFuncs()
Definition: FFT.cpp:330
EVT_BUTTON(wxID_NO, DependencyDialog::OnNo) EVT_BUTTON(wxID_YES
void PlotMouseEvent(wxMouseEvent &event)
Definition: FreqWindow.cpp:810
void OnPanScroller(wxScrollEvent &event)
Definition: FreqWindow.cpp:825
void SendRecalcEvent()
Definition: FreqWindow.cpp:994
virtual ~FreqWindow()
Definition: FreqWindow.cpp:537
Used for finding the peaks, for snapping to peaks.
Definition: FreqWindow.h:46
wxSlider * mZoomSlider
Definition: FreqWindow.h:198
bool AcceptsFocus() const
static wxString SelectFile(Operation op, const wxString &message, const wxString &default_path, const wxString &default_filename, const wxString &default_extension, const wxString &wildcard, int flags, wxWindow *parent)
Definition: FileNames.cpp:425
float GetProcessedValue(float freq0, float freq1) const
_("Move Track &Down")+wxT("\t")+(GetActiveProject() -> GetCommandManager() ->GetKeyFromName(wxT("TrackMoveDown")).Raw()), OnMoveTrack) POPUP_MENU_ITEM(OnMoveTopID, _("Move Track to &Top")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveTop")).Raw()), OnMoveTrack) POPUP_MENU_ITEM(OnMoveBottomID, _("Move Track to &Bottom")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveBottom")).Raw()), OnMoveTrack)#define SET_TRACK_NAME_PLUGIN_SYMBOLclass SetTrackNameCommand:public AudacityCommand
wxFont mFreqFont
Definition: FreqWindow.h:184
void DrawGrid(wxDC &dc, int length, bool minor=true, bool major=true, int xOffset=0, int yOffset=0)
Definition: Ruler.cpp:1427
Options & TickColour(const wxColour c)
Definition: Ruler.h:272
void SetFormat(RulerFormat format)
Definition: Ruler.cpp:144
void SetRange(double min, double max)
Definition: Ruler.cpp:192
AudacityProject * p
Definition: FreqWindow.h:169
wxChoice * mAxisChoice
Definition: FreqWindow.h:196
AUDACITY_DLL_API AudacityProject * GetActiveProject()
Definition: Project.cpp:311
wxChoice * mSizeChoice
Definition: FreqWindow.h:194
bool Show(bool show=true) override
Definition: FreqWindow.cpp:548
std::pair< double, double > Range
Definition: Ruler.h:248
Displays a spectrum plot of the waveform. Has options for selecting parameters of the plot...
Definition: FreqWindow.h:127
void PlotPaint(wxPaintEvent &event)
Definition: FreqWindow.cpp:872
ShuttleGui & Prop(int iProp)
Definition: ShuttleGui.h:418
wxTextCtrl * mPeakText
Definition: FreqWindow.h:200
wxColour & Colour(int iIndex)
Definition: Theme.cpp:1225
void AddStandardButtons(long buttons=eOkButton|eCancelButton, wxButton *extra=NULL)
FreqGauge * mProgress
Definition: FreqWindow.h:180
size_t mDataLen
Definition: FreqWindow.h:204
#define FREQ_WINDOW_HEIGHT
Definition: FreqWindow.cpp:112
END_EVENT_TABLE()
wxSizerItem * AddSpace(int width, int height)
TrackList * GetTracks()
Definition: Project.h:209
void OnGetURL(wxCommandEvent &event)
Definition: FreqWindow.cpp:541
static void OnSize(wxSizeEvent &evt)
Definition: VSTEffect.cpp:2765
void OnZoomSlider(wxCommandEvent &event)
Definition: FreqWindow.cpp:830
void SetBorder(int Border)
Definition: ShuttleGui.h:286
static const char * ZoomOut[]
Definition: FreqWindow.cpp:141
friend class FreqPlot
Definition: FreqWindow.h:222
void DrawPlot()
Definition: FreqWindow.cpp:659
static const char * ZoomIn[]
Definition: FreqWindow.cpp:115
std::unique_ptr< wxCursor > mCrossCursor
Definition: FreqWindow.h:187
wxChoice * mFuncChoice
Definition: FreqWindow.h:195
Algorithm mAlg
Definition: FreqWindow.h:82
RulerPanel * vRuler
Definition: FreqWindow.h:177
void create(Args &&...args)
Definition: MemoryX.h:256
wxButton * AddButton(const wxString &Text, int PositionFlags=wxALIGN_CENTRE)
Definition: ShuttleGui.cpp:349
void OnExport(wxCommandEvent &event)
Ruler ruler
Definition: Ruler.h:304
float CubicInterpolate(float y0, float y1, float y2, float y3, float x) const
void SetStretchyCol(int i)
Used to modify an already placed FlexGridSizer to make a column stretchy.
Definition: ShuttleGui.cpp:203
void SetStretchyRow(int i)
Used to modify an already placed FlexGridSizer to make a row stretchy.
Definition: ShuttleGui.cpp:213
EVT_COMMAND(OnTogglePinnedStateID, wxEVT_COMMAND_BUTTON_CLICKED, AdornedRulerPanel::OnTogglePinnedState) class AdornedRulerPanel
void StartVerticalLay(int iProp=1)