X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=src%2Faudioscopes%2Fspectrogram.cpp;h=a01a3e6eac5c8c35d08c8a5d43386c9fc07164a2;hb=c1175a011e7b2a0654b5bc10691917b5f4da8535;hp=cd1958c7623e509e217ba7cf7773e2507adb97a0;hpb=893498c6ea67208b800b6ae0c354ba1f5641edc2;p=kdenlive diff --git a/src/audioscopes/spectrogram.cpp b/src/audioscopes/spectrogram.cpp index cd1958c7..a01a3e6e 100644 --- a/src/audioscopes/spectrogram.cpp +++ b/src/audioscopes/spectrogram.cpp @@ -10,11 +10,37 @@ #include "spectrogram.h" +#define SPECTROGRAM_HISTORY_SIZE 1000 + +#define DEBUG_SPECTROGRAM +#ifdef DEBUG_SPECTROGRAM +#include +#endif + Spectrogram::Spectrogram(QWidget *parent) : - AbstractAudioScopeWidget(false, parent) + AbstractAudioScopeWidget(false, parent), + m_fftTools(), + m_fftHistory() { ui = new Ui::Spectrogram_UI; ui->setupUi(this); + + + + + ui->windowSize->addItem("256", QVariant(256)); + ui->windowSize->addItem("512", QVariant(512)); + ui->windowSize->addItem("1024", QVariant(1024)); + ui->windowSize->addItem("2048", QVariant(2048)); + + ui->windowFunction->addItem(i18n("Rectangular window"), FFTTools::Window_Rect); + ui->windowFunction->addItem(i18n("Triangular window"), FFTTools::Window_Triangle); + ui->windowFunction->addItem(i18n("Hamming window"), FFTTools::Window_Hamming); + + bool b = true; + b &= connect(ui->windowFunction, SIGNAL(currentIndexChanged(int)), this, SLOT(forceUpdate())); + Q_ASSERT(b); + AbstractScopeWidget::init(); } @@ -26,19 +52,189 @@ Spectrogram::~Spectrogram() void Spectrogram::readConfig() { AbstractScopeWidget::readConfig(); + + KSharedConfigPtr config = KGlobal::config(); + KConfigGroup scopeConfig(config, AbstractScopeWidget::configName()); + + m_dBmax = scopeConfig.readEntry("dBmax", 0); + m_dBmin = scopeConfig.readEntry("dBmin", -70); + m_freqMax = scopeConfig.readEntry("freqMax", 0); + + if (m_freqMax == 0) { + m_customFreq = false; + m_freqMax = 10000; + } else { + m_customFreq = true; + } +} +void Spectrogram::writeConfig() +{ + KSharedConfigPtr config = KGlobal::config(); + KConfigGroup scopeConfig(config, AbstractScopeWidget::configName()); + + scopeConfig.writeEntry("dBmax", m_dBmax); + scopeConfig.writeEntry("dBmin", m_dBmin); + + if (m_customFreq) { + scopeConfig.writeEntry("freqMax", m_freqMax); + } else { + scopeConfig.writeEntry("freqMax", 0); + } + + scopeConfig.sync(); } -void Spectrogram::writeConfig() {} QString Spectrogram::widgetName() const { return QString("Spectrogram"); } -QRect Spectrogram::scopeRect() { return AbstractScopeWidget::rect(); } +QRect Spectrogram::scopeRect() +{ + m_scopeRect = QRect( + QPoint( + 10, // Left + ui->verticalSpacer->geometry().top()+6 // Top + ), + AbstractAudioScopeWidget::rect().bottomRight() + ); + m_innerScopeRect = QRect( + QPoint( + m_scopeRect.left()+6, // Left + m_scopeRect.top()+6 // Top + ), QPoint( + ui->verticalSpacer->geometry().right()-70, + ui->verticalSpacer->geometry().bottom()-40 + ) + ); + return m_scopeRect; +} QImage Spectrogram::renderHUD(uint) { return QImage(); } -QImage Spectrogram::renderAudioScope(uint accelerationFactor, const QVector audioFrame, const int freq, const int num_channels, const int num_samples) { - return QImage(); +QImage Spectrogram::renderAudioScope(uint, const QVector audioFrame, const int freq, + const int num_channels, const int num_samples) { + if (audioFrame.size() > 63) { + if (!m_customFreq) { + m_freqMax = freq / 2; + } + + QTime start = QTime::currentTime(); + + int fftWindow = ui->windowSize->itemData(ui->windowSize->currentIndex()).toInt(); + if (fftWindow > num_samples) { + fftWindow = num_samples; + } + if ((fftWindow & 1) == 1) { + fftWindow--; + } + + // Show the window size used, for information + ui->labelFFTSizeNumber->setText(QVariant(fftWindow).toString()); + + + // Get the spectral power distribution of the input samples, + // using the given window size and function + float freqSpectrum[fftWindow/2]; + FFTTools::WindowType windowType = (FFTTools::WindowType) ui->windowFunction->itemData(ui->windowFunction->currentIndex()).toInt(); + m_fftTools.fftNormalized(audioFrame, 0, num_channels, freqSpectrum, windowType, fftWindow, 0); + + QVector spectrumVector(fftWindow/2); + memcpy(spectrumVector.data(), &freqSpectrum[0], fftWindow/2 * sizeof(float)); + m_fftHistory.prepend(spectrumVector); + + // Limit the maximum history size to avoid wasting space + while (m_fftHistory.size() > SPECTROGRAM_HISTORY_SIZE) { + m_fftHistory.removeLast(); + } + + // Draw the spectrum + QImage spectrum(m_scopeRect.size(), QImage::Format_ARGB32); + spectrum.fill(qRgba(0,0,0,0)); + const uint w = m_innerScopeRect.width(); + const uint h = m_innerScopeRect.height(); + const uint leftDist = m_innerScopeRect.left() - m_scopeRect.left(); + const uint topDist = m_innerScopeRect.top() - m_scopeRect.top(); + float f; + float x; + float x_prev = 0; + float val; + uint windowSize; + uint xi; + uint y = topDist; + + for (QList >::iterator it = m_fftHistory.begin(); it != m_fftHistory.end(); it++) { + + windowSize = (*it).size(); + + for (uint i = 0; i < w; i++) { + + // i: Pixel coordinate + // f: Target frequency + // x: Frequency array index (float!) corresponding to the pixel + // xi: floor(x) + // val: dB value at position x (Range: [-inf,0]) + + f = i/((float) w-1.0) * m_freqMax; + x = 2*f/freq * (windowSize - 1); + xi = (int) floor(x); + + if (x >= windowSize) { + break; + } + + // Use linear interpolation in order to get smoother display + if (i == 0 || xi == windowSize-1) { + // ... except if we are at the left or right border of the display or the spectrum + val = (*it)[xi]; + } else { + + if ((*it)[xi] > (*it)[xi+1] + && x_prev < xi) { + // This is a hack to preserve peaks. + // Consider f = {0, 100, 0} + // x = {0.5, 1.5} + // Then x is 50 both times, and the 100 peak is lost. + // Get it back here for the first x after the peak. + val = (*it)[xi]; + } else { + val = (xi+1 - x) * (*it)[xi] + + (x - xi) * (*it)[xi+1]; + } + } + + // Normalize to [0 1], 1 corresponding to 0 dB and 0 to dbMin dB + val = -val/m_dBmin + 1; + if (val < 0) { + val = 0; + } + + spectrum.setPixel(leftDist + i, topDist + h-y-1, qRgba(225, 182, 255, val * 255)); + + x_prev = x; + } + + y++; + if (y >= topDist + m_innerScopeRect.height()) { + break; + } + } + +#ifdef DEBUG_SPECTROGRAM + qDebug() << "Rendered " << y-topDist << "lines from " << m_fftHistory.size() << " available samples in " << start.elapsed() << " ms"; +#endif + + + emit signalScopeRenderingFinished(start.elapsed(), 1); + return spectrum; + } else { + emit signalScopeRenderingFinished(0, 1); + return QImage(); + } } QImage Spectrogram::renderBackground(uint) { return QImage(); } bool Spectrogram::isHUDDependingOnInput() const { return false; } bool Spectrogram::isScopeDependingOnInput() const { return false; } bool Spectrogram::isBackgroundDependingOnInput() const { return false; } + +#undef SPECTROGRAM_HISTORY_SIZE +#ifdef DEBUG_SPECTROGRAM +#undef DEBUG_SPECTROGRAM +#endif