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.Attachments::Find<SpectrogramSettings>(key1);
114 if (pSettings)
115 return *pSettings;
116 else
118}
119
121{
122 return Get(channel.GetTrack());
123}
124
126{
127 auto &track = wc.GetTrack();
128 auto pSettings = track.Attachments::Find<SpectrogramSettings>(key1);
129 if (!pSettings) {
130 auto uSettings = std::make_unique<SpectrogramSettings>();
131 pSettings = uSettings.get();
132 track.Attachments::Assign(key1, std::move(uSettings));
133 }
134 return *pSettings;
135}
136
138{
139 wc.GetTrack().Attachments::Assign(key1, nullptr);
140}
141
143{
144 LoadPrefs();
145}
146
148 : minFreq(other.minFreq)
149 , maxFreq(other.maxFreq)
150 , range(other.range)
151 , gain(other.gain)
153 , windowType(other.windowType)
154 , windowSize(other.windowSize)
156 , colorScheme(other.colorScheme)
157 , scaleType(other.scaleType)
158#ifndef SPECTRAL_SELECTION_GLOBAL_SWITCH
160#endif
161 , algorithm(other.algorithm)
162#ifdef EXPERIMENTAL_FFT_Y_GRID
163 , fftYGrid(other.fftYGrid)
164#endif
165#ifdef EXPERIMENTAL_FIND_NOTES
166 , fftFindNotes(other.fftFindNotes)
167 , findNotesMinA(other.findNotesMinA)
168 , numberOfMaxima(other.numberOfMaxima)
169 , findNotesQuantize(other.findNotesQuantize)
170#endif
171
172 // Do not copy these!
173 , hFFT{}
174 , window{}
175 , tWindow{}
176 , dWindow{}
177{
178}
179
181{
182 if (this != &other) {
183 minFreq = other.minFreq;
184 maxFreq = other.maxFreq;
185 range = other.range;
186 gain = other.gain;
188 windowType = other.windowType;
189 windowSize = other.windowSize;
191 colorScheme = other.colorScheme;
192 scaleType = other.scaleType;
193#ifndef SPECTRAL_SELECTION_GLOBAL_SWITCH
195#endif
196 algorithm = other.algorithm;
197#ifdef EXPERIMENTAL_FFT_Y_GRID
198 fftYGrid = other.fftYGrid;
199#endif
200#ifdef EXPERIMENTAL_FIND_NOTES
201 fftFindNotes = other.fftFindNotes;
202 findNotesMinA = other.findNotesMinA;
203 numberOfMaxima = other.numberOfMaxima;
204 findNotesQuantize = other.findNotesQuantize;
205#endif
206
207 // Invalidate the caches
209 }
210 return *this;
211}
212
214{
215 static SpectrogramSettings instance;
216 return instance;
217}
218
219//static
221{
222 static const EnumValueSymbols result{
223 // Keep in correspondence with enum SpectrogramSettings::ScaleType:
224 XO("Linear") ,
225 XO("Logarithmic") ,
226 /* i18n-hint: The name of a frequency scale in psychoacoustics */
227 XO("Mel") ,
228 /* i18n-hint: The name of a frequency scale in psychoacoustics, named for Heinrich Barkhausen */
229 XO("Bark") ,
230 /* i18n-hint: The name of a frequency scale in psychoacoustics, abbreviates Equivalent Rectangular Bandwidth */
231 XO("ERB") ,
232 /* i18n-hint: Time units, that is Period = 1 / Frequency */
233 XO("Period") ,
234 };
235 return result;
236}
237
238//static
240{
241 static const EnumValueSymbols result{
242 // Keep in correspondence with enum SpectrogramSettings::ColorScheme:
243 /* i18n-hint: New color scheme for spectrograms, Roseus is proper name of the color scheme */
244 { wxT("SpecColorNew"), XC("Color (Roseus)", "spectrum prefs") },
245 /* i18n-hint: Classic color scheme(from theme) for spectrograms */
246 { wxT("SpecColorTheme"), XC("Color (classic)", "spectrum prefs") },
247 /* i18n-hint: Grayscale color scheme for spectrograms */
248 { wxT("SpecGrayscale"), XC("Grayscale", "spectrum prefs") },
249 /* i18n-hint: Inverse grayscale color scheme for spectrograms */
250 { wxT("SpecInvGrayscale"), XC("Inverse grayscale", "spectrum prefs") },
251 };
252
253 wxASSERT(csNumColorScheme == result.size());
254 static_assert(csNumColorScheme == AColor::colorSchemes, "Broken correspondence");
255
256 return result;
257}
258
259
261{
262 // Migrate old grayscale option to Color scheme choice
263 bool isGrayscale = SpectrumGrayscale.Read();
264 if (isGrayscale && !gPrefs->Read(wxT("/Spectrum/ColorScheme"), &value)) {
265 value = GetColorSchemeNames().at(csGrayscale).Internal();
266 Write(value);
267 gPrefs->Flush();
268 }
269}
270
272 wxT("/Spectrum/ColorScheme"),
274 csColorNew, // default to Color(New)
276};
277
278
279//static
281{
282 static const TranslatableStrings results{
283 // Keep in correspondence with enum SpectrogramSettings::Algorithm:
284 XO("Frequencies") ,
285 /* i18n-hint: the Reassignment algorithm for spectrograms */
286 XO("Reassignment") ,
287 /* i18n-hint: EAC abbreviates "Enhanced Autocorrelation" */
288 XO("Pitch (EAC)") ,
289 };
290 return results;
291}
292
294{
295 if (!quiet &&
296 maxFreq < 100) {
297 AudacityMessageBox( XO("Maximum frequency must be 100 Hz or above") );
298 return false;
299 }
300 else
301 maxFreq = std::max(100, maxFreq);
302
303 if (!quiet &&
304 minFreq < 0) {
305 AudacityMessageBox( XO("Minimum frequency must be at least 0 Hz") );
306 return false;
307 }
308 else
309 minFreq = std::max(0, minFreq);
310
311 if (!quiet &&
312 maxFreq <= minFreq) {
314"Minimum frequency must be less than maximum frequency") );
315 return false;
316 }
317 else
318 maxFreq = std::max(1 + minFreq, maxFreq);
319
320 if (!quiet &&
321 range <= 0) {
322 AudacityMessageBox( XO("The range must be at least 1 dB") );
323 return false;
324 }
325 else
326 range = std::max(1, range);
327
328 if (!quiet &&
329 frequencyGain < 0) {
330 AudacityMessageBox( XO("The frequency gain cannot be negative") );
331 return false;
332 }
333 else if (!quiet &&
334 frequencyGain > 60) {
336"The frequency gain must be no more than 60 dB/dec") );
337 return false;
338 }
339 else
341 std::max(0, std::min(60, frequencyGain));
342
343 // The rest are controlled by drop-down menus so they can't go wrong
344 // in the Preferences dialog, but we also come here after reading fom saved
345 // preference files, which could be or from future versions. Validate quietly.
346 windowType =
347 std::max(0, std::min(NumWindowFuncs() - 1, windowType));
348 scaleType =
349 ScaleType(std::max(0,
351 (int)(scaleType))));
353 std::max(0, std::min<int>(csNumColorScheme-1, colorScheme))
354 );
356 std::max(0, std::min((int)(algNumAlgorithms) - 1, (int)(algorithm)))
357 );
360
361 return true;
362}
363
365{
367
369
373
375
377
379
381
382 scaleType = static_cast<ScaleType>(SpectrumScale.Read());
383
384#ifndef SPECTRAL_SELECTION_GLOBAL_SWITCH
386#endif
387
388 algorithm = static_cast<Algorithm>(SpectrumAlgorithm.Read());
389
390#ifdef EXPERIMENTAL_FFT_Y_GRID
391 fftYGrid = SpectrumYGrid.Read();
392#endif //EXPERIMENTAL_FFT_Y_GRID
393
394#ifdef EXPERIMENTAL_FIND_NOTES
395 fftFindNotes = SpectrumFindNotes.Read();
396 findNotesMinA = SpectrumFindNotesMinA.Read();
397 numberOfMaxima = SpectrumFindNotesN.Read();
398 findNotesQuantize = SpectrumFindNotesQuantize.Read();
399#endif //EXPERIMENTAL_FIND_NOTES
400
401 // Enforce legal values
402 Validate(true);
403
405}
406
408{
411
412 // Nothing wrote these. They only varied from the linear scale bounds in-session. -- PRL
413 // gPrefs->Write(wxT("/SpectrumLog/MaxFreq"), logMinFreq);
414 // gPrefs->Write(wxT("/SpectrumLog/MinFreq"), logMaxFreq);
415
419
421
423
425
427
428 SpectrumScale.Write(static_cast<int>(scaleType));
429
430#ifndef SPECTRAL_SELECTION_GLOBAL_SWITCH
432#endif
433
434 SpectrumAlgorithm.Write(static_cast<int>(algorithm));
435
436#ifdef EXPERIMENTAL_FFT_Y_GRID
437 SpectrumYGrid.Write(fftYGrid);
438#endif //EXPERIMENTAL_FFT_Y_GRID
439
440#ifdef EXPERIMENTAL_FIND_NOTES
441 SpectrumFindNotes.Write(fftFindNotes);
442 SpectrumFindNotesMinA.Write(findNotesMinA);
443 SpectrumFindNotesN.Write(numberOfMaxima);
444 SpectrumFindNotesQuantize.Write(findNotesQuantize);
445#endif //EXPERIMENTAL_FIND_NOTES
446}
447
448// This is a temporary hack until SpectrogramSettings gets fully integrated
450{
451 if (minFreq == defaults().minFreq)
453
454 if (maxFreq == defaults().maxFreq)
456
457 if (range == defaults().range)
459
460 if (gain == defaults().gain)
462
465
468
471
474
477 }
478
480 scaleType = static_cast<ScaleType>(SpectrumScale.Read());
481
482#ifndef SPECTRAL_SELECTION_GLOBAL_SWITCH
485#endif
486
488 algorithm = static_cast<Algorithm>(SpectrumAlgorithm.Read());
489
490#ifdef EXPERIMENTAL_FFT_Y_GRID
491 if (fftYGrid == defaults().fftYGrid)
492 fftYGrid = SpectrumYGrid.Read();
493#endif //EXPERIMENTAL_FFT_Y_GRID
494
495#ifdef EXPERIMENTAL_FIND_NOTES
496 if (fftFindNotes == defaults().fftFindNotes)
497 fftFindNotes = SpectrumFindNotes.Read();
498
499 if (findNotesMinA == defaults().findNotesMinA)
500 findNotesMinA = SpectrumFindNotesMinA.Read();
501
502 if (numberOfMaxima == defaults().numberOfMaxima)
503 numberOfMaxima = SpectrumFindNotesN.Read();
504
505 if (findNotesQuantize == defaults().findNotesQuantize)
506 findNotesQuantize = SpectrumFindNotesQuantize.Read();
507#endif //EXPERIMENTAL_FIND_NOTES
508
509 // Enforce legal values
510 Validate(true);
511}
512
514{
516}
517
519{
521}
522
524{
525 return std::make_unique<SpectrogramSettings>(*this);
526}
527
529{
530 hFFT.reset();
531 window.reset();
532 dWindow.reset();
533 tWindow.reset();
534}
535
536
537namespace
538{
541 Floats &window, int which, size_t fftLen,
542 size_t padding, int windowType, size_t windowSize, double &scale)
543 {
544 // Create the requested window function
545 window = Floats{ fftLen };
546 size_t ii;
547
548 const bool extra = padding > 0;
549 wxASSERT(windowSize % 2 == 0);
550 if (extra)
551 // For windows that do not go to 0 at the edges, this improves symmetry
552 ++windowSize;
553 const size_t endOfWindow = padding + windowSize;
554 // Left and right padding
555 for (ii = 0; ii < padding; ++ii) {
556 window[ii] = 0.0;
557 window[fftLen - ii - 1] = 0.0;
558 }
559 // Default rectangular window in the middle
560 for (; ii < endOfWindow; ++ii)
561 window[ii] = 1.0;
562 // Overwrite middle as needed
563 switch (which) {
564 case WINDOW:
565 NewWindowFunc(windowType, windowSize, extra, window.get() + padding);
566 break;
567 case TWINDOW:
568 NewWindowFunc(windowType, windowSize, extra, window.get() + padding);
569 {
570 for (int jj = padding, multiplier = -(int)windowSize / 2; jj < (int)endOfWindow; ++jj, ++multiplier)
571 window[jj] *= multiplier;
572 }
573 break;
574 case DWINDOW:
575 DerivativeOfWindowFunc(windowType, windowSize, extra, window.get() + padding);
576 break;
577 default:
578 wxASSERT(false);
579 }
580 // Scale the window function to give 0dB spectrum for 0dB sine tone
581 if (which == WINDOW) {
582 scale = 0.0;
583 for (ii = padding; ii < endOfWindow; ++ii)
584 scale += window[ii];
585 if (scale > 0)
586 scale = 2.0 / scale;
587 }
588 for (ii = padding; ii < endOfWindow; ++ii)
589 window[ii] *= scale;
590 }
591}
592
594{
595 if (hFFT == NULL || window == NULL) {
596
597 double scale;
598 auto factor = ZeroPaddingFactor();
599 const auto fftLen = WindowSize() * factor;
600 const auto padding = (WindowSize() * (factor - 1)) / 2;
601
602 hFFT = GetFFT(fftLen);
603 RecreateWindow(window, WINDOW, fftLen, padding, windowType, windowSize, scale);
604 if (algorithm == algReassignment) {
605 RecreateWindow(tWindow, TWINDOW, fftLen, padding, windowType, windowSize, scale);
606 RecreateWindow(dWindow, DWINDOW, fftLen, padding, windowType, windowSize, scale);
607 }
608 }
609}
610
612{
613 unsigned size;
614 int logarithm;
615
616 logarithm = -LogMinWindowSize;
617 size = unsigned(windowSize);
618 while (size > 1)
619 size >>= 1, ++logarithm;
620 windowSize = std::max(0, std::min(NumWindowSizes - 1, logarithm));
621
622 // Choices for zero padding begin at 1
623 logarithm = 0;
624 size = unsigned(zeroPaddingFactor);
625 while (zeroPaddingFactor > 1)
626 zeroPaddingFactor >>= 1, ++logarithm;
627 zeroPaddingFactor = std::max(0,
629 logarithm
630 ));
631}
632
634{
637}
638
639float SpectrogramSettings::findBin( float frequency, float binUnit ) const
640{
641 float linearBin = frequency / binUnit;
642 if (linearBin < 0)
643 return -1;
644 else
645 return linearBin;
646}
647
649{
650//#ifndef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS
651 // return windowSize;
652//#else
654//#endif
655}
656
658{
659 // Omit the Nyquist frequency bin
660 return GetFFTLength() / 2;
661}
662
663NumberScale SpectrogramSettings::GetScale( float minFreqIn, float maxFreqIn ) const
664{
666
667 // Don't assume the correspondence of the enums will remain direct in the future.
668 // Do this switch.
669 switch (scaleType) {
670 default:
671 wxASSERT(false);
672 case stLinear:
673 type = nstLinear; break;
674 case stLogarithmic:
675 type = nstLogarithmic; break;
676 case stMel:
677 type = nstMel; break;
678 case stBark:
679 type = nstBark; break;
680 case stErb:
681 type = nstErb; break;
682 case stPeriod:
683 type = nstPeriod; break;
684 }
685
686 return NumberScale(type, minFreqIn, maxFreqIn);
687}
688
690{
691#ifdef SPECTRAL_SELECTION_GLOBAL_SWITCH
692 return Globals::Get().spectralSelection;
693#else
694 return spectralSelection;
695#endif
696}
697
699key2{ [](auto &) { return std::make_unique<SpectrogramBounds>(); } };
700
702{
703 return track.Attachments::Get<SpectrogramBounds>(key2);
704}
705
707 const WaveTrack &track )
708{
709 return Get(const_cast<WaveTrack&>(track));
710}
711
713{
714 return Get(channel.GetTrack());
715}
716
718{
719 return Get(const_cast<WaveChannel&>(channel));
720}
721
723
725{
726 return std::make_unique<SpectrogramBounds>(*this);
727}
728
730 const WaveChannel &wc, float &min, float &max) const
731{
732 auto &wt = wc.GetTrack();
733 const double rate = wt.GetRate();
734
735 const auto &settings = SpectrogramSettings::Get(wt);
736 const auto type = settings.scaleType;
737
738 const float top = (rate / 2.);
739
740 float bottom;
742 bottom = 0.0f;
743 else if (type == SpectrogramSettings::stPeriod) {
744 // special case
745 const auto half = settings.GetFFTLength() / 2;
746 // EAC returns no data for below this frequency:
747 const float bin2 = rate / half;
748 bottom = bin2;
749 }
750 else
751 // logarithmic, etc.
752 bottom = 1.0f;
753
754 {
755 float spectrumMax = mSpectrumMax;
756 if (spectrumMax < 0)
757 spectrumMax = settings.maxFreq;
758 if (spectrumMax < 0)
759 max = top;
760 else
761 max = std::clamp(spectrumMax, bottom, top);
762 }
763
764 {
765 float spectrumMin = mSpectrumMin;
766 if (spectrumMin < 0)
767 spectrumMin = settings.minFreq;
768 if (spectrumMin < 0)
769 min = std::max(bottom, top / 1000.0f);
770 else
771 min = std::clamp(spectrumMin, bottom, top);
772 }
773}
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
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:69
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: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:840
A Track that contains audio waveform data.
Definition: WaveTrack.h:203
double GetRate() const override
Definition: WaveTrack.cpp:798
virtual bool Flush() noexcept=0
virtual bool Read(const wxString &key, bool *value) const =0
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