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