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 }