File indexing completed on 2024-04-28 04:52:21

0001 /*
0002     SPDX-FileCopyrightText: 2010 Simon Andreas Eugster <simon.eu@gmail.com>
0003     This file is part of kdenlive. See www.kdenlive.org.
0004 
0005 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 */
0007 
0008 #include "audiospectrum.h"
0009 
0010 #include <QElapsedTimer>
0011 #include <QPainter>
0012 
0013 #include "klocalizedstring.h"
0014 #include <KConfigGroup>
0015 #include <KSharedConfig>
0016 #include <iostream>
0017 
0018 // (defined in the header file)
0019 #ifdef DEBUG_AUDIOSPEC
0020 #include "kdenlive_debug.h"
0021 #endif
0022 
0023 // (defined in the header file)
0024 #ifdef DETECT_OVERMODULATION
0025 #include <cmath>
0026 #include <limits>
0027 #endif
0028 
0029 // Draw lines instead of single pixels.
0030 // This is about 25 % faster, especially when enlarging the scope to e.g. 1680x1050 px.
0031 #define AUDIOSPEC_LINES
0032 
0033 #define MIN_DB_VALUE -120
0034 #define MAX_FREQ_VALUE 96000
0035 #define MIN_FREQ_VALUE 1000
0036 #define ALPHA_MOVING_AVG 0.125
0037 #define MAX_OVM_COLOR 0.7f
0038 
0039 AudioSpectrum::AudioSpectrum(QWidget *parent)
0040     : AbstractAudioScopeWidget(true, parent)
0041     , m_fftTools()
0042     , m_lastFFT()
0043     , m_lastFFTLock(1)
0044     , m_peaks()
0045 #ifdef DEBUG_AUDIOSPEC
0046     , m_timeTotal(0)
0047     , m_showTotal(0)
0048 #endif
0049 
0050 {
0051     m_ui = new Ui::AudioSpectrum_UI;
0052     m_ui->setupUi(this);
0053 
0054     m_aResetHz = new QAction(i18n("Reset maximum frequency to sampling rate"), this);
0055     m_aTrackMouse = new QAction(i18n("Track mouse"), this);
0056     m_aTrackMouse->setCheckable(true);
0057     m_aShowMax = new QAction(i18n("Show maximum"), this);
0058     m_aShowMax->setCheckable(true);
0059 
0060     m_menu->addSeparator();
0061     m_menu->addAction(m_aResetHz);
0062     m_menu->addAction(m_aTrackMouse);
0063     m_menu->addAction(m_aShowMax);
0064     m_menu->removeAction(m_aRealtime);
0065 
0066     m_ui->windowSize->addItem(QStringLiteral("256"), QVariant(256));
0067     m_ui->windowSize->addItem(QStringLiteral("512"), QVariant(512));
0068     m_ui->windowSize->addItem(QStringLiteral("1024"), QVariant(1024));
0069     m_ui->windowSize->addItem(QStringLiteral("2048"), QVariant(2048));
0070 
0071     m_ui->windowFunction->addItem(i18n("Rectangular window"), FFTTools::Window_Rect);
0072     m_ui->windowFunction->addItem(i18n("Triangular window"), FFTTools::Window_Triangle);
0073     m_ui->windowFunction->addItem(i18n("Hamming window"), FFTTools::Window_Hamming);
0074 
0075     connect(m_aResetHz, &QAction::triggered, this, &AudioSpectrum::slotResetMaxFreq);
0076 
0077     connect(m_ui->windowFunction, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [&](int) { AudioSpectrum::forceUpdate(); });
0078     connect(this, &AudioSpectrum::signalMousePositionChanged, this, &AudioSpectrum::forceUpdateHUD);
0079 
0080     // Note: These strings are used in both Spectogram and AudioSpectrum. Ideally change both (if necessary) to reduce workload on translators
0081     m_ui->labelFFTSize->setToolTip(i18n("The maximum window size is limited by the number of samples per frame."));
0082     m_ui->windowSize->setToolTip(i18n("A bigger window improves the accuracy at the cost of computational power."));
0083     m_ui->windowFunction->setToolTip(i18n("The rectangular window function is good for signals with equal signal strength (narrow peak), but creates more "
0084                                           "smearing. See Window function on Wikipedia."));
0085 
0086     AbstractScopeWidget::init();
0087 }
0088 AudioSpectrum::~AudioSpectrum()
0089 {
0090     writeConfig();
0091 
0092     delete m_aResetHz;
0093     delete m_aTrackMouse;
0094     delete m_ui;
0095 }
0096 
0097 void AudioSpectrum::readConfig()
0098 {
0099     AbstractScopeWidget::readConfig();
0100 
0101     KSharedConfigPtr config = KSharedConfig::openConfig();
0102     KConfigGroup scopeConfig(config, AbstractScopeWidget::configName());
0103 
0104     m_ui->windowSize->setCurrentIndex(scopeConfig.readEntry("windowSize", 0));
0105     m_ui->windowFunction->setCurrentIndex(scopeConfig.readEntry("windowFunction", 0));
0106     m_aTrackMouse->setChecked(scopeConfig.readEntry("trackMouse", true));
0107     m_aShowMax->setChecked(scopeConfig.readEntry("showMax", true));
0108     m_dBmax = scopeConfig.readEntry("dBmax", 0);
0109     m_dBmin = scopeConfig.readEntry("dBmin", -70);
0110     m_freqMax = scopeConfig.readEntry("freqMax", 0);
0111 
0112     if (m_freqMax == 0) {
0113         m_customFreq = false;
0114         m_freqMax = 10000;
0115     } else {
0116         m_customFreq = true;
0117     }
0118 }
0119 void AudioSpectrum::writeConfig()
0120 {
0121     KSharedConfigPtr config = KSharedConfig::openConfig();
0122     KConfigGroup scopeConfig(config, AbstractScopeWidget::configName());
0123 
0124     scopeConfig.writeEntry("windowSize", m_ui->windowSize->currentIndex());
0125     scopeConfig.writeEntry("windowFunction", m_ui->windowFunction->currentIndex());
0126     scopeConfig.writeEntry("trackMouse", m_aTrackMouse->isChecked());
0127     scopeConfig.writeEntry("showMax", m_aShowMax->isChecked());
0128     scopeConfig.writeEntry("dBmax", m_dBmax);
0129     scopeConfig.writeEntry("dBmin", m_dBmin);
0130     if (m_customFreq) {
0131         scopeConfig.writeEntry("freqMax", m_freqMax);
0132     } else {
0133         scopeConfig.writeEntry("freqMax", 0);
0134     }
0135 
0136     scopeConfig.sync();
0137 }
0138 
0139 QString AudioSpectrum::widgetName() const
0140 {
0141     return QStringLiteral("AudioSpectrum");
0142 }
0143 
0144 QImage AudioSpectrum::renderBackground(uint)
0145 {
0146     return QImage();
0147 }
0148 
0149 QImage AudioSpectrum::renderAudioScope(uint, const audioShortVector &audioFrame, const int freq, const int num_channels, const int num_samples, const int)
0150 {
0151     if (audioFrame.size() > 63 && m_innerScopeRect.width() > 0 && m_innerScopeRect.height() > 0 // <= 0 if widget is too small (resized by user)
0152     ) {
0153         if (!m_customFreq) {
0154             m_freqMax = freq / 2;
0155         }
0156 
0157         QElapsedTimer timer;
0158         timer.start();
0159 
0160         /*******FIXME!!!
0161         #ifdef DETECT_OVERMODULATION
0162                 bool overmodulated = false;
0163                 int overmodulateCount = 0;
0164 
0165                 for (int i = 0; i < audioFrame.size(); ++i) {
0166                     if (
0167                             audioFrame[i] == std::numeric_limits<qint16>::max()
0168                             || audioFrame[i] == std::numeric_limits<qint16>::min()) {
0169                         overmodulateCount++;
0170                         if (overmodulateCount > 3) {
0171                             overmodulated = true;
0172                             break;
0173                         }
0174                     }
0175                 }
0176                 if (overmodulated) {
0177                     m_colorizeFactor = 1;
0178                 } else {
0179                     if (m_colorizeFactor > 0) {
0180                         m_colorizeFactor -= .08;
0181                         if (m_colorizeFactor < 0) {
0182                             m_colorizeFactor = 0;
0183                         }
0184                     }
0185                 }
0186         #endif
0187         *******/
0188 
0189         // Determine the window size to use. It should be
0190         // * not bigger than the number of samples actually available
0191         // * divisible by 2
0192         int fftWindow = m_ui->windowSize->itemData(m_ui->windowSize->currentIndex()).toInt();
0193         if (fftWindow > num_samples) {
0194             fftWindow = num_samples;
0195         }
0196         if ((fftWindow & 1) == 1) {
0197             fftWindow--;
0198         }
0199 
0200         // Show the window size used, for information
0201         m_ui->labelFFTSizeNumber->setText(QVariant(fftWindow).toString());
0202 
0203         // Get the spectral power distribution of the input samples,
0204         // using the given window size and function
0205         auto *freqSpectrum = new float[uint(fftWindow) / 2];
0206         FFTTools::WindowType windowType = FFTTools::WindowType(m_ui->windowFunction->itemData(m_ui->windowFunction->currentIndex()).toInt());
0207         m_fftTools.fftNormalized(audioFrame, 0, uint(num_channels), freqSpectrum, windowType, uint(fftWindow), 0);
0208 
0209         // Store the current FFT window (for the HUD) and run the interpolation
0210         // for easy pixel-based dB value access
0211         QVector<float> dbMap;
0212         m_lastFFTLock.acquire();
0213         m_lastFFT = QVector<float>(fftWindow / 2);
0214         memcpy(m_lastFFT.data(), &(freqSpectrum[0]), uint(fftWindow) / 2 * sizeof(float));
0215 
0216         uint right = uint(m_freqMax / (m_freq / 2.) * (m_lastFFT.size() - 1));
0217         dbMap = FFTTools::interpolatePeakPreserving(m_lastFFT, uint(m_innerScopeRect.width()), 0, right, -180);
0218         m_lastFFTLock.release();
0219 
0220 #ifdef DEBUG_AUDIOSPEC
0221         QTime drawTime = QTime::currentTime();
0222 #endif
0223         delete[] freqSpectrum;
0224         // Draw the spectrum
0225         QImage spectrum(m_scopeRect.size(), QImage::Format_ARGB32);
0226         spectrum.fill(qRgba(0, 0, 0, 0));
0227         const int w = m_innerScopeRect.width();
0228         const int h = m_innerScopeRect.height();
0229         const int leftDist = m_innerScopeRect.left() - m_scopeRect.left();
0230         const int topDist = m_innerScopeRect.top() - m_scopeRect.top();
0231         QColor spectrumColor(AbstractScopeWidget::colDarkWhite);
0232         int yMax;
0233 
0234 #ifdef DETECT_OVERMODULATION
0235         if (m_colorizeFactor > 0) {
0236             QColor col = AbstractScopeWidget::colHighlightDark;
0237             QColor spec = spectrumColor;
0238             float f = std::sin(float(M_PI_2) * m_colorizeFactor);
0239             spectrumColor = QColor(int(f * col.red() + (1.f - f) * spec.red()), int(f * col.green() + (1.f - f) * spec.green()),
0240                                    int(f * col.blue() + (1.f - f) * spec.blue()), spec.alpha());
0241             // Limit the maximum colorization for non-overmodulated frames to better
0242             // recognize consecutively overmodulated frames
0243             if (m_colorizeFactor > MAX_OVM_COLOR) {
0244                 m_colorizeFactor = MAX_OVM_COLOR;
0245             }
0246         }
0247 #endif
0248 
0249 #ifdef AUDIOSPEC_LINES
0250         QPainter davinci;
0251         bool ok = davinci.begin(&spectrum);
0252         if (!ok) {
0253             qDebug() << "Could not initialise QPainter for Audio spectrum.";
0254             return spectrum;
0255         }
0256         davinci.setPen(QPen(QBrush(spectrumColor.rgba()), 1, Qt::SolidLine));
0257 #endif
0258 
0259         for (int i = 0; i < w; ++i) {
0260             yMax = int((dbMap[i] - m_dBmin) / (m_dBmax - m_dBmin) * (h - 1));
0261             if (yMax < 0) {
0262                 yMax = 0;
0263             } else if (yMax >= h) {
0264                 yMax = h - 1;
0265             }
0266 #ifdef AUDIOSPEC_LINES
0267             davinci.drawLine(leftDist + i, topDist + h - 1, leftDist + i, topDist + h - 1 - yMax);
0268 #else
0269             for (int y = 0; y < yMax && y < (int)h; ++y) {
0270                 spectrum.setPixel(leftDist + i, topDist + h - y - 1, spectrumColor.rgba());
0271             }
0272 #endif
0273         }
0274 
0275         // Calculate the peak values. Use the new value if it is bigger, otherwise adapt to lower
0276         // values using the Moving Average formula
0277         if (m_aShowMax->isChecked()) {
0278             davinci.setPen(QPen(QBrush(AbstractScopeWidget::colHighlightLight), 2));
0279             if (m_peaks.size() != fftWindow / 2) {
0280                 m_peaks = QVector<float>(m_lastFFT);
0281             } else {
0282                 for (int i = 0; i < fftWindow / 2; ++i) {
0283                     if (m_lastFFT[i] > m_peaks[i]) {
0284                         m_peaks[i] = m_lastFFT[i];
0285                     } else {
0286                         m_peaks[i] = ALPHA_MOVING_AVG * m_lastFFT[i] + (1 - ALPHA_MOVING_AVG) * m_peaks[i];
0287                     }
0288                 }
0289             }
0290             int prev = 0;
0291             m_peakMap = FFTTools::interpolatePeakPreserving(m_peaks, uint(m_innerScopeRect.width()), 0, right, -180);
0292             for (int i = 0; i < w; ++i) {
0293                 yMax = int((m_peakMap[i] - m_dBmin) / (m_dBmax - m_dBmin) * (h - 1));
0294                 if (yMax < 0) {
0295                     yMax = 0;
0296                 } else if (yMax >= h) {
0297                     yMax = h - 1;
0298                 }
0299 
0300                 davinci.drawLine(leftDist + i - 1, topDist + h - prev - 1, leftDist + i, topDist + h - yMax - 1);
0301                 spectrum.setPixel(leftDist + i, topDist + h - yMax - 1, AbstractScopeWidget::colHighlightLight.rgba());
0302                 prev = yMax;
0303             }
0304         }
0305 
0306 #ifdef DEBUG_AUDIOSPEC
0307         m_showTotal++;
0308         m_timeTotal += drawTime.elapsed();
0309         qCDebug(KDENLIVE_LOG) << widgetName() << " took " << drawTime.elapsed() << " ms for drawing. Average: " << ((float)m_timeTotal / m_showTotal);
0310 #endif
0311 
0312         Q_EMIT signalScopeRenderingFinished(uint(timer.elapsed()), 1);
0313 
0314         return spectrum;
0315     }
0316     Q_EMIT signalScopeRenderingFinished(0, 1);
0317     return QImage();
0318 }
0319 QImage AudioSpectrum::renderHUD(uint)
0320 {
0321     QElapsedTimer timer;
0322     timer.start();
0323 
0324     if (m_innerScopeRect.height() > 0 && m_innerScopeRect.width() > 0) { // May be below 0 if widget is too small
0325 
0326         // Minimum distance between two lines
0327         const int minDistY = 30;
0328         const int minDistX = 40;
0329         const int textDistX = 10;
0330         const int textDistY = 25;
0331         const int topDist = m_innerScopeRect.top() - m_scopeRect.top();
0332         const int leftDist = m_innerScopeRect.left() - m_scopeRect.left();
0333         const int dbDiff = int(ceil((float(minDistY) * (m_dBmax - m_dBmin)) / m_innerScopeRect.height()));
0334         const int mouseX = m_mousePos.x() - m_innerScopeRect.left();
0335         const int mouseY = m_mousePos.y() - m_innerScopeRect.top();
0336 
0337         QImage hud(m_scopeRect.size(), QImage::Format_ARGB32);
0338         hud.fill(qRgba(0, 0, 0, 0));
0339 
0340         QPainter davinci;
0341         bool ok = davinci.begin(&hud);
0342         if (!ok) {
0343             qDebug() << "Could not initialise QPainter for Audio spectrum HUD.";
0344             return hud;
0345         }
0346         davinci.setPen(AbstractScopeWidget::penLight);
0347 
0348         int y;
0349         for (int db = -dbDiff; db > m_dBmin; db -= dbDiff) {
0350             y = topDist + (m_innerScopeRect.height() * db) / (m_dBmin - m_dBmax);
0351             if (y - topDist > m_innerScopeRect.height() - minDistY + 10) {
0352                 // Abort here, there is still a line left for min dB to paint which needs some room.
0353                 break;
0354             }
0355             davinci.drawLine(leftDist, y, leftDist + m_innerScopeRect.width() - 1, y);
0356             davinci.drawText(leftDist + m_innerScopeRect.width() + textDistX, y + 6, i18n("%1 dB", m_dBmax + db));
0357         }
0358         davinci.drawLine(leftDist, topDist, leftDist + m_innerScopeRect.width() - 1, topDist);
0359         davinci.drawText(leftDist + m_innerScopeRect.width() + textDistX, topDist + 6, i18n("%1 dB", m_dBmax));
0360         davinci.drawLine(leftDist, topDist + m_innerScopeRect.height() - 1, leftDist + m_innerScopeRect.width() - 1, topDist + m_innerScopeRect.height() - 1);
0361         davinci.drawText(leftDist + m_innerScopeRect.width() + textDistX, topDist + m_innerScopeRect.height() + 6, i18n("%1 dB", m_dBmin));
0362 
0363         const int hzDiff = int(ceil(float(minDistX) * m_freqMax / m_innerScopeRect.width() / 1000) * 1000);
0364         int x = 0;
0365         const int rightBorder = leftDist + m_innerScopeRect.width() - 1;
0366         y = topDist + m_innerScopeRect.height() + textDistY;
0367         for (int hz = 0; x <= rightBorder; hz += hzDiff) {
0368             davinci.setPen(AbstractScopeWidget::penLighter);
0369             x = leftDist + int(float(m_innerScopeRect.width()) * hz / m_freqMax);
0370 
0371             if (x <= rightBorder) {
0372                 davinci.drawLine(x, topDist, x, topDist + m_innerScopeRect.height() + 6);
0373             }
0374             if (hz < m_freqMax && x + textDistY < leftDist + m_innerScopeRect.width()) {
0375                 davinci.drawText(x - 4, y, QVariant(hz / 1000).toString());
0376             } else {
0377                 x = leftDist + m_innerScopeRect.width();
0378                 davinci.drawLine(x, topDist, x, topDist + m_innerScopeRect.height() + 6);
0379                 davinci.drawText(x - 10, y, i18n("%1 kHz", QString("%1").arg(m_freqMax / 1000., 0, 'f', 1)));
0380             }
0381 
0382             if (hz > 0) {
0383                 // Draw finer lines between the main lines
0384                 davinci.setPen(AbstractScopeWidget::penLightDots);
0385                 for (uint dHz = 3; dHz > 0; --dHz) {
0386                     x = leftDist + int(float(m_innerScopeRect.width()) * (hz - float(dHz) * hzDiff / 4.f) / m_freqMax);
0387                     if (x > rightBorder) {
0388                         break;
0389                     }
0390                     davinci.drawLine(x, topDist, x, topDist + m_innerScopeRect.height() - 1);
0391                 }
0392             }
0393         }
0394 
0395         if (m_aTrackMouse->isChecked() && m_mouseWithinWidget && mouseX < m_innerScopeRect.width() - 1) {
0396             davinci.setPen(AbstractScopeWidget::penThin);
0397 
0398             x = leftDist + mouseX;
0399 
0400             float db = 0;
0401             float freq = float(mouseX) / (m_innerScopeRect.width() - 1) * m_freqMax;
0402             bool drawDb = false;
0403 
0404             m_lastFFTLock.acquire();
0405             // We need to test whether the mouse is inside the widget
0406             // because the position could already have changed in the meantime (-> crash)
0407             if (!m_lastFFT.isEmpty() && mouseX >= 0 && mouseX < m_innerScopeRect.width()) {
0408                 uint right = uint(m_freqMax / (m_freq / 2.f) * (m_lastFFT.size() - 1));
0409                 QVector<float> dbMap = FFTTools::interpolatePeakPreserving(m_lastFFT, uint(m_innerScopeRect.width()), 0, right, -120);
0410 
0411                 db = dbMap[mouseX];
0412                 y = topDist + m_innerScopeRect.height() - 1 - int((dbMap[mouseX] - m_dBmin) / (m_dBmax - m_dBmin) * (m_innerScopeRect.height() - 1));
0413 
0414                 if (y < topDist + m_innerScopeRect.height() - 1) {
0415                     drawDb = true;
0416                     davinci.drawLine(x, y, leftDist + m_innerScopeRect.width() - 1, y);
0417                 }
0418             } else {
0419                 y = topDist + mouseY;
0420             }
0421             m_lastFFTLock.release();
0422 
0423             if (y > topDist + mouseY) {
0424                 y = topDist + mouseY;
0425             }
0426             davinci.drawLine(x, y, x, topDist + m_innerScopeRect.height() - 1);
0427 
0428             if (drawDb) {
0429                 QPoint dist(20, -20);
0430                 QRect rect(leftDist + mouseX + dist.x(), topDist + mouseY + dist.y(), 100, 40);
0431                 if (rect.right() > leftDist + m_innerScopeRect.width() - 1) {
0432                     // Mirror the rectangle at the y axis to keep it inside the widget
0433                     rect = QRect(rect.topLeft() - QPoint(rect.width() + 2 * dist.x(), 0), rect.size());
0434                 }
0435 
0436                 QRect textRect(rect.topLeft() + QPoint(12, 4), rect.size());
0437 
0438                 davinci.fillRect(rect, AbstractScopeWidget::penBackground.brush());
0439                 davinci.setPen(AbstractScopeWidget::penLighter);
0440                 davinci.drawRect(rect);
0441                 davinci.drawText(textRect,
0442                                  QString(i18n("%1 dB", QString("%1").arg(db, 0, 'f', 2)) + '\n' + i18n("%1 kHz", QString("%1").arg(freq / 1000, 0, 'f', 2))));
0443             }
0444         }
0445 
0446         Q_EMIT signalHUDRenderingFinished(uint(timer.elapsed()), 1);
0447         return hud;
0448     }
0449 #ifdef DEBUG_AUDIOSPEC
0450     qCDebug(KDENLIVE_LOG) << "Widget is too small for painting inside. Size of inner scope rect is " << m_innerScopeRect.width() << 'x'
0451                           << m_innerScopeRect.height() << ".";
0452 #endif
0453     Q_EMIT signalHUDRenderingFinished(0, 1);
0454     return QImage();
0455 }
0456 
0457 QRect AudioSpectrum::scopeRect()
0458 {
0459     m_scopeRect = QRect(QPoint(10,                                        // Left
0460                                m_ui->verticalSpacer->geometry().top() + 6 // Top
0461                                ),
0462                         AbstractAudioScopeWidget::rect().bottomRight());
0463     m_innerScopeRect = QRect(QPoint(m_scopeRect.left() + 6, // Left
0464                                     m_scopeRect.top() + 6   // Top
0465                                     ),
0466                              QPoint(m_ui->verticalSpacer->geometry().right() - 70, m_ui->verticalSpacer->geometry().bottom() - 40));
0467     return m_scopeRect;
0468 }
0469 
0470 void AudioSpectrum::slotResetMaxFreq()
0471 {
0472     m_customFreq = false;
0473     forceUpdateHUD();
0474     forceUpdateScope();
0475 }
0476 
0477 ///// EVENTS /////
0478 
0479 void AudioSpectrum::handleMouseDrag(const QPoint &movement, const RescaleDirection rescaleDirection, const Qt::KeyboardModifiers rescaleModifiers)
0480 {
0481     if (rescaleDirection == North) {
0482         // Nort-South direction: Adjust the dB scale
0483 
0484         if ((rescaleModifiers & Qt::ShiftModifier) == 0) {
0485 
0486             // By default adjust the min dB value
0487             m_dBmin += movement.y();
0488 
0489         } else {
0490 
0491             // Adjust max dB value if Shift is pressed.
0492             m_dBmax += movement.y();
0493         }
0494 
0495         // Ensure the dB values lie in [-100, 0] (or rather [MIN_DB_VALUE, 0])
0496         // 0 is the upper bound, everything below -70 dB is most likely noise
0497         if (m_dBmax > 0) {
0498             m_dBmax = 0;
0499         }
0500         if (m_dBmin < MIN_DB_VALUE) {
0501             m_dBmin = MIN_DB_VALUE;
0502         }
0503         // Ensure there is at least 6 dB between the minimum and the maximum value;
0504         // lower values hardly make sense
0505         if (m_dBmax - m_dBmin < 6) {
0506             if ((rescaleModifiers & Qt::ShiftModifier) == 0) {
0507                 // min was adjusted; Try to adjust the max value to maintain the
0508                 // minimum dB difference of 6 dB
0509                 m_dBmax = m_dBmin + 6;
0510                 if (m_dBmax > 0) {
0511                     m_dBmax = 0;
0512                     m_dBmin = -6;
0513                 }
0514             } else {
0515                 // max was adjusted, adjust min
0516                 m_dBmin = m_dBmax - 6;
0517                 if (m_dBmin < MIN_DB_VALUE) {
0518                     m_dBmin = MIN_DB_VALUE;
0519                     m_dBmax = MIN_DB_VALUE + 6;
0520                 }
0521             }
0522         }
0523 
0524         forceUpdateHUD();
0525         forceUpdateScope();
0526 
0527     } else if (rescaleDirection == East) {
0528         // East-West direction: Adjust the maximum frequency
0529         m_freqMax -= 100 * movement.x();
0530         if (m_freqMax < MIN_FREQ_VALUE) {
0531             m_freqMax = MIN_FREQ_VALUE;
0532         }
0533         if (m_freqMax > MAX_FREQ_VALUE) {
0534             m_freqMax = MAX_FREQ_VALUE;
0535         }
0536         m_customFreq = true;
0537 
0538         forceUpdateHUD();
0539         forceUpdateScope();
0540     }
0541 }