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