1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-05-04 09:39:42 +02:00
audacity/src/prefs/SpectrumPrefs.cpp
Paul Licameli b25994a82d Throw away EXPERIMENTAL_FFT_SKIP_POINTS...
... with James' consent.
2015-07-18 23:21:27 -04:00

516 lines
14 KiB
C++

/**********************************************************************
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 <wx/defs.h>
#include <wx/intl.h>
#include <wx/msgdlg.h>
#include "../Prefs.h"
#include "../Project.h"
#include "../ShuttleGui.h"
#include "../FFT.h"
#include <algorithm>
SpectrumPrefs::SpectrumPrefs(wxWindow * parent)
: PrefsPanel(parent, _("Spectrograms"))
{
int windowSize = gPrefs->Read(wxT("/Spectrum/FFTSize"), 256);
Populate(windowSize);
}
SpectrumPrefs::~SpectrumPrefs()
{
}
enum { maxWindowSize = 32768 };
enum {
ID_WINDOW_SIZE = 10001,
#ifdef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS
ID_PADDING_SIZE = 10002,
#endif
};
void SpectrumPrefs::Populate(int windowSize)
{
mSizeChoices.Add(_("8 - most wideband"));
mSizeChoices.Add(wxT("16"));
mSizeChoices.Add(wxT("32"));
mSizeChoices.Add(wxT("64"));
mSizeChoices.Add(wxT("128"));
mSizeChoices.Add(_("256 - default"));
mSizeChoices.Add(wxT("512"));
mSizeChoices.Add(wxT("1024"));
mSizeChoices.Add(wxT("2048"));
mSizeChoices.Add(wxT("4096"));
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);
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);
PopulateOrExchange(S);
// ----------------------- End of main section --------------
}
void SpectrumPrefs::PopulatePaddingChoices(int 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<wxChoice*>(wxWindow::FindWindowById(ID_PADDING_SIZE, this));
if (pPaddingSizeControl) {
mZeroPaddingChoice = pPaddingSizeControl->GetSelection();
pPaddingSizeControl->Clear();
}
mZeroPaddingCodes.Clear();
int padding = 1;
int numChoices = 0;
while (windowSize <= maxWindowSize) {
const wxString numeral = wxString::Format(wxT("%d"), padding);
mZeroPaddingChoices.Add(numeral);
mZeroPaddingCodes.Add(padding);
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)
{
S.SetBorder(2);
S.StartStatic(_("FFT Window"));
{
S.StartMultiColumn(2);
{
S.Id(ID_WINDOW_SIZE).TieChoice(_("Window &size:"),
wxT("/Spectrum/FFTSize"),
256,
mSizeChoices,
mSizeCodes);
S.SetSizeHints(mSizeChoices);
S.TieChoice(_("Window &type:"),
wxT("/Spectrum/WindowType"),
3,
mTypeChoices,
mTypeCodes);
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);
S.SetSizeHints(mZeroPaddingChoices);
#endif
}
S.EndMultiColumn();
}
S.EndStatic();
S.StartStatic(_("Display"));
{
S.StartTwoColumn();
{
mMinFreq =
S.TieNumericTextBox(_("Mi&nimum Frequency (Hz):"),
wxT("/Spectrum/MinFreq"),
0,
12);
mMaxFreq =
S.TieNumericTextBox(_("Ma&ximum Frequency (Hz):"),
wxT("/Spectrum/MaxFreq"),
8000,
12);
mGain =
S.TieNumericTextBox(_("&Gain (dB):"),
wxT("/Spectrum/Gain"),
20,
8);
mRange =
S.TieNumericTextBox(_("&Range (dB):"),
wxT("/Spectrum/Range"),
80,
8);
mFrequencyGain =
S.TieNumericTextBox(_("Frequency g&ain (dB/dec):"),
wxT("/Spectrum/FrequencyGain"),
0,
4);
}
S.EndTwoColumn();
S.TieCheckBox(_("S&how the spectrum using grayscale colors"),
wxT("/Spectrum/Grayscale"),
false);
#ifdef EXPERIMENTAL_FFT_Y_GRID
S.TieCheckBox(_("Show a grid along the &Y-axis"),
wxT("/Spectrum/FFTYGrid"),
false);
#endif //EXPERIMENTAL_FFT_Y_GRID
}
S.EndStatic();
#ifdef EXPERIMENTAL_FIND_NOTES
/* i18n-hint: FFT stands for Fast Fourier Transform and probably shouldn't be translated*/
S.StartStatic(_("FFT Find Notes"));
{
S.StartTwoColumn();
{
mFindNotesMinA =
S.TieNumericTextBox(_("Minimum Amplitude (dB):"),
wxT("/Spectrum/FindNotesMinA"),
-30L,
8);
mFindNotesN =
S.TieNumericTextBox(_("Max. Number of Notes (1..128):"),
wxT("/Spectrum/FindNotesN"),
5L,
8);
}
S.EndTwoColumn();
S.TieCheckBox(_("&Find Notes"),
wxT("/Spectrum/FFTFindNotes"),
false);
S.TieCheckBox(_("&Quantize Notes"),
wxT("/Spectrum/FindNotesQuantize"),
false);
}
S.EndStatic();
#endif //EXPERIMENTAL_FIND_NOTES
}
bool SpectrumPrefs::Validate()
{
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)) {
wxMessageBox(_("The minimum amplitude (dB) must be an integer"));
return false;
}
long findNotesN;
if (!mFindNotesN->GetValue().ToLong(&findNotesN)) {
wxMessageBox(_("The maximum number of notes must be an integer"));
return false;
}
if (findNotesN < 1 || findNotesN > 128) {
wxMessageBox(_("The maximum number of notes must be in the range 1..128"));
return false;
}
#endif //EXPERIMENTAL_FIND_NOTES
return true;
}
bool SpectrumPrefs::Apply()
{
ShuttleGui S(this, eIsSavingToPrefs);
PopulateOrExchange(S);
SpectrogramSettings::defaults().UpdatePrefs();
return true;
}
void SpectrumPrefs::OnWindowSize(wxCommandEvent &)
{
wxChoice *const pWindowSizeControl =
static_cast<wxChoice*>(wxWindow::FindWindowById(ID_WINDOW_SIZE, this));
int windowSize = 1 << (pWindowSizeControl->GetSelection() + 3);
PopulatePaddingChoices(windowSize);
}
BEGIN_EVENT_TABLE(SpectrumPrefs, PrefsPanel)
EVT_CHOICE(ID_WINDOW_SIZE, SpectrumPrefs::OnWindowSize)
END_EVENT_TABLE()
SpectrogramSettings::SpectrogramSettings()
: hFFT(0)
, window(0)
{
UpdatePrefs();
}
SpectrogramSettings& SpectrogramSettings::defaults()
{
static SpectrogramSettings instance;
return instance;
}
void SpectrogramSettings::UpdatePrefs()
{
bool destroy = false;
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;
}
#ifdef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS
const int newZeroPaddingFactor = gPrefs->Read(wxT("/Spectrum/ZeroPaddingFactor"), 1);
if (newZeroPaddingFactor != zeroPaddingFactor) {
destroy = true;
zeroPaddingFactor = newZeroPaddingFactor;
}
#endif
int newWindowType;
gPrefs->Read(wxT("/Spectrum/WindowType"), &newWindowType, 3);
if (newWindowType != windowType) {
destroy = true;
windowType = newWindowType;
}
isGrayscale = (gPrefs->Read(wxT("/Spectrum/Grayscale"), 0L) != 0);
#ifdef EXPERIMENTAL_FFT_Y_GRID
fftYGrid = (gPrefs->Read(wxT("/Spectrum/FFTYGrid"), 0L) != 0);
#endif //EXPERIMENTAL_FFT_Y_GRID
#ifdef EXPERIMENTAL_FIND_NOTES
fftFindNotes = (gPrefs->Read(wxT("/Spectrum/FFTFindNotes"), 0L) != 0);
findNotesMinA = gPrefs->Read(wxT("/Spectrum/FindNotesMinA"), -30.0);
numberOfMaxima = gPrefs->Read(wxT("/Spectrum/FindNotesN"), 5L);
findNotesQuantize = (gPrefs->Read(wxT("/Spectrum/FindNotesQuantize"), 0L) != 0);
#endif //EXPERIMENTAL_FIND_NOTES
if (destroy)
DestroyWindows();
}
SpectrogramSettings::~SpectrogramSettings()
{
DestroyWindows();
}
void SpectrogramSettings::DestroyWindows()
{
#ifdef EXPERIMENTAL_USE_REALFFTF
if (hFFT != NULL) {
EndFFT(hFFT);
hFFT = NULL;
}
if (window != NULL) {
delete[] window;
window = NULL;
}
#endif
}
namespace
{
enum { WINDOW, TWINDOW, DWINDOW };
void RecreateWindow(
float *&window, int which, int fftLen,
int padding, int windowType, int windowSize, double &scale)
{
if (window != NULL)
delete[] window;
// Create the requested window function
window = new float[fftLen];
int ii;
wxASSERT(windowSize % 2 == 0);
const int endOfWindow = padding + windowSize;
// Left and right padding
for (ii = 0; ii < padding; ++ii) {
window[ii] = 0.0;
window[fftLen - ii - 1] = 0.0;
}
// Default rectangular window in the middle
for (; ii < endOfWindow; ++ii)
window[ii] = 1.0;
// Overwrite middle as needed
switch (which) {
case WINDOW:
WindowFunc(windowType, windowSize, window + padding);
// NewWindowFunc(windowType, windowSize, extra, window + padding);
break;
case TWINDOW:
wxASSERT(false);
#if 0
// Future, reassignment
NewWindowFunc(windowType, windowSize, extra, window + padding);
for (int ii = padding, multiplier = -windowSize / 2; ii < endOfWindow; ++ii, ++multiplier)
window[ii] *= multiplier;
break;
#endif
case DWINDOW:
wxASSERT(false);
#if 0
// Future, reassignment
DerivativeOfWindowFunc(windowType, windowSize, extra, window + padding);
break;
#endif
default:
wxASSERT(false);
}
// Scale the window function to give 0dB spectrum for 0dB sine tone
if (which == WINDOW) {
scale = 0.0;
for (ii = padding; ii < endOfWindow; ++ii)
scale += window[ii];
if (scale > 0)
scale = 2.0 / scale;
}
for (ii = padding; ii < endOfWindow; ++ii)
window[ii] *= scale;
}
}
void SpectrogramSettings::CacheWindows() const
{
#ifdef EXPERIMENTAL_USE_REALFFTF
if (hFFT == NULL || window == NULL) {
double scale;
const int fftLen = windowSize * zeroPaddingFactor;
const int padding = (windowSize * (zeroPaddingFactor - 1)) / 2;
if (hFFT != NULL)
EndFFT(hFFT);
hFFT = InitializeFFT(fftLen);
RecreateWindow(window, WINDOW, fftLen, padding, windowType, windowSize, scale);
}
#endif // EXPERIMENTAL_USE_REALFFTF
}