/********************************************************************** Audacity: A Digital Audio Editor SpectrumPrefs.cpp Dominic Mazzoni James Crook *******************************************************************//** \class SpectrumPrefs \brief A PrefsPanel for spectrum settings. *//*******************************************************************/ #include "../Audacity.h" #include "SpectrumPrefs.h" #include "../Experimental.h" #include #include #include #include #include #include "../FFT.h" #include "../Project.h" #include "../ShuttleGui.h" #include "../TrackPanel.h" #include "../WaveTrack.h" #include "../tracks/playabletrack/wavetrack/ui/WaveTrackView.h" #include #include "../widgets/AudacityMessageBox.h" SpectrumPrefs::SpectrumPrefs(wxWindow * parent, wxWindowID winid, WaveTrack *wt) : PrefsPanel(parent, winid, wt ? XO("Spectrogram Settings") : XO("Spectrograms")) , mWt(wt) , mPopulating(false) { if (mWt) { SpectrogramSettings &settings = wt->GetSpectrogramSettings(); mOrigDefaulted = mDefaulted = (&SpectrogramSettings::defaults() == &settings); mTempSettings = mOrigSettings = settings; wt->GetSpectrumBounds(&mOrigMin, &mOrigMax); mTempSettings.maxFreq = mOrigMax; mTempSettings.minFreq = mOrigMin; mOrigPlacements = WaveTrackView::Get( *mWt ).SavePlacements(); } else { mTempSettings = mOrigSettings = SpectrogramSettings::defaults(); mOrigDefaulted = mDefaulted = false; } const auto windowSize = mTempSettings.WindowSize(); mTempSettings.ConvertToEnumeratedWindowSizes(); Populate(windowSize); } SpectrumPrefs::~SpectrumPrefs() { if (!mCommitted) Rollback(); } ComponentInterfaceSymbol SpectrumPrefs::GetSymbol() { return SPECTRUM_PREFS_PLUGIN_SYMBOL; } TranslatableString SpectrumPrefs::GetDescription() { return XO("Preferences for Spectrum"); } wxString SpectrumPrefs::HelpPageName() { // Currently (May2017) Spectrum Settings is the only preferences // we ever display in a dialog on its own without others. // We do so when it is configuring spectrums for a track. // Because this happens, we want to visit a different help page. // So we change the page name in the case of a page on its own. return mWt ? "Spectrogram_Settings" : "Spectrograms_Preferences"; } enum { ID_WINDOW_SIZE = 10001, #ifdef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS ID_WINDOW_TYPE, ID_PADDING_SIZE, ID_SCALE, ID_ALGORITHM, ID_MINIMUM, ID_MAXIMUM, ID_GAIN, ID_RANGE, ID_FREQUENCY_GAIN, ID_GRAYSCALE, ID_SPECTRAL_SELECTION, #endif ID_DEFAULTS, }; void SpectrumPrefs::Populate(size_t windowSize) { PopulatePaddingChoices(windowSize); for (int i = 0; i < NumWindowFuncs(); i++) { mTypeChoices.push_back( WindowFuncName(i) ); } //------------------------- Main section -------------------- // Now construct the GUI itself. ShuttleGui S(this, eIsCreatingFromPrefs); PopulateOrExchange(S); // ----------------------- End of main section -------------- } void SpectrumPrefs::PopulatePaddingChoices(size_t windowSize) { #ifdef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS mZeroPaddingChoice = 1; // The choice of window size restricts the choice of padding. // So the padding menu might grow or shrink. // If pPaddingSizeControl is NULL, we have not yet tied the choice control. // If it is not NULL, we rebuild the control by hand. // I don't yet know an easier way to do this with ShuttleGUI functions. // PRL wxChoice *const pPaddingSizeControl = static_cast(wxWindow::FindWindowById(ID_PADDING_SIZE, this)); if (pPaddingSizeControl) { mZeroPaddingChoice = pPaddingSizeControl->GetSelection(); pPaddingSizeControl->Clear(); } unsigned padding = 1; int numChoices = 0; const size_t maxWindowSize = 1 << (SpectrogramSettings::LogMaxWindowSize); while (windowSize <= maxWindowSize) { const auto numeral = wxString::Format(wxT("%d"), padding); mZeroPaddingChoices.push_back( Verbatim( numeral ) ); if (pPaddingSizeControl) pPaddingSizeControl->Append(numeral); windowSize <<= 1; padding <<= 1; ++numChoices; } mZeroPaddingChoice = std::min(mZeroPaddingChoice, numChoices - 1); if (pPaddingSizeControl) pPaddingSizeControl->SetSelection(mZeroPaddingChoice); #endif } void SpectrumPrefs::PopulateOrExchange(ShuttleGui & S) { mPopulating = true; S.SetBorder(2); S.StartScroller(); { // S.StartStatic(XO("Track Settings")); // { mDefaultsCheckbox = 0; if (mWt) { /* i18n-hint: use is a verb */ mDefaultsCheckbox = S.Id(ID_DEFAULTS).TieCheckBox(XO("&Use Preferences"), mDefaulted); } S.StartMultiColumn(2,wxEXPAND); { S.SetStretchyCol( 0 ); S.SetStretchyCol( 1 ); S.StartStatic(XO("Scale"),1); { S.StartMultiColumn(2,wxEXPAND); { S.SetStretchyCol( 0 ); S.SetStretchyCol( 1 ); S.Id(ID_SCALE).TieChoice(XO("S&cale:"), mTempSettings.scaleType, SpectrogramSettings::GetScaleNames() ); mMinFreq = S.Id(ID_MINIMUM).TieNumericTextBox(XO("Mi&n Frequency (Hz):"), mTempSettings.minFreq, 12); mMaxFreq = S.Id(ID_MAXIMUM).TieNumericTextBox(XO("Ma&x Frequency (Hz):"), mTempSettings.maxFreq, 12); } S.EndMultiColumn(); } S.EndStatic(); S.StartStatic(XO("Colors"),1); { S.StartMultiColumn(2,wxEXPAND); { S.SetStretchyCol( 0 ); S.SetStretchyCol( 1 ); mGain = S.Id(ID_GAIN).TieNumericTextBox(XO("&Gain (dB):"), mTempSettings.gain, 8); mRange = S.Id(ID_RANGE).TieNumericTextBox(XO("&Range (dB):"), mTempSettings.range, 8); mFrequencyGain = S.Id(ID_FREQUENCY_GAIN).TieNumericTextBox(XO("High &boost (dB/dec):"), mTempSettings.frequencyGain, 8); } S.EndMultiColumn(); S.Id(ID_GRAYSCALE).TieCheckBox(XO("Gra&yscale"), mTempSettings.isGrayscale); } S.EndStatic(); } S.EndMultiColumn(); S.StartStatic(XO("Algorithm")); { S.StartMultiColumn(2); { mAlgorithmChoice = S.Id(ID_ALGORITHM).TieChoice(XO("A&lgorithm:"), mTempSettings.algorithm, SpectrogramSettings::GetAlgorithmNames() ); S.Id(ID_WINDOW_SIZE).TieChoice(XO("Window &size:"), mTempSettings.windowSize, { XO("8 - most wideband"), XO("16"), XO("32"), XO("64"), XO("128"), XO("256"), XO("512"), XO("1024 - default"), XO("2048"), XO("4096"), XO("8192"), XO("16384"), XO("32768 - most narrowband"), } ); S.Id(ID_WINDOW_TYPE).TieChoice(XO("Window &type:"), mTempSettings.windowType, mTypeChoices); #ifdef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS mZeroPaddingChoiceCtrl = S.Id(ID_PADDING_SIZE).TieChoice(XO("&Zero padding factor:"), mTempSettings.zeroPaddingFactor, mZeroPaddingChoices); #endif } S.EndMultiColumn(); } S.EndStatic(); #ifndef SPECTRAL_SELECTION_GLOBAL_SWITCH S.Id(ID_SPECTRAL_SELECTION).TieCheckBox(XO("Ena&ble Spectral Selection"), mTempSettings.spectralSelection); #endif #ifdef EXPERIMENTAL_FFT_Y_GRID S.TieCheckBox(XO("Show a grid along the &Y-axis"), mTempSettings.fftYGrid); #endif //EXPERIMENTAL_FFT_Y_GRID #ifdef EXPERIMENTAL_FIND_NOTES /* i18n-hint: FFT stands for Fast Fourier Transform and probably shouldn't be translated*/ S.StartStatic(XO("FFT Find Notes")); { S.StartTwoColumn(); { mFindNotesMinA = S.TieNumericTextBox(XO("Minimum Amplitude (dB):"), mTempSettings.findNotesMinA, 8); mFindNotesN = S.TieNumericTextBox(XO("Max. Number of Notes (1..128):"), mTempSettings.numberOfMaxima, 8); } S.EndTwoColumn(); S.TieCheckBox(XO("&Find Notes"), mTempSettings.fftFindNotes); S.TieCheckBox(XO("&Quantize Notes"), mTempSettings.findNotesQuantize); } S.EndStatic(); #endif //EXPERIMENTAL_FIND_NOTES // S.EndStatic(); #ifdef SPECTRAL_SELECTION_GLOBAL_SWITCH S.StartStatic(XO("Global settings")); { S.TieCheckBox(XO("Ena&ble spectral selection"), SpectrogramSettings::Globals::Get().spectralSelection); } S.EndStatic(); #endif } S.EndScroller(); // Enabling and disabling belongs outside this function. if( S.GetMode() != eIsGettingMetadata ) EnableDisableSTFTOnlyControls(); mPopulating = false; } bool SpectrumPrefs::Validate() { // Do checking for whole numbers // ToDo: use wxIntegerValidator when available long maxFreq; if (!mMaxFreq->GetValue().ToLong(&maxFreq)) { AudacityMessageBox( XO("The maximum frequency must be an integer") ); return false; } long minFreq; if (!mMinFreq->GetValue().ToLong(&minFreq)) { AudacityMessageBox( XO("The minimum frequency must be an integer") ); return false; } long gain; if (!mGain->GetValue().ToLong(&gain)) { AudacityMessageBox( XO("The gain must be an integer") ); return false; } long range; if (!mRange->GetValue().ToLong(&range)) { AudacityMessageBox( XO("The range must be a positive integer") ); return false; } long frequencygain; if (!mFrequencyGain->GetValue().ToLong(&frequencygain)) { AudacityMessageBox( XO("The frequency gain must be an integer") ); return false; } #ifdef EXPERIMENTAL_FIND_NOTES long findNotesMinA; if (!mFindNotesMinA->GetValue().ToLong(&findNotesMinA)) { AudacityMessageBox( XO("The minimum amplitude (dB) must be an integer") ); return false; } long findNotesN; if (!mFindNotesN->GetValue().ToLong(&findNotesN)) { AudacityMessageBox( XO("The maximum number of notes must be an integer") ); return false; } if (findNotesN < 1 || findNotesN > 128) { AudacityMessageBox( XO( "The maximum number of notes must be in the range 1..128") ); return false; } #endif //EXPERIMENTAL_FIND_NOTES ShuttleGui S(this, eIsSavingToPrefs); PopulateOrExchange(S); // Delegate range checking to SpectrogramSettings class mTempSettings.ConvertToActualWindowSizes(); const bool result = mTempSettings.Validate(false); mTempSettings.ConvertToEnumeratedWindowSizes(); return result; } void SpectrumPrefs::Rollback() { if (mWt) { auto channels = TrackList::Channels(mWt); for (auto channel : channels) { if (mOrigDefaulted) { channel->SetSpectrogramSettings({}); channel->SetSpectrumBounds(-1, -1); } else { auto &settings = channel->GetIndependentSpectrogramSettings(); channel->SetSpectrumBounds(mOrigMin, mOrigMax); settings = mOrigSettings; } } } if (!mWt || mOrigDefaulted) { SpectrogramSettings *const pSettings = &SpectrogramSettings::defaults(); *pSettings = mOrigSettings; } const bool isOpenPage = this->IsShown(); if (mWt && isOpenPage) { auto channels = TrackList::Channels(mWt); for (auto channel : channels) WaveTrackView::Get( *channel ).RestorePlacements( mOrigPlacements ); } if (isOpenPage) { auto pProject = ::GetActiveProject(); if ( pProject ) { auto &tp = TrackPanel::Get ( *pProject ); tp.UpdateVRulers(); tp.Refresh(false); } } } void SpectrumPrefs::Preview() { if (!Validate()) return; const bool isOpenPage = this->IsShown(); ShuttleGui S(this, eIsSavingToPrefs); PopulateOrExchange(S); mTempSettings.ConvertToActualWindowSizes(); if (mWt) { for (auto channel : TrackList::Channels(mWt)) { if (mDefaulted) { channel->SetSpectrogramSettings({}); // ... and so that the vertical scale also defaults: channel->SetSpectrumBounds(-1, -1); } else { SpectrogramSettings &settings = channel->GetIndependentSpectrogramSettings(); channel->SetSpectrumBounds(mTempSettings.minFreq, mTempSettings.maxFreq); settings = mTempSettings; } } } if (!mWt || mDefaulted) { SpectrogramSettings *const pSettings = &SpectrogramSettings::defaults(); *pSettings = mTempSettings; } mTempSettings.ConvertToEnumeratedWindowSizes(); if (mWt && isOpenPage) { for (auto channel : TrackList::Channels(mWt)) WaveTrackView::Get( *channel ) .SetDisplay( WaveTrackViewConstants::Spectrum ); } if (isOpenPage) { auto pProject = ::GetActiveProject(); if ( pProject ) { auto &tp = TrackPanel::Get( *pProject ); tp.UpdateVRulers(); tp.Refresh(false); } } } bool SpectrumPrefs::Commit() { if (!Validate()) return false; mCommitted = true; SpectrogramSettings::Globals::Get().SavePrefs(); // always if (!mWt || mDefaulted) { SpectrogramSettings *const pSettings = &SpectrogramSettings::defaults(); pSettings->SavePrefs(); } return true; } bool SpectrumPrefs::ShowsPreviewButton() { return GetActiveProject() != nullptr; } void SpectrumPrefs::OnControl(wxCommandEvent&) { // Common routine for most controls // If any per-track setting is changed, break the association with defaults // Skip this, and View Settings... will be able to change defaults instead // when the checkbox is on, as in the original design. if (mDefaultsCheckbox && !mPopulating) { mDefaulted = false; mDefaultsCheckbox->SetValue(false); } } void SpectrumPrefs::OnWindowSize(wxCommandEvent &evt) { // Restrict choice of zero padding, so that product of window // size and padding may not exceed the largest window size. wxChoice *const pWindowSizeControl = static_cast(wxWindow::FindWindowById(ID_WINDOW_SIZE, this)); size_t windowSize = 1 << (pWindowSizeControl->GetSelection() + SpectrogramSettings::LogMinWindowSize); PopulatePaddingChoices(windowSize); // Do the common part OnControl(evt); } void SpectrumPrefs::OnDefaults(wxCommandEvent &) { if (mDefaultsCheckbox->IsChecked()) { mTempSettings = SpectrogramSettings::defaults(); mTempSettings.ConvertToEnumeratedWindowSizes(); mDefaulted = true; ShuttleGui S(this, eIsSettingToDialog); PopulateOrExchange(S); } } void SpectrumPrefs::OnAlgorithm(wxCommandEvent &evt) { EnableDisableSTFTOnlyControls(); OnControl(evt); } void SpectrumPrefs::EnableDisableSTFTOnlyControls() { // Enable or disable other controls that are applicable only to STFT. const bool STFT = (mAlgorithmChoice->GetSelection() != SpectrogramSettings::algPitchEAC); mGain->Enable(STFT); mRange->Enable(STFT); mFrequencyGain->Enable(STFT); #ifdef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS mZeroPaddingChoiceCtrl->Enable(STFT); #endif } BEGIN_EVENT_TABLE(SpectrumPrefs, PrefsPanel) EVT_CHOICE(ID_WINDOW_SIZE, SpectrumPrefs::OnWindowSize) EVT_CHECKBOX(ID_DEFAULTS, SpectrumPrefs::OnDefaults) EVT_CHOICE(ID_ALGORITHM, SpectrumPrefs::OnAlgorithm) // Several controls with common routine that unchecks the default box EVT_CHOICE(ID_WINDOW_TYPE, SpectrumPrefs::OnControl) EVT_CHOICE(ID_PADDING_SIZE, SpectrumPrefs::OnControl) EVT_CHOICE(ID_SCALE, SpectrumPrefs::OnControl) EVT_TEXT(ID_MINIMUM, SpectrumPrefs::OnControl) EVT_TEXT(ID_MAXIMUM, SpectrumPrefs::OnControl) EVT_TEXT(ID_GAIN, SpectrumPrefs::OnControl) EVT_TEXT(ID_RANGE, SpectrumPrefs::OnControl) EVT_TEXT(ID_FREQUENCY_GAIN, SpectrumPrefs::OnControl) EVT_CHECKBOX(ID_GRAYSCALE, SpectrumPrefs::OnControl) EVT_CHECKBOX(ID_SPECTRAL_SELECTION, SpectrumPrefs::OnControl) END_EVENT_TABLE() PrefsPanel::Factory SpectrumPrefsFactory( WaveTrack *wt ) { return [=](wxWindow *parent, wxWindowID winid, AudacityProject *) { wxASSERT(parent); // to justify safenew return safenew SpectrumPrefs(parent, winid, wt); }; }