From: Simon A. Eugster Date: Tue, 7 Dec 2010 14:02:03 +0000 (+0000) Subject: Audio Spectrum: Moved FFT calculation to FFTTools for re-use X-Git-Url: https://git.sesse.net/?a=commitdiff_plain;h=893498c6ea67208b800b6ae0c354ba1f5641edc2;p=kdenlive Audio Spectrum: Moved FFT calculation to FFTTools for re-use svn path=/trunk/kdenlive/; revision=5145 --- diff --git a/src/audioscopes/audiospectrum.cpp b/src/audioscopes/audiospectrum.cpp index 359c1531..f8cf3eba 100644 --- a/src/audioscopes/audiospectrum.cpp +++ b/src/audioscopes/audiospectrum.cpp @@ -36,8 +36,7 @@ const QString AudioSpectrum::directions[] = {"North", "Northeast", "East", "Sou AudioSpectrum::AudioSpectrum(QWidget *parent) : AbstractAudioScopeWidget(false, parent), - m_fftCfgs(), - m_windowFunctions(), + m_fftTools(), m_freqMax(10000), m_customFreq(false), m_rescaleMinDist(8), @@ -70,6 +69,7 @@ AudioSpectrum::AudioSpectrum(QWidget *parent) : bool b = true; b &= connect(m_aResetHz, SIGNAL(triggered()), this, SLOT(slotResetMaxFreq())); + b &= connect(ui->windowFunction, SIGNAL(currentIndexChanged(int)), this, SLOT(forceUpdate())); Q_ASSERT(b); @@ -83,10 +83,6 @@ AudioSpectrum::~AudioSpectrum() { writeConfig(); - QHash::iterator i; - for (i = m_fftCfgs.begin(); i != m_fftCfgs.end(); i++) { - free(*i); - } delete m_aResetHz; } @@ -159,78 +155,12 @@ QImage AudioSpectrum::renderAudioScope(uint, const QVector audioFrame, // Show the window size used, for information ui->labelFFTSizeNumber->setText(QVariant(fftWindow).toString()); - // Get the kiss_fft configuration from the config cache - // or build a new configuration if the requested one is not available. - kiss_fftr_cfg myCfg; - const QString signature = cfgSignature(fftWindow); - if (m_fftCfgs.contains(signature)) { -#ifdef DEBUG_AUDIOSPEC - qDebug() << "Re-using FFT configuration with size " << fftWindow; -#endif - myCfg = m_fftCfgs.value(signature); - } else { -#ifdef DEBUG_AUDIOSPEC - qDebug() << "Creating FFT configuration with size " << fftWindow; -#endif - myCfg = kiss_fftr_alloc(fftWindow, 0,0,0); - m_fftCfgs.insert(signature, myCfg); - } - float data[fftWindow]; + // Get the spectral power distribution of the input samples, + // using the given window size and function float freqSpectrum[fftWindow/2]; - - // Prepare frequency space vector. The resulting FFT vector is only half as long. - kiss_fft_cpx freqData[fftWindow/2]; - - - - // Copy the first channel's audio into a vector for the FFT display - // (only one channel handled at the moment) - if (num_samples < fftWindow) { - std::fill(&data[num_samples], &data[fftWindow-1], 0); - } - FFTTools::WindowType windowType = (FFTTools::WindowType) ui->windowFunction->itemData(ui->windowFunction->currentIndex()).toInt(); - QVector window; - float windowScaleFactor = 1; - if (windowType != FFTTools::Window_Rect) { - const QString signature = FFTTools::windowSignature(windowType, fftWindow, 0); - if (m_windowFunctions.contains(signature)) { -#ifdef DEBUG_AUDIOSPEC - qDebug() << "Re-using window function with signature " << signature; -#endif - window = m_windowFunctions.value(signature); - } else { -#ifdef DEBUG_AUDIOSPEC - qDebug() << "Building new window function with signature " << signature; -#endif - window = FFTTools::window(windowType, fftWindow, 0); - m_windowFunctions.insert(signature, window); - } - windowScaleFactor = 1.0/window[fftWindow]; - } - - // Normalize signals to [0,1] to get correct dB values later on - for (int i = 0; i < num_samples && i < fftWindow; i++) { - if (windowType != FFTTools::Window_Rect) { - data[i] = (float) audioFrame.data()[i*num_channels] / 32767.0f * window[i]; - } else { - data[i] = (float) audioFrame.data()[i*num_channels] / 32767.0f; - } - } - - // Calculate the Fast Fourier Transform for the input data - kiss_fftr(myCfg, data, freqData); - - - // Logarithmic scale: 20 * log ( 2 * magnitude / N ) with magnitude = sqrt(r² + i²) - // with N = FFT size (after FFT, 1/2 window size) - for (int i = 0; i < fftWindow/2; i++) { - // Logarithmic scale: 20 * log ( 2 * magnitude / N ) with magnitude = sqrt(r² + i²) - // with N = FFT size (after FFT, 1/2 window size) - freqSpectrum[i] = 20*log(pow(pow(fabs(freqData[i].r * windowScaleFactor),2) + pow(fabs(freqData[i].i * windowScaleFactor),2), .5)/((float)fftWindow/2.0f))/log(10);; - } - + m_fftTools.fftNormalized(audioFrame, 0, num_channels, freqSpectrum, windowType, fftWindow, 0); // Draw the spectrum diff --git a/src/audioscopes/audiospectrum.h b/src/audioscopes/audiospectrum.h index 45c21007..e3e2498e 100644 --- a/src/audioscopes/audiospectrum.h +++ b/src/audioscopes/audiospectrum.h @@ -24,6 +24,7 @@ #include "abstractaudioscopewidget.h" #include "ui_audiospectrum_ui.h" #include "tools/kiss_fftr.h" +#include "ffttools.h" class AudioSpectrum_UI; class AudioSpectrum : public AbstractAudioScopeWidget { @@ -59,11 +60,11 @@ protected: private: Ui::AudioSpectrum_UI *ui; - QHash m_fftCfgs; // FFT cfg cache - QHash > m_windowFunctions; // Window function cache QAction *m_aResetHz; + FFTTools m_fftTools; + /** Contains the plot only; m_scopeRect contains text and widgets as well */ QRect m_innerScopeRect; diff --git a/src/audioscopes/ffttools.cpp b/src/audioscopes/ffttools.cpp index fe7f2407..b333eaae 100644 --- a/src/audioscopes/ffttools.cpp +++ b/src/audioscopes/ffttools.cpp @@ -9,24 +9,39 @@ ***************************************************************************/ #include +#include #include #include "ffttools.h" -//#define DEBUG_FFTTOOLS +#define DEBUG_FFTTOOLS #ifdef DEBUG_FFTTOOLS #include +#include #endif -FFTTools::FFTTools() +FFTTools::FFTTools() : + m_fftCfgs(), + m_windowFunctions() { } +FFTTools::~FFTTools() +{ + QHash::iterator i; + for (i = m_fftCfgs.begin(); i != m_fftCfgs.end(); i++) { + free(*i); + } +} const QString FFTTools::windowSignature(const WindowType windowType, const int size, const float param) { return QString("s%1_t%2_p%3").arg(size).arg(windowType).arg(param, 0, 'f', 3); } +const QString FFTTools::cfgSignature(const int size) +{ + return QString("s%1").arg(size); +} // http://cplusplus.syntaxerrors.info/index.php?title=Cannot_declare_member_function_%E2%80%98static_int_Foo::bar%28%29%E2%80%99_to_have_static_linkage const QVector FFTTools::window(const WindowType windowType, const int size, const float param) @@ -91,6 +106,97 @@ const QVector FFTTools::window(const WindowType windowType, const int siz return QVector(); } +void FFTTools::fftNormalized(const QVector audioFrame, const uint channel, const uint numChannels, float *freqSpectrum, + const WindowType windowType, const uint windowSize, const float param) +{ +#ifdef DEBUG_FFTTOOLS + QTime start = QTime::currentTime(); +#endif + + const uint numSamples = audioFrame.size()/numChannels; + + Q_ASSERT((windowSize & 1) == 0); + + const QString cfgSig = cfgSignature(windowSize); + const QString winSig = windowSignature(windowType, windowSize, param); + + + // Get the kiss_fft configuration from the config cache + // or build a new configuration if the requested one is not available. + kiss_fftr_cfg myCfg; + if (m_fftCfgs.contains(cfgSig)) { +#ifdef DEBUG_FFTTOOLS + qDebug() << "Re-using FFT configuration with size " << windowSize; +#endif + myCfg = m_fftCfgs.value(cfgSig); + } else { +#ifdef DEBUG_FFTTOOLS + qDebug() << "Creating FFT configuration with size " << windowSize; +#endif + myCfg = kiss_fftr_alloc(windowSize, 0,0,0); + m_fftCfgs.insert(cfgSig, myCfg); + } + + // Get the window function from the cache + // (except for a rectangular window; nothing to to there. + QVector window; + float windowScaleFactor = 1; + if (windowType != FFTTools::Window_Rect) { + + if (m_windowFunctions.contains(winSig)) { +#ifdef DEBUG_FFTTOOLS + qDebug() << "Re-using window function with signature " << winSig; +#endif + window = m_windowFunctions.value(winSig); + } else { +#ifdef DEBUG_FFTTOOLS + qDebug() << "Building new window function with signature " << winSig; +#endif + window = FFTTools::window(windowType, windowSize, 0); + m_windowFunctions.insert(winSig, window); + } + windowScaleFactor = 1.0/window[windowSize]; + } + + + // Prepare frequency space vector. The resulting FFT vector is only half as long. + kiss_fft_cpx freqData[windowSize/2]; + float data[windowSize]; + + // Copy the first channel's audio into a vector for the FFT display; + // Fill the data vector indices that cannot be covered with sample data with 0 + if (numSamples < windowSize) { + std::fill(&data[numSamples], &data[windowSize-1], 0); + } + // Normalize signals to [0,1] to get correct dB values later on + for (int i = 0; i < numSamples && i < windowSize; i++) { + // Performance note: Benchmarking has shown that using the if/else inside the loop + // does not do noticeable worse than keeping it outside (perhaps the branch predictor + // is good enough), so it remains in there for better readability. + if (windowType != FFTTools::Window_Rect) { + data[i] = (float) audioFrame.data()[i*numChannels] / 32767.0f * window[i]; + } else { + data[i] = (float) audioFrame.data()[i*numChannels] / 32767.0f; + } + } + + // Calculate the Fast Fourier Transform for the input data + kiss_fftr(myCfg, data, freqData); + + + // Logarithmic scale: 20 * log ( 2 * magnitude / N ) with magnitude = sqrt(r² + i²) + // with N = FFT size (after FFT, 1/2 window size) + for (int i = 0; i < windowSize/2; i++) { + // Logarithmic scale: 20 * log ( 2 * magnitude / N ) with magnitude = sqrt(r² + i²) + // with N = FFT size (after FFT, 1/2 window size) + freqSpectrum[i] = 20*log(pow(pow(fabs(freqData[i].r * windowScaleFactor),2) + pow(fabs(freqData[i].i * windowScaleFactor),2), .5)/((float)windowSize/2.0f))/log(10);; + } + +#ifdef DEBUG_FFTTOOLS + qDebug() << "Calculated FFT in " << start.elapsed() << " ms."; +#endif +} + #ifdef DEBUG_FFTTOOLS #undef DEBUG_FFTTOOLS #endif diff --git a/src/audioscopes/ffttools.h b/src/audioscopes/ffttools.h index ef7fe0f9..9014d47e 100644 --- a/src/audioscopes/ffttools.h +++ b/src/audioscopes/ffttools.h @@ -12,11 +12,14 @@ #define FFTTOOLS_H #include +#include +#include class FFTTools { public: FFTTools(); + ~FFTTools(); enum WindowType { Window_Rect, Window_Triangle, Window_Hamming }; @@ -33,9 +36,29 @@ public: default is 0) * Nothing for the Hamming window */ - static const QVector window(const WindowType windowType, const int size, const float param); + static const QVector window(const WindowType windowType, const int size, const float param = 0); - static const QString windowSignature(const WindowType windowType, const int size, const float param); + static const QString windowSignature(const WindowType windowType, const int size, const float param = 0); + + /** Returns a signature for a kiss_fft configuration + used as a hash in the cache */ + static const QString cfgSignature(const int size); + + /** Calculates the Fourier Tranformation of the input audio frame. + The resulting values will be given in relative dezibel: The maximum power is 0 dB, lower powers have + negative dB values. + * audioFrame: Interleaved format with #numChannels channels + * freqSpectrum: Array pointer to write the data into + * windowSize must be divisible by 2, + * freqSpectrum has to be of size windowSize/2 + For windowType and param see the FFTTools::window() function above. + */ + void fftNormalized(const QVector audioFrame, const uint channel, const uint numChannels, float *freqSpectrum, + const WindowType windowType, const uint windowSize, const float param = 0); + +private: + QHash m_fftCfgs; // FFT cfg cache + QHash > m_windowFunctions; // Window function cache };