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 "monitoraudiolevel.h"
0009 #include "audiomixer/iecscale.h"
0010 #include "core.h"
0011 #include "profiles/profilemodel.hpp"
0012 
0013 #include "mlt++/Mlt.h"
0014 
0015 #include <cmath>
0016 
0017 #include <QFont>
0018 #include <QPaintEvent>
0019 #include <QPainter>
0020 
0021 MonitorAudioLevel::MonitorAudioLevel(int height, QWidget *parent)
0022     : ScopeWidget(parent)
0023     , audioChannels(2)
0024     , m_height(height)
0025     , m_channelHeight(height / 2)
0026     , m_channelDistance(1)
0027     , m_channelFillHeight(m_channelHeight)
0028 {
0029     setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
0030     isValid = true;
0031     connect(this, &MonitorAudioLevel::audioLevelsAvailable, this, &MonitorAudioLevel::setAudioValues);
0032 }
0033 
0034 MonitorAudioLevel::~MonitorAudioLevel() = default;
0035 
0036 void MonitorAudioLevel::refreshScope(const QSize & /*size*/, bool /*full*/)
0037 {
0038     SharedFrame sFrame;
0039     while (m_queue.count() > 0) {
0040         sFrame = m_queue.pop();
0041         if (sFrame.is_valid()) {
0042             int samples = sFrame.get_audio_samples();
0043             if (samples <= 0) {
0044                 continue;
0045             }
0046             // TODO: the 200 value is aligned with the MLT audiolevel filter, but seems arbitrary.
0047             samples = qMin(200, samples);
0048             int channels = sFrame.get_audio_channels();
0049             QVector<double> levels;
0050             const int16_t *audio = sFrame.get_audio();
0051             for (int c = 0; c < channels; c++) {
0052                 int16_t peak = 0;
0053                 const int16_t *p = audio + c;
0054                 for (int s = 0; s < samples; s++) {
0055                     int16_t sample = abs(*p);
0056                     if (sample > peak) peak = sample;
0057                     p += channels;
0058                 }
0059                 if (peak == 0) {
0060                     levels << -100;
0061                 } else {
0062                     levels << 20 * log10((double)peak / (double)std::numeric_limits<int16_t>::max());
0063                 }
0064             }
0065             Q_EMIT audioLevelsAvailable(levels);
0066         }
0067     }
0068 }
0069 
0070 void MonitorAudioLevel::resizeEvent(QResizeEvent *event)
0071 {
0072     drawBackground(m_peaks.size());
0073     ScopeWidget::resizeEvent(event);
0074 }
0075 
0076 void MonitorAudioLevel::refreshPixmap()
0077 {
0078     drawBackground(m_peaks.size());
0079 }
0080 
0081 void MonitorAudioLevel::drawBackground(int channels)
0082 {
0083     if (height() == 0) {
0084         return;
0085     }
0086     QSize newSize = QWidget::size();
0087     if (!newSize.isValid()) {
0088         return;
0089     }
0090     QFont ft = font();
0091     ft.setPixelSize(newSize.height() / 3);
0092     setFont(ft);
0093     int textHeight = fontMetrics().ascent();
0094     newSize.setHeight(newSize.height() - textHeight);
0095     // Channel labels are horizontal along the bottom.
0096     QVector<int> dbscale = {0, -6, -12, -18, -24, -30, -36, -42, -48, -54};
0097     int dbLabelCount = dbscale.size();
0098     m_maxDb = dbscale.first();
0099     QLinearGradient gradient(0, 0, newSize.width(), 0);
0100     double gradientVal = 0.;
0101     gradient.setColorAt(gradientVal, Qt::darkGreen);
0102     gradientVal = IEC_ScaleMax(-12, m_maxDb);
0103     gradient.setColorAt(gradientVal, Qt::green); // -12db, green
0104     gradientVal = IEC_ScaleMax(-8, m_maxDb);
0105     gradient.setColorAt(gradientVal, Qt::yellow); // -8db, yellow
0106     gradientVal = IEC_ScaleMax(-5, m_maxDb);
0107     gradient.setColorAt(gradientVal, QColor(255, 200, 20)); // -5db, orange
0108     gradient.setColorAt(1., Qt::red);                       // 0db, red
0109     m_pixmap = QPixmap(QWidget::size());
0110     if (m_pixmap.isNull()) {
0111         return;
0112     }
0113     m_pixmap.fill(Qt::transparent);
0114     int totalHeight;
0115     if (channels < 2) {
0116         m_channelHeight = newSize.height() / 2;
0117         totalHeight = m_channelHeight;
0118     } else {
0119         m_channelHeight = (newSize.height() - (channels - 1)) / channels;
0120         totalHeight = channels * m_channelHeight + (channels - 1);
0121     }
0122     QRect rect(0, 0, newSize.width(), totalHeight);
0123     QPainter p(&m_pixmap);
0124     p.setOpacity(0.6);
0125     p.setFont(ft);
0126     p.fillRect(rect, QBrush(gradient));
0127     // dB scale is horizontal along the bottom
0128     int y = totalHeight + textHeight;
0129     int prevX = -1;
0130     int x = 0;
0131     for (int i = 0; i < dbLabelCount; i++) {
0132         int value = dbscale[i];
0133         QString label = QString::asprintf("%d", value);
0134         int labelWidth = fontMetrics().horizontalAdvance(label);
0135         x = IEC_ScaleMax(value, m_maxDb) * m_pixmap.width();
0136         p.setPen(palette().window().color());
0137         p.drawLine(x, 0, x, totalHeight);
0138         x -= qRound(labelWidth / 2.);
0139         if (x + labelWidth > m_pixmap.width()) {
0140             x = m_pixmap.width() - labelWidth;
0141         }
0142         if (prevX < 0 || prevX - (x + labelWidth) > 2) {
0143             p.setPen(palette().text().color().rgb());
0144             p.drawText(x, y, label);
0145             prevX = x;
0146         }
0147     }
0148     p.setOpacity(1);
0149     p.setPen(palette().window().color());
0150     // Clear space between the 2 channels
0151     p.setCompositionMode(QPainter::CompositionMode_Source);
0152     m_channelDistance = 1;
0153     m_channelFillHeight = m_channelHeight;
0154     for (int i = 1; i < channels; i++) {
0155         p.drawLine(0, i * (m_channelHeight + m_channelDistance) - 1, rect.width() - 1, i * (m_channelHeight + m_channelDistance) - 1);
0156     }
0157     p.end();
0158 }
0159 
0160 // cppcheck-suppress unusedFunction
0161 void MonitorAudioLevel::setAudioValues(const QVector<double> &values)
0162 {
0163     m_values = values;
0164     if (m_peaks.size() != m_values.size()) {
0165         m_peaks = values;
0166         drawBackground(values.size());
0167     } else {
0168         for (int i = 0; i < m_values.size(); i++) {
0169             m_peaks[i] -= 0.2;
0170             if (m_values.at(i) > m_peaks.at(i)) {
0171                 m_peaks[i] = m_values.at(i);
0172             }
0173         }
0174     }
0175     update();
0176 }
0177 
0178 void MonitorAudioLevel::setVisibility(bool enable)
0179 {
0180     if (enable) {
0181         setVisible(true);
0182         setFixedHeight(m_height);
0183     } else {
0184         // set height to 0 so the toolbar layout is not affected
0185         setFixedHeight(0);
0186         setVisible(false);
0187     }
0188 }
0189 
0190 void MonitorAudioLevel::paintEvent(QPaintEvent *pe)
0191 {
0192     if (!isVisible()) {
0193         return;
0194     }
0195     QPainter p(this);
0196     p.setClipRect(pe->rect());
0197     QRect rect(0, 0, width(), height());
0198     if (m_values.isEmpty()) {
0199         p.setOpacity(0.2);
0200         p.drawPixmap(rect, m_pixmap);
0201         return;
0202     }
0203     p.drawPixmap(rect, m_pixmap);
0204     p.setOpacity(0.9);
0205     int width = m_channelDistance == 1 ? rect.width() : rect.width() - 1;
0206     for (int i = 0; i < m_values.count(); i++) {
0207         if (m_values.at(i) >= 100) {
0208             continue;
0209         }
0210         int val = IEC_ScaleMax(m_values.at(i), m_maxDb) * width;
0211         int peak = IEC_ScaleMax(m_peaks.at(i), m_maxDb) * width;
0212         p.fillRect(val, i * (m_channelHeight + m_channelDistance), width - val, m_channelFillHeight, palette().window());
0213         p.fillRect(peak, i * (m_channelHeight + m_channelDistance), 1, m_channelFillHeight, palette().text());
0214     }
0215 }