diff --git a/app/audio/audiomanager.cpp b/app/audio/audiomanager.cpp index 9186b9a83..d70d3a4e0 100644 --- a/app/audio/audiomanager.cpp +++ b/app/audio/audiomanager.cpp @@ -75,6 +75,8 @@ void AudioManager::StartOutput(const QString &filename, qint64 offset, int playb Q_ARG(const QString&, filename), Q_ARG(qint64, offset), Q_ARG(int, playback_speed)); + + emit OutputDeviceStarted(filename, offset, playback_speed); } void AudioManager::StopOutput() @@ -82,6 +84,8 @@ void AudioManager::StopOutput() QMetaObject::invokeMethod(&output_manager_, "ResetToPushMode", Qt::QueuedConnection); + + emit Stopped(); } void AudioManager::SetOutputDevice(const QAudioDeviceInfo &info) @@ -154,6 +158,8 @@ void AudioManager::SetOutputParams(const AudioRenderingParams ¶ms) // Refresh output device SetOutputDevice(output_device_info_); + + emit AudioParamsChanged(output_params_); } } diff --git a/app/audio/audiomanager.h b/app/audio/audiomanager.h index 0d5b1cb03..929374616 100644 --- a/app/audio/audiomanager.h +++ b/app/audio/audiomanager.h @@ -86,6 +86,12 @@ signals: void OutputNotified(); + void OutputDeviceStarted(const QString& filename, qint64 offset, int playback_speed); + + void AudioParamsChanged(const AudioRenderingParams& params); + + void Stopped(); + private: AudioManager(); diff --git a/app/render/audioparams.cpp b/app/render/audioparams.cpp index 43927c63d..0ca930ee5 100644 --- a/app/render/audioparams.cpp +++ b/app/render/audioparams.cpp @@ -70,6 +70,13 @@ AudioRenderingParams::AudioRenderingParams(const AudioParams ¶ms, const Samp { } +int AudioRenderingParams::time_to_bytes(const double &time) const +{ + Q_ASSERT(is_valid()); + + return time_to_samples(time) * channel_count() * bytes_per_sample_per_channel(); +} + const SampleFormat::Format &AudioRenderingParams::format() const { return format_; @@ -90,17 +97,20 @@ bool AudioRenderingParams::operator!=(const AudioRenderingParams &other) const } int AudioRenderingParams::time_to_bytes(const rational &time) const +{ + return time_to_bytes(time.toDouble()); +} + +int AudioRenderingParams::time_to_samples(const double &time) const { Q_ASSERT(is_valid()); - return time_to_samples(time) * channel_count() * bytes_per_sample_per_channel(); + return qFloor(time * sample_rate()); } int AudioRenderingParams::time_to_samples(const rational &time) const { - Q_ASSERT(is_valid()); - - return qFloor(time.toDouble() * sample_rate()); + return time_to_samples(time.toDouble()); } int AudioRenderingParams::samples_to_bytes(const int &samples) const diff --git a/app/render/audioparams.h b/app/render/audioparams.h index 6ced16845..600a6ffa7 100644 --- a/app/render/audioparams.h +++ b/app/render/audioparams.h @@ -51,7 +51,9 @@ public: AudioRenderingParams(const int& sample_rate, const uint64_t& channel_layout, const SampleFormat::Format& format); AudioRenderingParams(const AudioParams& params, const SampleFormat::Format& format); + int time_to_bytes(const double& time) const; int time_to_bytes(const rational& time) const; + int time_to_samples(const double& time) const; int time_to_samples(const rational& time) const; int samples_to_bytes(const int& samples) const; rational samples_to_time(const int& samples) const; diff --git a/app/widget/audiomonitor/audiomonitor.cpp b/app/widget/audiomonitor/audiomonitor.cpp index 7542965a9..a7737d530 100644 --- a/app/widget/audiomonitor/audiomonitor.cpp +++ b/app/widget/audiomonitor/audiomonitor.cpp @@ -31,17 +31,70 @@ OLIVE_NAMESPACE_ENTER const int kDecibelStep = 6; const int kDecibelMinimum = -200; -const int kClearTimerInterval = 500; +const int kMaximumSmoothness = 4; AudioMonitor::AudioMonitor(QWidget *parent) : - QWidget(parent) + QWidget(parent), + cached_channels_(0) { - clear_timer_.setInterval(kClearTimerInterval); - clear_timer_.setSingleShot(true); + update_timer_.setInterval(1000 / 60); // Hardcoded to 60 FPS + connect(&update_timer_, &QTimer::timeout, this, static_cast(&AudioMonitor::update)); + update_timer_.start(); - connect(&clear_timer_, &QTimer::timeout, this, &AudioMonitor::Clear); + values_.resize(kMaximumSmoothness); + + connect(AudioManager::instance(), &AudioManager::OutputDeviceStarted, this, &AudioMonitor::OutputDeviceSet); + connect(AudioManager::instance(), &AudioManager::AudioParamsChanged, this, &AudioMonitor::SetParams); + connect(AudioManager::instance(), &AudioManager::Stopped, this, &AudioMonitor::Stop); } +AudioMonitor::~AudioMonitor() +{ + Stop(); +} + +void AudioMonitor::SetParams(const AudioRenderingParams ¶ms) +{ + params_ = params; + + for (int i=0;i values) { values_ = values; @@ -56,18 +109,11 @@ void AudioMonitor::SetValues(QVector values) update(); } - -void AudioMonitor::Clear() -{ - values_.fill(0); - update(); -} +*/ void AudioMonitor::paintEvent(QPaintEvent *) { - int channels = values_.size(); - - if (channels == 0) { + if (!params_.channel_count()) { return; } @@ -75,75 +121,119 @@ void AudioMonitor::paintEvent(QPaintEvent *) QFontMetrics fm = p.fontMetrics(); int peaks_y = 0; - int peaks_height = fm.height(); + 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, peaks_height, 0, 0); + 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(), peaks_height, 0, 0); + full_meter_rect.adjust(db_labels_rect.width(), font_height, 0, 0); - // Draw decibel markings - QRect last_db_marking_rect; + // Width of each channel in the meter + int channel_width = full_meter_rect.width() / params_.channel_count(); - for (int i=0;i>=kDecibelMinimum;i-=kDecibelStep) { - QString db_label; + if (cached_background_.size() != size() + || cached_channels_ != params_.channel_count()) { - if (i <= kDecibelMinimum) { - db_label = "-∞"; - } else { - db_label = QStringLiteral("%1").arg(i); + 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; + } + } } - qreal log_val = QAudio::convertVolume(i, QAudio::DecibelVolumeScale, QAudio::LogarithmicVolumeScale); + { + // 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) + }); - 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()); + cached_painter.setPen(Qt::black); - // Prevent any dB markings overlapping - if (i == 0 || !db_marking_rect.intersects(last_db_marking_rect)) { - p.drawText(db_marking_rect, Qt::AlignRight, db_label); - p.drawLine(db_marking_rect.topLeft(), db_marking_rect.topRight()); + for (int i=0;i v(params_.channel_count(), 0); - for (int i=0;i vals = GetAverages(); + + p.setBrush(QColor(0, 0, 0, 128)); + p.setPen(Qt::NoPen); + + for (int i=0;i 1.0) { peaked_[i] = true; } @@ -151,13 +241,12 @@ void AudioMonitor::paintEvent(QPaintEvent *) // Convert val to logarithmic scale vol = QAudio::convertVolume(vol, QAudio::LinearVolumeScale, QAudio::LogarithmicVolumeScale); - p.setBrush(QColor(0, 0, 0, 128)); - meter_rect.adjust(0, 0, 0, -qRound(meter_rect.height() * vol)); p.drawRect(meter_rect); - if (!peaked_.at(i)) + if (!peaked_.at(i)) { p.drawRect(peaks_rect); + } } } @@ -167,4 +256,54 @@ void AudioMonitor::mousePressEvent(QMouseEvent *) 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_; + + // Determine how many bytes this is + int bytes_to_read = params_.time_to_bytes(static_cast(time_passed) * 0.001); + + QByteArray b = file_.read(bytes_to_read); + + 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); + } + } + + last_time_ = current_time; +} + +void AudioMonitor::PushValue(const QVector &v) +{ + values_.removeFirst(); + values_.append(v); +} + +QVector AudioMonitor::GetAverages() const +{ + QVector v(params_.channel_count(), 0); + + for (int i=0;i(values_.size()); + } + + return v; +} + OLIVE_NAMESPACE_EXIT diff --git a/app/widget/audiomonitor/audiomonitor.h b/app/widget/audiomonitor/audiomonitor.h index 226e8cdad..96d15c634 100644 --- a/app/widget/audiomonitor/audiomonitor.h +++ b/app/widget/audiomonitor/audiomonitor.h @@ -21,10 +21,12 @@ #ifndef AUDIOMONITORWIDGET_H #define AUDIOMONITORWIDGET_H +#include #include #include #include "common/define.h" +#include "render/audioparams.h" OLIVE_NAMESPACE_ENTER @@ -34,19 +36,41 @@ class AudioMonitor : public QWidget public: AudioMonitor(QWidget* parent = nullptr); + virtual ~AudioMonitor() override; + public slots: - void SetValues(QVector values); - void Clear(); + void SetParams(const AudioRenderingParams& params); + + void OutputDeviceSet(const QString& filename, qint64 offset, int playback_speed); + + void Stop(); protected: virtual void paintEvent(QPaintEvent* event) override; virtual void mousePressEvent(QMouseEvent* event) override; private: - QVector values_; + void UpdateValuesFromFile(QVector &v); + + void PushValue(const QVector& v); + + QVector GetAverages() const; + + AudioRenderingParams params_; + + QFile file_; + qint64 last_time_; + + int playback_speed_; + + QVector< QVector > values_; QVector peaked_; - QTimer clear_timer_; + QPixmap cached_background_; + int cached_channels_; + + QTimer update_timer_; + }; OLIVE_NAMESPACE_EXIT