1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-08-07 15:49:42 +02:00

Zero-padded spectrograms preference.

A new drop-down in Spectrogram preferences.  The choice of padding factor is
lmited so that the product of it and window size is not more than the mximum
allowed window size (32767).

When you choose more than 1, zero-padding before fft results in interpolation
in the frequency domain.

You do not get the finer frequency resolution of a longer window, but you do
get a smoothing of the spectrogram, with more and narrower bands of colors.
This commit is contained in:
Paul Licameli 2015-05-31 16:53:20 -04:00
commit 1410081345
8 changed files with 228 additions and 45 deletions

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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;i<hFFT->Points;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<windowSize; i++)
mWindow[i]=1.0;
WindowFunc(mWindowType, mWindowSize, mWindow);
// Scale the window function to give 0dB spectrum for 0dB sine tone
double ws=0;
for(i=0; i<windowSize; i++)
ws += mWindow[i];
if(ws > 0) {
ws = 2.0/ws;
for(i=0; i<windowSize; i++)
mWindow[i] *= ws;
}
hFFT = InitializeFFT(fftLen);
double scale;
RecreateWindow(mWindow, WINDOW, fftLen, padding, mWindowType, mWindowSize, scale);
}
#endif // EXPERIMENTAL_USE_REALFFTF
mZeroPaddingFactor = zeroPaddingFactor;
const bool match =
mSpecCache &&
mSpecCache->dirty == 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,

View File

@ -257,6 +257,7 @@ protected:
int mWindowType;
int mWindowSize;
#endif
int mZeroPaddingFactor;
samplePtr mAppendBuffer;
sampleCount mAppendBufferLen;

View File

@ -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<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);
@ -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<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()

View File

@ -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;