diff --git a/src/prefs/SpectrogramSettings.cpp b/src/prefs/SpectrogramSettings.cpp index ceffd4471..d67f1ed41 100644 --- a/src/prefs/SpectrogramSettings.cpp +++ b/src/prefs/SpectrogramSettings.cpp @@ -15,6 +15,10 @@ Paul Licameli #include "../Audacity.h" #include "SpectrogramSettings.h" + +#include +#include + #include "../FFT.h" #include "../Prefs.h" #include "../RealFFTf.h" @@ -26,7 +30,7 @@ SpectrogramSettings::SpectrogramSettings() : hFFT(0) , window(0) { - UpdatePrefs(); + LoadPrefs(); } SpectrogramSettings::SpectrogramSettings(const SpectrogramSettings &other) @@ -98,49 +102,82 @@ SpectrogramSettings& SpectrogramSettings::defaults() return instance; } -void SpectrogramSettings::UpdatePrefs() +bool SpectrogramSettings::Validate(bool quiet) { - bool destroy = false; + if (!quiet && + maxFreq < 100) { + wxMessageBox(_("Maximum frequency must be 100 Hz or above")); + return false; + } + else + maxFreq = std::max(100, maxFreq); + + if (!quiet && + minFreq < 0) { + wxMessageBox(_("Minimum frequency must be at least 0 Hz")); + return false; + } + else + minFreq = std::max(0, minFreq); + + if (!quiet && + maxFreq <= minFreq) { + wxMessageBox(_("Minimum frequency must be less than maximum frequency")); + return false; + } + else + maxFreq = std::max(1 + minFreq, maxFreq); + + if (!quiet && + range <= 0) { + wxMessageBox(_("The range must be at least 1 dB")); + return false; + } + else + range = std::max(1, range); + + if (!quiet && + frequencyGain < 0) { + wxMessageBox(_("The frequency gain cannot be negative")); + return false; + } + else if (!quiet && + frequencyGain > 60) { + wxMessageBox(_("The frequency gain must be no more than 60 dB/dec")); + return false; + } + else + frequencyGain = + std::max(0, std::min(60, frequencyGain)); + + // The rest are controlled by drop-down menus so they can't go wrong + // in the Preferences dialog, but we also come here after reading fom saved + // preference files, which could be or from future versions. Validate quietly. + windowType = + std::max(0, std::min(NumWindowFuncs() - 1, windowType)); + ConvertToEnumeratedWindowSizes(); + ConvertToActualWindowSizes(); + + return true; +} + +void SpectrogramSettings::LoadPrefs() +{ + minFreq = gPrefs->Read(wxT("/Spectrum/MinFreq"), 0L); - minFreq = gPrefs->Read(wxT("/Spectrum/MinFreq"), -1L); maxFreq = gPrefs->Read(wxT("/Spectrum/MaxFreq"), 8000L); - // These preferences are not written anywhere in the program as of now, - // but I keep this legacy here. Who knows, someone might edit prefs files - // directly. PRL - logMaxFreq = gPrefs->Read(wxT("/SpectrumLog/MaxFreq"), -1); - if (logMaxFreq < 0) - logMaxFreq = maxFreq; - logMinFreq = gPrefs->Read(wxT("/SpectrumLog/MinFreq"), -1); - if (logMinFreq < 0) - logMinFreq = minFreq; - if (logMinFreq < 1) - logMinFreq = 1; - range = gPrefs->Read(wxT("/Spectrum/Range"), 80L); gain = gPrefs->Read(wxT("/Spectrum/Gain"), 20L); frequencyGain = gPrefs->Read(wxT("/Spectrum/FrequencyGain"), 0L); - const int newWindowSize = gPrefs->Read(wxT("/Spectrum/FFTSize"), 256); - if (newWindowSize != windowSize) { - destroy = true; - windowSize = newWindowSize; - } + windowSize = gPrefs->Read(wxT("/Spectrum/FFTSize"), 256); #ifdef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS - const int newZeroPaddingFactor = gPrefs->Read(wxT("/Spectrum/ZeroPaddingFactor"), 1); - if (newZeroPaddingFactor != zeroPaddingFactor) { - destroy = true; - zeroPaddingFactor = newZeroPaddingFactor; - } + zeroPaddingFactor = gPrefs->Read(wxT("/Spectrum/ZeroPaddingFactor"), 1); #endif - int newWindowType; - gPrefs->Read(wxT("/Spectrum/WindowType"), &newWindowType, 3); - if (newWindowType != windowType) { - destroy = true; - windowType = newWindowType; - } + gPrefs->Read(wxT("/Spectrum/WindowType"), &windowType, eWinFuncHanning); isGrayscale = (gPrefs->Read(wxT("/Spectrum/Grayscale"), 0L) != 0); @@ -155,8 +192,64 @@ void SpectrogramSettings::UpdatePrefs() findNotesQuantize = (gPrefs->Read(wxT("/Spectrum/FindNotesQuantize"), 0L) != 0); #endif //EXPERIMENTAL_FIND_NOTES - if (destroy) - DestroyWindows(); + // Enforce legal values + Validate(true); + + // These preferences are not written anywhere in the program as of now, + // but I keep this legacy here. Who knows, someone might edit prefs files + // directly. PRL + logMinFreq = gPrefs->Read(wxT("/SpectrumLog/MinFreq"), -1); + if (logMinFreq < 0) + logMinFreq = minFreq; + if (logMinFreq < 1) + logMinFreq = 1; + logMaxFreq = gPrefs->Read(wxT("/SpectrumLog/MaxFreq"), -1); + if (logMaxFreq < 0) + logMaxFreq = maxFreq; + logMaxFreq = + std::max(logMinFreq + 1, logMaxFreq); + + InvalidateCaches(); +} + +void SpectrogramSettings::SavePrefs() +{ + gPrefs->Write(wxT("/Spectrum/MinFreq"), minFreq); + gPrefs->Write(wxT("/Spectrum/MaxFreq"), maxFreq); + + // Nothing wrote these. They only varied from the linear scale bounds in-session. -- PRL + // gPrefs->Write(wxT("/SpectrumLog/MaxFreq"), logMinFreq); + // gPrefs->Write(wxT("/SpectrumLog/MinFreq"), logMaxFreq); + + gPrefs->Write(wxT("/Spectrum/Range"), range); + gPrefs->Write(wxT("/Spectrum/Gain"), gain); + gPrefs->Write(wxT("/Spectrum/FrequencyGain"), frequencyGain); + + gPrefs->Write(wxT("/Spectrum/FFTSize"), windowSize); + +#ifdef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS + gPrefs->Write(wxT("/Spectrum/ZeroPaddingFactor"), zeroPaddingFactor); +#endif + + gPrefs->Write(wxT("/Spectrum/WindowType"), windowType); + + gPrefs->Write(wxT("/Spectrum/Grayscale"), isGrayscale); + +#ifdef EXPERIMENTAL_FFT_Y_GRID + gPrefs->Write(wxT("/Spectrum/FFTYGrid"), fftYGrid); +#endif //EXPERIMENTAL_FFT_Y_GRID + +#ifdef EXPERIMENTAL_FIND_NOTES + gPrefs->Write(wxT("/Spectrum/FFTFindNotes"), fftFindNotes); + gPrefs->Write(wxT("/Spectrum/FindNotesMinA"), findNotesMinA); + gPrefs->Write(wxT("/Spectrum/FindNotesN"), numberOfMaxima); + gPrefs->Write(wxT("/Spectrum/FindNotesQuantize"), findNotesQuantize); +#endif //EXPERIMENTAL_FIND_NOTES +} + +void SpectrogramSettings::InvalidateCaches() +{ + DestroyWindows(); } SpectrogramSettings::~SpectrogramSettings() @@ -257,6 +350,38 @@ void SpectrogramSettings::CacheWindows() const #endif // EXPERIMENTAL_USE_REALFFTF } +void SpectrogramSettings::ConvertToEnumeratedWindowSizes() +{ + unsigned size; + int logarithm; + + logarithm = -LogMinWindowSize; + size = unsigned(windowSize); + while (size > 1) + size >>= 1, ++logarithm; + windowSize = std::max(0, std::min(NumWindowSizes - 1, logarithm)); + +#ifdef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS + // Choices for zero padding begin at 1 + logarithm = 0; + size = unsigned(zeroPaddingFactor); + while (zeroPaddingFactor > 1) + zeroPaddingFactor >>= 1, ++logarithm; + zeroPaddingFactor = std::max(0, + std::min(LogMaxWindowSize - (windowSize + LogMinWindowSize), + logarithm + )); +#endif +} + +void SpectrogramSettings::ConvertToActualWindowSizes() +{ + windowSize = 1 << (windowSize + LogMinWindowSize); +#ifdef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS + zeroPaddingFactor = 1 << zeroPaddingFactor; +#endif +} + int SpectrogramSettings::GetMinFreq(double rate) const { const int top = lrint(rate / 2.); diff --git a/src/prefs/SpectrogramSettings.h b/src/prefs/SpectrogramSettings.h index 3d13aa528..f67e47b70 100644 --- a/src/prefs/SpectrogramSettings.h +++ b/src/prefs/SpectrogramSettings.h @@ -14,19 +14,39 @@ Paul Licameli #include "../Experimental.h" struct FFTParam; +class SpectrumPrefs; class SpectrogramSettings { + friend class SpectrumPrefs; public: + + enum { + LogMinWindowSize = 3, + LogMaxWindowSize = 15, + + NumWindowSizes = LogMaxWindowSize - LogMinWindowSize + 1, + }; + static SpectrogramSettings &defaults(); SpectrogramSettings(); SpectrogramSettings(const SpectrogramSettings &other); SpectrogramSettings& operator= (const SpectrogramSettings &other); ~SpectrogramSettings(); - void UpdatePrefs(); + bool IsDefault() const + { + return this == &defaults(); + } + + bool Validate(bool quiet); + void LoadPrefs(); + void SavePrefs(); + void InvalidateCaches(); void DestroyWindows(); void CacheWindows() const; + void ConvertToEnumeratedWindowSizes(); + void ConvertToActualWindowSizes(); private: int minFreq; @@ -78,4 +98,5 @@ public: mutable float *window; #endif }; + #endif diff --git a/src/prefs/SpectrumPrefs.cpp b/src/prefs/SpectrumPrefs.cpp index 766a43747..6cee1e767 100644 --- a/src/prefs/SpectrumPrefs.cpp +++ b/src/prefs/SpectrumPrefs.cpp @@ -21,26 +21,30 @@ #include #include -#include "../Prefs.h" +#include "../FFT.h" #include "../Project.h" #include "../ShuttleGui.h" -#include "../FFT.h" +#include "../WaveTrack.h" #include -SpectrumPrefs::SpectrumPrefs(wxWindow * parent) +SpectrumPrefs::SpectrumPrefs(wxWindow * parent, WaveTrack *wt) : PrefsPanel(parent, _("Spectrograms")) +, mWt(wt) { - int windowSize = gPrefs->Read(wxT("/Spectrum/FFTSize"), 256); - Populate(windowSize); + SpectrogramSettings *const pSettings = mWt + ? &mWt->GetIndependentSpectrogramSettings() + : &SpectrogramSettings::defaults(); + + mTempSettings = *pSettings; + mTempSettings.ConvertToEnumeratedWindowSizes(); + Populate(pSettings->windowSize); } SpectrumPrefs::~SpectrumPrefs() { } -enum { maxWindowSize = 32768 }; - enum { ID_WINDOW_SIZE = 10001, #ifdef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS @@ -63,26 +67,18 @@ void SpectrumPrefs::Populate(int windowSize) mSizeChoices.Add(wxT("8192")); mSizeChoices.Add(wxT("16384")); mSizeChoices.Add(_("32768 - most narrowband")); - - int lastCode = 0; - for (size_t i = 0; i < mSizeChoices.GetCount(); i++) { - mSizeCodes.Add(lastCode = 1 << (i + 3)); - } - wxASSERT(lastCode == maxWindowSize); + wxASSERT(mSizeChoices.size() == SpectrogramSettings::NumWindowSizes); PopulatePaddingChoices(windowSize); for (int i = 0; i < NumWindowFuncs(); i++) { mTypeChoices.Add(WindowFuncName(i)); - mTypeCodes.Add(i); } //------------------------- Main section -------------------- // Now construct the GUI itself. - // Use 'eIsCreatingFromPrefs' so that the GUI is - // initialised with values from gPrefs. - ShuttleGui S(this, eIsCreatingFromPrefs); + ShuttleGui S(this, eIsCreating); PopulateOrExchange(S); // ----------------------- End of main section -------------- } @@ -107,14 +103,12 @@ void SpectrumPrefs::PopulatePaddingChoices(int windowSize) pPaddingSizeControl->Clear(); } - mZeroPaddingCodes.Clear(); - int padding = 1; int numChoices = 0; + const int maxWindowSize = 1 << (SpectrogramSettings::LogMaxWindowSize); while (windowSize <= maxWindowSize) { const wxString numeral = wxString::Format(wxT("%d"), padding); mZeroPaddingChoices.Add(numeral); - mZeroPaddingCodes.Add(padding); if (pPaddingSizeControl) pPaddingSizeControl->Append(numeral); windowSize <<= 1; @@ -138,25 +132,19 @@ void SpectrumPrefs::PopulateOrExchange(ShuttleGui & S) S.StartMultiColumn(2); { S.Id(ID_WINDOW_SIZE).TieChoice(_("Window &size:"), - wxT("/Spectrum/FFTSize"), - 256, - mSizeChoices, - mSizeCodes); + mTempSettings.windowSize, + &mSizeChoices); S.SetSizeHints(mSizeChoices); S.TieChoice(_("Window &type:"), - wxT("/Spectrum/WindowType"), - 3, - mTypeChoices, - mTypeCodes); + mTempSettings.windowType, + &mTypeChoices); S.SetSizeHints(mTypeChoices); #ifdef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS S.Id(ID_PADDING_SIZE).TieChoice(_("&Zero padding factor") + wxString(wxT(":")), - wxT("/Spectrum/ZeroPaddingFactor"), - mZeroPaddingChoice, - mZeroPaddingChoices, - mZeroPaddingCodes); + mTempSettings.zeroPaddingFactor, + &mZeroPaddingChoices); S.SetSizeHints(mZeroPaddingChoices); #endif } @@ -170,44 +158,37 @@ void SpectrumPrefs::PopulateOrExchange(ShuttleGui & S) { mMinFreq = S.TieNumericTextBox(_("Mi&nimum Frequency (Hz):"), - wxT("/Spectrum/MinFreq"), - 0, + mTempSettings.minFreq, 12); mMaxFreq = S.TieNumericTextBox(_("Ma&ximum Frequency (Hz):"), - wxT("/Spectrum/MaxFreq"), - 8000, + mTempSettings.maxFreq, 12); mGain = S.TieNumericTextBox(_("&Gain (dB):"), - wxT("/Spectrum/Gain"), - 20, + mTempSettings.gain, 8); mRange = S.TieNumericTextBox(_("&Range (dB):"), - wxT("/Spectrum/Range"), - 80, + mTempSettings.range, 8); mFrequencyGain = S.TieNumericTextBox(_("Frequency g&ain (dB/dec):"), - wxT("/Spectrum/FrequencyGain"), - 0, + mTempSettings.frequencyGain, 4); } S.EndTwoColumn(); S.TieCheckBox(_("S&how the spectrum using grayscale colors"), - wxT("/Spectrum/Grayscale"), - false); + mTempSettings.isGrayscale); #ifdef EXPERIMENTAL_FFT_Y_GRID S.TieCheckBox(_("Show a grid along the &Y-axis"), - wxT("/Spectrum/FFTYGrid"), - false); + mTempSettings.fftYGrid); #endif //EXPERIMENTAL_FFT_Y_GRID } S.EndStatic(); @@ -220,25 +201,21 @@ void SpectrumPrefs::PopulateOrExchange(ShuttleGui & S) { mFindNotesMinA = S.TieNumericTextBox(_("Minimum Amplitude (dB):"), - wxT("/Spectrum/FindNotesMinA"), - -30L, + mTempSettings.fftFindNotes, 8); mFindNotesN = S.TieNumericTextBox(_("Max. Number of Notes (1..128):"), - wxT("/Spectrum/FindNotesN"), - 5L, + mTempSettings.findNotesMinA, 8); } S.EndTwoColumn(); S.TieCheckBox(_("&Find Notes"), - wxT("/Spectrum/FFTFindNotes"), - false); + mTempSettings.numberOfMaxima); S.TieCheckBox(_("&Quantize Notes"), - wxT("/Spectrum/FindNotesQuantize"), - false); + mTempSettings.findNotesQuantize); } S.EndStatic(); #endif //EXPERIMENTAL_FIND_NOTES @@ -246,59 +223,40 @@ void SpectrumPrefs::PopulateOrExchange(ShuttleGui & S) bool SpectrumPrefs::Validate() { + // Do checking for whole numbers + + // ToDo: use wxIntegerValidator when available + long maxFreq; if (!mMaxFreq->GetValue().ToLong(&maxFreq)) { wxMessageBox(_("The maximum frequency must be an integer")); return false; } - if (maxFreq < 100) { - wxMessageBox(_("Maximum frequency must be 100 Hz or above")); - return false; - } long minFreq; if (!mMinFreq->GetValue().ToLong(&minFreq)) { wxMessageBox(_("The minimum frequency must be an integer")); return false; } - if (minFreq < 0) { - wxMessageBox(_("Minimum frequency must be at least 0 Hz")); - return false; - } - - if (maxFreq < minFreq) { - wxMessageBox(_("Minimum frequency must be less than maximum frequency")); - return false; - } long gain; if (!mGain->GetValue().ToLong(&gain)) { wxMessageBox(_("The gain must be an integer")); return false; } + long range; if (!mRange->GetValue().ToLong(&range)) { wxMessageBox(_("The range must be a positive integer")); return false; } - if (range <= 0) { - wxMessageBox(_("The range must be at least 1 dB")); - return false; - } long frequencygain; if (!mFrequencyGain->GetValue().ToLong(&frequencygain)) { wxMessageBox(_("The frequency gain must be an integer")); return false; } - if (frequencygain < 0) { - wxMessageBox(_("The frequency gain cannot be negative")); - return false; - } - if (frequencygain > 60) { - wxMessageBox(_("The frequency gain must be no more than 60 dB/dec")); - return false; - } + #ifdef EXPERIMENTAL_FIND_NOTES long findNotesMinA; if (!mFindNotesMinA->GetValue().ToLong(&findNotesMinA)) { @@ -317,15 +275,52 @@ bool SpectrumPrefs::Validate() } #endif //EXPERIMENTAL_FIND_NOTES - return true; + ShuttleGui S(this, eIsGettingFromDialog); + PopulateOrExchange(S); + + // Delegate range checking to SpectrogramSettings class + mTempSettings.ConvertToActualWindowSizes(); + const bool result = mTempSettings.Validate(false); + mTempSettings.ConvertToEnumeratedWindowSizes(); + return result; } bool SpectrumPrefs::Apply() { - ShuttleGui S(this, eIsSavingToPrefs); + const bool isOpenPage = this->IsShown(); + + WaveTrack *const partner = + mWt ? static_cast(mWt->GetLink()) : 0; + + ShuttleGui S(this, eIsGettingFromDialog); PopulateOrExchange(S); - SpectrogramSettings::defaults().UpdatePrefs(); + mTempSettings.ConvertToActualWindowSizes(); + if (mWt) { + SpectrogramSettings *pSettings = + &mWt->GetIndependentSpectrogramSettings(); + *pSettings = mTempSettings; + if (partner) { + pSettings = &partner->GetIndependentSpectrogramSettings(); + *pSettings = mTempSettings; + } + } + else { + SpectrogramSettings *const pSettings = + &SpectrogramSettings::defaults(); + *pSettings = mTempSettings; + pSettings->SavePrefs(); + } + mTempSettings.ConvertToEnumeratedWindowSizes(); + + if (mWt && isOpenPage) { + // Future: open page will determine the track view type + /* + mWt->SetDisplay(WaveTrack::Spectrum); + if (partner) + partner->SetDisplay(WaveTrack::Spectrum); + */ + } return true; } @@ -334,7 +329,8 @@ void SpectrumPrefs::OnWindowSize(wxCommandEvent &) { wxChoice *const pWindowSizeControl = static_cast(wxWindow::FindWindowById(ID_WINDOW_SIZE, this)); - int windowSize = 1 << (pWindowSizeControl->GetSelection() + 3); + int windowSize = 1 << + (pWindowSizeControl->GetSelection() + SpectrogramSettings::LogMinWindowSize); PopulatePaddingChoices(windowSize); } @@ -342,7 +338,12 @@ BEGIN_EVENT_TABLE(SpectrumPrefs, PrefsPanel) EVT_CHOICE(ID_WINDOW_SIZE, SpectrumPrefs::OnWindowSize) END_EVENT_TABLE() +SpectrumPrefsFactory::SpectrumPrefsFactory(WaveTrack *wt) +: mWt(wt) +{ +} + PrefsPanel *SpectrumPrefsFactory::Create(wxWindow *parent) { - return new SpectrumPrefs(parent); + return new SpectrumPrefs(parent, mWt); } diff --git a/src/prefs/SpectrumPrefs.h b/src/prefs/SpectrumPrefs.h index 8e316f36a..07f60cdae 100644 --- a/src/prefs/SpectrumPrefs.h +++ b/src/prefs/SpectrumPrefs.h @@ -33,11 +33,13 @@ class wxTextCtrl; struct FFTParam; class ShuttleGui; +class SpectrogramSettings; +class WaveTrack; class SpectrumPrefs:public PrefsPanel { public: - SpectrumPrefs(wxWindow * parent); + SpectrumPrefs(wxWindow * parent, WaveTrack *wt); virtual ~SpectrumPrefs(); virtual bool Apply(); virtual bool Validate(); @@ -50,6 +52,8 @@ class SpectrumPrefs:public PrefsPanel void OnWindowSize(wxCommandEvent &event); DECLARE_EVENT_TABLE() + WaveTrack *const mWt; + wxTextCtrl *mMinFreq; wxTextCtrl *mMaxFreq; wxTextCtrl *mGain; @@ -57,28 +61,31 @@ class SpectrumPrefs:public PrefsPanel wxTextCtrl *mFrequencyGain; wxArrayString mSizeChoices; - wxArrayInt mSizeCodes; #ifdef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS int mZeroPaddingChoice; wxArrayString mZeroPaddingChoices; - wxArrayInt mZeroPaddingCodes; #endif wxArrayString mTypeChoices; - wxArrayInt mTypeCodes; #ifdef EXPERIMENTAL_FIND_NOTES wxTextCtrl *mFindNotesMinA; wxTextCtrl *mFindNotesN; #endif + + SpectrogramSettings mTempSettings; }; class SpectrumPrefsFactory : public PrefsPanelFactory { public: + explicit SpectrumPrefsFactory(WaveTrack *wt = 0); virtual PrefsPanel *Create(wxWindow *parent); + +private: + WaveTrack *const mWt; }; #endif