]> git.sesse.net Git - kdenlive/blob - src/audioscopes/spectrogram.cpp
Spectrogram added (but not yet efficient)
[kdenlive] / src / audioscopes / spectrogram.cpp
1 /***************************************************************************
2  *   Copyright (C) 2010 by Simon Andreas Eugster (simon.eu@gmail.com)      *
3  *   This file is part of kdenlive. See www.kdenlive.org.                  *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  ***************************************************************************/
10
11 #include "spectrogram.h"
12
13 #define SPECTROGRAM_HISTORY_SIZE 1000
14
15 #define DEBUG_SPECTROGRAM
16 #ifdef DEBUG_SPECTROGRAM
17 #include <QDebug>
18 #endif
19
20 Spectrogram::Spectrogram(QWidget *parent) :
21         AbstractAudioScopeWidget(false, parent),
22         m_fftTools(),
23         m_fftHistory()
24 {
25     ui = new Ui::Spectrogram_UI;
26     ui->setupUi(this);
27
28
29
30
31     ui->windowSize->addItem("256", QVariant(256));
32     ui->windowSize->addItem("512", QVariant(512));
33     ui->windowSize->addItem("1024", QVariant(1024));
34     ui->windowSize->addItem("2048", QVariant(2048));
35
36     ui->windowFunction->addItem(i18n("Rectangular window"), FFTTools::Window_Rect);
37     ui->windowFunction->addItem(i18n("Triangular window"), FFTTools::Window_Triangle);
38     ui->windowFunction->addItem(i18n("Hamming window"), FFTTools::Window_Hamming);
39
40     bool b = true;
41     b &= connect(ui->windowFunction, SIGNAL(currentIndexChanged(int)), this, SLOT(forceUpdate()));
42     Q_ASSERT(b);
43
44     AbstractScopeWidget::init();
45 }
46
47 Spectrogram::~Spectrogram()
48 {
49     writeConfig();
50 }
51
52 void Spectrogram::readConfig()
53 {
54     AbstractScopeWidget::readConfig();
55
56     KSharedConfigPtr config = KGlobal::config();
57     KConfigGroup scopeConfig(config, AbstractScopeWidget::configName());
58
59     m_dBmax = scopeConfig.readEntry("dBmax", 0);
60     m_dBmin = scopeConfig.readEntry("dBmin", -70);
61     m_freqMax = scopeConfig.readEntry("freqMax", 0);
62
63     if (m_freqMax == 0) {
64         m_customFreq = false;
65         m_freqMax = 10000;
66     } else {
67         m_customFreq = true;
68     }
69 }
70 void Spectrogram::writeConfig()
71 {
72     KSharedConfigPtr config = KGlobal::config();
73     KConfigGroup scopeConfig(config, AbstractScopeWidget::configName());
74
75     scopeConfig.writeEntry("dBmax", m_dBmax);
76     scopeConfig.writeEntry("dBmin", m_dBmin);
77
78     if (m_customFreq) {
79         scopeConfig.writeEntry("freqMax", m_freqMax);
80     } else {
81         scopeConfig.writeEntry("freqMax", 0);
82     }
83
84     scopeConfig.sync();
85 }
86
87 QString Spectrogram::widgetName() const { return QString("Spectrogram"); }
88
89 QRect Spectrogram::scopeRect()
90 {
91     m_scopeRect = QRect(
92             QPoint(
93                     10,                                     // Left
94                     ui->verticalSpacer->geometry().top()+6  // Top
95             ),
96             AbstractAudioScopeWidget::rect().bottomRight()
97     );
98     m_innerScopeRect = QRect(
99             QPoint(
100                     m_scopeRect.left()+6,                   // Left
101                     m_scopeRect.top()+6                     // Top
102             ), QPoint(
103                     ui->verticalSpacer->geometry().right()-70,
104                     ui->verticalSpacer->geometry().bottom()-40
105             )
106     );
107     return m_scopeRect;
108 }
109
110 QImage Spectrogram::renderHUD(uint) { return QImage(); }
111 QImage Spectrogram::renderAudioScope(uint, const QVector<int16_t> audioFrame, const int freq,
112                                      const int num_channels, const int num_samples) {
113     if (audioFrame.size() > 63) {
114         if (!m_customFreq) {
115             m_freqMax = freq / 2;
116         }
117
118         QTime start = QTime::currentTime();
119
120         int fftWindow = ui->windowSize->itemData(ui->windowSize->currentIndex()).toInt();
121         if (fftWindow > num_samples) {
122             fftWindow = num_samples;
123         }
124         if ((fftWindow & 1) == 1) {
125             fftWindow--;
126         }
127
128         // Show the window size used, for information
129         ui->labelFFTSizeNumber->setText(QVariant(fftWindow).toString());
130
131
132         // Get the spectral power distribution of the input samples,
133         // using the given window size and function
134         float freqSpectrum[fftWindow/2];
135         FFTTools::WindowType windowType = (FFTTools::WindowType) ui->windowFunction->itemData(ui->windowFunction->currentIndex()).toInt();
136         m_fftTools.fftNormalized(audioFrame, 0, num_channels, freqSpectrum, windowType, fftWindow, 0);
137
138         QVector<float> spectrumVector(fftWindow/2);
139         memcpy(spectrumVector.data(), &freqSpectrum[0], fftWindow/2 * sizeof(float));
140         m_fftHistory.prepend(spectrumVector);
141
142         // Limit the maximum history size to avoid wasting space
143         while (m_fftHistory.size() > SPECTROGRAM_HISTORY_SIZE) {
144             m_fftHistory.removeLast();
145         }
146
147         // Draw the spectrum
148         QImage spectrum(m_scopeRect.size(), QImage::Format_ARGB32);
149         spectrum.fill(qRgba(0,0,0,0));
150         const uint w = m_innerScopeRect.width();
151         const uint h = m_innerScopeRect.height();
152         const uint leftDist = m_innerScopeRect.left() - m_scopeRect.left();
153         const uint topDist = m_innerScopeRect.top() - m_scopeRect.top();
154         float f;
155         float x;
156         float x_prev = 0;
157         float val;
158         uint windowSize;
159         uint xi;
160         uint y = topDist;
161
162         for (QList<QVector<float> >::iterator it = m_fftHistory.begin(); it != m_fftHistory.end(); it++) {
163
164             windowSize = (*it).size();
165
166             for (uint i = 0; i < w; i++) {
167
168                 // i:   Pixel coordinate
169                 // f:   Target frequency
170                 // x:   Frequency array index (float!) corresponding to the pixel
171                 // xi:  floor(x)
172                 // val: dB value at position x (Range: [-inf,0])
173
174                 f = i/((float) w-1.0) * m_freqMax;
175                 x = 2*f/freq * (windowSize - 1);
176                 xi = (int) floor(x);
177
178                 if (x >= windowSize) {
179                     break;
180                 }
181
182                 // Use linear interpolation in order to get smoother display
183                 if (i == 0 || xi == windowSize-1) {
184                     // ... except if we are at the left or right border of the display or the spectrum
185                     val = (*it)[xi];
186                 } else {
187
188                     if ((*it)[xi] > (*it)[xi+1]
189                         && x_prev < xi) {
190                         // This is a hack to preserve peaks.
191                         // Consider f = {0, 100, 0}
192                         //          x = {0.5,  1.5}
193                         // Then x is 50 both times, and the 100 peak is lost.
194                         // Get it back here for the first x after the peak.
195                         val = (*it)[xi];
196                     } else {
197                         val =   (xi+1 - x) * (*it)[xi]
198                               + (x - xi)   * (*it)[xi+1];
199                     }
200                 }
201
202                 // Normalize to [0 1], 1 corresponding to 0 dB and 0 to dbMin dB
203                 val = -val/m_dBmin + 1;
204                 if (val < 0) {
205                     val = 0;
206                 }
207
208                 spectrum.setPixel(leftDist + i, topDist + h-y-1, qRgba(225, 182, 255, val * 255));
209
210                 x_prev = x;
211             }
212
213             y++;
214             if (y >= topDist + m_innerScopeRect.height()) {
215                 break;
216             }
217         }
218
219 #ifdef DEBUG_SPECTROGRAM
220         qDebug() << "Rendered " << y-topDist << "lines from " << m_fftHistory.size() << " available samples in " << start.elapsed() << " ms";
221 #endif
222
223
224         emit signalScopeRenderingFinished(start.elapsed(), 1);
225         return spectrum;
226     } else {
227         emit signalScopeRenderingFinished(0, 1);
228         return QImage();
229     }
230 }
231 QImage Spectrogram::renderBackground(uint) { return QImage(); }
232
233 bool Spectrogram::isHUDDependingOnInput() const { return false; }
234 bool Spectrogram::isScopeDependingOnInput() const { return false; }
235 bool Spectrogram::isBackgroundDependingOnInput() const { return false; }
236
237 #undef SPECTROGRAM_HISTORY_SIZE
238 #ifdef DEBUG_SPECTROGRAM
239 #undef DEBUG_SPECTROGRAM
240 #endif