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