File indexing completed on 2024-04-21 04:51:03

0001 /*
0002 SPDX-FileCopyrightText: 2016 Jean-Baptiste Mardelle <jb@kdenlive.org>
0003 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0004 */
0005 
0006 #include "audiolevelwidget.hpp"
0007 #include "core.h"
0008 #include "iecscale.h"
0009 #include "mlt++/Mlt.h"
0010 #include "profiles/profilemodel.hpp"
0011 
0012 #include <KLocalizedString>
0013 #include <QFont>
0014 #include <QFontDatabase>
0015 #include <QPaintEvent>
0016 #include <QPainter>
0017 #include <QToolTip>
0018 
0019 AudioLevelWidget::AudioLevelWidget(int width, int sliderHandle, QWidget *parent)
0020     : QWidget(parent)
0021     , audioChannels(pCore->audioChannels())
0022     , m_width(width)
0023     , m_offset(fontMetrics().boundingRect(QStringLiteral("-45")).width() + 4)
0024     , m_channelWidth(width / 2)
0025     , m_channelDistance(2)
0026     , m_channelFillWidth(m_channelWidth)
0027     , m_displayToolTip(false)
0028     , m_sliderHandle(sliderHandle)
0029 {
0030     setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
0031     QFont ft(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
0032     ft.setPointSizeF(ft.pointSize() * 0.8);
0033     setFont(ft);
0034     setMinimumWidth(4);
0035 }
0036 
0037 AudioLevelWidget::~AudioLevelWidget() = default;
0038 
0039 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0040 void AudioLevelWidget::enterEvent(QEnterEvent *event)
0041 #else
0042 void AudioLevelWidget::enterEvent(QEvent *event)
0043 #endif
0044 {
0045     QWidget::enterEvent(event);
0046     m_displayToolTip = true;
0047     updateToolTip();
0048 }
0049 
0050 void AudioLevelWidget::leaveEvent(QEvent *event)
0051 {
0052     QWidget::leaveEvent(event);
0053     m_displayToolTip = false;
0054 }
0055 
0056 void AudioLevelWidget::resizeEvent(QResizeEvent *event)
0057 {
0058     QWidget::resizeEvent(event);
0059     drawBackground(m_peaks.size());
0060 }
0061 
0062 void AudioLevelWidget::refreshPixmap()
0063 {
0064     drawBackground(m_peaks.size());
0065 }
0066 
0067 void AudioLevelWidget::changeEvent(QEvent *event)
0068 {
0069     if (event->type() == QEvent::EnabledChange) {
0070         drawBackground(m_peaks.size());
0071     }
0072     QWidget::changeEvent(event);
0073 }
0074 
0075 void AudioLevelWidget::drawBackground(int channels)
0076 {
0077     if (height() == 0 || channels == 0) {
0078         return;
0079     }
0080     QSize newSize = QWidget::size();
0081     if (!newSize.isValid()) {
0082         return;
0083     }
0084     m_pixmap = QPixmap(newSize);
0085     if (m_pixmap.isNull()) {
0086         return;
0087     }
0088     // Channel labels are vertical along the left.
0089     const QVector<int> dbscale = {0, -6, -12, -18, -24, -30, -36, -42, -48, -54};
0090     m_maxDb = dbscale.first();
0091     int dbLabelCount = dbscale.size();
0092 
0093     // Ensure width is a multiple of channels count
0094     int maxChannelsWidth = newSize.width() - 2 * m_offset - 4;
0095     // Calculate channels width
0096     m_channelDistance = channels < 2 ? 0 : 2;
0097     maxChannelsWidth -= m_channelDistance * (channels - 1);
0098     m_channelWidth = maxChannelsWidth / channels;
0099     if (m_channelWidth < 4) {
0100         m_channelDistance = 1;
0101         maxChannelsWidth += (channels - 1);
0102         m_channelWidth = maxChannelsWidth / channels;
0103     }
0104     m_channelWidth = qMax(m_channelWidth, 1);
0105     int newWidth = channels * m_channelWidth + m_channelDistance * (channels - 1) + (m_channelDistance == 1 ? 3 : 4);
0106     newSize.setWidth(newWidth);
0107     QLinearGradient gradient(0, newSize.height() - 4, 0, 0);
0108     double gradientVal = 0.;
0109     gradient.setColorAt(gradientVal, Qt::darkGreen);
0110     gradientVal = IEC_ScaleMax(-12, m_maxDb);
0111     gradient.setColorAt(gradientVal, Qt::green); // -12db, green
0112     gradientVal = IEC_ScaleMax(-8, m_maxDb);
0113     gradient.setColorAt(gradientVal, Qt::yellow); // -8db, yellow
0114     gradientVal = IEC_ScaleMax(-5, m_maxDb);
0115     gradient.setColorAt(gradientVal, QColor(255, 200, 20)); // -5db, orange
0116     gradient.setColorAt(1., Qt::red);                       // 0db, red
0117     m_pixmap.fill(Qt::transparent);
0118     QRect rect(m_offset, 1, newWidth - 2, newSize.height() - 2);
0119     QPainter p(&m_pixmap);
0120     p.setOpacity(isEnabled() ? 0.8 : 0.4);
0121     p.setFont(font());
0122     QPen pen = p.pen();
0123     pen.setColor(palette().dark().color());
0124     pen.setWidth(2);
0125     p.setPen(pen);
0126     p.fillRect(QRect(m_offset, 2, newWidth - 2, newSize.height() - 4), QBrush(gradient));
0127     p.setOpacity(1);
0128     p.drawRect(rect);
0129 
0130     // dB scale is vertical along the bottom
0131     int labelHeight = fontMetrics().ascent();
0132 
0133     int prevY = -1;
0134     int y = 0;
0135     if (newSize.height() > 2 * labelHeight) {
0136         for (int i = 0; i < dbLabelCount; i++) {
0137             int value = dbscale.at(i);
0138             y = newSize.height() - 2 - qRound(IEC_ScaleMax(value, m_maxDb) * ((double)newSize.height() - 4));
0139             p.setOpacity(0.6);
0140             p.setPen(palette().window().color().rgb());
0141             p.drawLine(m_offset + 1, y, m_offset + newWidth - 4, y);
0142             y -= qRound(labelHeight / 2.0);
0143             if (y < 0) {
0144                 y = 0;
0145             }
0146             if (prevY < 0 || y - prevY > 2) {
0147                 const QString label = QString::asprintf("%d", value);
0148                 p.setOpacity(0.8);
0149                 p.setPen(palette().text().color().rgb());
0150                 p.drawText(QRectF(0, y, m_offset - 5, labelHeight), label, QTextOption(Qt::AlignRight));
0151                 prevY = y + labelHeight;
0152             }
0153         }
0154         prevY = -1;
0155         const QVector<int> gains = {-24, -10, -4, 0, 4, 10, 24};
0156         p.setOpacity(0.8);
0157         p.setPen(palette().text().color().rgb());
0158         int sliderHeight = newSize.height() - m_sliderHandle;
0159         for (int i = 0; i < gains.count(); i++) {
0160             y = newSize.height() - m_sliderHandle / 2 - (fromDB(gains.at(i)) * sliderHeight / 100.);
0161             y -= qRound(labelHeight / 2.0);
0162             y = qBound(0, y, newSize.height() - labelHeight);
0163             if (prevY < 0 || prevY - y > 2) {
0164                 const QString label = QString::asprintf("%d", gains.at(i));
0165                 p.drawText(QRectF(m_offset + newWidth, y, m_pixmap.width() - (m_offset + newWidth) - 2, labelHeight), label, QTextOption(Qt::AlignRight));
0166                 prevY = qMax(0, y - labelHeight);
0167             }
0168         }
0169     }
0170 
0171     p.setOpacity(isEnabled() ? 1 : 0.5);
0172     pen = p.pen();
0173     pen.setColor(palette().dark().color());
0174     pen.setWidth(2);
0175     p.setPen(pen);
0176     if (m_channelWidth < 4) {
0177         // too many audio channels, simple line between channels
0178         m_channelFillWidth = m_channelWidth;
0179         pen.setWidth(1);
0180         p.setPen(pen);
0181         // p.drawRect(m_offset, 0, channels * (m_channelWidth + m_channelDistance) - 2, rect.height() - 1);
0182         for (int i = 1; i < channels; i++) {
0183             p.drawLine(m_offset + i * (m_channelWidth + m_channelDistance), 0, m_offset + i * (m_channelWidth + m_channelDistance), rect.height() - 1);
0184         }
0185     } else if (channels > 1) {
0186         m_channelFillWidth = m_channelWidth - 1;
0187         // p.drawRect(m_offset, 0, channels * (m_channelWidth + m_channelDistance) - 2, rect.height() - 1);
0188         for (int i = 1; i < channels; i++) {
0189             int x = m_offset + i * (m_channelWidth + m_channelDistance);
0190             p.drawLine(x, 2, x, rect.height() - 1);
0191         }
0192     } else {
0193         m_channelDistance = 0;
0194         m_channelFillWidth = m_channelWidth;
0195         // p.drawRect(m_offset, 0, m_channelWidth - 1, rect.height() - 1);
0196     }
0197     p.end();
0198 }
0199 
0200 // cppcheck-suppress unusedFunction
0201 void AudioLevelWidget::setAudioValues(const QVector<double> &values)
0202 {
0203     m_values = values;
0204     if (m_peaks.size() != m_values.size()) {
0205         m_peaks = values;
0206         drawBackground(values.size());
0207     } else {
0208         for (int i = 0; i < m_values.size(); i++) {
0209             m_peaks[i] -= .2;
0210             if (m_values.at(i) > m_peaks.at(i)) {
0211                 m_peaks[i] = m_values.at(i);
0212             }
0213         }
0214     }
0215     update();
0216 }
0217 
0218 void AudioLevelWidget::setVisibility(bool enable)
0219 {
0220     if (enable) {
0221         setVisible(true);
0222         setFixedWidth(m_width);
0223     } else {
0224         // set height to 0 so the toolbar layout is not affected
0225         setFixedHeight(0);
0226         setVisible(false);
0227     }
0228 }
0229 
0230 void AudioLevelWidget::paintEvent(QPaintEvent * /*pe*/)
0231 {
0232     if (!isVisible()) {
0233         return;
0234     }
0235     QPainter p(this);
0236     // p.setClipRect(pe->rect());
0237     QRect rect(0, 0, width(), height());
0238     if (m_values.isEmpty()) {
0239         p.setOpacity(0.3);
0240         p.drawPixmap(0, 0, m_pixmap);
0241         return;
0242     }
0243     p.drawPixmap(0, 0, m_pixmap);
0244     p.setOpacity(0.9);
0245     int levelsHeight = height() - 4;
0246     for (int i = 0; i < m_values.count(); i++) {
0247         if (m_values.at(i) >= 100) {
0248             continue;
0249         }
0250         int val = IEC_ScaleMax(m_values.at(i), m_maxDb) * levelsHeight;
0251         int peak = IEC_ScaleMax(m_peaks.at(i), m_maxDb) * levelsHeight;
0252         int xPos = m_offset + 1 + i * (m_channelWidth + m_channelDistance);
0253         p.fillRect(xPos, 2, m_channelFillWidth + (m_channelDistance == 2 ? 1 : 0), levelsHeight - val, palette().window());
0254         p.fillRect(xPos, 2 + levelsHeight - peak, m_channelFillWidth + (m_channelDistance == 2 ? 1 : 0), 1, palette().text());
0255     }
0256     if (m_displayToolTip) {
0257         updateToolTip();
0258     }
0259 }
0260 
0261 void AudioLevelWidget::updateToolTip()
0262 {
0263     QString tip;
0264     int channels = m_values.count();
0265     for (int i = 0; i < channels; i++) {
0266         if (m_values.at(i) >= 100) {
0267             tip.append(QStringLiteral("-100dB"));
0268         } else {
0269             tip.append(QString::number(m_values.at(i), 'f', 2) + QStringLiteral("dB"));
0270         }
0271         if (channels == 2 && i == 0) {
0272             tip.prepend(i18nc("L as in Left", "L:"));
0273             tip.append(i18nc("R as in Right", "\nR:"));
0274         }
0275     }
0276     QToolTip::showText(QCursor::pos(), tip, this);
0277 }