From: Simon A. Eugster Date: Thu, 9 Dec 2010 18:39:00 +0000 (+0000) Subject: Audio Scopes: X-Git-Url: https://git.sesse.net/?a=commitdiff_plain;h=82e0bd9676b65c3e74259bc73b3d5f6ef8c60486;p=kdenlive Audio Scopes: * Audio Spectrum now shows frequency and volume at the mouse position * Moved the peak-preserving linear interpolation to an own function (re-use) * Grid in the Spectrogram can be disabled * Increased accuracy of volume value at the mouse position in the Spectogram svn path=/trunk/kdenlive/; revision=5155 --- diff --git a/src/abstractscopewidget.cpp b/src/abstractscopewidget.cpp index af113407..9bec497d 100644 --- a/src/abstractscopewidget.cpp +++ b/src/abstractscopewidget.cpp @@ -40,6 +40,7 @@ const QPen AbstractScopeWidget::penLightDots(QBrush(QColor(200, 200, 250, 150)), const QPen AbstractScopeWidget::penLighter(QBrush(QColor(225, 225, 250, 225)), 1, Qt::SolidLine); const QPen AbstractScopeWidget::penDark(QBrush(QColor(0, 0, 20, 250)), 1, Qt::SolidLine); const QPen AbstractScopeWidget::penDarkDots(QBrush(QColor(0, 0, 20, 250)), 1, Qt::DotLine); +const QPen AbstractScopeWidget::penBackground(QBrush(dark2), 1, Qt::SolidLine); const QString AbstractScopeWidget::directions[] = {"North", "Northeast", "East", "Southeast"}; diff --git a/src/abstractscopewidget.h b/src/abstractscopewidget.h index 8188c16f..d1c72654 100644 --- a/src/abstractscopewidget.h +++ b/src/abstractscopewidget.h @@ -89,6 +89,7 @@ public: static const QPen penLighter; static const QPen penDark; static const QPen penDarkDots; + static const QPen penBackground; static const QString directions[]; // Mainly for debug output diff --git a/src/audioscopes/abstractaudioscopewidget.cpp b/src/audioscopes/abstractaudioscopewidget.cpp index 272accdd..099a8e6e 100644 --- a/src/audioscopes/abstractaudioscopewidget.cpp +++ b/src/audioscopes/abstractaudioscopewidget.cpp @@ -29,11 +29,11 @@ AbstractAudioScopeWidget::AbstractAudioScopeWidget(bool trackMouse, QWidget *parent) : AbstractScopeWidget(trackMouse, parent), - m_audioFrame(), - m_freq(0), - m_nChannels(0), - m_nSamples(0), - m_newData(0) + m_freq(0), + m_nChannels(0), + m_nSamples(0), + m_audioFrame(), + m_newData(0) { } diff --git a/src/audioscopes/abstractaudioscopewidget.h b/src/audioscopes/abstractaudioscopewidget.h index 58a9bfba..8a6578d3 100644 --- a/src/audioscopes/abstractaudioscopewidget.h +++ b/src/audioscopes/abstractaudioscopewidget.h @@ -41,11 +41,12 @@ protected: const QVector audioFrame, const int freq, const int num_channels, const int num_samples, const int newData) = 0; -private: - QVector m_audioFrame; int m_freq; int m_nChannels; int m_nSamples; + +private: + QVector m_audioFrame; QAtomicInt m_newData; private slots: diff --git a/src/audioscopes/audiospectrum.cpp b/src/audioscopes/audiospectrum.cpp index 71b1cd0d..ef0e3c68 100644 --- a/src/audioscopes/audiospectrum.cpp +++ b/src/audioscopes/audiospectrum.cpp @@ -22,10 +22,9 @@ // Enables debugging, like writing a GNU Octave .m file to /tmp //#define DEBUG_AUDIOSPEC + #ifdef DEBUG_AUDIOSPEC -#include #include -bool fileWritten = false; #endif #define MIN_DB_VALUE -120 @@ -33,8 +32,10 @@ bool fileWritten = false; #define MIN_FREQ_VALUE 1000 AudioSpectrum::AudioSpectrum(QWidget *parent) : - AbstractAudioScopeWidget(false, parent), - m_fftTools() + AbstractAudioScopeWidget(true, parent), + m_fftTools(), + m_lastFFT(), + m_lastFFTLock(1) { ui = new Ui::AudioSpectrum_UI; ui->setupUi(this); @@ -61,6 +62,7 @@ AudioSpectrum::AudioSpectrum(QWidget *parent) : bool b = true; b &= connect(m_aResetHz, SIGNAL(triggered()), this, SLOT(slotResetMaxFreq())); b &= connect(ui->windowFunction, SIGNAL(currentIndexChanged(int)), this, SLOT(forceUpdate())); + b &= connect(this, SIGNAL(signalMousePositionChanged()), this, SLOT(forceUpdateHUD())); Q_ASSERT(b); @@ -156,6 +158,18 @@ QImage AudioSpectrum::renderAudioScope(uint, const QVector audioFrame, m_fftTools.fftNormalized(audioFrame, 0, num_channels, freqSpectrum, windowType, fftWindow, 0); + // Store the current FFT window (for the HUD) and run the interpolation + // for easy pixel-based dB value access + QVector dbMap; + m_lastFFTLock.acquire(); + m_lastFFT = QVector(fftWindow/2); + memcpy(m_lastFFT.data(), &(freqSpectrum[0]), fftWindow/2 * sizeof(float)); + + uint right = ((float) m_freqMax)/(m_freq) * (m_lastFFT.size() - 1); + dbMap = interpolatePeakPreserving(m_lastFFT, m_innerScopeRect.width(), 0, right, -120); + m_lastFFTLock.release(); + + // Draw the spectrum QImage spectrum(m_scopeRect.size(), QImage::Format_ARGB32); spectrum.fill(qRgba(0,0,0,0)); @@ -163,84 +177,22 @@ QImage AudioSpectrum::renderAudioScope(uint, const QVector audioFrame, 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; - int xi; - 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) - - f = i/((float) w-1.0) * m_freqMax; - x = 2*f/freq * (fftWindow/2 - 1); - xi = (int) floor(x); - - if (x >= fftWindow/2) { - break; - } - - // Use linear interpolation in order to get smoother display - if (i == 0 || xi == fftWindow/2-1) { - // ... except if we are at the left or right border of the display or the spectrum - val = freqSpectrum[xi]; - } else { + int yMax; - if (freqSpectrum[xi] > freqSpectrum[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 = freqSpectrum[xi]; - } else { - val = (xi+1 - x) * freqSpectrum[xi] - + (x - xi) * freqSpectrum[xi+1]; - } + for (uint i = 0; i < w; i++) { + yMax = (dbMap[i] - m_dBmin) / (m_dBmax-m_dBmin) * (h-1); + if (yMax < 0) { + yMax = 0; + } else if (yMax >= (int)h) { + yMax = h-1; } - - // freqSpectrum values range from 0 to -inf as they are relative dB values. - for (uint y = 0; y < h*(1 - (val - m_dBmax)/(m_dBmin-m_dBmax)) && y < h; y++) { + for (int y = 0; y < yMax && y < (int)h; y++) { spectrum.setPixel(leftDist + i, topDist + h-y-1, qRgba(225, 182, 255, 255)); } - - x_prev = x; } emit signalScopeRenderingFinished(start.elapsed(), 1); -#ifdef DEBUG_AUDIOSPEC - if (!fileWritten || true) { - std::ofstream mFile; - mFile.open("/tmp/freq.m"); - if (!mFile) { - qDebug() << "Opening file failed."; - } else { - mFile << "val = [ "; - - for (int sample = 0; sample < 256; sample++) { - mFile << data[sample] << " "; - } - mFile << " ];\n"; - - mFile << "freq = [ "; - for (int sample = 0; sample < 256; sample++) { - mFile << freqData[sample].r << "+" << freqData[sample].i << "*i "; - } - mFile << " ];\n"; - - mFile.close(); - fileWritten = true; - qDebug() << "File written."; - } - } else { - qDebug() << "File already written."; - } -#endif return spectrum; } else { @@ -260,6 +212,8 @@ QImage AudioSpectrum::renderHUD(uint) const uint topDist = m_innerScopeRect.top() - m_scopeRect.top(); const uint leftDist = m_innerScopeRect.left() - m_scopeRect.left(); const uint dbDiff = ceil((float)minDistY/m_innerScopeRect.height() * (m_dBmax-m_dBmin)); + const int mouseX = m_mousePos.x() - m_innerScopeRect.left(); + const int mouseY = m_mousePos.y() - m_innerScopeRect.top(); QImage hud(m_scopeRect.size(), QImage::Format_ARGB32); hud.fill(qRgba(0,0,0,0)); @@ -314,6 +268,68 @@ QImage AudioSpectrum::renderHUD(uint) } } + if (m_mouseWithinWidget && mouseX < m_innerScopeRect.width()-1) { + davinci.setPen(AbstractScopeWidget::penThin); + + x = leftDist + mouseX; + + float db = 0; + float freq = ((float) mouseX)/(m_innerScopeRect.width()-1) * m_freqMax; + bool drawDb = false; + + m_lastFFTLock.acquire(); + if (m_lastFFT.size() > 0) { + uint right = ((float) m_freqMax)/(m_freq) * (m_lastFFT.size() - 1); + QVector dbMap = AudioSpectrum::interpolatePeakPreserving(m_lastFFT, m_innerScopeRect.width(), 0, right, -120); + + db = dbMap[mouseX]; + y = topDist + m_innerScopeRect.height()-1 - (dbMap[mouseX] - m_dBmin) / (m_dBmax-m_dBmin) * (m_innerScopeRect.height()-1); + + if (y < (int)topDist + m_innerScopeRect.height()-1) { + drawDb = true; + davinci.drawLine(x, y, leftDist + m_innerScopeRect.width()-1, y); + } + } else { + y = topDist + mouseY; + } + m_lastFFTLock.release(); + + if (y > (int)topDist + mouseY) { + y = topDist+ mouseY; + } + davinci.drawLine(x, y, x, topDist + m_innerScopeRect.height()-1); + + if (drawDb) { + QPoint dist(20, -20); + QRect rect( + leftDist + mouseX + dist.x(), + topDist + mouseY + dist.y(), + 100, + 40 + ); + if (rect.right() > (int)leftDist + m_innerScopeRect.width()-1) { + // Mirror the rectangle at the y axis to keep it inside the widget + rect = QRect( + rect.topLeft() - QPoint(rect.width() + 2*dist.x(), 0), + rect.size()); + } + + QRect textRect( + rect.topLeft() + QPoint(12, 4), + rect.size() + ); + + davinci.fillRect(rect, AbstractScopeWidget::penBackground.brush()); + davinci.setPen(AbstractScopeWidget::penLighter); + davinci.drawRect(rect); + davinci.drawText(textRect, QString( + i18n("%1 dB", QString("%1").arg(db, 0, 'f', 2)) + + "\n" + + i18n("%1 kHz", QString("%1").arg(freq/1000, 0, 'f', 2)))); + } + + } + emit signalHUDRenderingFinished(start.elapsed(), 1); return hud; @@ -416,6 +432,68 @@ void AudioSpectrum::handleMouseDrag(const QPoint movement, const RescaleDirectio } +const QVector AudioSpectrum::interpolatePeakPreserving(const QVector in, const uint targetSize, uint left, uint right, float fill) +{ + if (right == 0) { + right = in.size()-1; + } + Q_ASSERT(targetSize > 0); + Q_ASSERT(left < right); + + QVector out(targetSize); + + + float x; + float x_prev = 0; + int xi; + uint i; + for (i = 0; i < targetSize; i++) { + + // i: Target index + // x: Interpolated source index (float!) + // xi: floor(x) + + // Transform [0,targetSize-1] to [left,right] + x = ((float) i) / (targetSize-1) * (right-left) + left; + xi = (int) floor(x); + + if (x > in.size()-1) { + // This may happen if right > in.size()-1; Fill the rest of the vector + // with the default value now. + break; + } + + + // Use linear interpolation in order to get smoother display + if (i == 0 || i == targetSize-1) { + // ... except if we are at the left or right border of the display or the spectrum + out[i] = in[xi]; + } else { + if (in[xi] > in[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 (which is at xi). + // (x is the first after the peak if the previous x was smaller than floor(x).) + out[i] = in[xi]; + } else { + out[i] = (xi+1 - x) * in[xi] + + (x - xi) * in[xi+1]; + } + } + x_prev = x; + } + // Fill the rest of the vector if the right border exceeds the input vector. + for (; i < targetSize; i++) { + out[i] = fill; + } + + return out; +} + + #ifdef DEBUG_AUDIOSPEC #undef DEBUG_AUDIOSPEC #endif diff --git a/src/audioscopes/audiospectrum.h b/src/audioscopes/audiospectrum.h index 4a3c507d..b48d4fdb 100644 --- a/src/audioscopes/audiospectrum.h +++ b/src/audioscopes/audiospectrum.h @@ -58,6 +58,8 @@ private: QAction *m_aResetHz; FFTTools m_fftTools; + QVector m_lastFFT; + QSemaphore m_lastFFTLock; /** Contains the plot only; m_scopeRect contains text and widgets as well */ QRect m_innerScopeRect; @@ -73,6 +75,20 @@ private: /** The user has chosen a custom frequency. */ bool m_customFreq; + /** This is linear interpolation with the special property that it preserves peaks, which is required + for e.g. showing correct Decibel values (where the peak values are of interest). + Consider f = {0, 100, 0} + x = {0.5, 1.5}: With default linear interpolation x0 and x1 would both be mapped to 50. + This function maps x1 (the first position after the peak) to 100. + + @param in The source vector containing the data + @param targetSize Number of interpolation nodes between ... + @param left the left array index in the in-vector and ... + @param right the right array index (both inclusive). + @param fill If right lies outside of the array bounds (which is perfectly fine here) then this value + will be used for filling the missing information. + */ + static const QVector interpolatePeakPreserving(const QVector in, const uint targetSize, uint left = 0, uint right = 0, float fill = 0.0); private slots: diff --git a/src/audioscopes/ffttools.cpp b/src/audioscopes/ffttools.cpp index 53d02f35..1464c7ef 100644 --- a/src/audioscopes/ffttools.cpp +++ b/src/audioscopes/ffttools.cpp @@ -15,10 +15,13 @@ #include "ffttools.h" +// Uncomment for debugging //#define DEBUG_FFTTOOLS + #ifdef DEBUG_FFTTOOLS #include #include +#include #endif FFTTools::FFTTools() : @@ -172,7 +175,7 @@ void FFTTools::fftNormalized(const QVector audioFrame, const uint chann std::fill(&data[numSamples], &data[windowSize-1], 0); } // Normalize signals to [0,1] to get correct dB values later on - for (int i = 0; i < numSamples && i < windowSize; i++) { + for (uint i = 0; i < numSamples && i < windowSize; i++) { // Performance note: Benchmarking has shown that using the if/else inside the loop // does not do noticeable worse than keeping it outside (perhaps the branch predictor // is good enough), so it remains in there for better readability. @@ -189,12 +192,37 @@ void FFTTools::fftNormalized(const QVector audioFrame, const uint chann // Logarithmic scale: 20 * log ( 2 * magnitude / N ) with magnitude = sqrt(r² + i²) // with N = FFT size (after FFT, 1/2 window size) - for (int i = 0; i < windowSize/2; i++) { + for (uint i = 0; i < windowSize/2; i++) { // Logarithmic scale: 20 * log ( 2 * magnitude / N ) with magnitude = sqrt(r² + i²) // with N = FFT size (after FFT, 1/2 window size) freqSpectrum[i] = 20*log(pow(pow(fabs(freqData[i].r * windowScaleFactor),2) + pow(fabs(freqData[i].i * windowScaleFactor),2), .5)/((float)windowSize/2.0f))/log(10);; } + +#ifdef DEBUG_FFTTOOLS + std::ofstream mFile; + mFile.open("/tmp/freq.m"); + if (!mFile) { + qDebug() << "Opening file failed."; + } else { + mFile << "val = [ "; + + for (int sample = 0; sample < 256; sample++) { + mFile << data[sample] << " "; + } + mFile << " ];\n"; + + mFile << "freq = [ "; + for (int sample = 0; sample < 256; sample++) { + mFile << freqData[sample].r << "+" << freqData[sample].i << "*i "; + } + mFile << " ];\n"; + + mFile.close(); + qDebug() << "File written."; + } +#endif + #ifdef DEBUG_FFTTOOLS qDebug() << "Calculated FFT in " << start.elapsed() << " ms."; #endif diff --git a/src/audioscopes/spectrogram.cpp b/src/audioscopes/spectrogram.cpp index 8bb941f5..28ea1762 100644 --- a/src/audioscopes/spectrogram.cpp +++ b/src/audioscopes/spectrogram.cpp @@ -42,10 +42,13 @@ Spectrogram::Spectrogram(QWidget *parent) : m_aResetHz = new QAction(i18n("Reset maximum frequency to sampling rate"), this); + m_aGrid = new QAction(i18n("Draw grid"), this); + m_aGrid->setCheckable(true); m_menu->addSeparator(); m_menu->addAction(m_aResetHz); + m_menu->addAction(m_aGrid); m_menu->removeAction(m_aRealtime); @@ -77,6 +80,7 @@ Spectrogram::~Spectrogram() writeConfig(); delete m_aResetHz; + delete m_aGrid; } void Spectrogram::readConfig() @@ -88,6 +92,7 @@ void Spectrogram::readConfig() ui->windowSize->setCurrentIndex(scopeConfig.readEntry("windowSize", 0)); ui->windowFunction->setCurrentIndex(scopeConfig.readEntry("windowFunction", 0)); + m_aGrid->setChecked(scopeConfig.readEntry("drawGrid", true)); m_dBmax = scopeConfig.readEntry("dBmax", 0); m_dBmin = scopeConfig.readEntry("dBmin", -70); m_freqMax = scopeConfig.readEntry("freqMax", 0); @@ -106,6 +111,7 @@ void Spectrogram::writeConfig() scopeConfig.writeEntry("windowSize", ui->windowSize->currentIndex()); scopeConfig.writeEntry("windowFunction", ui->windowFunction->currentIndex()); + scopeConfig.writeEntry("drawGrid", m_aGrid->isChecked()); scopeConfig.writeEntry("dBmax", m_dBmax); scopeConfig.writeEntry("dBmin", m_dBmin); @@ -153,8 +159,8 @@ QImage Spectrogram::renderHUD(uint) const uint textDistY = 25; const uint topDist = m_innerScopeRect.top() - m_scopeRect.top(); const uint leftDist = m_innerScopeRect.left() - m_scopeRect.left(); - const uint mouseX = m_mousePos.x() - m_innerScopeRect.left(); - const uint mouseY = m_mousePos.y() - m_innerScopeRect.top(); + const int mouseX = m_mousePos.x() - m_innerScopeRect.left(); + const int mouseY = m_mousePos.y() - m_innerScopeRect.top(); bool hideText; QImage hud(m_scopeRect.size(), QImage::Format_ARGB32); @@ -165,13 +171,15 @@ QImage Spectrogram::renderHUD(uint) // Frame display - for (int frameNumber = 0; frameNumber < m_innerScopeRect.height(); frameNumber += minDistY) { - y = topDist + m_innerScopeRect.height()-1 - frameNumber; - hideText = m_mouseWithinWidget && abs(y - mouseY) < textDistY && mouseY < m_innerScopeRect.height() && mouseX < m_innerScopeRect.width(); - - davinci.drawLine(leftDist, y, leftDist + m_innerScopeRect.width()-1, y); - if (!hideText) { - davinci.drawText(leftDist + m_innerScopeRect.width() + textDistX, y + 6, QVariant(frameNumber).toString()); + if (m_aGrid->isChecked()) { + for (int frameNumber = 0; frameNumber < m_innerScopeRect.height(); frameNumber += minDistY) { + y = topDist + m_innerScopeRect.height()-1 - frameNumber; + hideText = m_mouseWithinWidget && abs(y - mouseY) < (int)textDistY && mouseY < m_innerScopeRect.height() && mouseX < m_innerScopeRect.width(); + + davinci.drawLine(leftDist, y, leftDist + m_innerScopeRect.width()-1, y); + if (!hideText) { + davinci.drawText(leftDist + m_innerScopeRect.width() + textDistX, y + 6, QVariant(frameNumber).toString()); + } } } // Draw a line through the mouse position with the correct Frame number @@ -183,7 +191,7 @@ QImage Spectrogram::renderHUD(uint) if (y < 0) { y = 0; } - if (y > topDist + m_innerScopeRect.height()-1 - 30) { + if (y > (int)topDist + m_innerScopeRect.height()-1 - 30) { y = topDist + m_innerScopeRect.height()-1 - 30; } davinci.drawLine(x, topDist + mouseY, leftDist + m_innerScopeRect.width()-1, topDist + mouseY); @@ -200,42 +208,44 @@ QImage Spectrogram::renderHUD(uint) const int rightBorder = leftDist + m_innerScopeRect.width()-1; x = 0; y = topDist + m_innerScopeRect.height() + textDistY; - for (uint hz = 0; x <= rightBorder; hz += hzDiff) { - davinci.setPen(AbstractScopeWidget::penLight); - x = leftDist + (m_innerScopeRect.width()-1) * ((float)hz)/m_freqMax; + if (m_aGrid->isChecked()) { + for (uint hz = 0; x <= rightBorder; hz += hzDiff) { + davinci.setPen(AbstractScopeWidget::penLight); + x = leftDist + (m_innerScopeRect.width()-1) * ((float)hz)/m_freqMax; - // Hide text if it would overlap with the text drawn at the mouse position - hideText = m_mouseWithinWidget && abs(x-(leftDist + mouseX + 18)) < minDistX && mouseX < m_innerScopeRect.width(); + // Hide text if it would overlap with the text drawn at the mouse position + hideText = m_mouseWithinWidget && abs(x-(leftDist + mouseX + 20)) < (int) minDistX + 16 && mouseX < m_innerScopeRect.width(); - if (x <= rightBorder) { - davinci.drawLine(x, topDist, x, topDist + m_innerScopeRect.height()+6); - } - if (x+textDistY < leftDist + m_innerScopeRect.width()) { - // Only draw the text label if there is still enough room for the final one at the right. - if (!hideText) { - davinci.drawText(x-4, y, QVariant(hz/1000).toString()); + if (x <= rightBorder) { + davinci.drawLine(x, topDist, x, topDist + m_innerScopeRect.height()+6); + } + if (x+textDistY < leftDist + m_innerScopeRect.width()) { + // Only draw the text label if there is still enough room for the final one at the right. + if (!hideText) { + davinci.drawText(x-4, y, QVariant(hz/1000).toString()); + } } - } - if (hz > 0) { - // Draw finer lines between the main lines - davinci.setPen(AbstractScopeWidget::penLightDots); - for (uint dHz = 3; dHz > 0; dHz--) { - x = leftDist + m_innerScopeRect.width() * ((float)hz - dHz * hzDiff/4.0f)/m_freqMax; - if (x > rightBorder) { - break; + if (hz > 0) { + // Draw finer lines between the main lines + davinci.setPen(AbstractScopeWidget::penLightDots); + for (uint dHz = 3; dHz > 0; dHz--) { + x = leftDist + m_innerScopeRect.width() * ((float)hz - dHz * hzDiff/4.0f)/m_freqMax; + if (x > rightBorder) { + break; + } + davinci.drawLine(x, topDist, x, topDist + m_innerScopeRect.height()-1); } - davinci.drawLine(x, topDist, x, topDist + m_innerScopeRect.height()-1); } } - } - // Draw the line at the very right (maximum frequency) - x = leftDist + m_innerScopeRect.width()-1; - hideText = m_mouseWithinWidget && abs(x-(leftDist + mouseX + 24)) < minDistX && mouseX < m_innerScopeRect.width(); - davinci.drawLine(x, topDist, x, topDist + m_innerScopeRect.height()+6); - if (!hideText) { - davinci.drawText(x-10, y, i18n("%1 kHz").arg((double)m_freqMax/1000, 0, 'f', 1)); + // Draw the line at the very right (maximum frequency) + x = leftDist + m_innerScopeRect.width()-1; + hideText = m_mouseWithinWidget && abs(x-(leftDist + mouseX + 30)) < (int) minDistX && mouseX < m_innerScopeRect.width(); + davinci.drawLine(x, topDist, x, topDist + m_innerScopeRect.height()+6); + if (!hideText) { + davinci.drawText(x-10, y, i18n("%1 kHz").arg((double)m_freqMax/1000, 0, 'f', 1)); + } } // Draw a line through the mouse position with the correct frequency label @@ -243,16 +253,17 @@ QImage Spectrogram::renderHUD(uint) davinci.setPen(AbstractScopeWidget::penThin); x = leftDist + mouseX; davinci.drawLine(x, topDist, x, topDist + m_innerScopeRect.height()+6); - davinci.drawText(x-10, y, i18n("%1 kHz").arg((double)(m_mousePos.x()-m_innerScopeRect.left())/m_innerScopeRect.width() * m_freqMax/1000, 0, 'f', 1)); + davinci.drawText(x-10, y, i18n("%1 kHz") + .arg((double)(m_mousePos.x()-m_innerScopeRect.left())/m_innerScopeRect.width() * m_freqMax/1000, 0, 'f', 2)); } // Draw the dB brightness scale float val; davinci.setPen(AbstractScopeWidget::penLighter); - for (y = topDist; y < topDist + m_innerScopeRect.height(); y++) { + for (y = topDist; y < (int)topDist + m_innerScopeRect.height(); y++) { val = 1-((float)y-topDist)/(m_innerScopeRect.height()-1); int col = qRgba(255, 255, 255, 255.0 * val); - for (x = leftDist-6; x >= leftDist-13; x--) { + for (x = leftDist-6; x >= (int)leftDist-13; x--) { hud.setPixel(x, y, col); } } diff --git a/src/audioscopes/spectrogram.h b/src/audioscopes/spectrogram.h index 699d5b96..77e9b1e8 100644 --- a/src/audioscopes/spectrogram.h +++ b/src/audioscopes/spectrogram.h @@ -59,6 +59,7 @@ private: Ui::Spectrogram_UI *ui; FFTTools m_fftTools; QAction *m_aResetHz; + QAction *m_aGrid; QList > m_fftHistory; QImage m_fftHistoryImg;