From 6a1227f039d8a3eb5d2451a98186e4cc2d0aea05 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Sun, 31 May 2015 13:23:21 -0400 Subject: [PATCH 1/2] zero-padding factor for spectrograms, internals --- src/TrackArtist.cpp | 21 +++++--- src/TrackArtist.h | 5 +- src/TrackPanel.cpp | 16 ++++-- src/WaveClip.cpp | 128 ++++++++++++++++++++++++++++++++++---------- src/WaveClip.h | 1 + 5 files changed, 131 insertions(+), 40 deletions(-) diff --git a/src/TrackArtist.cpp b/src/TrackArtist.cpp index fa080fed8..6031181c6 100644 --- a/src/TrackArtist.cpp +++ b/src/TrackArtist.cpp @@ -1803,6 +1803,7 @@ static float sumFreqValues( // Helper function to decide on which color set to use. // dashCount counts both dashes and the spaces between them. +inline AColor::ColorGradientChoice ChooseColorSet( float bin0, float bin1, float selBinLo, float selBinCenter, float selBinHi, int dashCount, bool isSpectral ) { @@ -1959,8 +1960,8 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &cache, if (!image)return; unsigned char *data = image->GetData(); - int windowSize = GetSpectrumWindowSize(); - int half = windowSize/2; + int windowSize = GetSpectrumWindowSize(!autocorrelation); + const int half = windowSize / 2; float *freq = new float[mid.width * half]; sampleCount *where = new sampleCount[mid.width+1]; @@ -2106,7 +2107,7 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &cache, AColor::ColorGradientChoice selected = AColor::ColorGradientUnselected; - // If we are in the time selected range, then we may use a differnt color set. + // If we are in the time selected range, then we may use a different color set. if (ssel0 <= w0 && w1 < ssel1) { bool isSpectral = ((track->GetDisplay() == WaveTrack::SpectralSelectionDisplay) || @@ -2220,7 +2221,7 @@ void TrackArtist::DrawClipSpectrum(WaveTrackCache &cache, AColor::ColorGradientChoice selected = AColor::ColorGradientUnselected; - // If we are in the time selected range, then we may use a differnt color set. + // If we are in the time selected range, then we may use a different color set. if (ssel0 <= w0 && w1 < ssel1) { bool isSpectral = ((track->GetDisplay() == WaveTrack::SpectralSelectionDisplay) || @@ -3029,6 +3030,9 @@ void TrackArtist::UpdatePrefs() mLogMinFreq = 1; mWindowSize = gPrefs->Read(wxT("/Spectrum/FFTSize"), 256); +#ifdef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS + mZeroPaddingFactor = gPrefs->Read(wxT("/Spectrum/ZeroPaddingFactor"), 1); +#endif mIsGrayscale = (gPrefs->Read(wxT("/Spectrum/Grayscale"), 0L) != 0); #ifdef EXPERIMENTAL_FFT_Y_GRID @@ -3070,9 +3074,14 @@ int TrackArtist::GetSpectrumLogMaxFreq(int deffreq) return mLogMaxFreq < 0 ? deffreq : mLogMaxFreq; } -int TrackArtist::GetSpectrumWindowSize() +int TrackArtist::GetSpectrumWindowSize(bool includeZeroPadding) { - return mWindowSize; +#ifdef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS + if (includeZeroPadding) + return mWindowSize * mZeroPaddingFactor; + else +#endif + return mWindowSize; } #ifdef EXPERIMENTAL_FFT_SKIP_POINTS diff --git a/src/TrackArtist.h b/src/TrackArtist.h index fcee3033e..467c775cb 100644 --- a/src/TrackArtist.h +++ b/src/TrackArtist.h @@ -73,7 +73,7 @@ class AUDACITY_DLL_API TrackArtist { int GetSpectrumMaxFreq(int deffreq); int GetSpectrumLogMinFreq(int deffreq); int GetSpectrumLogMaxFreq(int deffreq); - int GetSpectrumWindowSize(); + int GetSpectrumWindowSize(bool includeZeroPadding); #ifdef EXPERIMENTAL_FFT_SKIP_POINTS int GetSpectrumFftSkipPoints(); @@ -184,6 +184,9 @@ class AUDACITY_DLL_API TrackArtist { int mMaxFreq; // "/Spectrum/MaxFreq" int mMinFreq; // "/Spectrum/MinFreq" int mWindowSize; // "/Spectrum/FFTSize" +#ifdef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS + int mZeroPaddingFactor; // "/Spectrum/ZeroPaddingFactor" +#endif bool mIsGrayscale; // "/Spectrum/Grayscale" bool mbShowTrackNameInWaveform; // "/GUI/ShowTrackNameInWaveform" diff --git a/src/TrackPanel.cpp b/src/TrackPanel.cpp index 55b4d677a..c37c206e6 100644 --- a/src/TrackPanel.cpp +++ b/src/TrackPanel.cpp @@ -2977,7 +2977,8 @@ inline double findMaxRatio(double center, double rate) void TrackPanel::SnapCenterOnce(WaveTrack *pTrack, bool up) { - const int windowSize = mTrackArtist->GetSpectrumWindowSize(); + // Always spectrogram, never pitch view, pass true + const int windowSize = mTrackArtist->GetSpectrumWindowSize(true); const double rate = pTrack->GetRate(); const double nyq = rate / 2.0; const double binFrequency = rate / windowSize; @@ -3038,7 +3039,10 @@ void TrackPanel::StartSnappingFreqSelection (WaveTrack *pTrack) // Use same settings as are now used for spectrogram display, // except, shrink the window as needed so we get some answers - int windowSize = mTrackArtist->GetSpectrumWindowSize(); + + // Always spectrogram, never pitch view, pass true + int windowSize = mTrackArtist->GetSpectrumWindowSize(true); + while(windowSize > effectiveLength) windowSize >>= 1; int windowType; @@ -4714,7 +4718,9 @@ void TrackPanel::HandleVZoomButtonUp( wxMouseEvent & event ) max = mTrackArtist->GetSpectrumMaxFreq(8000); if(max > rate/2.) max = rate/2.; - windowSize = mTrackArtist->GetSpectrumWindowSize(); + + // Always spectrogram, never pitch view, pass true + windowSize = mTrackArtist->GetSpectrumWindowSize(true); #ifdef EXPERIMENTAL_FFT_SKIP_POINTS fftSkipPoints = mTrackArtist->GetSpectrumFftSkipPoints(); #endif //EXPERIMENTAL_FFT_SKIP_POINTS @@ -4729,7 +4735,9 @@ void TrackPanel::HandleVZoomButtonUp( wxMouseEvent & event ) max = mTrackArtist->GetSpectrumLogMaxFreq(lrint(rate/2.)); if(max > rate/2.) max = rate/2.; - windowSize = mTrackArtist->GetSpectrumWindowSize(); + + // Always spectrogram, never pitch view, pass true + windowSize = mTrackArtist->GetSpectrumWindowSize(true); #ifdef EXPERIMENTAL_FFT_SKIP_POINTS fftSkipPoints = mTrackArtist->GetSpectrumFftSkipPoints(); #endif //EXPERIMENTAL_FFT_SKIP_POINTS diff --git a/src/WaveClip.cpp b/src/WaveClip.cpp index 61a6373a3..48f2f5e16 100644 --- a/src/WaveClip.cpp +++ b/src/WaveClip.cpp @@ -226,6 +226,7 @@ public: rangeOld = -1; windowTypeOld = -1; windowSizeOld = -1; + zeroPaddingFactorOld = 1; frequencyGainOld = false; #ifdef EXPERIMENTAL_FFT_SKIP_POINTS fftSkipPointsOld = -1; @@ -252,6 +253,7 @@ public: int rangeOld; int windowTypeOld; int windowSizeOld; + int zeroPaddingFactorOld; int frequencyGainOld; #ifdef EXPERIMENTAL_FFT_SKIP_POINTS int fftSkipPointsOld; @@ -284,8 +286,9 @@ static void ComputeSpectrumUsingRealFFTf(float *buffer, HFFT hFFT, float *window else out[0] = 10.0*log10(power); for(i=1;iPoints;i++) { - power = (buffer[hFFT->BitReversed[i] ]*buffer[hFFT->BitReversed[i] ]) - + (buffer[hFFT->BitReversed[i]+1]*buffer[hFFT->BitReversed[i]+1]); + const int index = hFFT->BitReversed[i]; + const float re = buffer[index], im = buffer[index + 1]; + power = re * re + im * im; if(power <= 0) out[i] = -160.0; else @@ -307,6 +310,7 @@ WaveClip::WaveClip(DirManager *projDirManager, sampleFormat format, int rate) hFFT = NULL; mWindow = NULL; #endif + mZeroPaddingFactor = 1; mSpecCache = new SpecCache(0, 1, false); mSpecPxCache = new SpecPxCache(1); mAppendBuffer = NULL; @@ -335,6 +339,7 @@ WaveClip::WaveClip(const WaveClip& orig, DirManager *projDirManager) hFFT = NULL; mWindow = NULL; #endif + mZeroPaddingFactor = 1; mSpecCache = new SpecCache(0, 1, false); mSpecPxCache = new SpecPxCache(1); @@ -754,6 +759,67 @@ bool WaveClip::GetWaveDisplay(float *min, float *max, float *rms,int* bl, return true; } +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; +} +} + bool WaveClip::GetSpectrogram(WaveTrackCache &waveTrackCache, float *freq, sampleCount *where, int numPixels, @@ -771,37 +837,34 @@ bool WaveClip::GetSpectrogram(WaveTrackCache &waveTrackCache, int fftSkipPoints = gPrefs->Read(wxT("/Spectrum/FFTSkipPoints"), 0L); int fftSkipPoints1 = fftSkipPoints+1; #endif //EXPERIMENTAL_FFT_SKIP_POINTS - int half = windowSize/2; + const int zeroPaddingFactor = + autocorrelation ? 1 : gPrefs->Read(wxT("/Spectrum/ZeroPaddingFactor"), 1); gPrefs->Read(wxT("/Spectrum/WindowType"), &windowType, 3); + // FFT length may be longer than the window of samples that affect results + // because of zero padding done for increased frequency resolution + const int fftLen = windowSize * zeroPaddingFactor; + const int half = fftLen / 2; + const int padding = (windowSize * (zeroPaddingFactor - 1)) / 2; + #ifdef EXPERIMENTAL_USE_REALFFTF // Update the FFT and window if necessary if((mWindowType != windowType) || (mWindowSize != windowSize) - || (hFFT == NULL) || (mWindow == NULL) || (mWindowSize != hFFT->Points*2) ) { + || (hFFT == NULL) || (mWindow == NULL) || (fftLen != hFFT->Points * 2) + || (mZeroPaddingFactor != zeroPaddingFactor)) { mWindowType = windowType; mWindowSize = windowSize; if(hFFT != NULL) EndFFT(hFFT); - hFFT = InitializeFFT(mWindowSize); - if(mWindow != NULL) delete[] mWindow; - // Create the requested window function - mWindow = new float[mWindowSize]; - int i; - for(i=0; i 0) { - ws = 2.0/ws; - for(i=0; idirty == mDirty && @@ -811,6 +874,7 @@ bool WaveClip::GetSpectrogram(WaveTrackCache &waveTrackCache, mSpecCache->gainOld == gain && mSpecCache->windowTypeOld == windowType && mSpecCache->windowSizeOld == windowSize && + mSpecCache->zeroPaddingFactorOld == zeroPaddingFactor && mSpecCache->frequencyGainOld == frequencygain && #ifdef EXPERIMENTAL_FFT_SKIP_POINTS mSpecCache->fftSkipPointsOld == fftSkipPoints && @@ -914,19 +978,26 @@ bool WaveClip::GetSpectrogram(WaveTrackCache &waveTrackCache, } } - float *useBuffer; + float *useBuffer = 0; #ifdef EXPERIMENTAL_FFT_SKIP_POINTS - float *buffer = new float[windowSize*fftSkipPoints1]; + float *buffer = new float[fftLen*fftSkipPoints1]; mSpecCache->fftSkipPointsOld = fftSkipPoints; #else //!EXPERIMENTAL_FFT_SKIP_POINTS - float *buffer = new float[windowSize]; + float *buffer = new float[fftLen]; #endif //EXPERIMENTAL_FFT_SKIP_POINTS + // Initialize zero padding in the buffer + for (int ii = 0; ii < padding; ++ii) { + buffer[ii] = 0.0; + buffer[fftLen - ii - 1] = 0.0; + } + mSpecCache->minFreqOld = minFreq; mSpecCache->maxFreqOld = maxFreq; mSpecCache->gainOld = gain; mSpecCache->rangeOld = range; mSpecCache->windowTypeOld = windowType; mSpecCache->windowSizeOld = windowSize; + mSpecCache->zeroPaddingFactorOld = zeroPaddingFactor; mSpecCache->frequencyGainOld = frequencygain; float *gainfactor = NULL; @@ -953,10 +1024,9 @@ bool WaveClip::GetSpectrogram(WaveTrackCache &waveTrackCache, mSpecCache->freq[half * x + i] = 0; } - else - { - bool copy = !autocorrelation; - float *adj = buffer; + else { + bool copy = !autocorrelation || (padding > 0); + float *adj = buffer + padding; start -= windowSize >> 1; if (start < 0) { @@ -1015,7 +1085,7 @@ bool WaveClip::GetSpectrogram(WaveTrackCache &waveTrackCache, mRate, &mSpecCache->freq[half * x], autocorrelation, windowType); } else { - ComputeSpectrumUsingRealFFTf(useBuffer, hFFT, mWindow, mWindowSize, &mSpecCache->freq[half * x]); + ComputeSpectrumUsingRealFFTf(useBuffer, hFFT, mWindow, fftLen, &mSpecCache->freq[half * x]); } #else // EXPERIMENTAL_USE_REALFFTF ComputeSpectrum(buffer, windowSize, windowSize, diff --git a/src/WaveClip.h b/src/WaveClip.h index f303a32f9..1d2d581ab 100644 --- a/src/WaveClip.h +++ b/src/WaveClip.h @@ -257,6 +257,7 @@ protected: int mWindowType; int mWindowSize; #endif + int mZeroPaddingFactor; samplePtr mAppendBuffer; sampleCount mAppendBufferLen; From 0480f55e6ce46faf5c01a4f1df98c6bdd7365024 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Sun, 31 May 2015 13:29:11 -0400 Subject: [PATCH 2/2] Zero-padded spectograms, preferences interface to turn it on --- src/Experimental.h | 5 +++ src/prefs/SpectrumPrefs.cpp | 85 +++++++++++++++++++++++++++++++++++-- src/prefs/SpectrumPrefs.h | 12 +++++- 3 files changed, 97 insertions(+), 5 deletions(-) diff --git a/src/Experimental.h b/src/Experimental.h index 32c609269..88401f2c1 100644 --- a/src/Experimental.h +++ b/src/Experimental.h @@ -198,5 +198,10 @@ #undef EXPERIMENTAL_CRASH_REPORT #endif +// Paul Licameli (PRL) 31 May 2015 +// Zero-padding factor for spectrograms can smooth the display of spectrograms by +// interpolating in frequency domain. +#define EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS + #endif diff --git a/src/prefs/SpectrumPrefs.cpp b/src/prefs/SpectrumPrefs.cpp index 8ea330737..344fbc2fc 100644 --- a/src/prefs/SpectrumPrefs.cpp +++ b/src/prefs/SpectrumPrefs.cpp @@ -29,14 +29,24 @@ SpectrumPrefs::SpectrumPrefs(wxWindow * parent) : PrefsPanel(parent, _("Spectrograms")) { - Populate(); + int windowSize = gPrefs->Read(wxT("/Spectrum/FFTSize"), 256); + Populate(windowSize); } SpectrumPrefs::~SpectrumPrefs() { } -void SpectrumPrefs::Populate() +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")); @@ -52,9 +62,13 @@ void SpectrumPrefs::Populate() mSizeChoices.Add(wxT("16384")); mSizeChoices.Add(_("32768 - most narrowband")); + int lastCode = 0; for (size_t i = 0; i < mSizeChoices.GetCount(); i++) { - mSizeCodes.Add(1 << (i + 3)); + mSizeCodes.Add(lastCode = 1 << (i + 3)); } + wxASSERT(lastCode == maxWindowSize); + + PopulatePaddingChoices(windowSize); for (int i = 0; i < NumWindowFuncs(); i++) { mTypeChoices.Add(WindowFuncName(i)); @@ -71,6 +85,48 @@ void SpectrumPrefs::Populate() // ----------------------- 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(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); @@ -79,7 +135,7 @@ void SpectrumPrefs::PopulateOrExchange(ShuttleGui & S) { S.StartMultiColumn(2); { - S.TieChoice(_("Window &size:"), + S.Id(ID_WINDOW_SIZE).TieChoice(_("Window &size:"), wxT("/Spectrum/FFTSize"), 256, mSizeChoices, @@ -92,6 +148,15 @@ void SpectrumPrefs::PopulateOrExchange(ShuttleGui & S) 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(); } @@ -289,3 +354,15 @@ bool SpectrumPrefs::Apply() return true; } + +void SpectrumPrefs::OnWindowSize(wxCommandEvent &event) +{ + wxChoice *const pWindowSizeControl = + static_cast(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() diff --git a/src/prefs/SpectrumPrefs.h b/src/prefs/SpectrumPrefs.h index 2a9de18db..acb08866e 100644 --- a/src/prefs/SpectrumPrefs.h +++ b/src/prefs/SpectrumPrefs.h @@ -39,9 +39,13 @@ class SpectrumPrefs:public PrefsPanel virtual bool Validate(); private: - void Populate(); + void Populate(int windowSize); + void PopulatePaddingChoices(int windowSize); void PopulateOrExchange(ShuttleGui & S); + void OnWindowSize(wxCommandEvent &event); + DECLARE_EVENT_TABLE() + wxTextCtrl *mMinFreq; wxTextCtrl *mMaxFreq; wxTextCtrl *mGain; @@ -51,6 +55,12 @@ class SpectrumPrefs:public PrefsPanel wxArrayString mSizeChoices; wxArrayInt mSizeCodes; +#ifdef EXPERIMENTAL_ZERO_PADDED_SPECTROGRAMS + int mZeroPaddingChoice; + wxArrayString mZeroPaddingChoices; + wxArrayInt mZeroPaddingCodes; +#endif + wxArrayString mTypeChoices; wxArrayInt mTypeCodes;