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 "NumberScale.h"
20
21#include <algorithm>
22
23#include "FFT.h"
24#include "Prefs.h"
25#include "WaveTrack.h"
26
27#include <cmath>
28
29#include "BasicUI.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
107key1{ [](auto &) { return nullptr; } };
108
110{
111 auto &mutTrack = const_cast<WaveTrack&>(track);
112 auto pSettings = mutTrack.Attachments::Find<SpectrogramSettings>(key1);
113 if (pSettings)
114 return *pSettings;
115 else
117}
118
120{
121 return Get(channel.GetTrack());
122}
123
125{
126 auto &track = wc.GetTrack();
127 auto pSettings = track.Attachments::Find<SpectrogramSettings>(key1);
128 if (!pSettings) {
129 auto uSettings = std::make_unique<SpectrogramSettings>();
130 pSettings = uSettings.get();
131 track.Attachments::Assign(key1, std::move(uSettings));
132 }
133 return *pSettings;
134}
135
137{
138 wc.GetTrack().Attachments::Assign(key1, nullptr);
139}
140
142{
143 LoadPrefs();
144}
145
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 // Do not copy these!
172 , hFFT{}
173 , window{}
174 , tWindow{}
175 , dWindow{}
176{
177}
178
180{
181 if (this != &other) {
182 minFreq = other.minFreq;
183 maxFreq = other.maxFreq;
184 range = other.range;
185 gain = other.gain;
187 windowType = other.windowType;
188 windowSize = other.windowSize;
190 colorScheme = other.colorScheme;
191 scaleType = other.scaleType;
192#ifndef SPECTRAL_SELECTION_GLOBAL_SWITCH
194#endif
195 algorithm = other.algorithm;
196#ifdef EXPERIMENTAL_FFT_Y_GRID
197 fftYGrid = other.fftYGrid;
198#endif
199#ifdef EXPERIMENTAL_FIND_NOTES
200 fftFindNotes = other.fftFindNotes;
201 findNotesMinA = other.findNotesMinA;
202 numberOfMaxima = other.numberOfMaxima;
203 findNotesQuantize = other.findNotesQuantize;
204#endif
205
206 // Invalidate the caches
208 }
209 return *this;
210}
211
213{
214 static SpectrogramSettings instance;
215 return instance;
216}
217
218//static
220{
221 static const EnumValueSymbols result{
222 // Keep in correspondence with enum SpectrogramSettings::ScaleType:
223 XO("Linear") ,
224 XO("Logarithmic") ,
225 /* i18n-hint: The name of a frequency scale in psychoacoustics */
226 XO("Mel") ,
227 /* i18n-hint: The name of a frequency scale in psychoacoustics, named for Heinrich Barkhausen */
228 XO("Bark") ,
229 /* i18n-hint: The name of a frequency scale in psychoacoustics, abbreviates Equivalent Rectangular Bandwidth */
230 XO("ERB") ,
231 /* i18n-hint: Time units, that is Period = 1 / Frequency */
232 XO("Period") ,
233 };
234 return result;
235}
236
237//static
239{
240 static const EnumValueSymbols result{
241 // Keep in correspondence with enum SpectrogramSettings::ColorScheme:
242 /* i18n-hint: New color scheme for spectrograms, Roseus is proper name of the color scheme */
243 { wxT("SpecColorNew"), XC("Color (Roseus)", "spectrum prefs") },
244 /* i18n-hint: Classic color scheme(from theme) for spectrograms */
245 { wxT("SpecColorTheme"), XC("Color (classic)", "spectrum prefs") },
246 /* i18n-hint: Grayscale color scheme for spectrograms */
247 { wxT("SpecGrayscale"), XC("Grayscale", "spectrum prefs") },
248 /* i18n-hint: Inverse grayscale color scheme for spectrograms */
249 { wxT("SpecInvGrayscale"), XC("Inverse grayscale", "spectrum prefs") },
250 };
251
252 wxASSERT(csNumColorScheme == result.size());
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 BasicUI::ShowMessageBox( 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 BasicUI::ShowMessageBox( 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 BasicUI::ShowMessageBox( 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 BasicUI::ShowMessageBox( 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.Attachments::Get<SpectrogramBounds>(key2);
702}
703
705 const WaveTrack &track )
706{
707 return Get(const_cast<WaveTrack&>(track));
708}
709
711{
712 return Get(channel.GetTrack());
713}
714
716{
717 return Get(const_cast<WaveChannel&>(channel));
718}
719
721
723{
724 return std::make_unique<SpectrogramBounds>(*this);
725}
726
728 const WaveChannel &wc, float &min, float &max) const
729{
730 auto &wt = wc.GetTrack();
731 const double rate = wt.GetRate();
732
733 const auto &settings = SpectrogramSettings::Get(wt);
734 const auto type = settings.scaleType;
735
736 const float top = (rate / 2.);
737
738 float bottom;
740 bottom = 0.0f;
741 else if (type == SpectrogramSettings::stPeriod) {
742 // special case
743 const auto half = settings.GetFFTLength() / 2;
744 // EAC returns no data for below this frequency:
745 const float bin2 = rate / half;
746 bottom = bin2;
747 }
748 else
749 // logarithmic, etc.
750 bottom = 1.0f;
751
752 {
753 float spectrumMax = mSpectrumMax;
754 if (spectrumMax < 0)
755 spectrumMax = settings.maxFreq;
756 if (spectrumMax < 0)
757 max = top;
758 else
759 max = std::clamp(spectrumMax, bottom, top);
760 }
761
762 {
763 float spectrumMin = mSpectrumMin;
764 if (spectrumMin < 0)
765 spectrumMin = settings.minFreq;
766 if (spectrumMin < 0)
767 min = std::max(bottom, top / 1000.0f);
768 else
769 min = std::clamp(spectrumMin, bottom, top);
770 }
771}
wxT("CloseDown"))
Toolkit-neutral facade for basic user interface services.
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
audacity::BasicSettings * gPrefs
Definition: Prefs.cpp:68
HFFT GetFFT(size_t fftlen)
Definition: RealFFTf.cpp:104
IntSetting SpectrumMaxFreq
static const ChannelGroup::Attachments::RegisteredFactory key2
static const ChannelGroup::Attachments::RegisteredFactory key1
static Settings & settings()
Definition: TrackInfo.cpp:51
std::vector< TranslatableString > TranslatableStrings
This specialization of Setting for bool adds a Toggle method to negate the saved value.
Definition: Prefs.h:346
bool Write(const wxString &value)
Definition: Prefs.cpp:424
Client code makes static instance from a factory of attachments; passes it to Get or Find as a retrie...
Definition: ClientData.h:275
Specialization of Setting for double.
Definition: Prefs.h:363
bool WriteEnum(Enum value)
Definition: Prefs.h:546
Enum ReadEnum() const
Definition: Prefs.h:534
Specialization of Setting for int.
Definition: Prefs.h:356
bool Write(const T &value)
Write value to config and return true if successful.
Definition: Prefs.h:259
bool Read(T *pVar) const
overload of Read returning a boolean that is true if the value was previously defined *‍/
Definition: Prefs.h:207
PointerType Clone() const override
~SpectrogramBounds() override
void GetBounds(const WaveChannel &wc, float &min, float &max) const
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 const EnumValueSymbols & GetScaleNames()
static SpectrogramSettings & Get(const WaveTrack &track)
SpectrogramSettings & operator=(const SpectrogramSettings &other)
static const EnumValueSymbols & GetColorSchemeNames()
static SpectrogramSettings & Own(WaveChannel &wc)
PointerType Clone() const override
static ColorSchemeEnumSetting colorSchemeSetting
size_t ZeroPaddingFactor() const
float findBin(float frequency, float binUnit) const
static void Reset(WaveChannel &channel)
Make channel lose indpendent settings and use defaults.
WaveTrack & GetTrack()
Definition: WaveTrack.h:841
A Track that contains audio waveform data.
Definition: WaveTrack.h:203
double GetRate() const override
Definition: WaveTrack.cpp:821
virtual bool Flush() noexcept=0
virtual bool Read(const wxString &key, bool *value) const =0
MessageBoxResult ShowMessageBox(const TranslatableString &message, MessageBoxOptions options={})
Show a modal message box with either Ok or Yes and No, and optionally Cancel.
Definition: BasicUI.h:287
void RecreateWindow(Floats &window, int which, size_t fftLen, size_t padding, int windowType, size_t windowSize, double &scale)
Owner< Base > PointerType
Definition: ClientData.h:54