]> git.sesse.net Git - kdenlive/commitdiff
Spectrogram added (but not yet efficient)
authorSimon A. Eugster <simon.eu@gmail.com>
Tue, 7 Dec 2010 14:02:26 +0000 (14:02 +0000)
committerSimon A. Eugster <simon.eu@gmail.com>
Tue, 7 Dec 2010 14:02:26 +0000 (14:02 +0000)
svn path=/trunk/kdenlive/; revision=5146

src/audioscopes/audiospectrum.cpp
src/audioscopes/ffttools.cpp
src/audioscopes/spectrogram.cpp
src/audioscopes/spectrogram.h
src/widgets/spectrogram_ui.ui

index f8cf3ebaa114e6d3e86672b4a4f21f1c8816591a..b4e3a6b9307adadc674371b4e578067a16f48ce7 100644 (file)
@@ -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
index b333eaae8e1eb2a48471551951734911c531b9e1..53d02f356824e5e5a0f2a4260e3fae047939b726 100644 (file)
@@ -15,7 +15,7 @@
 
 #include "ffttools.h"
 
-#define DEBUG_FFTTOOLS
+//#define DEBUG_FFTTOOLS
 #ifdef DEBUG_FFTTOOLS
 #include <QDebug>
 #include <QTime>
@@ -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<float> 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<int16_t> 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<int16_t> 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;
         }
     }
 
index cd1958c7623e509e217ba7cf7773e2507adb97a0..a01a3e6eac5c8c35d08c8a5d43386c9fc07164a2 100644 (file)
 
 #include "spectrogram.h"
 
+#define SPECTROGRAM_HISTORY_SIZE 1000
+
+#define DEBUG_SPECTROGRAM
+#ifdef DEBUG_SPECTROGRAM
+#include <QDebug>
+#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<int16_t> audioFrame, const int freq, const int num_channels, const int num_samples) {
-    return QImage();
+QImage Spectrogram::renderAudioScope(uint, const QVector<int16_t> 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<float> 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<QVector<float> >::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
index fbcb60f5b443f2aaa8911e2d330c210e337b73b8..c601ac1dbc3053ceb0a6aab9a62735bc3af5f15a 100644 (file)
@@ -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<QVector<float> > m_fftHistory;
+
+    int m_dBmin;
+    int m_dBmax;
+
+    uint m_freqMax;
+    bool m_customFreq;
+
+    QRect m_innerScopeRect;
 };
 
 #endif // SPECTROGRAM_H
index 4ff9d131f9bcbda085c90e94486381448711b2a6..0c85c7d8b43370b91e150da60a6402fea931b2fd 100644 (file)
   <property name="windowTitle">
    <string>Form</string>
   </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="0" column="5">
+    <widget class="QComboBox" name="windowSize"/>
+   </item>
+   <item row="0" column="4">
+    <widget class="QComboBox" name="windowFunction"/>
+   </item>
+   <item row="1" column="5">
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>40</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item row="0" column="3">
+    <spacer name="horizontalSpacer">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>40</width>
+       <height>20</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item row="0" column="0">
+    <spacer name="horizontalSpacer_2">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="sizeType">
+      <enum>QSizePolicy::Fixed</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>10</width>
+       <height>20</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item row="0" column="1">
+    <widget class="QLabel" name="labelFFTSize">
+     <property name="text">
+      <string>True FFT size:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="2">
+    <widget class="QLabel" name="labelFFTSizeNumber">
+     <property name="text">
+      <string/>
+     </property>
+    </widget>
+   </item>
+  </layout>
  </widget>
  <resources/>
  <connections/>