/*** Olive - Non-Linear Video Editor Copyright (C) 2019 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "audiomonitor.h" #include #include #include #include "audio/audiomanager.h" #include "common/qtutils.h" OLIVE_NAMESPACE_ENTER const int kDecibelStep = 6; const int kDecibelMinimum = -200; const int kMaximumSmoothness = 8; AudioMonitor::AudioMonitor(QWidget *parent) : QOpenGLWidget(parent), cached_channels_(0) { values_.resize(kMaximumSmoothness); connect(AudioManager::instance(), &AudioManager::OutputDeviceStarted, this, &AudioMonitor::OutputDeviceSet); connect(AudioManager::instance(), &AudioManager::OutputPushed, this, &AudioMonitor::OutputPushed); connect(AudioManager::instance(), &AudioManager::AudioParamsChanged, this, &AudioMonitor::SetParams); connect(AudioManager::instance(), &AudioManager::Stopped, this, &AudioMonitor::Stop); } AudioMonitor::~AudioMonitor() { Stop(); } void AudioMonitor::SetParams(const AudioParams ¶ms) { params_ = params; for (int i=0;i v(params_.channel_count(), 0); BytesToSampleSummary(d, v); PushValue(v); SetUpdateLoop(true); } void AudioMonitor::SetUpdateLoop(bool e) { if (e) { connect(this, &AudioMonitor::frameSwapped, this, static_cast(&AudioMonitor::update)); update(); } else { disconnect(this, &AudioMonitor::frameSwapped, this, static_cast(&AudioMonitor::update)); } } void AudioMonitor::paintGL() { QPainter p(this); p.fillRect(rect(), palette().window().color()); if (!params_.channel_count()) { return; } QFontMetrics fm = p.fontMetrics(); int peaks_y = 0; int font_height = fm.height(); // Create rect where decibel markings will go on the side QRect db_labels_rect = rect(); db_labels_rect.setWidth(QFontMetricsWidth(p.fontMetrics(), "-00")); db_labels_rect.adjust(0, font_height, 0, 0); // Determine rect where the main meter will go QRect full_meter_rect = rect(); full_meter_rect.adjust(db_labels_rect.width(), font_height, 0, 0); // Width of each channel in the meter int channel_width = full_meter_rect.width() / params_.channel_count(); if (cached_background_.size() != size() || cached_channels_ != params_.channel_count()) { cached_channels_ = params_.channel_count(); // Generate new background cached_background_ = QPixmap(size()); cached_background_.fill(Qt::transparent); QPainter cached_painter(&cached_background_); { // Draw decibel markings QRect last_db_marking_rect; cached_painter.setPen(palette().text().color()); for (int i=0;i>=kDecibelMinimum;i-=kDecibelStep) { QString db_label; if (i <= kDecibelMinimum) { db_label = "-∞"; } else { db_label = QStringLiteral("%1").arg(i); } qreal log_val = QAudio::convertVolume(i, QAudio::DecibelVolumeScale, QAudio::LogarithmicVolumeScale); QRect db_marking_rect = db_labels_rect; db_marking_rect.adjust(0, db_labels_rect.height() - qRound(log_val * db_labels_rect.height()), 0, 0); db_marking_rect.setHeight(fm.height()); // Prevent any dB markings overlapping if (i == 0 || !db_marking_rect.intersects(last_db_marking_rect)) { cached_painter.drawText(db_marking_rect, Qt::AlignRight, db_label); cached_painter.drawLine(db_marking_rect.topLeft(), db_marking_rect.topRight()); last_db_marking_rect = db_marking_rect; } } } { // Draw bars QLinearGradient g(full_meter_rect.topLeft(), full_meter_rect.bottomLeft()); g.setStops({ QGradientStop(0.0, Qt::red), QGradientStop(0.25, Qt::yellow), QGradientStop(1.0, Qt::green) }); cached_painter.setPen(Qt::black); for (int i=0;i v(params_.channel_count(), 0); if (file_.isOpen()) { UpdateValuesFromFile(v); } PushValue(v); QVector vals = GetAverages(); p.setBrush(QColor(0, 0, 0, 128)); p.setPen(Qt::NoPen); bool all_zeroes = true; for (int i=0;i 1.0) { peaked_[i] = true; } if (all_zeroes && !qIsNull(vol)) { all_zeroes = false; } // Convert val to logarithmic scale vol = QAudio::convertVolume(vol, QAudio::LinearVolumeScale, QAudio::LogarithmicVolumeScale); meter_rect.adjust(0, 0, 0, -qRound(meter_rect.height() * vol)); p.drawRect(meter_rect); if (!peaked_.at(i)) { p.drawRect(peaks_rect); } } if (all_zeroes && !file_.isOpen()) { // Optimize by disabling the update loop SetUpdateLoop(false); } } void AudioMonitor::mousePressEvent(QMouseEvent *) { peaked_.fill(false); update(); } void AudioMonitor::UpdateValuesFromFile(QVector& v) { // Determines how many milliseconds have passed since last update qint64 current_time = QDateTime::currentMSecsSinceEpoch(); qint64 time_passed = current_time - last_time_; int abs_speed = qAbs(playback_speed_); // Multiply by speed if the speed is not 1 if (abs_speed != 1) { time_passed *= abs_speed; } // Convert ms to float seconds and determine how many bytes that is qint64 bytes_to_read = params_.time_to_bytes(static_cast(time_passed) * 0.001); if (playback_speed_ < 0) { // If reversing, jump back by the amount of bytes we're going to read bytes_to_read = qMin(bytes_to_read, file_.pos()); file_.seek(file_.pos() - bytes_to_read); } // Read bytes in from file QByteArray b = file_.read(bytes_to_read); if (playback_speed_ < 0) { // If reversing, head back to where we were before the read so that the next read starts // from where we left off file_.seek(file_.pos() - bytes_to_read); } // If speed is not 1, transform it here if (abs_speed != 1) { int sample_sz = params_.samples_to_bytes(1); int in_nb_samples = params_.bytes_to_samples(b.size()); int out_nb_samples = in_nb_samples / abs_speed; QByteArray speed_adjusted(out_nb_samples * sample_sz, Qt::Uninitialized); for (int i=0;i &v) { values_.removeFirst(); values_.append(v); } void AudioMonitor::BytesToSampleSummary(const QByteArray &b, QVector &v) { const float* samples = reinterpret_cast(b.constData()); int nb_samples = b.size() / sizeof(float); for (int i=0;i v.at(channel)) { v.replace(channel, abs_sample); } } } QVector AudioMonitor::GetAverages() const { QVector v(params_.channel_count(), 0); for (int i=0;i(values_.size()); } return v; } OLIVE_NAMESPACE_EXIT