Audacity  2.2.2
SpectrogramSettings.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3 Audacity: A Digital Audio Editor
4 
5 SpectrogramSettings.cpp
6 
7 Paul Licameli
8 
9 *******************************************************************//*******************************************************************/
15 
16 #include "../Audacity.h"
17 #include "SpectrogramSettings.h"
18 #include "../NumberScale.h"
19 #include "../TranslatableStringArray.h"
20 
21 #include <algorithm>
22 
23 #include "../FFT.h"
24 #include "../Prefs.h"
25 #include "../RealFFTf.h"
26 
27 #include <cmath>
28 
29 #include "../Experimental.h"
30 #include "../widgets/ErrorDialog.h"
31 #include "../Internat.h"
32 
34 {
35  LoadPrefs();
36 }
37 
39 {
40 #ifdef SPECTRAL_SELECTION_GLOBAL_SWITCH
41  gPrefs->Write(wxT("/Spectrum/EnableSpectralSelection"), spectralSelection);
42 #endif
43 }
44 
46 {
47 #ifdef SPECTRAL_SELECTION_GLOBAL_SWITCH
49  = (gPrefs->Read(wxT("/Spectrum/EnableSpectralSelection"), 0L) != 0);
50 #endif
51 }
52 
55 {
56  static Globals instance;
57  return instance;
58 }
59 
61 {
62  LoadPrefs();
63 }
64 
66  : minFreq(other.minFreq)
67  , maxFreq(other.maxFreq)
68  , range(other.range)
69  , gain(other.gain)
71  , windowType(other.windowType)
72  , windowSize(other.windowSize)
75 #endif
76  , isGrayscale(other.isGrayscale)
77  , scaleType(other.scaleType)
78 #ifndef SPECTRAL_SELECTION_GLOBAL_SWITCH
80 #endif
81  , algorithm(other.algorithm)
82 #ifdef EXPERIMENTAL_FFT_Y_GRID
83  , fftYGrid(other.fftYGrid)
84 #endif
85 #ifdef EXPERIMENTAL_FIND_NOTES
86  , fftFindNotes(other.fftFindNotes)
87  , findNotesMinA(other.findNotesMinA)
88  , numberOfMaxima(other.numberOfMaxima)
89  , findNotesQuantize(other.findNotesQuantize)
90 #endif
91 
92  // Do not copy these!
93  , hFFT{}
94  , window{}
95  , tWindow{}
96  , dWindow{}
97 {
98 }
99 
101 {
102  if (this != &other) {
103  minFreq = other.minFreq;
104  maxFreq = other.maxFreq;
105  range = other.range;
106  gain = other.gain;
108  windowType = other.windowType;
109  windowSize = other.windowSize;
110 #ifdef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS
112 #endif
113  isGrayscale = other.isGrayscale;
114  scaleType = other.scaleType;
115 #ifndef SPECTRAL_SELECTION_GLOBAL_SWITCH
117 #endif
118  algorithm = other.algorithm;
119 #ifdef EXPERIMENTAL_FFT_Y_GRID
120  fftYGrid = other.fftYGrid;
121 #endif
122 #ifdef EXPERIMENTAL_FIND_NOTES
123  fftFindNotes = other.fftFindNotes;
124  findNotesMinA = other.findNotesMinA;
125  numberOfMaxima = other.numberOfMaxima;
126  findNotesQuantize = other.findNotesQuantize;
127 #endif
128 
129  // Invalidate the caches
130  DestroyWindows();
131  }
132  return *this;
133 }
134 
136 {
137  static SpectrogramSettings instance;
138  return instance;
139 }
140 
141 //static
143 {
144  class ScaleNamesArray final : public TranslatableStringArray
145  {
146  void Populate() override
147  {
148  // Keep in correspondence with enum SpectrogramSettings::ScaleType:
149  mContents.Add(_("Linear"));
150  mContents.Add(_("Logarithmic"));
151  /* i18n-hint: The name of a frequency scale in psychoacoustics */
152  mContents.Add(_("Mel"));
153  /* i18n-hint: The name of a frequency scale in psychoacoustics, named for Heinrich Barkhausen */
154  mContents.Add(_("Bark"));
155  /* i18n-hint: The name of a frequency scale in psychoacoustics, abbreviates Equivalent Rectangular Bandwidth */
156  mContents.Add(_("ERB"));
157  /* i18n-hint: Time units, that is Period = 1 / Frequency */
158  mContents.Add(_("Period"));
159  }
160  };
161 
162  static ScaleNamesArray theArray;
163  return theArray.Get();
164 }
165 
166 //static
168 {
169  class AlgorithmNamesArray final : public TranslatableStringArray
170  {
171  void Populate() override
172  {
173  // Keep in correspondence with enum SpectrogramSettings::Algorithm:
174  mContents.Add(_("Frequencies"));
175  /* i18n-hint: the Reassignment algorithm for spectrograms */
176  mContents.Add(_("Reassignment"));
177  /* i18n-hint: EAC abbreviates "Enhanced Autocorrelation" */
178  mContents.Add(_("Pitch (EAC)"));
179  }
180  };
181 
182  static AlgorithmNamesArray theArray;
183  return theArray.Get();
184 }
185 
187 {
188  if (!quiet &&
189  maxFreq < 100) {
190  AudacityMessageBox(_("Maximum frequency must be 100 Hz or above"));
191  return false;
192  }
193  else
194  maxFreq = std::max(100, maxFreq);
195 
196  if (!quiet &&
197  minFreq < 0) {
198  AudacityMessageBox(_("Minimum frequency must be at least 0 Hz"));
199  return false;
200  }
201  else
202  minFreq = std::max(0, minFreq);
203 
204  if (!quiet &&
205  maxFreq <= minFreq) {
206  AudacityMessageBox(_("Minimum frequency must be less than maximum frequency"));
207  return false;
208  }
209  else
210  maxFreq = std::max(1 + minFreq, maxFreq);
211 
212  if (!quiet &&
213  range <= 0) {
214  AudacityMessageBox(_("The range must be at least 1 dB"));
215  return false;
216  }
217  else
218  range = std::max(1, range);
219 
220  if (!quiet &&
221  frequencyGain < 0) {
222  AudacityMessageBox(_("The frequency gain cannot be negative"));
223  return false;
224  }
225  else if (!quiet &&
226  frequencyGain > 60) {
227  AudacityMessageBox(_("The frequency gain must be no more than 60 dB/dec"));
228  return false;
229  }
230  else
231  frequencyGain =
232  std::max(0, std::min(60, frequencyGain));
233 
234  // The rest are controlled by drop-down menus so they can't go wrong
235  // in the Preferences dialog, but we also come here after reading fom saved
236  // preference files, which could be or from future versions. Validate quietly.
237  windowType =
238  std::max(0, std::min(NumWindowFuncs() - 1, windowType));
239  scaleType =
240  ScaleType(std::max(0,
242  (int)(scaleType))));
244  std::max(0, std::min((int)(algNumAlgorithms) - 1, (int)(algorithm)))
245  );
248 
249  return true;
250 }
251 
253 {
254  minFreq = gPrefs->Read(wxT("/Spectrum/MinFreq"), 0L);
255 
256  maxFreq = gPrefs->Read(wxT("/Spectrum/MaxFreq"), 8000L);
257 
258  range = gPrefs->Read(wxT("/Spectrum/Range"), 80L);
259  gain = gPrefs->Read(wxT("/Spectrum/Gain"), 20L);
260  frequencyGain = gPrefs->Read(wxT("/Spectrum/FrequencyGain"), 0L);
261 
262  windowSize = gPrefs->Read(wxT("/Spectrum/FFTSize"), 1024);
263 
264 #ifdef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS
265  zeroPaddingFactor = gPrefs->Read(wxT("/Spectrum/ZeroPaddingFactor"), 1);
266 #endif
267 
268  gPrefs->Read(wxT("/Spectrum/WindowType"), &windowType, eWinFuncHanning);
269 
270  isGrayscale = (gPrefs->Read(wxT("/Spectrum/Grayscale"), 0L) != 0);
271 
272  scaleType = ScaleType(gPrefs->Read(wxT("/Spectrum/ScaleType"), 0L));
273 
274 #ifndef SPECTRAL_SELECTION_GLOBAL_SWITCH
275  spectralSelection = (gPrefs->Read(wxT("/Spectrum/EnableSpectralSelection"), 1L) != 0);
276 #endif
277 
278  algorithm = Algorithm(gPrefs->Read(wxT("/Spectrum/Algorithm"), 0L));
279 
280 #ifdef EXPERIMENTAL_FFT_Y_GRID
281  fftYGrid = (gPrefs->Read(wxT("/Spectrum/FFTYGrid"), 0L) != 0);
282 #endif //EXPERIMENTAL_FFT_Y_GRID
283 
284 #ifdef EXPERIMENTAL_FIND_NOTES
285  fftFindNotes = (gPrefs->Read(wxT("/Spectrum/FFTFindNotes"), 0L) != 0);
286  findNotesMinA = gPrefs->Read(wxT("/Spectrum/FindNotesMinA"), -30.0);
287  numberOfMaxima = gPrefs->Read(wxT("/Spectrum/FindNotesN"), 5L);
288  findNotesQuantize = (gPrefs->Read(wxT("/Spectrum/FindNotesQuantize"), 0L) != 0);
289 #endif //EXPERIMENTAL_FIND_NOTES
290 
291  // Enforce legal values
292  Validate(true);
293 
295 }
296 
298 {
299  gPrefs->Write(wxT("/Spectrum/MinFreq"), minFreq);
300  gPrefs->Write(wxT("/Spectrum/MaxFreq"), maxFreq);
301 
302  // Nothing wrote these. They only varied from the linear scale bounds in-session. -- PRL
303  // gPrefs->Write(wxT("/SpectrumLog/MaxFreq"), logMinFreq);
304  // gPrefs->Write(wxT("/SpectrumLog/MinFreq"), logMaxFreq);
305 
306  gPrefs->Write(wxT("/Spectrum/Range"), range);
307  gPrefs->Write(wxT("/Spectrum/Gain"), gain);
308  gPrefs->Write(wxT("/Spectrum/FrequencyGain"), frequencyGain);
309 
310  gPrefs->Write(wxT("/Spectrum/FFTSize"), windowSize);
311 
312 #ifdef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS
313  gPrefs->Write(wxT("/Spectrum/ZeroPaddingFactor"), zeroPaddingFactor);
314 #endif
315 
316  gPrefs->Write(wxT("/Spectrum/WindowType"), windowType);
317 
318  gPrefs->Write(wxT("/Spectrum/Grayscale"), isGrayscale);
319 
320  gPrefs->Write(wxT("/Spectrum/ScaleType"), (int) scaleType);
321 
322 #ifndef SPECTRAL_SELECTION_GLOBAL_SWITCH
323  gPrefs->Write(wxT("/Spectrum/EnableSpectralSelection"), spectralSelection);
324 #endif
325 
326  gPrefs->Write(wxT("/Spectrum/Algorithm"), (int) algorithm);
327 
328 #ifdef EXPERIMENTAL_FFT_Y_GRID
329  gPrefs->Write(wxT("/Spectrum/FFTYGrid"), fftYGrid);
330 #endif //EXPERIMENTAL_FFT_Y_GRID
331 
332 #ifdef EXPERIMENTAL_FIND_NOTES
333  gPrefs->Write(wxT("/Spectrum/FFTFindNotes"), fftFindNotes);
334  gPrefs->Write(wxT("/Spectrum/FindNotesMinA"), findNotesMinA);
335  gPrefs->Write(wxT("/Spectrum/FindNotesN"), numberOfMaxima);
336  gPrefs->Write(wxT("/Spectrum/FindNotesQuantize"), findNotesQuantize);
337 #endif //EXPERIMENTAL_FIND_NOTES
338 }
339 
341 {
342  DestroyWindows();
343 }
344 
346 {
347  DestroyWindows();
348 }
349 
351 {
352  hFFT.reset();
353  window.reset();
354  dWindow.reset();
355  tWindow.reset();
356 }
357 
358 
359 namespace
360 {
361  enum { WINDOW, TWINDOW, DWINDOW };
362  void RecreateWindow(
363  Floats &window, int which, size_t fftLen,
364  size_t padding, int windowType, size_t windowSize, double &scale)
365  {
366  // Create the requested window function
367  window = Floats{ fftLen };
368  size_t ii;
369 
370  const bool extra = padding > 0;
371  wxASSERT(windowSize % 2 == 0);
372  if (extra)
373  // For windows that do not go to 0 at the edges, this improves symmetry
374  ++windowSize;
375  const size_t endOfWindow = padding + windowSize;
376  // Left and right padding
377  for (ii = 0; ii < padding; ++ii) {
378  window[ii] = 0.0;
379  window[fftLen - ii - 1] = 0.0;
380  }
381  // Default rectangular window in the middle
382  for (; ii < endOfWindow; ++ii)
383  window[ii] = 1.0;
384  // Overwrite middle as needed
385  switch (which) {
386  case WINDOW:
387  NewWindowFunc(windowType, windowSize, extra, window.get() + padding);
388  break;
389  case TWINDOW:
390  NewWindowFunc(windowType, windowSize, extra, window.get() + padding);
391  for (int ii = padding, multiplier = -(int)windowSize / 2; ii < (int)endOfWindow; ++ii, ++multiplier)
392  window[ii] *= multiplier;
393  break;
394  case DWINDOW:
395  DerivativeOfWindowFunc(windowType, windowSize, extra, window.get() + padding);
396  break;
397  default:
398  wxASSERT(false);
399  }
400  // Scale the window function to give 0dB spectrum for 0dB sine tone
401  if (which == WINDOW) {
402  scale = 0.0;
403  for (ii = padding; ii < endOfWindow; ++ii)
404  scale += window[ii];
405  if (scale > 0)
406  scale = 2.0 / scale;
407  }
408  for (ii = padding; ii < endOfWindow; ++ii)
409  window[ii] *= scale;
410  }
411 }
412 
414 {
415  if (hFFT == NULL || window == NULL) {
416 
417  double scale;
418  const auto fftLen = WindowSize() * ZeroPaddingFactor();
419  const auto padding = (WindowSize() * (zeroPaddingFactor - 1)) / 2;
420 
421  hFFT = GetFFT(fftLen);
422  RecreateWindow(window, WINDOW, fftLen, padding, windowType, windowSize, scale);
423  if (algorithm == algReassignment) {
424  RecreateWindow(tWindow, TWINDOW, fftLen, padding, windowType, windowSize, scale);
425  RecreateWindow(dWindow, DWINDOW, fftLen, padding, windowType, windowSize, scale);
426  }
427  }
428 }
429 
431 {
432  unsigned size;
433  int logarithm;
434 
435  logarithm = -LogMinWindowSize;
436  size = unsigned(windowSize);
437  while (size > 1)
438  size >>= 1, ++logarithm;
439  windowSize = std::max(0, std::min(NumWindowSizes - 1, logarithm));
440 
441 #ifdef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS
442  // Choices for zero padding begin at 1
443  logarithm = 0;
444  size = unsigned(zeroPaddingFactor);
445  while (zeroPaddingFactor > 1)
446  zeroPaddingFactor >>= 1, ++logarithm;
447  zeroPaddingFactor = std::max(0,
448  std::min(LogMaxWindowSize - (windowSize + LogMinWindowSize),
449  logarithm
450  ));
451 #endif
452 }
453 
455 {
456  windowSize = 1 << (windowSize + LogMinWindowSize);
457 #ifdef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS
459 #endif
460 }
461 
462 float SpectrogramSettings::findBin( float frequency, float binUnit ) const
463 {
464  float linearBin = frequency / binUnit;
465  if (linearBin < 0)
466  return -1;
467  else
468  return linearBin;
469 }
470 
472 {
473  return windowSize
474 #ifdef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS
475  * ((algorithm != algPitchEAC) ? zeroPaddingFactor : 1);
476 #endif
477  ;
478 }
479 
481 {
482  // Omit the Nyquist frequency bin
483  return GetFFTLength() / 2;
484 }
485 
486 NumberScale SpectrogramSettings::GetScale( float minFreq, float maxFreq ) const
487 {
488  NumberScaleType type = nstLinear;
489 
490  // Don't assume the correspondence of the enums will remain direct in the future.
491  // Do this switch.
492  switch (scaleType) {
493  default:
494  wxASSERT(false);
495  case stLinear:
496  type = nstLinear; break;
497  case stLogarithmic:
498  type = nstLogarithmic; break;
499  case stMel:
500  type = nstMel; break;
501  case stBark:
502  type = nstBark; break;
503  case stErb:
504  type = nstErb; break;
505  case stPeriod:
506  type = nstPeriod; break;
507  }
508 
509  return NumberScale(type, minFreq, maxFreq);
510 }
511 
513 {
514 #ifdef SPECTRAL_SELECTION_GLOBAL_SWITCH
515  return Globals::Get().spectralSelection;
516 #else
517  return spectralSelection;
518 #endif
519 }
AudacityPrefs * gPrefs
Definition: Prefs.cpp:73
Spectrogram settings, either for one track or as defaults.
SpectrogramSettings & operator=(const SpectrogramSettings &other)
bool SpectralSelectionEnabled() const
HFFT GetFFT(size_t fftlen)
Definition: RealFFTf.cpp:110
size_t GetFFTLength() const
static const wxArrayString & GetScaleNames()
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
NumberScale GetScale(float minFreq, float maxFreq) const
static const wxArrayString & GetAlgorithmNames()
size_t WindowSize() const
void NewWindowFunc(int whichFunction, size_t NumSamplesIn, bool extraSample, float *in)
Definition: FFT.cpp:362
float findBin(float frequency, float binUnit) const
NumberScaleType
Definition: NumberScale.h:19
int min(int a, int b)
void DerivativeOfWindowFunc(int whichFunction, size_t NumSamples, bool extraSample, float *in)
Definition: FFT.cpp:530
int NumWindowFuncs()
Definition: FFT.cpp:330
#define EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS
Definition: Project.h:233
_("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
size_t ZeroPaddingFactor() const
static SpectrogramSettings & defaults()