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 }