implemented new audio monitor widget

This commit is contained in:
itsmattkc
2019-10-23 03:21:05 +11:00
parent c38b4dca99
commit 52f4c59bec
20 changed files with 348 additions and 18 deletions
+42 -11
View File
@@ -26,7 +26,8 @@
AudioHybridDevice::AudioHybridDevice(QObject *parent) :
QIODevice(parent),
device_(nullptr),
sample_index_(0)
sample_index_(0),
enable_sending_samples_(false)
{
}
@@ -67,7 +68,47 @@ bool AudioHybridDevice::IsIdle()
return device_ == nullptr && pushed_samples_.isEmpty();
}
void AudioHybridDevice::SetEnableSendingSamples(bool e)
{
enable_sending_samples_ = e;
}
qint64 AudioHybridDevice::readData(char *data, qint64 maxSize)
{
qint64 read_size = read_internal(data, maxSize);
if (enable_sending_samples_ && read_size > 0) {
// FIXME: Assumes float and stereo
float* samples = reinterpret_cast<float*>(data);
int sample_count = static_cast<int>(read_size / static_cast<int>(sizeof(float)));
int channels = 2;
// Create array of samples to send
QVector<double> averages(channels);
averages.fill(0);
// Add all samples together
for (int i=0;i<sample_count;i++) {
averages[i%channels] = qMax(averages[i%channels], static_cast<double>(qAbs(samples[i])));
}
emit SentSamples(averages);
}
return read_size;
}
qint64 AudioHybridDevice::writeData(const char *data, qint64 maxSize)
{
Q_UNUSED(data)
Q_UNUSED(maxSize)
// This device doesn't support writing
return -1;
}
qint64 AudioHybridDevice::read_internal(char *data, qint64 maxSize)
{
// If a device is connected, passthrough to it
if (device_ != nullptr) {
@@ -103,13 +144,3 @@ qint64 AudioHybridDevice::readData(char *data, qint64 maxSize)
memset(data, 0, static_cast<size_t>(maxSize));
return maxSize;
}
qint64 AudioHybridDevice::writeData(const char *data, qint64 maxSize)
{
Q_UNUSED(data)
Q_UNUSED(maxSize)
// This device doesn't support writing
return -1;
}
+19
View File
@@ -81,12 +81,27 @@ public:
*/
bool IsIdle();
/**
* @brief If enabled, this will emit the SentSamples() signal whenever samples are sent to the output device
*/
void SetEnableSendingSamples(bool e);
signals:
/**
* @brief Signal emitted when this leaves "idle" state and has valid audio data that is ready to be sent
*/
void HasSamples();
/**
* @brief Signal emitted when samples are sent to the output device
*
* This sends an array of values corresponding to the channel count. Each value is an average of all the samples just
* sent to that channel. Useful for connecting to AudioMonitor.
*
* Can be disabled with SetEnableSendingSamples() to save CPU usage.
*/
void SentSamples(QVector<double> averages);
protected:
/**
* @brief Internal QIODevice function for reading
@@ -115,11 +130,15 @@ protected:
virtual qint64 writeData(const char *data, qint64 maxSize) override;
private:
qint64 read_internal(char *data, qint64 maxSize);
QIODevice* device_;
QByteArray pushed_samples_;
qint64 sample_index_;
bool enable_sending_samples_;
};
#endif // AUDIOHYBRIDDEVICE_H
+2
View File
@@ -133,7 +133,9 @@ AudioManager::AudioManager() :
RefreshDevices();
connect(&output_manager_, SIGNAL(HasSamples()), this, SLOT(OutputManagerHasSamples()));
connect(&output_manager_, SIGNAL(SentSamples(QVector<double>)), this, SIGNAL(SentSamples(QVector<double>)));
output_manager_.SetEnableSendingSamples(true);
output_manager_.open(AudioHybridDevice::ReadOnly);
}
+2
View File
@@ -94,6 +94,8 @@ public:
signals:
void DeviceListReady();
void SentSamples(QVector<double> averages);
private:
AudioManager();
+1
View File
@@ -14,6 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
add_subdirectory(audiomonitor)
add_subdirectory(node)
add_subdirectory(param)
add_subdirectory(project)
+22
View File
@@ -0,0 +1,22 @@
# 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 <http://www.gnu.org/licenses/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
panel/audiomonitor/audiomonitor.h
panel/audiomonitor/audiomonitor.cpp
PARENT_SCOPE
)
+27
View File
@@ -0,0 +1,27 @@
#include "audiomonitor.h"
AudioMonitorPanel::AudioMonitorPanel(QWidget *parent) :
PanelWidget(parent)
{
// FIXME: This won't work if there's ever more than one of this panel
setObjectName("AudioMonitor");
audio_monitor_ = new AudioMonitor(this);
setWidget(audio_monitor_);
Retranslate();
}
void AudioMonitorPanel::changeEvent(QEvent *e)
{
if (e->type() == QEvent::LanguageChange) {
Retranslate();
}
PanelWidget::changeEvent(e);
}
void AudioMonitorPanel::Retranslate()
{
SetTitle(tr("Audio Monitor"));
}
+24
View File
@@ -0,0 +1,24 @@
#ifndef AUDIOMONITORPANEL_H
#define AUDIOMONITORPANEL_H
#include "widget/audiomonitor/audiomonitor.h"
#include "widget/panel/panel.h"
/**
* @brief PanelWidget wrapper around an AudioMonitor
*/
class AudioMonitorPanel : public PanelWidget
{
public:
AudioMonitorPanel(QWidget* parent = nullptr);
protected:
virtual void changeEvent(QEvent* e) override;
private:
void Retranslate();
AudioMonitor* audio_monitor_;
};
#endif // AUDIOMONITORPANEL_H
+1 -1
View File
@@ -49,7 +49,7 @@ void NodePanel::changeEvent(QEvent *e)
if (e->type() == QEvent::LanguageChange) {
Retranslate();
}
QDockWidget::changeEvent(e);
PanelWidget::changeEvent(e);
}
void NodePanel::Retranslate()
+1 -1
View File
@@ -45,7 +45,7 @@ void ParamPanel::changeEvent(QEvent *e)
if (e->type() == QEvent::LanguageChange) {
Retranslate();
}
QDockWidget::changeEvent(e);
PanelWidget::changeEvent(e);
}
void ParamPanel::Retranslate()
+1 -1
View File
@@ -102,7 +102,7 @@ void ProjectPanel::changeEvent(QEvent *e)
if (e->type() == QEvent::LanguageChange) {
Retranslate();
}
QDockWidget::changeEvent(e);
PanelWidget::changeEvent(e);
}
void ProjectPanel::Retranslate()
+1 -1
View File
@@ -46,7 +46,7 @@ void TaskManagerPanel::changeEvent(QEvent *e)
if (e->type() == QEvent::LanguageChange) {
Retranslate();
}
QDockWidget::changeEvent(e);
PanelWidget::changeEvent(e);
}
void TaskManagerPanel::Retranslate()
+1 -1
View File
@@ -114,7 +114,7 @@ void TimelinePanel::changeEvent(QEvent *e)
if (e->type() == QEvent::LanguageChange) {
Retranslate();
}
QDockWidget::changeEvent(e);
PanelWidget::changeEvent(e);
}
void TimelinePanel::Retranslate()
+1 -1
View File
@@ -50,7 +50,7 @@ void ToolPanel::changeEvent(QEvent *e)
if (e->type() == QEvent::LanguageChange) {
Retranslate();
}
QDockWidget::changeEvent(e);
PanelWidget::changeEvent(e);
}
void ToolPanel::Retranslate()
+1 -1
View File
@@ -122,7 +122,7 @@ void ViewerPanel::changeEvent(QEvent *e)
if (e->type() == QEvent::LanguageChange) {
Retranslate();
}
QDockWidget::changeEvent(e);
PanelWidget::changeEvent(e);
}
void ViewerPanel::Retranslate()
+1
View File
@@ -14,6 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
add_subdirectory(audiomonitor)
add_subdirectory(columnedgridlayout)
add_subdirectory(flowlayout)
add_subdirectory(footagecombobox)
+22
View File
@@ -0,0 +1,22 @@
# 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 <http://www.gnu.org/licenses/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
widget/audiomonitor/audiomonitor.h
widget/audiomonitor/audiomonitor.cpp
PARENT_SCOPE
)
+147
View File
@@ -0,0 +1,147 @@
#include "audiomonitor.h"
#include <QAudio>
#include <QDebug>
#include <QPainter>
#include "audio/audiomanager.h"
#include "common/qtversionabstraction.h"
const int kDecibelStep = 6;
const int kDecibelMinimum = -200;
const int kClearTimerInterval = 500;
AudioMonitor::AudioMonitor(QWidget *parent) :
QWidget(parent)
{
clear_timer_.setInterval(kClearTimerInterval);
connect(AudioManager::instance(), SIGNAL(SentSamples(QVector<double>)), this, SLOT(SetValues(QVector<double>)));
connect(&clear_timer_, SIGNAL(timeout()), this, SLOT(Clear()));
connect(&clear_timer_, SIGNAL(timeout()), &clear_timer_, SLOT(stop()));
}
void AudioMonitor::SetValues(QVector<double> values)
{
values_ = values;
if (values_.size() != peaked_.size()) {
peaked_.resize(values_.size());
peaked_.fill(false);
}
clear_timer_.stop();
clear_timer_.start();
update();
}
void AudioMonitor::Clear()
{
values_.fill(0);
update();
}
void AudioMonitor::paintEvent(QPaintEvent *)
{
int channels = values_.size();
if (channels == 0) {
return;
}
QPainter p(this);
QFontMetrics fm = p.fontMetrics();
int peaks_y = 0;
int peaks_height = fm.height();
QRect db_labels_rect = rect();
db_labels_rect.setWidth(QFontMetricsWidth(p.fontMetrics(), "-00"));
db_labels_rect.adjust(0, peaks_height, 0, 0);
QRect full_meter_rect = rect();
full_meter_rect.adjust(db_labels_rect.width(), peaks_height, 0, 0);
// Draw decibel markings
QRect last_db_marking_rect;
for (int i=0;i>=kDecibelMinimum;i-=kDecibelStep) {
QString db_label;
if (i <= kDecibelMinimum) {
db_label = "-∞";
} else {
db_label = QString("%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.y() + 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)) {
p.drawText(db_marking_rect, Qt::AlignRight, db_label);
p.drawLine(db_marking_rect.topLeft(), db_marking_rect.topRight());
last_db_marking_rect = db_marking_rect;
}
}
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)
});
int channel_width = full_meter_rect.width() / channels;
for (int i=0;i<channels;i++) {
int channel_x = full_meter_rect.x() + channel_width * i;
QRect peaks_rect(channel_x, peaks_y, channel_width, peaks_height);
QRect meter_rect = full_meter_rect;
meter_rect.setX(channel_x);
meter_rect.setWidth(channel_width);
p.setPen(Qt::black);
// Draw peak rects
p.setBrush(Qt::red);
p.drawRect(peaks_rect);
// Draw gradient meter
p.setBrush(g);
p.drawRect(meter_rect);
// Draw inverted semi-transparent black overlay depending on information
p.setPen(Qt::NoPen);
// Validate value and whether it peaked
double vol = values_.at(i);
if (vol > 1.0) {
peaked_[i] = true;
}
// 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))
p.drawRect(peaks_rect);
}
}
void AudioMonitor::mousePressEvent(QMouseEvent *)
{
peaked_.fill(false);
update();
}
+28
View File
@@ -0,0 +1,28 @@
#ifndef AUDIOMONITORWIDGET_H
#define AUDIOMONITORWIDGET_H
#include <QTimer>
#include <QWidget>
class AudioMonitor : public QWidget
{
Q_OBJECT
public:
AudioMonitor(QWidget* parent = nullptr);
public slots:
void SetValues(QVector<double> values);
void Clear();
protected:
virtual void paintEvent(QPaintEvent* event) override;
virtual void mousePressEvent(QMouseEvent* event) override;
private:
QVector<double> values_;
QVector<bool> peaked_;
QTimer clear_timer_;
};
#endif // AUDIOMONITORWIDGET_H
+4
View File
@@ -26,6 +26,7 @@
// Panel objects
#include "panel/panelmanager.h"
#include "panel/audiomonitor/audiomonitor.h"
#include "panel/node/node.h"
#include "panel/param/param.h"
#include "panel/project/project.h"
@@ -128,6 +129,9 @@ void olive::MainWindow::ProjectOpen(Project* p)
TimelinePanel* timeline_panel = olive::panel_manager->CreatePanel<TimelinePanel>(this);
addDockWidget(Qt::BottomDockWidgetArea, timeline_panel);
AudioMonitorPanel* audio_monitor_panel = olive::panel_manager->CreatePanel<AudioMonitorPanel>(this);
addDockWidget(Qt::BottomDockWidgetArea, audio_monitor_panel);
TaskManagerPanel* task_man_panel = olive::panel_manager->CreatePanel<TaskManagerPanel>(this);
addDockWidget(Qt::BottomDockWidgetArea, task_man_panel);
task_man_panel->setFloating(true);