1 /**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 SpectrumPrefs.cpp
6
7 Dominic Mazzoni
8 James Crook
9
10 *******************************************************************//**
11
12 \class SpectrumPrefs
13 \brief A PrefsPanel for spectrum settings.
14
15 *//*******************************************************************/
16
17
18 #include "SpectrumPrefs.h"
19
20 #include <wx/choice.h>
21 #include <wx/defs.h>
22 #include <wx/intl.h>
23 #include <wx/checkbox.h>
24 #include <wx/textctrl.h>
25
26 #include "FFT.h"
27 #include "Project.h"
28 #include "../ShuttleGui.h"
29
30 #include "../TrackPanel.h"
31 #include "../WaveTrack.h"
32 #include "../tracks/playabletrack/wavetrack/ui/WaveTrackView.h"
33
34 #include <algorithm>
35
36 #include "../widgets/AudacityMessageBox.h"
37
SpectrumPrefs(wxWindow * parent,wxWindowID winid,AudacityProject * pProject,WaveTrack * wt)38 SpectrumPrefs::SpectrumPrefs(wxWindow * parent, wxWindowID winid,
39 AudacityProject *pProject, WaveTrack *wt)
40 : PrefsPanel(parent, winid, wt ? XO("Spectrogram Settings") : XO("Spectrograms"))
41 , mProject{ pProject }
42 , mWt(wt)
43 , mPopulating(false)
44 {
45 if (mWt) {
46 SpectrogramSettings &settings = wt->GetSpectrogramSettings();
47 mOrigDefaulted = mDefaulted = (&SpectrogramSettings::defaults() == &settings);
48 mTempSettings = mOrigSettings = settings;
49 wt->GetSpectrumBounds(&mOrigMin, &mOrigMax);
50 mTempSettings.maxFreq = mOrigMax;
51 mTempSettings.minFreq = mOrigMin;
52 mOrigPlacements = WaveTrackView::Get( *mWt ).SavePlacements();
53 }
54 else {
55 mTempSettings = mOrigSettings = SpectrogramSettings::defaults();
56 mOrigDefaulted = mDefaulted = false;
57 }
58
59 const auto windowSize = mTempSettings.WindowSize();
60 mTempSettings.ConvertToEnumeratedWindowSizes();
61 Populate(windowSize);
62 }
63
~SpectrumPrefs()64 SpectrumPrefs::~SpectrumPrefs()
65 {
66 if (!mCommitted)
67 Rollback();
68 }
69
GetSymbol()70 ComponentInterfaceSymbol SpectrumPrefs::GetSymbol()
71 {
72 return SPECTRUM_PREFS_PLUGIN_SYMBOL;
73 }
74
GetDescription()75 TranslatableString SpectrumPrefs::GetDescription()
76 {
77 return XO("Preferences for Spectrum");
78 }
79
HelpPageName()80 ManualPageID SpectrumPrefs::HelpPageName()
81 {
82 // Currently (May2017) Spectrum Settings is the only preferences
83 // we ever display in a dialog on its own without others.
84 // We do so when it is configuring spectrums for a track.
85 // Because this happens, we want to visit a different help page.
86 // So we change the page name in the case of a page on its own.
87 return mWt
88 ? "Spectrogram_Settings"
89 : "Spectrograms_Preferences";
90 }
91
92 enum {
93 ID_WINDOW_SIZE = 10001,
94 ID_WINDOW_TYPE,
95 ID_PADDING_SIZE,
96 ID_SCALE,
97 ID_ALGORITHM,
98 ID_MINIMUM,
99 ID_MAXIMUM,
100 ID_GAIN,
101 ID_RANGE,
102 ID_FREQUENCY_GAIN,
103 ID_COLOR_SCHEME,
104 ID_SPECTRAL_SELECTION,
105 ID_DEFAULTS,
106 };
107
Populate(size_t windowSize)108 void SpectrumPrefs::Populate(size_t windowSize)
109 {
110 PopulatePaddingChoices(windowSize);
111
112 for (int i = 0; i < NumWindowFuncs(); i++) {
113 mTypeChoices.push_back( WindowFuncName(i) );
114 }
115
116 //------------------------- Main section --------------------
117 // Now construct the GUI itself.
118 ShuttleGui S(this, eIsCreatingFromPrefs);
119 PopulateOrExchange(S);
120 // ----------------------- End of main section --------------
121 }
122
PopulatePaddingChoices(size_t windowSize)123 void SpectrumPrefs::PopulatePaddingChoices(size_t windowSize)
124 {
125 mZeroPaddingChoice = 1;
126
127 // The choice of window size restricts the choice of padding.
128 // So the padding menu might grow or shrink.
129
130 // If pPaddingSizeControl is NULL, we have not yet tied the choice control.
131 // If it is not NULL, we rebuild the control by hand.
132 // I don't yet know an easier way to do this with ShuttleGUI functions.
133 // PRL
134 wxChoice *const pPaddingSizeControl =
135 static_cast<wxChoice*>(wxWindow::FindWindowById(ID_PADDING_SIZE, this));
136
137 if (pPaddingSizeControl) {
138 mZeroPaddingChoice = pPaddingSizeControl->GetSelection();
139 pPaddingSizeControl->Clear();
140 }
141
142 unsigned padding = 1;
143 int numChoices = 0;
144 const size_t maxWindowSize = 1 << (SpectrogramSettings::LogMaxWindowSize);
145 while (windowSize <= maxWindowSize) {
146 const auto numeral = wxString::Format(wxT("%d"), padding);
147 mZeroPaddingChoices.push_back( Verbatim( numeral ) );
148 if (pPaddingSizeControl)
149 pPaddingSizeControl->Append(numeral);
150 windowSize <<= 1;
151 padding <<= 1;
152 ++numChoices;
153 }
154
155 mZeroPaddingChoice = std::min(mZeroPaddingChoice, numChoices - 1);
156
157 if (pPaddingSizeControl)
158 pPaddingSizeControl->SetSelection(mZeroPaddingChoice);
159 }
160
PopulateOrExchange(ShuttleGui & S)161 void SpectrumPrefs::PopulateOrExchange(ShuttleGui & S)
162 {
163 mPopulating = true;
164 S.SetBorder(2);
165 S.StartScroller(); {
166
167 // S.StartStatic(XO("Track Settings"));
168 // {
169
170
171 mDefaultsCheckbox = 0;
172 if (mWt) {
173 /* i18n-hint: use is a verb */
174 mDefaultsCheckbox = S.Id(ID_DEFAULTS).TieCheckBox(XXO("&Use Preferences"), mDefaulted);
175 }
176
177 S.StartMultiColumn(2,wxEXPAND);
178 {
179 S.SetStretchyCol( 0 );
180 S.SetStretchyCol( 1 );
181 S.StartStatic(XO("Scale"),1);
182 {
183 S.StartMultiColumn(2,wxEXPAND);
184 {
185 S.SetStretchyCol( 0 );
186 S.SetStretchyCol( 1 );
187 S.Id(ID_SCALE).TieChoice(XXO("S&cale:"),
188 mTempSettings.scaleType,
189 Msgids( SpectrogramSettings::GetScaleNames() ) );
190 mMinFreq =
191 S.Id(ID_MINIMUM).TieNumericTextBox(XXO("Mi&n Frequency (Hz):"),
192 mTempSettings.minFreq,
193 12);
194 mMaxFreq =
195 S.Id(ID_MAXIMUM).TieNumericTextBox(XXO("Ma&x Frequency (Hz):"),
196 mTempSettings.maxFreq,
197 12);
198 }
199 S.EndMultiColumn();
200 }
201 S.EndStatic();
202
203 S.StartStatic(XO("Colors"),1);
204 {
205 S.StartMultiColumn(2,wxEXPAND);
206 {
207 S.SetStretchyCol( 0 );
208 S.SetStretchyCol( 1 );
209 mGain =
210 S.Id(ID_GAIN).TieNumericTextBox(XXO("&Gain (dB):"),
211 mTempSettings.gain,
212 8);
213 mRange =
214 S.Id(ID_RANGE).TieNumericTextBox(XXO("&Range (dB):"),
215 mTempSettings.range,
216 8);
217
218 mFrequencyGain =
219 S.Id(ID_FREQUENCY_GAIN).TieNumericTextBox(XXO("High &boost (dB/dec):"),
220 mTempSettings.frequencyGain,
221 8);
222
223 // i18n-hint Scheme refers to a color scheme for spectrogram colors
224 S.Id(ID_COLOR_SCHEME).TieChoice(XC("Sche&me", "spectrum prefs"),
225 (int&)mTempSettings.colorScheme,
226 Msgids( SpectrogramSettings::GetColorSchemeNames() ) );
227 }
228 S.EndMultiColumn();
229 }
230 S.EndStatic();
231 }
232 S.EndMultiColumn();
233
234 S.StartStatic(XO("Algorithm"));
235 {
236 S.StartMultiColumn(2);
237 {
238 mAlgorithmChoice =
239 S.Id(ID_ALGORITHM).TieChoice(XXO("A&lgorithm:"),
240 mTempSettings.algorithm,
241 SpectrogramSettings::GetAlgorithmNames() );
242
243 S.Id(ID_WINDOW_SIZE).TieChoice(XXO("Window &size:"),
244 mTempSettings.windowSize,
245 {
246 XO("8 - most wideband"),
247 XO("16"),
248 XO("32"),
249 XO("64"),
250 XO("128"),
251 XO("256"),
252 XO("512"),
253 XO("1024 - default"),
254 XO("2048"),
255 XO("4096"),
256 XO("8192"),
257 XO("16384"),
258 XO("32768 - most narrowband"),
259 }
260 );
261
262 S.Id(ID_WINDOW_TYPE).TieChoice(XXO("Window &type:"),
263 mTempSettings.windowType,
264 mTypeChoices);
265
266 mZeroPaddingChoiceCtrl =
267 S.Id(ID_PADDING_SIZE).TieChoice(XXO("&Zero padding factor:"),
268 mTempSettings.zeroPaddingFactor,
269 mZeroPaddingChoices);
270 }
271 S.EndMultiColumn();
272 }
273 S.EndStatic();
274
275 #ifndef SPECTRAL_SELECTION_GLOBAL_SWITCH
276 S.Id(ID_SPECTRAL_SELECTION).TieCheckBox(XXO("Ena&ble Spectral Selection"),
277 mTempSettings.spectralSelection);
278 #endif
279
280 #ifdef EXPERIMENTAL_FFT_Y_GRID
281 S.TieCheckBox(XO("Show a grid along the &Y-axis"),
282 mTempSettings.fftYGrid);
283 #endif //EXPERIMENTAL_FFT_Y_GRID
284
285 #ifdef EXPERIMENTAL_FIND_NOTES
286 /* i18n-hint: FFT stands for Fast Fourier Transform and probably shouldn't be translated*/
287 S.StartStatic(XO("FFT Find Notes"));
288 {
289 S.StartTwoColumn();
290 {
291 mFindNotesMinA =
292 S.TieNumericTextBox(XXO("Minimum Amplitude (dB):"),
293 mTempSettings.findNotesMinA,
294 8);
295
296 mFindNotesN =
297 S.TieNumericTextBox(XXO("Max. Number of Notes (1..128):"),
298 mTempSettings.numberOfMaxima,
299 8);
300 }
301 S.EndTwoColumn();
302
303 S.TieCheckBox(XXO("&Find Notes"),
304 mTempSettings.fftFindNotes);
305
306 S.TieCheckBox(XXO("&Quantize Notes"),
307 mTempSettings.findNotesQuantize);
308 }
309 S.EndStatic();
310 #endif //EXPERIMENTAL_FIND_NOTES
311 // S.EndStatic();
312
313 #ifdef SPECTRAL_SELECTION_GLOBAL_SWITCH
314 S.StartStatic(XO("Global settings"));
315 {
316 S.TieCheckBox(XXO("Ena&ble spectral selection"),
317 SpectrogramSettings::Globals::Get().spectralSelection);
318 }
319 S.EndStatic();
320 #endif
321
322 } S.EndScroller();
323
324 // Enabling and disabling belongs outside this function.
325 if( S.GetMode() != eIsGettingMetadata )
326 EnableDisableSTFTOnlyControls();
327
328 mPopulating = false;
329 }
330
Validate()331 bool SpectrumPrefs::Validate()
332 {
333 // Do checking for whole numbers
334
335 // ToDo: use wxIntegerValidator<unsigned> when available
336
337 long maxFreq;
338 if (!mMaxFreq->GetValue().ToLong(&maxFreq)) {
339 AudacityMessageBox( XO("The maximum frequency must be an integer") );
340 return false;
341 }
342
343 long minFreq;
344 if (!mMinFreq->GetValue().ToLong(&minFreq)) {
345 AudacityMessageBox( XO("The minimum frequency must be an integer") );
346 return false;
347 }
348
349 long gain;
350 if (!mGain->GetValue().ToLong(&gain)) {
351 AudacityMessageBox( XO("The gain must be an integer") );
352 return false;
353 }
354
355 long range;
356 if (!mRange->GetValue().ToLong(&range)) {
357 AudacityMessageBox( XO("The range must be a positive integer") );
358 return false;
359 }
360
361 long frequencygain;
362 if (!mFrequencyGain->GetValue().ToLong(&frequencygain)) {
363 AudacityMessageBox( XO("The frequency gain must be an integer") );
364 return false;
365 }
366
367 #ifdef EXPERIMENTAL_FIND_NOTES
368 long findNotesMinA;
369 if (!mFindNotesMinA->GetValue().ToLong(&findNotesMinA)) {
370 AudacityMessageBox( XO("The minimum amplitude (dB) must be an integer") );
371 return false;
372 }
373
374 long findNotesN;
375 if (!mFindNotesN->GetValue().ToLong(&findNotesN)) {
376 AudacityMessageBox( XO("The maximum number of notes must be an integer") );
377 return false;
378 }
379 if (findNotesN < 1 || findNotesN > 128) {
380 AudacityMessageBox( XO(
381 "The maximum number of notes must be in the range 1..128") );
382 return false;
383 }
384 #endif //EXPERIMENTAL_FIND_NOTES
385
386 ShuttleGui S(this, eIsSavingToPrefs);
387 PopulateOrExchange(S);
388
389 // Delegate range checking to SpectrogramSettings class
390 mTempSettings.ConvertToActualWindowSizes();
391 const bool result = mTempSettings.Validate(false);
392 mTempSettings.ConvertToEnumeratedWindowSizes();
393 return result;
394 }
395
Rollback()396 void SpectrumPrefs::Rollback()
397 {
398 if (mWt) {
399 auto channels = TrackList::Channels(mWt);
400
401 for (auto channel : channels) {
402 if (mOrigDefaulted) {
403 channel->SetSpectrogramSettings({});
404 channel->SetSpectrumBounds(-1, -1);
405 }
406 else {
407 auto &settings =
408 channel->GetIndependentSpectrogramSettings();
409 channel->SetSpectrumBounds(mOrigMin, mOrigMax);
410 settings = mOrigSettings;
411 }
412 }
413 }
414
415 if (!mWt || mOrigDefaulted) {
416 SpectrogramSettings *const pSettings = &SpectrogramSettings::defaults();
417 *pSettings = mOrigSettings;
418 }
419
420 const bool isOpenPage = this->IsShown();
421 if (mWt && isOpenPage) {
422 auto channels = TrackList::Channels(mWt);
423 for (auto channel : channels)
424 WaveTrackView::Get( *channel ).RestorePlacements( mOrigPlacements );
425 }
426
427 if (isOpenPage) {
428 if ( mProject ) {
429 auto &tp = TrackPanel::Get ( *mProject );
430 tp.UpdateVRulers();
431 tp.Refresh(false);
432 }
433 }
434 }
435
Preview()436 void SpectrumPrefs::Preview()
437 {
438 if (!Validate())
439 return;
440
441 const bool isOpenPage = this->IsShown();
442
443 ShuttleGui S(this, eIsSavingToPrefs);
444 PopulateOrExchange(S);
445
446
447 mTempSettings.ConvertToActualWindowSizes();
448
449 if (mWt) {
450 for (auto channel : TrackList::Channels(mWt)) {
451 if (mDefaulted) {
452 channel->SetSpectrogramSettings({});
453 // ... and so that the vertical scale also defaults:
454 channel->SetSpectrumBounds(-1, -1);
455 }
456 else {
457 SpectrogramSettings &settings =
458 channel->GetIndependentSpectrogramSettings();
459 channel->SetSpectrumBounds(mTempSettings.minFreq, mTempSettings.maxFreq);
460 settings = mTempSettings;
461 }
462 }
463 }
464
465 if (!mWt || mDefaulted) {
466 SpectrogramSettings *const pSettings = &SpectrogramSettings::defaults();
467 *pSettings = mTempSettings;
468 }
469 mTempSettings.ConvertToEnumeratedWindowSizes();
470
471 // Bug 2278
472 // This code destroys any Multi-view we had.
473 // Commenting it out as it seems not to be needed.
474 /*
475 if (mWt && isOpenPage) {
476 for (auto channel : TrackList::Channels(mWt))
477 WaveTrackView::Get( *channel )
478 .SetDisplay( WaveTrackViewConstants::Spectrum );
479 }
480 */
481
482 if (isOpenPage) {
483 if ( mProject ) {
484 auto &tp = TrackPanel::Get( *mProject );
485 tp.UpdateVRulers();
486 tp.Refresh(false);
487 }
488 }
489 }
490
Commit()491 bool SpectrumPrefs::Commit()
492 {
493 if (!Validate())
494 return false;
495
496 mCommitted = true;
497 SpectrogramSettings::Globals::Get().SavePrefs(); // always
498 SpectrogramSettings *const pSettings = &SpectrogramSettings::defaults();
499 if (!mWt || mDefaulted) {
500 pSettings->SavePrefs();
501 }
502 pSettings->LoadPrefs(); // always; in case Globals changed
503
504 return true;
505 }
506
ShowsPreviewButton()507 bool SpectrumPrefs::ShowsPreviewButton()
508 {
509 return mProject != nullptr;
510 }
511
OnControl(wxCommandEvent &)512 void SpectrumPrefs::OnControl(wxCommandEvent&)
513 {
514 // Common routine for most controls
515 // If any per-track setting is changed, break the association with defaults
516 // Skip this, and View Settings... will be able to change defaults instead
517 // when the checkbox is on, as in the original design.
518
519 if (mDefaultsCheckbox && !mPopulating) {
520 mDefaulted = false;
521 mDefaultsCheckbox->SetValue(false);
522 }
523 }
524
OnWindowSize(wxCommandEvent & evt)525 void SpectrumPrefs::OnWindowSize(wxCommandEvent &evt)
526 {
527 // Restrict choice of zero padding, so that product of window
528 // size and padding may not exceed the largest window size.
529 wxChoice *const pWindowSizeControl =
530 static_cast<wxChoice*>(wxWindow::FindWindowById(ID_WINDOW_SIZE, this));
531 size_t windowSize = 1 <<
532 (pWindowSizeControl->GetSelection() + SpectrogramSettings::LogMinWindowSize);
533 PopulatePaddingChoices(windowSize);
534
535 // Do the common part
536 OnControl(evt);
537 }
538
OnDefaults(wxCommandEvent &)539 void SpectrumPrefs::OnDefaults(wxCommandEvent &)
540 {
541 if (mDefaultsCheckbox->IsChecked()) {
542 mTempSettings = SpectrogramSettings::defaults();
543 mTempSettings.ConvertToEnumeratedWindowSizes();
544 mDefaulted = true;
545 ShuttleGui S(this, eIsSettingToDialog);
546 PopulateOrExchange(S);
547 }
548 }
549
OnAlgorithm(wxCommandEvent & evt)550 void SpectrumPrefs::OnAlgorithm(wxCommandEvent &evt)
551 {
552 EnableDisableSTFTOnlyControls();
553 OnControl(evt);
554 }
555
EnableDisableSTFTOnlyControls()556 void SpectrumPrefs::EnableDisableSTFTOnlyControls()
557 {
558 // Enable or disable other controls that are applicable only to STFT.
559 const bool STFT =
560 (mAlgorithmChoice->GetSelection() != SpectrogramSettings::algPitchEAC);
561 mGain->Enable(STFT);
562 mRange->Enable(STFT);
563 mFrequencyGain->Enable(STFT);
564 mZeroPaddingChoiceCtrl->Enable(STFT);
565 }
566
BEGIN_EVENT_TABLE(SpectrumPrefs,PrefsPanel)567 BEGIN_EVENT_TABLE(SpectrumPrefs, PrefsPanel)
568 EVT_CHOICE(ID_WINDOW_SIZE, SpectrumPrefs::OnWindowSize)
569 EVT_CHECKBOX(ID_DEFAULTS, SpectrumPrefs::OnDefaults)
570 EVT_CHOICE(ID_ALGORITHM, SpectrumPrefs::OnAlgorithm)
571
572 // Several controls with common routine that unchecks the default box
573 EVT_CHOICE(ID_WINDOW_TYPE, SpectrumPrefs::OnControl)
574 EVT_CHOICE(ID_PADDING_SIZE, SpectrumPrefs::OnControl)
575 EVT_CHOICE(ID_SCALE, SpectrumPrefs::OnControl)
576 EVT_TEXT(ID_MINIMUM, SpectrumPrefs::OnControl)
577 EVT_TEXT(ID_MAXIMUM, SpectrumPrefs::OnControl)
578 EVT_TEXT(ID_GAIN, SpectrumPrefs::OnControl)
579 EVT_TEXT(ID_RANGE, SpectrumPrefs::OnControl)
580 EVT_TEXT(ID_FREQUENCY_GAIN, SpectrumPrefs::OnControl)
581 EVT_CHOICE(ID_COLOR_SCHEME, SpectrumPrefs::OnControl)
582 EVT_CHECKBOX(ID_SPECTRAL_SELECTION, SpectrumPrefs::OnControl)
583
584 END_EVENT_TABLE()
585
586 PrefsPanel::Factory
587 SpectrumPrefsFactory( WaveTrack *wt )
588 {
589 return [=](wxWindow *parent, wxWindowID winid, AudacityProject *pProject)
590 {
591 wxASSERT(parent); // to justify safenew
592 return safenew SpectrumPrefs(parent, winid, pProject, wt);
593 };
594 }
595
596 namespace{
597 PrefsPanel::Registration sAttachment{ "Spectrum",
598 SpectrumPrefsFactory( nullptr ),
599 false,
600 // Place it at a lower tree level
601 { "Tracks" }
602 };
603 }
604