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#include "WaveTrack.h"
27
28#include <cmath>
29
30#include "AudacityMessageBox.h"
31
33 L"/Spectrum/MaxFreq", 20000 };
34
35namespace {
36// Other settings not yet used outside of this file
37
38// To do: migrate these to ChoiceSetting preferences, which will store an
39// Identifier instead of a number in the preference file.
41 L"/Spectrum/Algorithm", 0 }; // Default to Frequencies
43 L"/Spectrum/ScaleType", 2 }; // Default to Mel
45 L"/Spectrum/WindowType", eWinFuncHann };
46
48 L"/Spectrum/EnableSpectralSelection", true };
50 L"/Spectrum/FFTSize", 2048 };
52 L"/Spectrum/FrequencyGain", 0 };
54 L"/Spectrum/Gain", 20 };
56 L"/Spectrum/Grayscale", false };
58 L"/Spectrum/MinFreq", 0 };
60 L"/Spectrum/Range", 80 };
62 L"/Spectrum/ZeroPaddingFactor", 2 };
63
64#ifdef EXPERIMENTAL_FIND_NOTES
65BoolSetting SpectrumFindNotes{
66 L"/Spectrum/FFTFindNotes", false };
67DoubleSetting SpectrumFindNotesMinA{
68 L"/Spectrum/FindNotesMinA", -30.0 };
69IntSetting SpectrumFindNotesN{
70 L"/Spectrum/FindNotesN", 5 };
71BoolSetting SpectrumFindNotesQuantize{
72 L"/Spectrum/FindNotesQuantize", false };
73#endif //EXPERIMENTAL_FIND_NOTES
74
75#ifdef EXPERIMENTAL_FFT_Y_GRID
76BoolSetting SpectrumYGrid{
77 L"/Spectrum/FFTYGrid", false};
78#endif
79}
80
82{
83 LoadPrefs();
84}
85
87{
88#ifdef SPECTRAL_SELECTION_GLOBAL_SWITCH
90#endif
91}
92
94{
95#ifdef SPECTRAL_SELECTION_GLOBAL_SWITCH
97#endif
98}
99
102{
103 static Globals instance;
104 return instance;
105}
106
108key1{ [](auto &) { return nullptr; } };
109
111{
112 auto &mutTrack = const_cast<WaveTrack&>(track);
113 auto pSettings = mutTrack.GetGroupData().Track::ChannelGroupAttachments
114 ::Find<SpectrogramSettings>(key1);
115 if (pSettings)
116 return *pSettings;
117 else
119}
120
122{
123 auto pSettings = track.GetGroupData().Track::ChannelGroupAttachments
124 ::Find<SpectrogramSettings>(key1);
125 if (!pSettings) {
126 auto uSettings = std::make_unique<SpectrogramSettings>();
127 pSettings = uSettings.get();
128 track.GetGroupData().Track::ChannelGroupAttachments
129 ::Assign(key1, std::move(uSettings));
130 }
131 return *pSettings;
132}
133
135{
136 track.GetGroupData().Track::ChannelGroupAttachments
137 ::Assign(key1, nullptr);
138}
139
141{
142 LoadPrefs();
143}
144
146 : minFreq(other.minFreq)
147 , maxFreq(other.maxFreq)
148 , range(other.range)
149 , gain(other.gain)
151 , windowType(other.windowType)
152 , windowSize(other.windowSize)
154 , colorScheme(other.colorScheme)
155 , scaleType(other.scaleType)
156#ifndef SPECTRAL_SELECTION_GLOBAL_SWITCH
158#endif
159 , algorithm(other.algorithm)
160#ifdef EXPERIMENTAL_FFT_Y_GRID
161 , fftYGrid(other.fftYGrid)
162#endif
163#ifdef EXPERIMENTAL_FIND_NOTES
164 , fftFindNotes(other.fftFindNotes)
165 , findNotesMinA(other.findNotesMinA)
166 , numberOfMaxima(other.numberOfMaxima)
167 , findNotesQuantize(other.findNotesQuantize)
168#endif
169
170 // Do not copy these!
171 , hFFT{}
172 , window{}
173 , tWindow{}
174 , dWindow{}
175{
176}
177
179{
180 if (this != &other) {
181 minFreq = other.minFreq;
182 maxFreq = other.maxFreq;
183 range = other.range;
184 gain = other.gain;
186 windowType = other.windowType;
187 windowSize = other.windowSize;
189 colorScheme = other.colorScheme;
190 scaleType = other.scaleType;
191#ifndef SPECTRAL_SELECTION_GLOBAL_SWITCH
193#endif
194 algorithm = other.algorithm;
195#ifdef EXPERIMENTAL_FFT_Y_GRID
196 fftYGrid = other.fftYGrid;
197#endif
198#ifdef EXPERIMENTAL_FIND_NOTES
199 fftFindNotes = other.fftFindNotes;
200 findNotesMinA = other.findNotesMinA;
201 numberOfMaxima = other.numberOfMaxima;
202 findNotesQuantize = other.findNotesQuantize;
203#endif
204
205 // Invalidate the caches
207 }
208 return *this;
209}
210
212{
213 static SpectrogramSettings instance;
214 return instance;
215}
216
217//static
219{
220 static const EnumValueSymbols result{
221 // Keep in correspondence with enum SpectrogramSettings::ScaleType:
222 XO("Linear") ,
223 XO("Logarithmic") ,
224 /* i18n-hint: The name of a frequency scale in psychoacoustics */
225 XO("Mel") ,
226 /* i18n-hint: The name of a frequency scale in psychoacoustics, named for Heinrich Barkhausen */
227 XO("Bark") ,
228 /* i18n-hint: The name of a frequency scale in psychoacoustics, abbreviates Equivalent Rectangular Bandwidth */
229 XO("ERB") ,
230 /* i18n-hint: Time units, that is Period = 1 / Frequency */
231 XO("Period") ,
232 };
233 return result;
234}
235
236//static
238{
239 static const EnumValueSymbols result{
240 // Keep in correspondence with enum SpectrogramSettings::ColorScheme:
241 /* i18n-hint: New color scheme for spectrograms */
242 { wxT("SpecColorNew"), XC("Color (default)", "spectrum prefs") },
243 /* i18n-hint: Classic color scheme(from theme) for spectrograms */
244 { wxT("SpecColorTheme"), XC("Color (classic)", "spectrum prefs") },
245 /* i18n-hint: Grayscale color scheme for spectrograms */
246 { wxT("SpecGrayscale"), XC("Grayscale", "spectrum prefs") },
247 /* i18n-hint: Inverse grayscale color scheme for spectrograms */
248 { wxT("SpecInvGrayscale"), XC("Inverse grayscale", "spectrum prefs") },
249 };
250
251 wxASSERT(csNumColorScheme == result.size());
252 static_assert(csNumColorScheme == AColor::colorSchemes, "Broken correspondence");
253
254 return result;
255}
256
257
259{
260 // Migrate old grayscale option to Color scheme choice
261 bool isGrayscale = SpectrumGrayscale.Read();
262 if (isGrayscale && !gPrefs->Read(wxT("/Spectrum/ColorScheme"), &value)) {
263 value = GetColorSchemeNames().at(csGrayscale).Internal();
264 Write(value);
265 gPrefs->Flush();
266 }
267}
268
270 wxT("/Spectrum/ColorScheme"),
272 csColorNew, // default to Color(New)
274};
275
276
277//static
279{
280 static const TranslatableStrings results{
281 // Keep in correspondence with enum SpectrogramSettings::Algorithm:
282 XO("Frequencies") ,
283 /* i18n-hint: the Reassignment algorithm for spectrograms */
284 XO("Reassignment") ,
285 /* i18n-hint: EAC abbreviates "Enhanced Autocorrelation" */
286 XO("Pitch (EAC)") ,
287 };
288 return results;
289}
290
292{
293 if (!quiet &&
294 maxFreq < 100) {
295 AudacityMessageBox( XO("Maximum frequency must be 100 Hz or above") );
296 return false;
297 }
298 else
299 maxFreq = std::max(100, maxFreq);
300
301 if (!quiet &&
302 minFreq < 0) {
303 AudacityMessageBox( XO("Minimum frequency must be at least 0 Hz") );
304 return false;
305 }
306 else
307 minFreq = std::max(0, minFreq);
308
309 if (!quiet &&
310 maxFreq <= minFreq) {
312"Minimum frequency must be less than maximum frequency") );
313 return false;
314 }
315 else
316 maxFreq = std::max(1 + minFreq, maxFreq);
317
318 if (!quiet &&
319 range <= 0) {
320 AudacityMessageBox( XO("The range must be at least 1 dB") );
321 return false;
322 }
323 else
324 range = std::max(1, range);
325
326 if (!quiet &&
327 frequencyGain < 0) {
328 AudacityMessageBox( XO("The frequency gain cannot be negative") );
329 return false;
330 }
331 else if (!quiet &&
332 frequencyGain > 60) {
334"The frequency gain must be no more than 60 dB/dec") );
335 return false;
336 }
337 else
339 std::max(0, std::min(60, frequencyGain));
340
341 // The rest are controlled by drop-down menus so they can't go wrong
342 // in the Preferences dialog, but we also come here after reading fom saved
343 // preference files, which could be or from future versions. Validate quietly.
344 windowType =
345 std::max(0, std::min(NumWindowFuncs() - 1, windowType));
346 scaleType =
347 ScaleType(std::max(0,
349 (int)(scaleType))));
351 std::max(0, std::min<int>(csNumColorScheme-1, colorScheme))
352 );
354 std::max(0, std::min((int)(algNumAlgorithms) - 1, (int)(algorithm)))
355 );
358
359 return true;
360}
361
363{
365
367
371
373
375
377
379
380 scaleType = static_cast<ScaleType>(SpectrumScale.Read());
381
382#ifndef SPECTRAL_SELECTION_GLOBAL_SWITCH
384#endif
385
386 algorithm = static_cast<Algorithm>(SpectrumAlgorithm.Read());
387
388#ifdef EXPERIMENTAL_FFT_Y_GRID
389 fftYGrid = SpectrumYGrid.Read();
390#endif //EXPERIMENTAL_FFT_Y_GRID
391
392#ifdef EXPERIMENTAL_FIND_NOTES
393 fftFindNotes = SpectrumFindNotes.Read();
394 findNotesMinA = SpectrumFindNotesMinA.Read();
395 numberOfMaxima = SpectrumFindNotesN.Read();
396 findNotesQuantize = SpectrumFindNotesQuantize.Read();
397#endif //EXPERIMENTAL_FIND_NOTES
398
399 // Enforce legal values
400 Validate(true);
401
403}
404
406{
409
410 // Nothing wrote these. They only varied from the linear scale bounds in-session. -- PRL
411 // gPrefs->Write(wxT("/SpectrumLog/MaxFreq"), logMinFreq);
412 // gPrefs->Write(wxT("/SpectrumLog/MinFreq"), logMaxFreq);
413
417
419
421
423
425
426 SpectrumScale.Write(static_cast<int>(scaleType));
427
428#ifndef SPECTRAL_SELECTION_GLOBAL_SWITCH
430#endif
431
432 SpectrumAlgorithm.Write(static_cast<int>(algorithm));
433
434#ifdef EXPERIMENTAL_FFT_Y_GRID
435 SpectrumYGrid.Write(fftYGrid);
436#endif //EXPERIMENTAL_FFT_Y_GRID
437
438#ifdef EXPERIMENTAL_FIND_NOTES
439 SpectrumFindNotes.Write(fftFindNotes);
440 SpectrumFindNotesMinA.Write(findNotesMinA);
441 SpectrumFindNotesN.Write(numberOfMaxima);
442 SpectrumFindNotesQuantize.Write(findNotesQuantize);
443#endif //EXPERIMENTAL_FIND_NOTES
444}
445
446// This is a temporary hack until SpectrogramSettings gets fully integrated
448{
449 if (minFreq == defaults().minFreq)
451
452 if (maxFreq == defaults().maxFreq)
454
455 if (range == defaults().range)
457
458 if (gain == defaults().gain)
460
463
466
469
472
475 }
476
478 scaleType = static_cast<ScaleType>(SpectrumScale.Read());
479
480#ifndef SPECTRAL_SELECTION_GLOBAL_SWITCH
483#endif
484
486 algorithm = static_cast<Algorithm>(SpectrumAlgorithm.Read());
487
488#ifdef EXPERIMENTAL_FFT_Y_GRID
489 if (fftYGrid == defaults().fftYGrid)
490 fftYGrid = SpectrumYGrid.Read();
491#endif //EXPERIMENTAL_FFT_Y_GRID
492
493#ifdef EXPERIMENTAL_FIND_NOTES
494 if (fftFindNotes == defaults().fftFindNotes)
495 fftFindNotes = SpectrumFindNotes.Read();
496
497 if (findNotesMinA == defaults().findNotesMinA)
498 findNotesMinA = SpectrumFindNotesMinA.Read();
499
500 if (numberOfMaxima == defaults().numberOfMaxima)
501 numberOfMaxima = SpectrumFindNotesN.Read();
502
503 if (findNotesQuantize == defaults().findNotesQuantize)
504 findNotesQuantize = SpectrumFindNotesQuantize.Read();
505#endif //EXPERIMENTAL_FIND_NOTES
506
507 // Enforce legal values
508 Validate(true);
509}
510
512{
514}
515
517{
519}
520
522{
523 return std::make_unique<SpectrogramSettings>(*this);
524}
525
527{
528 hFFT.reset();
529 window.reset();
530 dWindow.reset();
531 tWindow.reset();
532}
533
534
535namespace
536{
539 Floats &window, int which, size_t fftLen,
540 size_t padding, int windowType, size_t windowSize, double &scale)
541 {
542 // Create the requested window function
543 window = Floats{ fftLen };
544 size_t ii;
545
546 const bool extra = padding > 0;
547 wxASSERT(windowSize % 2 == 0);
548 if (extra)
549 // For windows that do not go to 0 at the edges, this improves symmetry
550 ++windowSize;
551 const size_t endOfWindow = padding + windowSize;
552 // Left and right padding
553 for (ii = 0; ii < padding; ++ii) {
554 window[ii] = 0.0;
555 window[fftLen - ii - 1] = 0.0;
556 }
557 // Default rectangular window in the middle
558 for (; ii < endOfWindow; ++ii)
559 window[ii] = 1.0;
560 // Overwrite middle as needed
561 switch (which) {
562 case WINDOW:
563 NewWindowFunc(windowType, windowSize, extra, window.get() + padding);
564 break;
565 case TWINDOW:
566 NewWindowFunc(windowType, windowSize, extra, window.get() + padding);
567 {
568 for (int jj = padding, multiplier = -(int)windowSize / 2; jj < (int)endOfWindow; ++jj, ++multiplier)
569 window[jj] *= multiplier;
570 }
571 break;
572 case DWINDOW:
573 DerivativeOfWindowFunc(windowType, windowSize, extra, window.get() + padding);
574 break;
575 default:
576 wxASSERT(false);
577 }
578 // Scale the window function to give 0dB spectrum for 0dB sine tone
579 if (which == WINDOW) {
580 scale = 0.0;
581 for (ii = padding; ii < endOfWindow; ++ii)
582 scale += window[ii];
583 if (scale > 0)
584 scale = 2.0 / scale;
585 }
586 for (ii = padding; ii < endOfWindow; ++ii)
587 window[ii] *= scale;
588 }
589}
590
592{
593 if (hFFT == NULL || window == NULL) {
594
595 double scale;
596 auto factor = ZeroPaddingFactor();
597 const auto fftLen = WindowSize() * factor;
598 const auto padding = (WindowSize() * (factor - 1)) / 2;
599
600 hFFT = GetFFT(fftLen);
601 RecreateWindow(window, WINDOW, fftLen, padding, windowType, windowSize, scale);
602 if (algorithm == algReassignment) {
603 RecreateWindow(tWindow, TWINDOW, fftLen, padding, windowType, windowSize, scale);
604 RecreateWindow(dWindow, DWINDOW, fftLen, padding, windowType, windowSize, scale);
605 }
606 }
607}
608
610{
611 unsigned size;
612 int logarithm;
613
614 logarithm = -LogMinWindowSize;
615 size = unsigned(windowSize);
616 while (size > 1)
617 size >>= 1, ++logarithm;
618 windowSize = std::max(0, std::min(NumWindowSizes - 1, logarithm));
619
620 // Choices for zero padding begin at 1
621 logarithm = 0;
622 size = unsigned(zeroPaddingFactor);
623 while (zeroPaddingFactor > 1)
624 zeroPaddingFactor >>= 1, ++logarithm;
625 zeroPaddingFactor = std::max(0,
627 logarithm
628 ));
629}
630
632{
635}
636
637float SpectrogramSettings::findBin( float frequency, float binUnit ) const
638{
639 float linearBin = frequency / binUnit;
640 if (linearBin < 0)
641 return -1;
642 else
643 return linearBin;
644}
645
647{
648//#ifndef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS
649 // return windowSize;
650//#else
652//#endif
653}
654
656{
657 // Omit the Nyquist frequency bin
658 return GetFFTLength() / 2;
659}
660
661NumberScale SpectrogramSettings::GetScale( float minFreqIn, float maxFreqIn ) const
662{
664
665 // Don't assume the correspondence of the enums will remain direct in the future.
666 // Do this switch.
667 switch (scaleType) {
668 default:
669 wxASSERT(false);
670 case stLinear:
671 type = nstLinear; break;
672 case stLogarithmic:
673 type = nstLogarithmic; break;
674 case stMel:
675 type = nstMel; break;
676 case stBark:
677 type = nstBark; break;
678 case stErb:
679 type = nstErb; break;
680 case stPeriod:
681 type = nstPeriod; break;
682 }
683
684 return NumberScale(type, minFreqIn, maxFreqIn);
685}
686
688{
689#ifdef SPECTRAL_SELECTION_GLOBAL_SWITCH
690 return Globals::Get().spectralSelection;
691#else
692 return spectralSelection;
693#endif
694}
695
697key2{ [](auto &) { return std::make_unique<SpectrogramBounds>(); } };
698
700{
701 return track.GetGroupData().Track::ChannelGroupAttachments
702 ::Get<SpectrogramBounds>(key2);
703}
704
706 const WaveTrack &track )
707{
708 return Get(const_cast<WaveTrack&>(track));
709}
710
712
714{
715 return std::make_unique<SpectrogramBounds>(*this);
716}
717
719 const WaveTrack &wt, float &min, float &max) const
720{
721 const double rate = wt.GetRate();
722
723 const auto &settings = SpectrogramSettings::Get(wt);
724 const auto type = settings.scaleType;
725
726 const float top = (rate / 2.);
727
728 float bottom;
730 bottom = 0.0f;
731 else if (type == SpectrogramSettings::stPeriod) {
732 // special case
733 const auto half = settings.GetFFTLength() / 2;
734 // EAC returns no data for below this frequency:
735 const float bin2 = rate / half;
736 bottom = bin2;
737 }
738 else
739 // logarithmic, etc.
740 bottom = 1.0f;
741
742 {
743 float spectrumMax = mSpectrumMax;
744 if (spectrumMax < 0)
745 spectrumMax = settings.maxFreq;
746 if (spectrumMax < 0)
747 max = top;
748 else
749 max = std::clamp(spectrumMax, bottom, top);
750 }
751
752 {
753 float spectrumMin = mSpectrumMin;
754 if (spectrumMin < 0)
755 spectrumMin = settings.minFreq;
756 if (spectrumMin < 0)
757 min = std::max(bottom, top / 1000.0f);
758 else
759 min = std::clamp(spectrumMin, bottom, top);
760 }
761}
wxT("CloseDown"))
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:368
void DerivativeOfWindowFunc(int whichFunction, size_t NumSamples, bool extraSample, float *in)
Definition: FFT.cpp:536
int NumWindowFuncs()
Definition: FFT.cpp:327
@ eWinFuncHann
Definition: FFT.h:114
XO("Cut/Copy/Paste")
#define XC(s, c)
Definition: Internat.h:37
NumberScaleType
Definition: NumberScale.h:18
@ nstMel
Definition: NumberScale.h:21
@ nstErb
Definition: NumberScale.h:23
@ nstPeriod
Definition: NumberScale.h:24
@ nstLinear
Definition: NumberScale.h:19
@ nstLogarithmic
Definition: NumberScale.h:20
@ nstBark
Definition: NumberScale.h:22
FileConfig * gPrefs
Definition: Prefs.cpp:70
HFFT GetFFT(size_t fftlen)
Definition: RealFFTf.cpp:104
IntSetting SpectrumMaxFreq
static const Track::ChannelGroupAttachments::RegisteredFactory key1
static const Track::ChannelGroupAttachments::RegisteredFactory key2
static Settings & settings()
Definition: TrackInfo.cpp:83
std::vector< TranslatableString > TranslatableStrings
static const int colorSchemes
Definition: AColor.h:136
This specialization of Setting for bool adds a Toggle method to negate the saved value.
Definition: Prefs.h:339
bool Write(const wxString &value)
Definition: Prefs.cpp:390
Client code makes static instance from a factory of attachments; passes it to Get or Find as a retrie...
Definition: ClientData.h:266
Specialization of Setting for double.
Definition: Prefs.h:356
bool WriteEnum(Enum value)
Definition: Prefs.h:539
Enum ReadEnum() const
Definition: Prefs.h:527
virtual bool Flush(bool bCurrentOnly=false) wxOVERRIDE
Definition: FileConfig.cpp:143
Specialization of Setting for int.
Definition: Prefs.h:349
bool Write(const T &value)
Write value to config and return true if successful.
Definition: Prefs.h:252
bool Read(T *pVar) const
overload of Read returning a boolean that is true if the value was previously defined *‍/
Definition: Prefs.h:200
void GetBounds(const WaveTrack &wt, float &min, float &max) const
PointerType Clone() const override
~SpectrogramBounds() override
static SpectrogramBounds & Get(WaveTrack &track)
Get either the global default settings, or the track's own if previously created.
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 void Reset(WaveTrack &track)
Make track lose indpendent settings and use defaults.
static const EnumValueSymbols & GetScaleNames()
static SpectrogramSettings & Get(const WaveTrack &track)
Mutative access to attachment even if the track argument is const.
SpectrogramSettings & operator=(const SpectrogramSettings &other)
static const EnumValueSymbols & GetColorSchemeNames()
PointerType Clone() const override
static ColorSchemeEnumSetting colorSchemeSetting
size_t ZeroPaddingFactor() const
float findBin(float frequency, float binUnit) const
static SpectrogramSettings & Own(WaveTrack &track)
ChannelGroupData & GetGroupData()
Definition: Track.cpp:171
A Track that contains audio waveform data.
Definition: WaveTrack.h:51
double GetRate() const override
Definition: WaveTrack.cpp:396
void RecreateWindow(Floats &window, int which, size_t fftLen, size_t padding, int windowType, size_t windowSize, double &scale)
ClientData::UniquePtr< Base > PointerType
Definition: ClientData.h:50