]> git.sesse.net Git - kdenlive/blob - src/audioscopes/audiospectrum.cpp
Color scopes now using the common abstract scope as well. --> Common functions, more...
[kdenlive] / src / audioscopes / audiospectrum.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 "audiospectrum.h"
12 #include "tools/kiss_fftr.h"
13
14 #include <QMenu>
15 #include <QPainter>
16
17 // Linear interpolation.
18 //#include <iostream>
19 //#include <fstream>
20
21 bool fileWritten = false;
22
23 AudioSpectrum::AudioSpectrum(QWidget *parent) :
24         AbstractAudioScopeWidget(true, parent)
25 {
26     ui = new Ui::AudioSpectrum_UI;
27     ui->setupUi(this);
28
29     m_distance = QSize(65, 30);
30     m_dBmin = -120;
31     m_dBmax = 0;
32     m_freqMax = 10000;
33
34
35     m_aLin = new QAction(i18n("Linear scale"), this);
36     m_aLin->setCheckable(true);
37     m_aLog = new QAction(i18n("Logarithmic scale"), this);
38     m_aLog->setCheckable(true);
39
40     m_agScale = new QActionGroup(this);
41     m_agScale->addAction(m_aLin);
42     m_agScale->addAction(m_aLog);
43
44     m_menu->addSeparator()->setText(i18n("Scale"));
45     m_menu->addAction(m_aLin);
46     m_menu->addAction(m_aLog);
47
48     ui->windowSize->addItem("256", QVariant(256));
49     ui->windowSize->addItem("512", QVariant(512));
50     ui->windowSize->addItem("1024", QVariant(1024));
51     ui->windowSize->addItem("2048", QVariant(2048));
52
53     m_cfg = kiss_fftr_alloc(ui->windowSize->itemData(ui->windowSize->currentIndex()).toInt(), 0,0,0);
54
55
56     bool b = true;
57     b &= connect(ui->windowSize, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateCfg()));
58     Q_ASSERT(b);
59
60     AbstractScopeWidget::init();
61 }
62 AudioSpectrum::~AudioSpectrum()
63 {
64     free(m_cfg);
65     delete m_agScale;
66     delete m_aLin;
67     delete m_aLog;
68 }
69
70 void AudioSpectrum::readConfig()
71 {
72     AbstractScopeWidget::readConfig();
73
74     KSharedConfigPtr config = KGlobal::config();
75     KConfigGroup scopeConfig(config, AbstractScopeWidget::configName());
76     QString scale = scopeConfig.readEntry("scale");
77     if (scale == "lin") {
78         m_aLin->setChecked(true);
79     } else {
80         m_aLog->setChecked(true);
81     }
82     ui->windowSize->setCurrentIndex(scopeConfig.readEntry("windowSize", 0));
83
84 }
85 void AudioSpectrum::writeConfig()
86 {
87     KSharedConfigPtr config = KGlobal::config();
88     KConfigGroup scopeConfig(config, AbstractScopeWidget::configName());
89     QString scale;
90     if (m_aLin->isChecked()) {
91         scale = "lin";
92     } else {
93         scale = "log";
94     }
95     scopeConfig.writeEntry("scale", scale);
96     scopeConfig.writeEntry("windowSize", ui->windowSize->currentIndex());
97     scopeConfig.sync();
98 }
99
100 QString AudioSpectrum::widgetName() const { return QString("audiospectrum"); }
101
102 bool AudioSpectrum::isBackgroundDependingOnInput() const { return false; }
103 bool AudioSpectrum::isScopeDependingOnInput() const { return true; }
104 bool AudioSpectrum::isHUDDependingOnInput() const { return false; }
105
106 QImage AudioSpectrum::renderBackground(uint) { return QImage(); }
107 QImage AudioSpectrum::renderAudioScope(uint, const QVector<int16_t> audioFrame, const int freq, const int num_channels, const int num_samples)
108 {
109     if (audioFrame.size() > 63) {
110         m_freqMax = freq / 2;
111
112         QTime start = QTime::currentTime();
113
114         bool customCfg = false;
115         kiss_fftr_cfg myCfg = m_cfg;
116         int fftWindow = ui->windowSize->itemData(ui->windowSize->currentIndex()).toInt();
117         if (fftWindow > num_samples) {
118             fftWindow = num_samples;
119             customCfg = true;
120         }
121         if ((fftWindow & 1) == 1) {
122             fftWindow--;
123             customCfg = true;
124         }
125         if (customCfg) {
126             myCfg = kiss_fftr_alloc(fftWindow, 0,0,0);
127         }
128
129         float data[fftWindow];
130         float freqSpectrum[fftWindow/2];
131
132         int16_t maxSig = 0;
133         for (int i = 0; i < fftWindow; i++) {
134             if (audioFrame.data()[i*num_channels] > maxSig) {
135                 maxSig = audioFrame.data()[i*num_channels];
136             }
137         }
138         qDebug() << "Max audio signal is " << maxSig;
139
140         // The resulting FFT vector is only half as long
141         kiss_fft_cpx freqData[fftWindow/2];
142
143         // Copy the first channel's audio into a vector for the FFT display
144         // (only one channel handled at the moment)
145         for (int i = 0; i < fftWindow; i++) {
146             // Normalize signals to [0,1] to get correct dB values later on
147             data[i] = (float) audioFrame.data()[i*num_channels] / 32767.0f;
148         }
149         // Calculate the Fast Fourier Transform for the input data
150         kiss_fftr(m_cfg, data, freqData);
151
152
153         float val;
154         // Get the minimum and the maximum value of the Fourier transformed (for scaling)
155         for (int i = 0; i < fftWindow/2; i++) {
156             if (m_aLog->isChecked()) {
157                 // Logarithmic scale: 20 * log ( 2 * magnitude / N )
158                 // with N = FFT size (after FFT, 1/2 window size)
159                 val = 20*log(pow(pow(fabs(freqData[i].r),2) + pow(fabs(freqData[i].i),2), .5)/((float)fftWindow/2.0f))/log(10);
160             } else {
161                 // sqrt(r² + i²)
162                 val = pow(pow(fabs(freqData[i].r),2) + pow(fabs(freqData[i].i),2), .5);
163             }
164             freqSpectrum[i] = val;
165         }
166
167
168
169         // Draw the spectrum
170         QImage spectrum(scopeRect().size(), QImage::Format_ARGB32);
171         spectrum.fill(qRgba(0,0,0,0));
172         uint w = scopeRect().size().width();
173         uint h = scopeRect().size().height();
174         float x;
175         for (uint i = 0; i < w; i++) {
176
177             x = i/((float) w) * fftWindow/2;
178
179             // Use linear interpolation in order to get smoother display
180             if (i == 0 || i == w-1) {
181                 val = freqSpectrum[i];
182             } else {
183                 // Use floor(x)+1 instead of ceil(x) as floor(x) == ceil(x) is possible.
184                 val = (floor(x)+1 - x)*freqSpectrum[(int) floor(x)] + (x-floor(x))*freqSpectrum[(int) floor(x)+1];
185             }
186
187             // freqSpectrum values range from 0 to -inf as they are relative dB values.
188             qDebug() << val << "/" <<  (1 - (val - m_dBmax)/(m_dBmin-m_dBmax));
189             for (uint y = 0; y < h*(1 - (val - m_dBmax)/(m_dBmin-m_dBmax)) && y < h; y++) {
190                 spectrum.setPixel(i, h-y-1, qRgba(225, 182, 255, 255));
191             }
192         }
193
194         emit signalScopeRenderingFinished(start.elapsed(), 1);
195
196         /*
197         if (!fileWritten || true) {
198             std::ofstream mFile;
199             mFile.open("/tmp/freq.m");
200             if (!mFile) {
201                 qDebug() << "Opening file failed.";
202             } else {
203                 mFile << "val = [ ";
204
205                 for (int sample = 0; sample < 256; sample++) {
206                     mFile << data[sample] << " ";
207                 }
208                 mFile << " ];\n";
209
210                 mFile << "freq = [ ";
211                 for (int sample = 0; sample < 256; sample++) {
212                     mFile << freqData[sample].r << "+" << freqData[sample].i << "*i ";
213                 }
214                 mFile << " ];\n";
215
216                 mFile.close();
217                 fileWritten = true;
218                 qDebug() << "File written.";
219             }
220         } else {
221             qDebug() << "File already written.";
222         }
223         */
224
225         if (customCfg) {
226             free(myCfg);
227         }
228
229         return spectrum;
230     } else {
231         emit signalScopeRenderingFinished(0, 1);
232         return QImage();
233     }
234 }
235 QImage AudioSpectrum::renderHUD(uint)
236 {
237     QTime start = QTime::currentTime();
238
239     const QRect rect = scopeRect();
240     // Minimum distance between two lines
241     const uint minDistY = 30;
242     const uint minDistX = 40;
243     const uint textDist = 5;
244     const uint dbDiff = ceil((float)minDistY/rect.height() * (m_dBmax-m_dBmin));
245
246     QImage hud(AbstractAudioScopeWidget::rect().size(), QImage::Format_ARGB32);
247     hud.fill(qRgba(0,0,0,0));
248
249     QPainter davinci(&hud);
250     davinci.setPen(AbstractAudioScopeWidget::penLight);
251
252     int y;
253     for (int db = -dbDiff; db > m_dBmin; db -= dbDiff) {
254         y = rect.height() * ((float)db)/(m_dBmin - m_dBmax);
255         davinci.drawLine(0, y, rect.width()-1, y);
256         davinci.drawText(rect.width() + textDist, y + 8, i18n("%1 dB", db));
257     }
258
259
260     const uint hzDiff = ceil( ((float)minDistX)/rect.width() * m_freqMax / 1000 ) * 1000;
261     int x;
262     for (uint hz = hzDiff; hz < m_freqMax; hz += hzDiff) {
263         x = rect.width() * ((float)hz)/m_freqMax;
264         davinci.drawLine(x, 0, x, rect.height()+4);
265         davinci.drawText(x-4, rect.height() + 20, QVariant(hz/1000).toString());
266     }
267     davinci.drawText(rect.width(), rect.height() + 20, "[kHz]");
268
269
270     emit signalHUDRenderingFinished(start.elapsed(), 1);
271     return hud;
272 }
273
274 QRect AudioSpectrum::scopeRect() {
275     return QRect(QPoint(0, 0), AbstractAudioScopeWidget::rect().size() - m_distance);
276 }
277
278
279 void AudioSpectrum::slotUpdateCfg()
280 {
281     free(m_cfg);
282     m_cfg = kiss_fftr_alloc(ui->windowSize->itemData(ui->windowSize->currentIndex()).toInt(), 0,0,0);
283 }