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

0001 /*
0002     SPDX-FileCopyrightText: 2016 Jean-Baptiste Mardelle <jb@kdenlive.org>
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 "audiographspectrum.h"
0009 #include "../monitormanager.h"
0010 #include "core.h"
0011 #include "kdenlivesettings.h"
0012 #include "profiles/profilemodel.hpp"
0013 
0014 #include <QAction>
0015 #include <QFontDatabase>
0016 #include <QGridLayout>
0017 #include <QPaintEvent>
0018 #include <QPainter>
0019 #include <QVBoxLayout>
0020 
0021 #include <KLocalizedString>
0022 #include <KMessageWidget>
0023 
0024 #include <cmath>
0025 
0026 #include <mlt++/Mlt.h>
0027 
0028 // Code borrowed from Shotcut's audiospectum by Brian Matherly <code@brianmatherly.com> (GPL)
0029 
0030 static const int WINDOW_SIZE = 8000; // 6 Hz FFT bins at 48kHz
0031 
0032 struct band
0033 {
0034     float low;    // Low frequency
0035     float center; // Center frequency
0036     float high;   // High frequency
0037     const char *label;
0038 };
0039 
0040 // Preferred frequencies from ISO R 266-1997 / ANSI S1.6-1984
0041 static const band BAND_TAB[] = {
0042     //     Low      Preferred  High                Band
0043     //     Freq      Center    Freq     Label       Num
0044     {1.12f, 1.25f, 1.41f, "1.25"},            //  1
0045     {1.41f, 1.60f, 1.78f, "1.6"},             //  2
0046     {1.78f, 2.00f, 2.24f, "2.0"},             //  3
0047     {2.24f, 2.50f, 2.82f, "2.5"},             //  4
0048     {2.82f, 3.15f, 3.55f, "3.15"},            //  5
0049     {3.55f, 4.00f, 4.44f, "4.0"},             //  6
0050     {4.44f, 5.00f, 6.00f, "5.0"},             //  7
0051     {6.00f, 6.30f, 7.00f, "6.3"},             //  8
0052     {7.00f, 8.00f, 9.00f, "8.0"},             //  9
0053     {9.00f, 10.00f, 11.00f, "10"},            // 10
0054     {11.00f, 12.50f, 14.00f, "12.5"},         // 11
0055     {14.00f, 16.00f, 18.00f, "16"},           // 12
0056     {18.00f, 20.00f, 22.00f, "20"},           // 13 - First in audible range
0057     {22.00f, 25.00f, 28.00f, "25"},           // 14
0058     {28.00f, 31.50f, 35.00f, "31"},           // 15
0059     {35.00f, 40.00f, 45.00f, "40"},           // 16
0060     {45.00f, 50.00f, 56.00f, "50"},           // 17
0061     {56.00f, 63.00f, 71.00f, "63"},           // 18
0062     {71.00f, 80.00f, 90.00f, "80"},           // 19
0063     {90.00f, 100.00f, 112.00f, "100"},        // 20
0064     {112.00f, 125.00f, 140.00f, "125"},       // 21
0065     {140.00f, 160.00f, 179.00f, "160"},       // 22
0066     {179.00f, 200.00f, 224.00f, "200"},       // 23
0067     {224.00f, 250.00f, 282.00f, "250"},       // 24
0068     {282.00f, 315.00f, 353.00f, "315"},       // 25
0069     {353.00f, 400.00f, 484.00f, "400"},       // 26
0070     {484.00f, 500.00f, 560.00f, "500"},       // 27
0071     {560.00f, 630.00f, 706.00f, "630"},       // 28
0072     {706.00f, 800.00f, 897.00f, "800"},       // 29
0073     {897.00f, 1000.00f, 1121.00f, "1k"},      // 30
0074     {1121.00f, 1250.00f, 1401.00f, "1.3k"},   // 31
0075     {1401.00f, 1600.00f, 1794.00f, "1.6k"},   // 32
0076     {1794.00f, 2000.00f, 2242.00f, "2k"},     // 33
0077     {2242.00f, 2500.00f, 2803.00f, "2.5k"},   // 34
0078     {2803.00f, 3150.00f, 3531.00f, "3.2k"},   // 35
0079     {3531.00f, 4000.00f, 4484.00f, "4k"},     // 36
0080     {4484.00f, 5000.00f, 5605.00f, "5k"},     // 37
0081     {5605.00f, 6300.00f, 7062.00f, "6.3k"},   // 38
0082     {7062.00f, 8000.00f, 8908.00f, "8k"},     // 39
0083     {8908.00f, 10000.00f, 11210.00f, "10k"},  // 40
0084     {11210.00f, 12500.00f, 14012.00f, "13k"}, // 41
0085     {14012.00f, 16000.00f, 17936.00f, "16k"}, // 42
0086     {17936.00f, 20000.00f, 22421.00f, "20k"}, // 43 - Last in audible range
0087 };
0088 
0089 static const int FIRST_AUDIBLE_BAND_INDEX = 12;
0090 static const int LAST_AUDIBLE_BAND_INDEX = 42;
0091 static const int AUDIBLE_BAND_COUNT = LAST_AUDIBLE_BAND_INDEX - FIRST_AUDIBLE_BAND_INDEX + 1;
0092 
0093 const float log_factor = 1.0f / log10f(1.0f / 127);
0094 
0095 static inline float levelToDB(float dB)
0096 {
0097     if (dB <= 0) {
0098         return 0;
0099     }
0100     return (1.0f - log10f(dB) * log_factor);
0101 }
0102 
0103 /*EqualizerWidget::EqualizerWidget(QWidget *parent) : QWidget(parent)
0104 {
0105     QGridLayout *box = new QGridLayout(this);
0106     QStringList labels;
0107     labels << i18n("Master") << "50Hz" <<
0108 "100Hz"<<"156Hz"<<"220Hz"<<"311Hz"<<"440Hz"<<"622Hz"<<"880Hz"<<"1.25kHz"<<"1.75kHz"<<"2.5kHz"<<"3.5kHz"<<"5kHz"<<"10kHz"<<"20kHz";
0109     for (int i = 0; i < 16; i++) {
0110         QSlider *sl = new QSlider(Qt::Vertical, this);
0111         sl->setObjectName(QString::number(i));
0112         box->addWidget(sl, 0, i);
0113         QLabel *lab = new QLabel(labels.at(i), this);
0114         box->addWidget(lab, 1, i);
0115     }
0116     setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
0117 }*/
0118 
0119 AudioGraphWidget::AudioGraphWidget(QWidget *parent)
0120     : QWidget(parent)
0121 {
0122     m_dbLabels << -45 << -30 << -20 << -15 << -10 << -5 << -2 << 0;
0123     for (int i = FIRST_AUDIBLE_BAND_INDEX; i <= LAST_AUDIBLE_BAND_INDEX; i++) {
0124         m_freqLabels << BAND_TAB[i].label;
0125     }
0126     m_maxDb = 0;
0127     setMinimumWidth(2 * m_freqLabels.size() + fontMetrics().horizontalAdvance(QStringLiteral("888")) + 2);
0128     setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
0129     setMinimumHeight(100);
0130     setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
0131 }
0132 
0133 void AudioGraphWidget::showAudio(const QVector<float> &bands)
0134 {
0135     m_levels = bands;
0136     update();
0137 }
0138 
0139 void AudioGraphWidget::drawDbLabels(QPainter &p, const QRect &rect)
0140 {
0141     int dbLabelCount = m_dbLabels.size();
0142     int textHeight = fontMetrics().ascent();
0143 
0144     if (dbLabelCount == 0) {
0145         return;
0146     }
0147 
0148     int maxWidth = fontMetrics().horizontalAdvance(QStringLiteral("-45"));
0149     // dB scale is vertical along the left side
0150     int prevY = height();
0151     QColor textCol = palette().text().color();
0152     p.setPen(textCol);
0153     for (int i = 0; i < dbLabelCount; i++) {
0154         QString label = QString::number(m_dbLabels.at(i));
0155         int x = rect.left() + maxWidth - fontMetrics().horizontalAdvance(label);
0156         int yline = int(rect.bottom() - pow(10.0, double(m_dbLabels.at(i)) / 50.0) * rect.height() * 40.0 / 42);
0157         int y = yline + textHeight / 2;
0158         if (y - textHeight < 0) {
0159             y = textHeight;
0160         }
0161         if (prevY - y >= 2) {
0162             p.drawText(x, y, label);
0163             p.drawLine(rect.left() + maxWidth + 2, yline, rect.width(), yline);
0164             prevY = y - textHeight;
0165         }
0166     }
0167 }
0168 
0169 void AudioGraphWidget::drawChanLabels(QPainter &p, const QRect &rect, int barWidth)
0170 {
0171     int chanLabelCount = m_freqLabels.size();
0172     int stride = 1;
0173 
0174     if (chanLabelCount == 0) {
0175         return;
0176     }
0177 
0178     p.setPen(palette().text().color().rgb());
0179 
0180     // Channel labels are horizontal along the bottom.
0181 
0182     // Find the widest channel label
0183     int chanLabelWidth = 0;
0184     for (int i = 0; i < chanLabelCount; i++) {
0185         int width = fontMetrics().horizontalAdvance(m_freqLabels.at(i)) + 2;
0186         chanLabelWidth = width > chanLabelWidth ? width : chanLabelWidth;
0187     }
0188     int length = rect.width();
0189     while (chanLabelWidth * chanLabelCount / stride > length) {
0190         stride++;
0191     }
0192 
0193     int prevX = 0;
0194     int y = rect.bottom();
0195     for (int i = 0; i < chanLabelCount; i += stride) {
0196         QString label = m_freqLabels.at(i);
0197         int x = rect.left() + (2 * i) + i * barWidth + barWidth / 2 - fontMetrics().horizontalAdvance(label) / 2;
0198         if (x > prevX) {
0199             p.drawText(x, y, label);
0200             prevX = x + fontMetrics().horizontalAdvance(label);
0201         }
0202     }
0203 }
0204 
0205 void AudioGraphWidget::resizeEvent(QResizeEvent *event)
0206 {
0207     drawBackground();
0208     QWidget::resizeEvent(event);
0209 }
0210 
0211 void AudioGraphWidget::drawBackground()
0212 {
0213     QSize s = size();
0214     if (!s.isValid()) {
0215         return;
0216     }
0217     m_pixmap = QPixmap(s);
0218     if (m_pixmap.isNull()) {
0219         return;
0220     }
0221     m_pixmap.fill(palette().base().color());
0222     QPainter p(&m_pixmap);
0223     QRect rect(0, 0, width() - 3, height());
0224     p.setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
0225     p.setOpacity(0.6);
0226     int offset = fontMetrics().horizontalAdvance(QStringLiteral("888")) + 2;
0227     if (rect.width() - offset > 10) {
0228         drawDbLabels(p, rect);
0229         rect.adjust(offset, 0, 0, 0);
0230     }
0231     int barWidth = (rect.width() - (2 * (AUDIBLE_BAND_COUNT - 1))) / AUDIBLE_BAND_COUNT;
0232     drawChanLabels(p, rect, barWidth);
0233     rect.adjust(0, 0, 0, -fontMetrics().height());
0234     m_rect = rect;
0235 }
0236 
0237 void AudioGraphWidget::paintEvent(QPaintEvent *pe)
0238 {
0239     QPainter p(this);
0240     p.setClipRect(pe->rect());
0241     p.drawPixmap(0, 0, m_pixmap);
0242     if (m_levels.isEmpty()) {
0243         return;
0244     }
0245     int chanCount = m_levels.size();
0246     int height = m_rect.height();
0247     double barWidth = (m_rect.width() - (2.0 * (AUDIBLE_BAND_COUNT - 1))) / AUDIBLE_BAND_COUNT;
0248     p.setOpacity(0.6);
0249     QRectF rect(m_rect.left(), 0, barWidth, height);
0250     for (int i = 0; i < chanCount; i++) {
0251         float level = (0.5 + m_levels.at(i)) / 1.5 * height;
0252         if (level < 0) {
0253             continue;
0254         }
0255         rect.moveLeft(m_rect.left() + i * barWidth + (2 * i));
0256         rect.setHeight(level);
0257         rect.moveBottom(height);
0258         p.fillRect(rect, Qt::darkGreen);
0259     }
0260 }
0261 
0262 AudioGraphSpectrum::AudioGraphSpectrum(MonitorManager *manager, QWidget *parent)
0263     : ScopeWidget(parent)
0264     , m_manager(manager)
0265 {
0266     auto *lay = new QVBoxLayout(this);
0267     m_graphWidget = new AudioGraphWidget(this);
0268     lay->addWidget(m_graphWidget);
0269 
0270     /*m_equalizer = new EqualizerWidget(this);
0271     lay->addWidget(m_equalizer);
0272     lay->setStretchFactor(m_graphWidget, 5);
0273     lay->setStretchFactor(m_equalizer, 3);*/
0274 
0275     m_filter = new Mlt::Filter(pCore->getProjectProfile(), "fft");
0276     if (!m_filter->is_valid()) {
0277         KdenliveSettings::setEnableaudiospectrum(false);
0278         auto *mw = new KMessageWidget(this);
0279         mw->setCloseButtonVisible(false);
0280         mw->setWordWrap(true);
0281         mw->setMessageType(KMessageWidget::Information);
0282         mw->setText(i18n("MLT must be compiled with libfftw3 to enable Audio Spectrum"));
0283         layout()->addWidget(mw);
0284         mw->show();
0285         setEnabled(false);
0286         return;
0287     }
0288     m_filter->set("window_size", WINDOW_SIZE);
0289     QAction *a = new QAction(i18n("Enable Audio Spectrum"), this);
0290     a->setCheckable(true);
0291     a->setChecked(KdenliveSettings::enableaudiospectrum());
0292     if (KdenliveSettings::enableaudiospectrum() && isVisible()) {
0293         connect(m_manager, &MonitorManager::frameDisplayed, this, &ScopeWidget::onNewFrame, Qt::UniqueConnection);
0294     }
0295     connect(a, &QAction::triggered, this, &AudioGraphSpectrum::activate);
0296     addAction(a);
0297     setContextMenuPolicy(Qt::ActionsContextMenu);
0298 }
0299 
0300 AudioGraphSpectrum::~AudioGraphSpectrum()
0301 {
0302     delete m_graphWidget;
0303     delete m_filter;
0304 }
0305 
0306 void AudioGraphSpectrum::dockVisible(bool visible)
0307 {
0308     if (KdenliveSettings::enableaudiospectrum()) {
0309         if (!visible) {
0310             disconnect(m_manager, &MonitorManager::frameDisplayed, this, &ScopeWidget::onNewFrame);
0311         } else {
0312             connect(m_manager, &MonitorManager::frameDisplayed, this, &ScopeWidget::onNewFrame);
0313         }
0314     }
0315 }
0316 
0317 void AudioGraphSpectrum::activate(bool enable)
0318 {
0319     if (enable) {
0320         connect(m_manager, &MonitorManager::frameDisplayed, this, &ScopeWidget::onNewFrame, Qt::UniqueConnection);
0321     } else {
0322         disconnect(m_manager, &MonitorManager::frameDisplayed, this, &ScopeWidget::onNewFrame);
0323     }
0324     KdenliveSettings::setEnableaudiospectrum(enable);
0325 }
0326 
0327 void AudioGraphSpectrum::refreshPixmap()
0328 {
0329     if (m_graphWidget) {
0330         m_graphWidget->drawBackground();
0331     }
0332 }
0333 
0334 void AudioGraphSpectrum::refreshScope(const QSize & /*size*/, bool /*full*/)
0335 {
0336     SharedFrame sFrame;
0337     while (m_queue.count() > 0) {
0338         sFrame = m_queue.pop();
0339         if (sFrame.is_valid() && sFrame.get_audio_samples() > 0) {
0340             mlt_audio_format format = mlt_audio_s16;
0341             int channels = sFrame.get_audio_channels();
0342             int frequency = sFrame.get_audio_frequency();
0343             int samples = sFrame.get_audio_samples();
0344             Mlt::Frame mFrame = sFrame.clone(true, false, false);
0345             m_filter->process(mFrame);
0346             mFrame.get_audio(format, frequency, channels, samples);
0347             if (samples == 0 || format == 0) {
0348                 // There was an error processing audio from frame
0349                 continue;
0350             }
0351             processSpectrum();
0352         }
0353     }
0354 }
0355 
0356 void AudioGraphSpectrum::processSpectrum()
0357 {
0358     QVector<float> bands(AUDIBLE_BAND_COUNT);
0359     auto *bins = static_cast<float *>(m_filter->get_data("bins"));
0360     int bin_count = m_filter->get_int("bin_count");
0361     float bin_width = float(m_filter->get_double("bin_width"));
0362 
0363     int band = 0;
0364     bool firstBandFound = false;
0365     for (int bin = 0; bin < bin_count; bin++) {
0366         // Loop through all the FFT bins and align bin frequencies with
0367         // band frequencies.
0368         float F = bin_width * bin;
0369 
0370         if (!firstBandFound) {
0371             // Skip bins that come before the first band.
0372             if (BAND_TAB[band + FIRST_AUDIBLE_BAND_INDEX].low > F) {
0373                 continue;
0374             } else {
0375                 firstBandFound = true;
0376                 bands[band] = bins[bin];
0377             }
0378         } else if (BAND_TAB[band + FIRST_AUDIBLE_BAND_INDEX].high < F) {
0379             // This bin is outside of this band - move to the next band.
0380             band++;
0381             if ((band + FIRST_AUDIBLE_BAND_INDEX) > LAST_AUDIBLE_BAND_INDEX) {
0382                 // Skip bins that come after the last band.
0383                 break;
0384             }
0385             bands[band] = bins[bin];
0386         } else if (bands[band] < bins[bin]) {
0387             // Pick the highest bin level within this band to represent the
0388             // whole band.
0389             bands[band] = bins[bin];
0390         }
0391     }
0392 
0393     // At this point, bands contains the magnitude of the signal for each
0394     // band. Convert to dB.
0395     for (band = 0; band < bands.size(); band++) {
0396         float mag = bands[band];
0397         float dB = mag > 0.0f ? levelToDB(mag) : -100.0;
0398         bands[band] = dB;
0399     }
0400 
0401     // Update the audio signal widget
0402     QMetaObject::invokeMethod(m_graphWidget, "showAudio", Qt::QueuedConnection, Q_ARG(QVector<float>, bands));
0403 }