Audacity 3.2.0
SpectrogramSettings.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3Audacity: A Digital Audio Editor
4
5SpectrogramSettings.cpp
6
7Paul Licameli
8
9*******************************************************************//*******************************************************************/
15
16
17#include "SpectrogramSettings.h"
18
19#include "AColor.h"
20#include "NumberScale.h"
21
22#include <algorithm>
23
24#include "FFT.h"
25#include "Prefs.h"
26
27#include <cmath>
28
29#include "../widgets/AudacityMessageBox.h"
30
32 L"/Spectrum/MaxFreq", 20000 };
33
34namespace {
35// Other settings not yet used outside of this file
36
37// To do: migrate these to ChoiceSetting preferences, which will store an
38// Identifier instead of a number in the preference file.
40 L"/Spectrum/Algorithm", 0 }; // Default to Frequencies
42 L"/Spectrum/ScaleType", 2 }; // Default to Mel
44 L"/Spectrum/WindowType", eWinFuncHann };
45
47 L"/Spectrum/EnableSpectralSelection", true };
49 L"/Spectrum/FFTSize", 2048 };
51 L"/Spectrum/FrequencyGain", 0 };
53 L"/Spectrum/Gain", 20 };
55 L"/Spectrum/Grayscale", false };
57 L"/Spectrum/MinFreq", 0 };
59 L"/Spectrum/Range", 80 };
61 L"/Spectrum/ZeroPaddingFactor", 2 };
62
63#ifdef EXPERIMENTAL_FIND_NOTES
64BoolSetting SpectrumFindNotes{
65 L"/Spectrum/FFTFindNotes", false };
66DoubleSetting SpectrumFindNotesMinA{
67 L"/Spectrum/FindNotesMinA", -30.0 };
68IntSetting SpectrumFindNotesN{
69 L"/Spectrum/FindNotesN", 5 };
70BoolSetting SpectrumFindNotesQuantize{
71 L"/Spectrum/FindNotesQuantize", false };
72#endif //EXPERIMENTAL_FIND_NOTES
73
74#ifdef EXPERIMENTAL_FFT_Y_GRID
75BoolSetting SpectrumYGrid{
76 L"/Spectrum/FFTYGrid", false};
77#endif
78}
79
81{
82 LoadPrefs();
83}
84
86{
87#ifdef SPECTRAL_SELECTION_GLOBAL_SWITCH
89#endif
90}
91
93{
94#ifdef SPECTRAL_SELECTION_GLOBAL_SWITCH
96#endif
97}
98
101{
102 static Globals instance;
103 return instance;
104}
105
107{
108 LoadPrefs();
109}
110
112 : minFreq(other.minFreq)
113 , maxFreq(other.maxFreq)
114 , range(other.range)
115 , gain(other.gain)
117 , windowType(other.windowType)
118 , windowSize(other.windowSize)
120 , colorScheme(other.colorScheme)
121 , scaleType(other.scaleType)
122#ifndef SPECTRAL_SELECTION_GLOBAL_SWITCH
124#endif
125 , algorithm(other.algorithm)
126#ifdef EXPERIMENTAL_FFT_Y_GRID
127 , fftYGrid(other.fftYGrid)
128#endif
129#ifdef EXPERIMENTAL_FIND_NOTES
130 , fftFindNotes(other.fftFindNotes)
131 , findNotesMinA(other.findNotesMinA)
132 , numberOfMaxima(other.numberOfMaxima)
133 , findNotesQuantize(other.findNotesQuantize)
134#endif
135
136 // Do not copy these!
137 , hFFT{}
138 , window{}
139 , tWindow{}
140 , dWindow{}
141{
142}
143
145{
146 if (this != &other) {
147 minFreq = other.minFreq;
148 maxFreq = other.maxFreq;
149 range = other.range;
150 gain = other.gain;
152 windowType = other.windowType;
153 windowSize = other.windowSize;
155 colorScheme = other.colorScheme;
156 scaleType = other.scaleType;
157#ifndef SPECTRAL_SELECTION_GLOBAL_SWITCH
159#endif
160 algorithm = other.algorithm;
161#ifdef EXPERIMENTAL_FFT_Y_GRID
162 fftYGrid = other.fftYGrid;
163#endif
164#ifdef EXPERIMENTAL_FIND_NOTES
165 fftFindNotes = other.fftFindNotes;
166 findNotesMinA = other.findNotesMinA;
167 numberOfMaxima = other.numberOfMaxima;
168 findNotesQuantize = other.findNotesQuantize;
169#endif
170
171 // Invalidate the caches
173 }
174 return *this;
175}
176
178{
179 static SpectrogramSettings instance;
180 return instance;
181}
182
183//static
185{
186 static const EnumValueSymbols result{
187 // Keep in correspondence with enum SpectrogramSettings::ScaleType:
188 XO("Linear") ,
189 XO("Logarithmic") ,
190 /* i18n-hint: The name of a frequency scale in psychoacoustics */
191 XO("Mel") ,
192 /* i18n-hint: The name of a frequency scale in psychoacoustics, named for Heinrich Barkhausen */
193 XO("Bark") ,
194 /* i18n-hint: The name of a frequency scale in psychoacoustics, abbreviates Equivalent Rectangular Bandwidth */
195 XO("ERB") ,
196 /* i18n-hint: Time units, that is Period = 1 / Frequency */
197 XO("Period") ,
198 };
199 return result;
200}
201
202//static
204{
205 static const EnumValueSymbols result{
206 // Keep in correspondence with enum SpectrogramSettings::ColorScheme:
207 /* i18n-hint: New color scheme for spectrograms */
208 { wxT("SpecColorNew"), XC("Color (default)", "spectrum prefs") },
209 /* i18n-hint: Classic color scheme(from theme) for spectrograms */
210 { wxT("SpecColorTheme"), XC("Color (classic)", "spectrum prefs") },
211 /* i18n-hint: Grayscale color scheme for spectrograms */
212 { wxT("SpecGrayscale"), XC("Grayscale", "spectrum prefs") },
213 /* i18n-hint: Inverse grayscale color scheme for spectrograms */
214 { wxT("SpecInvGrayscale"), XC("Inverse grayscale", "spectrum prefs") },
215 };
216
217 wxASSERT(csNumColorScheme == result.size());
218 static_assert(csNumColorScheme == AColor::colorSchemes, "Broken correspondence");
219
220 return result;
221}
222
223
225{
226 // Migrate old grayscale option to Color scheme choice
227 bool isGrayscale = SpectrumGrayscale.Read();
228 if (isGrayscale && !gPrefs->Read(wxT("/Spectrum/ColorScheme"), &value)) {
229 value = GetColorSchemeNames().at(csGrayscale).Internal();
230 Write(value);
231 gPrefs->Flush();
232 }
233}
234
236 wxT("/Spectrum/ColorScheme"),
238 csColorNew, // default to Color(New)
240};
241
242
243//static
245{
246 static const TranslatableStrings results{
247 // Keep in correspondence with enum SpectrogramSettings::Algorithm:
248 XO("Frequencies") ,
249 /* i18n-hint: the Reassignment algorithm for spectrograms */
250 XO("Reassignment") ,
251 /* i18n-hint: EAC abbreviates "Enhanced Autocorrelation" */
252 XO("Pitch (EAC)") ,
253 };
254 return results;
255}
256
258{
259 if (!quiet &&
260 maxFreq < 100) {
261 AudacityMessageBox( XO("Maximum frequency must be 100 Hz or above") );
262 return false;
263 }
264 else
265 maxFreq = std::max(100, maxFreq);
266
267 if (!quiet &&
268 minFreq < 0) {
269 AudacityMessageBox( XO("Minimum frequency must be at least 0 Hz") );
270 return false;
271 }
272 else
273 minFreq = std::max(0, minFreq);
274
275 if (!quiet &&
276 maxFreq <= minFreq) {
278"Minimum frequency must be less than maximum frequency") );
279 return false;
280 }
281 else
282 maxFreq = std::max(1 + minFreq, maxFreq);
283
284 if (!quiet &&
285 range <= 0) {
286 AudacityMessageBox( XO("The range must be at least 1 dB") );
287 return false;
288 }
289 else
290 range = std::max(1, range);
291
292 if (!quiet &&
293 frequencyGain < 0) {
294 AudacityMessageBox( XO("The frequency gain cannot be negative") );
295 return false;
296 }
297 else if (!quiet &&
298 frequencyGain > 60) {
300"The frequency gain must be no more than 60 dB/dec") );
301 return false;
302 }
303 else
305 std::max(0, std::min(60, frequencyGain));
306
307 // The rest are controlled by drop-down menus so they can't go wrong
308 // in the Preferences dialog, but we also come here after reading fom saved
309 // preference files, which could be or from future versions. Validate quietly.
310 windowType =
311 std::max(0, std::min(NumWindowFuncs() - 1, windowType));
312 scaleType =
313 ScaleType(std::max(0,
315 (int)(scaleType))));
317 std::max(0, std::min<int>(csNumColorScheme-1, colorScheme))
318 );
320 std::max(0, std::min((int)(algNumAlgorithms) - 1, (int)(algorithm)))
321 );
324
325 return true;
326}
327
329{
331
333
337
339
341
343
345
346 scaleType = static_cast<ScaleType>(SpectrumScale.Read());
347
348#ifndef SPECTRAL_SELECTION_GLOBAL_SWITCH
350#endif
351
352 algorithm = static_cast<Algorithm>(SpectrumAlgorithm.Read());
353
354#ifdef EXPERIMENTAL_FFT_Y_GRID
355 fftYGrid = SpectrumYGrid.Read();
356#endif //EXPERIMENTAL_FFT_Y_GRID
357
358#ifdef EXPERIMENTAL_FIND_NOTES
359 fftFindNotes = SpectrumFindNotes.Read();
360 findNotesMinA = SpectrumFindNotesMinA.Read();
361 numberOfMaxima = SpectrumFindNotesN.Read();
362 findNotesQuantize = SpectrumFindNotesQuantize.Read();
363#endif //EXPERIMENTAL_FIND_NOTES
364
365 // Enforce legal values
366 Validate(true);
367
369}
370
372{
375
376 // Nothing wrote these. They only varied from the linear scale bounds in-session. -- PRL
377 // gPrefs->Write(wxT("/SpectrumLog/MaxFreq"), logMinFreq);
378 // gPrefs->Write(wxT("/SpectrumLog/MinFreq"), logMaxFreq);
379
383
385
387
389
391
392 SpectrumScale.Write(static_cast<int>(scaleType));
393
394#ifndef SPECTRAL_SELECTION_GLOBAL_SWITCH
396#endif
397
398 SpectrumAlgorithm.Write(static_cast<int>(algorithm));
399
400#ifdef EXPERIMENTAL_FFT_Y_GRID
401 SpectrumYGrid.Write(fftYGrid);
402#endif //EXPERIMENTAL_FFT_Y_GRID
403
404#ifdef EXPERIMENTAL_FIND_NOTES
405 SpectrumFindNotes.Write(fftFindNotes);
406 SpectrumFindNotesMinA.Write(findNotesMinA);
407 SpectrumFindNotesN.Write(numberOfMaxima);
408 SpectrumFindNotesQuantize.Write(findNotesQuantize);
409#endif //EXPERIMENTAL_FIND_NOTES
410}
411
412// This is a temporary hack until SpectrogramSettings gets fully integrated
414{
415 if (minFreq == defaults().minFreq)
417
418 if (maxFreq == defaults().maxFreq)
420
421 if (range == defaults().range)
423
424 if (gain == defaults().gain)
426
429
432
435
438
441 }
442
444 scaleType = static_cast<ScaleType>(SpectrumScale.Read());
445
446#ifndef SPECTRAL_SELECTION_GLOBAL_SWITCH
449#endif
450
452 algorithm = static_cast<Algorithm>(SpectrumAlgorithm.Read());
453
454#ifdef EXPERIMENTAL_FFT_Y_GRID
455 if (fftYGrid == defaults().fftYGrid)
456 fftYGrid = SpectrumYGrid.Read();
457#endif //EXPERIMENTAL_FFT_Y_GRID
458
459#ifdef EXPERIMENTAL_FIND_NOTES
460 if (fftFindNotes == defaults().fftFindNotes)
461 fftFindNotes = SpectrumFindNotes.Read();
462
463 if (findNotesMinA == defaults().findNotesMinA)
464 findNotesMinA = SpectrumFindNotesMinA.Read();
465
466 if (numberOfMaxima == defaults().numberOfMaxima)
467 numberOfMaxima = SpectrumFindNotesN.Read();
468
469 if (findNotesQuantize == defaults().findNotesQuantize)
470 findNotesQuantize = SpectrumFindNotesQuantize.Read();
471#endif //EXPERIMENTAL_FIND_NOTES
472
473 // Enforce legal values
474 Validate(true);
475}
476
478{
480}
481
483{
485}
486
488{
489 hFFT.reset();
490 window.reset();
491 dWindow.reset();
492 tWindow.reset();
493}
494
495
496namespace
497{
500 Floats &window, int which, size_t fftLen,
501 size_t padding, int windowType, size_t windowSize, double &scale)
502 {
503 // Create the requested window function
504 window = Floats{ fftLen };
505 size_t ii;
506
507 const bool extra = padding > 0;
508 wxASSERT(windowSize % 2 == 0);
509 if (extra)
510 // For windows that do not go to 0 at the edges, this improves symmetry
511 ++windowSize;
512 const size_t endOfWindow = padding + windowSize;
513 // Left and right padding
514 for (ii = 0; ii < padding; ++ii) {
515 window[ii] = 0.0;
516 window[fftLen - ii - 1] = 0.0;
517 }
518 // Default rectangular window in the middle
519 for (; ii < endOfWindow; ++ii)
520 window[ii] = 1.0;
521 // Overwrite middle as needed
522 switch (which) {
523 case WINDOW:
524 NewWindowFunc(windowType, windowSize, extra, window.get() + padding);
525 break;
526 case TWINDOW:
527 NewWindowFunc(windowType, windowSize, extra, window.get() + padding);
528 {
529 for (int jj = padding, multiplier = -(int)windowSize / 2; jj < (int)endOfWindow; ++jj, ++multiplier)
530 window[jj] *= multiplier;
531 }
532 break;
533 case DWINDOW:
534 DerivativeOfWindowFunc(windowType, windowSize, extra, window.get() + padding);
535 break;
536 default:
537 wxASSERT(false);
538 }
539 // Scale the window function to give 0dB spectrum for 0dB sine tone
540 if (which == WINDOW) {
541 scale = 0.0;
542 for (ii = padding; ii < endOfWindow; ++ii)
543 scale += window[ii];
544 if (scale > 0)
545 scale = 2.0 / scale;
546 }
547 for (ii = padding; ii < endOfWindow; ++ii)
548 window[ii] *= scale;
549 }
550}
551
553{
554 if (hFFT == NULL || window == NULL) {
555
556 double scale;
557 auto factor = ZeroPaddingFactor();
558 const auto fftLen = WindowSize() * factor;
559 const auto padding = (WindowSize() * (factor - 1)) / 2;
560
561 hFFT = GetFFT(fftLen);
562 RecreateWindow(window, WINDOW, fftLen, padding, windowType, windowSize, scale);
563 if (algorithm == algReassignment) {
564 RecreateWindow(tWindow, TWINDOW, fftLen, padding, windowType, windowSize, scale);
565 RecreateWindow(dWindow, DWINDOW, fftLen, padding, windowType, windowSize, scale);
566 }
567 }
568}
569
571{
572 unsigned size;
573 int logarithm;
574
575 logarithm = -LogMinWindowSize;
576 size = unsigned(windowSize);
577 while (size > 1)
578 size >>= 1, ++logarithm;
579 windowSize = std::max(0, std::min(NumWindowSizes - 1, logarithm));
580
581 // Choices for zero padding begin at 1
582 logarithm = 0;
583 size = unsigned(zeroPaddingFactor);
584 while (zeroPaddingFactor > 1)
585 zeroPaddingFactor >>= 1, ++logarithm;
586 zeroPaddingFactor = std::max(0,
588 logarithm
589 ));
590}
591
593{
596}
597
598float SpectrogramSettings::findBin( float frequency, float binUnit ) const
599{
600 float linearBin = frequency / binUnit;
601 if (linearBin < 0)
602 return -1;
603 else
604 return linearBin;
605}
606
608{
609//#ifndef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS
610 // return windowSize;
611//#else
613//#endif
614}
615
617{
618 // Omit the Nyquist frequency bin
619 return GetFFTLength() / 2;
620}
621
622NumberScale SpectrogramSettings::GetScale( float minFreqIn, float maxFreqIn ) const
623{
625
626 // Don't assume the correspondence of the enums will remain direct in the future.
627 // Do this switch.
628 switch (scaleType) {
629 default:
630 wxASSERT(false);
631 case stLinear:
632 type = nstLinear; break;
633 case stLogarithmic:
634 type = nstLogarithmic; break;
635 case stMel:
636 type = nstMel; break;
637 case stBark:
638 type = nstBark; break;
639 case stErb:
640 type = nstErb; break;
641 case stPeriod:
642 type = nstPeriod; break;
643 }
644
645 return NumberScale(type, minFreqIn, maxFreqIn);
646}
647
649{
650#ifdef SPECTRAL_SELECTION_GLOBAL_SWITCH
651 return Globals::Get().spectralSelection;
652#else
653 return spectralSelection;
654#endif
655}
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption, long style, wxWindow *parent, int x, int y)
int min(int a, int b)
void NewWindowFunc(int whichFunction, size_t NumSamplesIn, bool extraSample, float *in)
Definition: FFT.cpp:370
void DerivativeOfWindowFunc(int whichFunction, size_t NumSamples, bool extraSample, float *in)
Definition: FFT.cpp:538
int NumWindowFuncs()
Definition: FFT.cpp:329
@ eWinFuncHann
Definition: FFT.h:114
#define XO(s)
Definition: Internat.h:31
#define XC(s, c)
Definition: Internat.h:37
NumberScaleType
Definition: NumberScale.h:17
@ nstMel
Definition: NumberScale.h:20
@ nstErb
Definition: NumberScale.h:22
@ nstPeriod
Definition: NumberScale.h:23
@ nstLinear
Definition: NumberScale.h:18
@ nstLogarithmic
Definition: NumberScale.h:19
@ nstBark
Definition: NumberScale.h:21
FileConfig * gPrefs
Definition: Prefs.cpp:71
HFFT GetFFT(size_t fftlen)
Definition: RealFFTf.cpp:105
IntSetting SpectrumMaxFreq
std::vector< TranslatableString > TranslatableStrings
static const int colorSchemes
Definition: AColor.h:129
This specialization of Setting for bool adds a Toggle method to negate the saved value.
Definition: Prefs.h:286
bool Write(const wxString &value)
Definition: Prefs.cpp:359
Specialization of Setting for double.
Definition: Prefs.h:303
bool WriteEnum(Enum value)
Definition: Prefs.h:467
Enum ReadEnum() const
Definition: Prefs.h:455
virtual bool Flush(bool bCurrentOnly=false) wxOVERRIDE
Definition: FileConfig.cpp:143
Specialization of Setting for int.
Definition: Prefs.h:296
bool Write(const T &value)
Write value to config and return true if successful.
Definition: Prefs.h:229
bool Read(T *pVar) const
overload of Read returning a boolean that is true if the value was previously defined *‍/
Definition: Prefs.h:185
Spectrogram settings, either for one track or as defaults.
static SpectrogramSettings & defaults()
static const TranslatableStrings & GetAlgorithmNames()
NumberScale GetScale(float minFreq, float maxFreq) const
bool SpectralSelectionEnabled() const
static const EnumValueSymbols & GetScaleNames()
SpectrogramSettings & operator=(const SpectrogramSettings &other)
static const EnumValueSymbols & GetColorSchemeNames()
static ColorSchemeEnumSetting colorSchemeSetting
size_t ZeroPaddingFactor() const
float findBin(float frequency, float binUnit) const
void RecreateWindow(Floats &window, int which, size_t fftLen, size_t padding, int windowType, size_t windowSize, double &scale)