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 ***************************************************************************/
11 #include "spectrogram.h"
13 #define SPECTROGRAM_HISTORY_SIZE 1000
15 #define DEBUG_SPECTROGRAM
16 #ifdef DEBUG_SPECTROGRAM
20 Spectrogram::Spectrogram(QWidget *parent) :
21 AbstractAudioScopeWidget(false, parent),
25 ui = new Ui::Spectrogram_UI;
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));
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);
41 b &= connect(ui->windowFunction, SIGNAL(currentIndexChanged(int)), this, SLOT(forceUpdate()));
44 AbstractScopeWidget::init();
47 Spectrogram::~Spectrogram()
52 void Spectrogram::readConfig()
54 AbstractScopeWidget::readConfig();
56 KSharedConfigPtr config = KGlobal::config();
57 KConfigGroup scopeConfig(config, AbstractScopeWidget::configName());
59 m_dBmax = scopeConfig.readEntry("dBmax", 0);
60 m_dBmin = scopeConfig.readEntry("dBmin", -70);
61 m_freqMax = scopeConfig.readEntry("freqMax", 0);
70 void Spectrogram::writeConfig()
72 KSharedConfigPtr config = KGlobal::config();
73 KConfigGroup scopeConfig(config, AbstractScopeWidget::configName());
75 scopeConfig.writeEntry("dBmax", m_dBmax);
76 scopeConfig.writeEntry("dBmin", m_dBmin);
79 scopeConfig.writeEntry("freqMax", m_freqMax);
81 scopeConfig.writeEntry("freqMax", 0);
87 QString Spectrogram::widgetName() const { return QString("Spectrogram"); }
89 QRect Spectrogram::scopeRect()
94 ui->verticalSpacer->geometry().top()+6 // Top
96 AbstractAudioScopeWidget::rect().bottomRight()
98 m_innerScopeRect = QRect(
100 m_scopeRect.left()+6, // Left
101 m_scopeRect.top()+6 // Top
103 ui->verticalSpacer->geometry().right()-70,
104 ui->verticalSpacer->geometry().bottom()-40
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) {
115 m_freqMax = freq / 2;
118 QTime start = QTime::currentTime();
120 int fftWindow = ui->windowSize->itemData(ui->windowSize->currentIndex()).toInt();
121 if (fftWindow > num_samples) {
122 fftWindow = num_samples;
124 if ((fftWindow & 1) == 1) {
128 // Show the window size used, for information
129 ui->labelFFTSizeNumber->setText(QVariant(fftWindow).toString());
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);
138 QVector<float> spectrumVector(fftWindow/2);
139 memcpy(spectrumVector.data(), &freqSpectrum[0], fftWindow/2 * sizeof(float));
140 m_fftHistory.prepend(spectrumVector);
142 // Limit the maximum history size to avoid wasting space
143 while (m_fftHistory.size() > SPECTROGRAM_HISTORY_SIZE) {
144 m_fftHistory.removeLast();
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();
162 for (QList<QVector<float> >::iterator it = m_fftHistory.begin(); it != m_fftHistory.end(); it++) {
164 windowSize = (*it).size();
166 for (uint i = 0; i < w; i++) {
168 // i: Pixel coordinate
169 // f: Target frequency
170 // x: Frequency array index (float!) corresponding to the pixel
172 // val: dB value at position x (Range: [-inf,0])
174 f = i/((float) w-1.0) * m_freqMax;
175 x = 2*f/freq * (windowSize - 1);
178 if (x >= windowSize) {
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
188 if ((*it)[xi] > (*it)[xi+1]
190 // This is a hack to preserve peaks.
191 // Consider f = {0, 100, 0}
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.
197 val = (xi+1 - x) * (*it)[xi]
198 + (x - xi) * (*it)[xi+1];
202 // Normalize to [0 1], 1 corresponding to 0 dB and 0 to dbMin dB
203 val = -val/m_dBmin + 1;
208 spectrum.setPixel(leftDist + i, topDist + h-y-1, qRgba(225, 182, 255, val * 255));
214 if (y >= topDist + m_innerScopeRect.height()) {
219 #ifdef DEBUG_SPECTROGRAM
220 qDebug() << "Rendered " << y-topDist << "lines from " << m_fftHistory.size() << " available samples in " << start.elapsed() << " ms";
224 emit signalScopeRenderingFinished(start.elapsed(), 1);
227 emit signalScopeRenderingFinished(0, 1);
231 QImage Spectrogram::renderBackground(uint) { return QImage(); }
233 bool Spectrogram::isHUDDependingOnInput() const { return false; }
234 bool Spectrogram::isScopeDependingOnInput() const { return false; }
235 bool Spectrogram::isBackgroundDependingOnInput() const { return false; }
237 #undef SPECTROGRAM_HISTORY_SIZE
238 #ifdef DEBUG_SPECTROGRAM
239 #undef DEBUG_SPECTROGRAM