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 }