From c1175a011e7b2a0654b5bc10691917b5f4da8535 Mon Sep 17 00:00:00 2001 From: "Simon A. Eugster" Date: Tue, 7 Dec 2010 14:02:26 +0000 Subject: [PATCH] Spectrogram added (but not yet efficient) svn path=/trunk/kdenlive/; revision=5146 --- src/audioscopes/audiospectrum.cpp | 5 +- src/audioscopes/ffttools.cpp | 9 +- src/audioscopes/spectrogram.cpp | 206 +++++++++++++++++++++++++++++- src/audioscopes/spectrogram.h | 12 ++ src/widgets/spectrogram_ui.ui | 64 ++++++++++ 5 files changed, 285 insertions(+), 11 deletions(-) diff --git a/src/audioscopes/audiospectrum.cpp b/src/audioscopes/audiospectrum.cpp index f8cf3eba..b4e3a6b9 100644 --- a/src/audioscopes/audiospectrum.cpp +++ b/src/audioscopes/audiospectrum.cpp @@ -37,8 +37,6 @@ const QString AudioSpectrum::directions[] = {"North", "Northeast", "East", "Sou AudioSpectrum::AudioSpectrum(QWidget *parent) : AbstractAudioScopeWidget(false, parent), m_fftTools(), - m_freqMax(10000), - m_customFreq(false), m_rescaleMinDist(8), m_rescaleVerticalThreshold(2.0f), m_rescaleActive(false), @@ -326,7 +324,8 @@ QImage AudioSpectrum::renderHUD(uint) return hud; } -QRect AudioSpectrum::scopeRect() { +QRect AudioSpectrum::scopeRect() +{ m_scopeRect = QRect( QPoint( 10, // Left diff --git a/src/audioscopes/ffttools.cpp b/src/audioscopes/ffttools.cpp index b333eaae..53d02f35 100644 --- a/src/audioscopes/ffttools.cpp +++ b/src/audioscopes/ffttools.cpp @@ -15,7 +15,7 @@ #include "ffttools.h" -#define DEBUG_FFTTOOLS +//#define DEBUG_FFTTOOLS #ifdef DEBUG_FFTTOOLS #include #include @@ -46,6 +46,8 @@ const QString FFTTools::cfgSignature(const int 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) { + Q_ASSERT(size > 0); + // Deliberately avoid converting size to a float // to keep mid an integer. float mid = (size-1)/2; @@ -116,6 +118,7 @@ void FFTTools::fftNormalized(const QVector audioFrame, const uint chann const uint numSamples = audioFrame.size()/numChannels; Q_ASSERT((windowSize & 1) == 0); + Q_ASSERT(windowSize > 0); const QString cfgSig = cfgSignature(windowSize); const QString winSig = windowSignature(windowType, windowSize, param); @@ -174,9 +177,9 @@ void FFTTools::fftNormalized(const QVector audioFrame, const uint chann // 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]; + data[i] = (float) audioFrame.data()[i*numChannels + channel] / 32767.0f * window[i]; } else { - data[i] = (float) audioFrame.data()[i*numChannels] / 32767.0f; + data[i] = (float) audioFrame.data()[i*numChannels + channel] / 32767.0f; } } diff --git a/src/audioscopes/spectrogram.cpp b/src/audioscopes/spectrogram.cpp index cd1958c7..a01a3e6e 100644 --- a/src/audioscopes/spectrogram.cpp +++ b/src/audioscopes/spectrogram.cpp @@ -10,11 +10,37 @@ #include "spectrogram.h" +#define SPECTROGRAM_HISTORY_SIZE 1000 + +#define DEBUG_SPECTROGRAM +#ifdef DEBUG_SPECTROGRAM +#include +#endif + Spectrogram::Spectrogram(QWidget *parent) : - AbstractAudioScopeWidget(false, parent) + AbstractAudioScopeWidget(false, parent), + m_fftTools(), + m_fftHistory() { ui = new Ui::Spectrogram_UI; ui->setupUi(this); + + + + + ui->windowSize->addItem("256", QVariant(256)); + ui->windowSize->addItem("512", QVariant(512)); + ui->windowSize->addItem("1024", QVariant(1024)); + ui->windowSize->addItem("2048", QVariant(2048)); + + ui->windowFunction->addItem(i18n("Rectangular window"), FFTTools::Window_Rect); + ui->windowFunction->addItem(i18n("Triangular window"), FFTTools::Window_Triangle); + ui->windowFunction->addItem(i18n("Hamming window"), FFTTools::Window_Hamming); + + bool b = true; + b &= connect(ui->windowFunction, SIGNAL(currentIndexChanged(int)), this, SLOT(forceUpdate())); + Q_ASSERT(b); + AbstractScopeWidget::init(); } @@ -26,19 +52,189 @@ Spectrogram::~Spectrogram() void Spectrogram::readConfig() { AbstractScopeWidget::readConfig(); + + KSharedConfigPtr config = KGlobal::config(); + KConfigGroup scopeConfig(config, AbstractScopeWidget::configName()); + + m_dBmax = scopeConfig.readEntry("dBmax", 0); + m_dBmin = scopeConfig.readEntry("dBmin", -70); + m_freqMax = scopeConfig.readEntry("freqMax", 0); + + if (m_freqMax == 0) { + m_customFreq = false; + m_freqMax = 10000; + } else { + m_customFreq = true; + } +} +void Spectrogram::writeConfig() +{ + KSharedConfigPtr config = KGlobal::config(); + KConfigGroup scopeConfig(config, AbstractScopeWidget::configName()); + + scopeConfig.writeEntry("dBmax", m_dBmax); + scopeConfig.writeEntry("dBmin", m_dBmin); + + if (m_customFreq) { + scopeConfig.writeEntry("freqMax", m_freqMax); + } else { + scopeConfig.writeEntry("freqMax", 0); + } + + scopeConfig.sync(); } -void Spectrogram::writeConfig() {} QString Spectrogram::widgetName() const { return QString("Spectrogram"); } -QRect Spectrogram::scopeRect() { return AbstractScopeWidget::rect(); } +QRect Spectrogram::scopeRect() +{ + m_scopeRect = QRect( + QPoint( + 10, // Left + ui->verticalSpacer->geometry().top()+6 // Top + ), + AbstractAudioScopeWidget::rect().bottomRight() + ); + m_innerScopeRect = QRect( + QPoint( + m_scopeRect.left()+6, // Left + m_scopeRect.top()+6 // Top + ), QPoint( + ui->verticalSpacer->geometry().right()-70, + ui->verticalSpacer->geometry().bottom()-40 + ) + ); + return m_scopeRect; +} QImage Spectrogram::renderHUD(uint) { return QImage(); } -QImage Spectrogram::renderAudioScope(uint accelerationFactor, const QVector audioFrame, const int freq, const int num_channels, const int num_samples) { - return QImage(); +QImage Spectrogram::renderAudioScope(uint, const QVector audioFrame, const int freq, + const int num_channels, const int num_samples) { + if (audioFrame.size() > 63) { + if (!m_customFreq) { + m_freqMax = freq / 2; + } + + QTime start = QTime::currentTime(); + + int fftWindow = ui->windowSize->itemData(ui->windowSize->currentIndex()).toInt(); + if (fftWindow > num_samples) { + fftWindow = num_samples; + } + if ((fftWindow & 1) == 1) { + fftWindow--; + } + + // Show the window size used, for information + ui->labelFFTSizeNumber->setText(QVariant(fftWindow).toString()); + + + // Get the spectral power distribution of the input samples, + // using the given window size and function + float freqSpectrum[fftWindow/2]; + FFTTools::WindowType windowType = (FFTTools::WindowType) ui->windowFunction->itemData(ui->windowFunction->currentIndex()).toInt(); + m_fftTools.fftNormalized(audioFrame, 0, num_channels, freqSpectrum, windowType, fftWindow, 0); + + QVector spectrumVector(fftWindow/2); + memcpy(spectrumVector.data(), &freqSpectrum[0], fftWindow/2 * sizeof(float)); + m_fftHistory.prepend(spectrumVector); + + // Limit the maximum history size to avoid wasting space + while (m_fftHistory.size() > SPECTROGRAM_HISTORY_SIZE) { + m_fftHistory.removeLast(); + } + + // Draw the spectrum + QImage spectrum(m_scopeRect.size(), QImage::Format_ARGB32); + spectrum.fill(qRgba(0,0,0,0)); + const uint w = m_innerScopeRect.width(); + const uint h = m_innerScopeRect.height(); + const uint leftDist = m_innerScopeRect.left() - m_scopeRect.left(); + const uint topDist = m_innerScopeRect.top() - m_scopeRect.top(); + float f; + float x; + float x_prev = 0; + float val; + uint windowSize; + uint xi; + uint y = topDist; + + for (QList >::iterator it = m_fftHistory.begin(); it != m_fftHistory.end(); it++) { + + windowSize = (*it).size(); + + for (uint i = 0; i < w; i++) { + + // i: Pixel coordinate + // f: Target frequency + // x: Frequency array index (float!) corresponding to the pixel + // xi: floor(x) + // val: dB value at position x (Range: [-inf,0]) + + f = i/((float) w-1.0) * m_freqMax; + x = 2*f/freq * (windowSize - 1); + xi = (int) floor(x); + + if (x >= windowSize) { + break; + } + + // Use linear interpolation in order to get smoother display + if (i == 0 || xi == windowSize-1) { + // ... except if we are at the left or right border of the display or the spectrum + val = (*it)[xi]; + } else { + + if ((*it)[xi] > (*it)[xi+1] + && x_prev < xi) { + // This is a hack to preserve peaks. + // Consider f = {0, 100, 0} + // x = {0.5, 1.5} + // Then x is 50 both times, and the 100 peak is lost. + // Get it back here for the first x after the peak. + val = (*it)[xi]; + } else { + val = (xi+1 - x) * (*it)[xi] + + (x - xi) * (*it)[xi+1]; + } + } + + // Normalize to [0 1], 1 corresponding to 0 dB and 0 to dbMin dB + val = -val/m_dBmin + 1; + if (val < 0) { + val = 0; + } + + spectrum.setPixel(leftDist + i, topDist + h-y-1, qRgba(225, 182, 255, val * 255)); + + x_prev = x; + } + + y++; + if (y >= topDist + m_innerScopeRect.height()) { + break; + } + } + +#ifdef DEBUG_SPECTROGRAM + qDebug() << "Rendered " << y-topDist << "lines from " << m_fftHistory.size() << " available samples in " << start.elapsed() << " ms"; +#endif + + + emit signalScopeRenderingFinished(start.elapsed(), 1); + return spectrum; + } else { + emit signalScopeRenderingFinished(0, 1); + return QImage(); + } } QImage Spectrogram::renderBackground(uint) { return QImage(); } bool Spectrogram::isHUDDependingOnInput() const { return false; } bool Spectrogram::isScopeDependingOnInput() const { return false; } bool Spectrogram::isBackgroundDependingOnInput() const { return false; } + +#undef SPECTROGRAM_HISTORY_SIZE +#ifdef DEBUG_SPECTROGRAM +#undef DEBUG_SPECTROGRAM +#endif diff --git a/src/audioscopes/spectrogram.h b/src/audioscopes/spectrogram.h index fbcb60f5..c601ac1d 100644 --- a/src/audioscopes/spectrogram.h +++ b/src/audioscopes/spectrogram.h @@ -15,6 +15,7 @@ #include "abstractaudioscopewidget.h" #include "ui_spectrogram_ui.h" +#include "ffttools.h" class Spectrogram_UI; class Spectrogram : public AbstractAudioScopeWidget { @@ -40,6 +41,17 @@ protected: private: Ui::Spectrogram_UI *ui; + FFTTools m_fftTools; + + QList > m_fftHistory; + + int m_dBmin; + int m_dBmax; + + uint m_freqMax; + bool m_customFreq; + + QRect m_innerScopeRect; }; #endif // SPECTROGRAM_H diff --git a/src/widgets/spectrogram_ui.ui b/src/widgets/spectrogram_ui.ui index 4ff9d131..0c85c7d8 100644 --- a/src/widgets/spectrogram_ui.ui +++ b/src/widgets/spectrogram_ui.ui @@ -13,6 +13,70 @@ Form + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 10 + 20 + + + + + + + + True FFT size: + + + + + + + + + + + -- 2.39.2