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 "audiospectrum.h"
12 #include "tools/kiss_fftr.h"
17 // Linear interpolation.
21 bool fileWritten = false;
23 AudioSpectrum::AudioSpectrum(Monitor *projMonitor, Monitor *clipMonitor, QWidget *parent) :
24 AbstractAudioScopeWidget(projMonitor, clipMonitor, true, parent)
26 ui = new Ui::AudioSpectrum_UI;
29 m_distance = QSize(65, 30);
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);
40 m_agScale = new QActionGroup(this);
41 m_agScale->addAction(m_aLin);
42 m_agScale->addAction(m_aLog);
44 m_menu->addSeparator()->setText(i18n("Scale"));
45 m_menu->addAction(m_aLin);
46 m_menu->addAction(m_aLog);
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));
53 m_cfg = kiss_fftr_alloc(ui->windowSize->itemData(ui->windowSize->currentIndex()).toInt(), 0,0,0);
57 b &= connect(ui->windowSize, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateCfg()));
60 AbstractScopeWidget::init();
62 AudioSpectrum::~AudioSpectrum()
70 void AudioSpectrum::readConfig()
72 AbstractScopeWidget::readConfig();
74 KSharedConfigPtr config = KGlobal::config();
75 KConfigGroup scopeConfig(config, AbstractScopeWidget::configName());
76 QString scale = scopeConfig.readEntry("scale");
78 m_aLin->setChecked(true);
80 m_aLog->setChecked(true);
82 ui->windowSize->setCurrentIndex(scopeConfig.readEntry("windowSize", 0));
85 void AudioSpectrum::writeConfig()
87 KSharedConfigPtr config = KGlobal::config();
88 KConfigGroup scopeConfig(config, AbstractScopeWidget::configName());
90 if (m_aLin->isChecked()) {
95 scopeConfig.writeEntry("scale", scale);
96 scopeConfig.writeEntry("windowSize", ui->windowSize->currentIndex());
100 QString AudioSpectrum::widgetName() const { return QString("audiospectrum"); }
102 bool AudioSpectrum::isBackgroundDependingOnInput() const { return false; }
103 bool AudioSpectrum::isScopeDependingOnInput() const { return true; }
104 bool AudioSpectrum::isHUDDependingOnInput() const { return false; }
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)
109 if (audioFrame.size() > 63) {
110 m_freqMax = freq / 2;
112 QTime start = QTime::currentTime();
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;
121 if ((fftWindow & 1) == 1) {
126 myCfg = kiss_fftr_alloc(fftWindow, 0,0,0);
129 float data[fftWindow];
130 float freqSpectrum[fftWindow/2];
133 for (int i = 0; i < fftWindow; i++) {
134 if (audioFrame.data()[i*num_channels] > maxSig) {
135 maxSig = audioFrame.data()[i*num_channels];
138 qDebug() << "Max audio signal is " << maxSig;
140 // The resulting FFT vector is only half as long
141 kiss_fft_cpx freqData[fftWindow/2];
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;
149 // Calculate the Fast Fourier Transform for the input data
150 kiss_fftr(m_cfg, data, freqData);
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);
162 val = pow(pow(fabs(freqData[i].r),2) + pow(fabs(freqData[i].i),2), .5);
164 freqSpectrum[i] = val;
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();
175 for (uint i = 0; i < w; i++) {
177 x = i/((float) w) * fftWindow/2;
179 // Use linear interpolation in order to get smoother display
180 if (i == 0 || i == w-1) {
181 val = freqSpectrum[i];
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];
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));
194 emit signalScopeRenderingFinished(start.elapsed(), 1);
197 if (!fileWritten || true) {
199 mFile.open("/tmp/freq.m");
201 qDebug() << "Opening file failed.";
205 for (int sample = 0; sample < 256; sample++) {
206 mFile << data[sample] << " ";
210 mFile << "freq = [ ";
211 for (int sample = 0; sample < 256; sample++) {
212 mFile << freqData[sample].r << "+" << freqData[sample].i << "*i ";
218 qDebug() << "File written.";
221 qDebug() << "File already written.";
231 emit signalScopeRenderingFinished(0, 1);
235 QImage AudioSpectrum::renderHUD(uint)
237 QTime start = QTime::currentTime();
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));
246 QImage hud(AbstractAudioScopeWidget::rect().size(), QImage::Format_ARGB32);
247 hud.fill(qRgba(0,0,0,0));
249 QPainter davinci(&hud);
250 davinci.setPen(AbstractAudioScopeWidget::penLight);
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));
260 const uint hzDiff = ceil( ((float)minDistX)/rect.width() * m_freqMax / 1000 ) * 1000;
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());
267 davinci.drawText(rect.width(), rect.height() + 20, "[kHz]");
270 emit signalHUDRenderingFinished(start.elapsed(), 1);
274 QRect AudioSpectrum::scopeRect() {
275 return QRect(QPoint(0, 0), AbstractAudioScopeWidget::rect().size() - m_distance);
279 void AudioSpectrum::slotUpdateCfg()
282 m_cfg = kiss_fftr_alloc(ui->windowSize->itemData(ui->windowSize->currentIndex()).toInt(), 0,0,0);