1 /***************************************************************************
2 * Copyright (C) 2010 by Simon Andreas Eugster (simon.eu@gmail.com) *
3 * This file is part of kdenlive. See www.kdenlive.org. *
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 ***************************************************************************/
13 #include "spectrogram.h"
15 // Defines the number of FFT samples to store.
16 // Around 4 kB for a window size of 2000. Should be at least as large as the
17 // highest vertical screen resolution available for complete reconstruction.
18 // Can be less as a pre-rendered image is kept in space.
19 #define SPECTROGRAM_HISTORY_SIZE 1000
21 //#define DEBUG_SPECTROGRAM
22 #ifdef DEBUG_SPECTROGRAM
26 Spectrogram::Spectrogram(QWidget *parent) :
27 AbstractAudioScopeWidget(false, parent),
32 ui = new Ui::Spectrogram_UI;
38 ui->windowSize->addItem("256", QVariant(256));
39 ui->windowSize->addItem("512", QVariant(512));
40 ui->windowSize->addItem("1024", QVariant(1024));
41 ui->windowSize->addItem("2048", QVariant(2048));
43 ui->windowFunction->addItem(i18n("Rectangular window"), FFTTools::Window_Rect);
44 ui->windowFunction->addItem(i18n("Triangular window"), FFTTools::Window_Triangle);
45 ui->windowFunction->addItem(i18n("Hamming window"), FFTTools::Window_Hamming);
48 b &= connect(ui->windowFunction, SIGNAL(currentIndexChanged(int)), this, SLOT(forceUpdate()));
51 AbstractScopeWidget::init();
54 Spectrogram::~Spectrogram()
59 void Spectrogram::readConfig()
61 AbstractScopeWidget::readConfig();
63 KSharedConfigPtr config = KGlobal::config();
64 KConfigGroup scopeConfig(config, AbstractScopeWidget::configName());
66 ui->windowSize->setCurrentIndex(scopeConfig.readEntry("windowSize", 0));
67 ui->windowFunction->setCurrentIndex(scopeConfig.readEntry("windowFunction", 0));
68 m_dBmax = scopeConfig.readEntry("dBmax", 0);
69 m_dBmin = scopeConfig.readEntry("dBmin", -70);
70 m_freqMax = scopeConfig.readEntry("freqMax", 0);
79 void Spectrogram::writeConfig()
81 KSharedConfigPtr config = KGlobal::config();
82 KConfigGroup scopeConfig(config, AbstractScopeWidget::configName());
84 scopeConfig.writeEntry("windowSize", ui->windowSize->currentIndex());
85 scopeConfig.writeEntry("windowFunction", ui->windowFunction->currentIndex());
86 scopeConfig.writeEntry("dBmax", m_dBmax);
87 scopeConfig.writeEntry("dBmin", m_dBmin);
90 scopeConfig.writeEntry("freqMax", m_freqMax);
92 scopeConfig.writeEntry("freqMax", 0);
98 QString Spectrogram::widgetName() const { return QString("Spectrogram"); }
100 QRect Spectrogram::scopeRect()
105 ui->verticalSpacer->geometry().top()+6 // Top
107 AbstractAudioScopeWidget::rect().bottomRight()
109 m_innerScopeRect = QRect(
111 m_scopeRect.left()+6, // Left
112 m_scopeRect.top()+6 // Top
114 ui->verticalSpacer->geometry().right()-70,
115 ui->verticalSpacer->geometry().bottom()-40
121 QImage Spectrogram::renderHUD(uint) { return QImage(); }
122 QImage Spectrogram::renderAudioScope(uint, const QVector<int16_t> audioFrame, const int freq,
123 const int num_channels, const int num_samples) {
124 if (audioFrame.size() > 63) {
126 m_freqMax = freq / 2;
129 QTime start = QTime::currentTime();
131 int fftWindow = ui->windowSize->itemData(ui->windowSize->currentIndex()).toInt();
132 if (fftWindow > num_samples) {
133 fftWindow = num_samples;
135 if ((fftWindow & 1) == 1) {
139 // Show the window size used, for information
140 ui->labelFFTSizeNumber->setText(QVariant(fftWindow).toString());
143 // Get the spectral power distribution of the input samples,
144 // using the given window size and function
145 float freqSpectrum[fftWindow/2];
146 FFTTools::WindowType windowType = (FFTTools::WindowType) ui->windowFunction->itemData(ui->windowFunction->currentIndex()).toInt();
147 m_fftTools.fftNormalized(audioFrame, 0, num_channels, freqSpectrum, windowType, fftWindow, 0);
149 QVector<float> spectrumVector(fftWindow/2);
150 memcpy(spectrumVector.data(), &freqSpectrum[0], fftWindow/2 * sizeof(float));
151 m_fftHistory.prepend(spectrumVector);
153 // Limit the maximum history size to avoid wasting space
154 while (m_fftHistory.size() > SPECTROGRAM_HISTORY_SIZE) {
155 m_fftHistory.removeLast();
159 QImage spectrum(m_scopeRect.size(), QImage::Format_ARGB32);
160 spectrum.fill(qRgba(0,0,0,0));
161 QPainter davinci(&spectrum);
162 const uint w = m_innerScopeRect.width();
163 const uint h = m_innerScopeRect.height();
164 const uint leftDist = m_innerScopeRect.left() - m_scopeRect.left();
165 const uint topDist = m_innerScopeRect.top() - m_scopeRect.top();
173 bool completeRedraw = true;
175 if (m_fftHistoryImg.size() == m_scopeRect.size()) {
176 // The size of the widget has not changed since last time, so we can re-use it,
177 // shift it by one pixel, and render the single remaining line. Usually about
178 // 10 times faster for a widget height of around 400 px.
179 davinci.drawImage(0, -1, m_fftHistoryImg);
180 completeRedraw = false;
183 for (QList<QVector<float> >::iterator it = m_fftHistory.begin(); it != m_fftHistory.end(); it++) {
185 windowSize = (*it).size();
187 for (uint i = 0; i < w; i++) {
189 // i: Pixel coordinate
190 // f: Target frequency
191 // x: Frequency array index (float!) corresponding to the pixel
193 // val: dB value at position x (Range: [-inf,0])
195 f = i/((float) w-1.0) * m_freqMax;
196 x = 2*f/freq * (windowSize - 1);
199 if (x >= windowSize) {
203 // Use linear interpolation in order to get smoother display
204 if (i == 0 || xi == windowSize-1) {
205 // ... except if we are at the left or right border of the display or the spectrum
209 if ((*it)[xi] > (*it)[xi+1]
211 // This is a hack to preserve peaks.
212 // Consider f = {0, 100, 0}
214 // Then x is 50 both times, and the 100 peak is lost.
215 // Get it back here for the first x after the peak.
218 val = (xi+1 - x) * (*it)[xi]
219 + (x - xi) * (*it)[xi+1];
223 // Normalize to [0 1], 1 corresponding to 0 dB and 0 to dbMin dB
224 val = -val/m_dBmin + 1;
229 spectrum.setPixel(leftDist + i, topDist + h-y-1, qRgba(255, 255, 255, val * 255));
235 if (y >= topDist + m_innerScopeRect.height()) {
238 if (!completeRedraw) {
243 #ifdef DEBUG_SPECTROGRAM
244 qDebug() << "Rendered " << y-topDist << "lines from " << m_fftHistory.size() << " available samples in " << start.elapsed() << " ms"
245 << (completeRedraw ? " (re-used old image)" : "");
246 uint storedBytes = 0;
247 for (QList< QVector<float> >::iterator it = m_fftHistory.begin(); it != m_fftHistory.end(); it++) {
248 storedBytes += (*it).size() * sizeof((*it)[0]);
250 qDebug() << QString("Total storage used: %1 kB").arg((double)storedBytes/1000, 0, 'f', 2);
253 m_fftHistoryImg = spectrum;
255 emit signalScopeRenderingFinished(start.elapsed(), 1);
258 emit signalScopeRenderingFinished(0, 1);
262 QImage Spectrogram::renderBackground(uint) { return QImage(); }
264 bool Spectrogram::isHUDDependingOnInput() const { return false; }
265 bool Spectrogram::isScopeDependingOnInput() const { return false; }
266 bool Spectrogram::isBackgroundDependingOnInput() const { return false; }
268 #undef SPECTROGRAM_HISTORY_SIZE
269 #ifdef DEBUG_SPECTROGRAM
270 #undef DEBUG_SPECTROGRAM