AudioSpectrum::AudioSpectrum(QWidget *parent) :
AbstractAudioScopeWidget(false, parent),
- m_fftCfgs(),
- m_windowFunctions(),
+ m_fftTools(),
m_freqMax(10000),
m_customFreq(false),
m_rescaleMinDist(8),
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);
{
writeConfig();
- QHash<QString, kiss_fftr_cfg>::iterator i;
- for (i = m_fftCfgs.begin(); i != m_fftCfgs.end(); i++) {
- free(*i);
- }
delete m_aResetHz;
}
// 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<float> 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
#include "abstractaudioscopewidget.h"
#include "ui_audiospectrum_ui.h"
#include "tools/kiss_fftr.h"
+#include "ffttools.h"
class AudioSpectrum_UI;
class AudioSpectrum : public AbstractAudioScopeWidget {
private:
Ui::AudioSpectrum_UI *ui;
- QHash<QString, kiss_fftr_cfg> m_fftCfgs; // FFT cfg cache
- QHash<QString, QVector<float> > 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;
***************************************************************************/
#include <math.h>
+#include <iostream>
#include <QString>
#include "ffttools.h"
-//#define DEBUG_FFTTOOLS
+#define DEBUG_FFTTOOLS
#ifdef DEBUG_FFTTOOLS
#include <QDebug>
+#include <QTime>
#endif
-FFTTools::FFTTools()
+FFTTools::FFTTools() :
+ m_fftCfgs(),
+ m_windowFunctions()
{
}
+FFTTools::~FFTTools()
+{
+ QHash<QString, kiss_fftr_cfg>::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<float> FFTTools::window(const WindowType windowType, const int size, const float param)
return QVector<float>();
}
+void FFTTools::fftNormalized(const QVector<int16_t> 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<float> 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
#define FFTTOOLS_H
#include <QVector>
+#include <QHash>
+#include <tools/kiss_fftr.h>
class FFTTools
{
public:
FFTTools();
+ ~FFTTools();
enum WindowType { Window_Rect, Window_Triangle, Window_Hamming };
default is 0)
* Nothing for the Hamming window
*/
- static const QVector<float> window(const WindowType windowType, const int size, const float param);
+ static const QVector<float> 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<int16_t> audioFrame, const uint channel, const uint numChannels, float *freqSpectrum,
+ const WindowType windowType, const uint windowSize, const float param = 0);
+
+private:
+ QHash<QString, kiss_fftr_cfg> m_fftCfgs; // FFT cfg cache
+ QHash<QString, QVector<float> > m_windowFunctions; // Window function cache
};