implemented new audio monitor widget
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -94,6 +94,8 @@ public:
|
||||
signals:
|
||||
void DeviceListReady();
|
||||
|
||||
void SentSamples(QVector<double> averages);
|
||||
|
||||
private:
|
||||
AudioManager();
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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"));
|
||||
}
|
||||
@@ -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
|
||||
@@ -49,7 +49,7 @@ void NodePanel::changeEvent(QEvent *e)
|
||||
if (e->type() == QEvent::LanguageChange) {
|
||||
Retranslate();
|
||||
}
|
||||
QDockWidget::changeEvent(e);
|
||||
PanelWidget::changeEvent(e);
|
||||
}
|
||||
|
||||
void NodePanel::Retranslate()
|
||||
|
||||
@@ -45,7 +45,7 @@ void ParamPanel::changeEvent(QEvent *e)
|
||||
if (e->type() == QEvent::LanguageChange) {
|
||||
Retranslate();
|
||||
}
|
||||
QDockWidget::changeEvent(e);
|
||||
PanelWidget::changeEvent(e);
|
||||
}
|
||||
|
||||
void ParamPanel::Retranslate()
|
||||
|
||||
@@ -102,7 +102,7 @@ void ProjectPanel::changeEvent(QEvent *e)
|
||||
if (e->type() == QEvent::LanguageChange) {
|
||||
Retranslate();
|
||||
}
|
||||
QDockWidget::changeEvent(e);
|
||||
PanelWidget::changeEvent(e);
|
||||
}
|
||||
|
||||
void ProjectPanel::Retranslate()
|
||||
|
||||
@@ -46,7 +46,7 @@ void TaskManagerPanel::changeEvent(QEvent *e)
|
||||
if (e->type() == QEvent::LanguageChange) {
|
||||
Retranslate();
|
||||
}
|
||||
QDockWidget::changeEvent(e);
|
||||
PanelWidget::changeEvent(e);
|
||||
}
|
||||
|
||||
void TaskManagerPanel::Retranslate()
|
||||
|
||||
@@ -114,7 +114,7 @@ void TimelinePanel::changeEvent(QEvent *e)
|
||||
if (e->type() == QEvent::LanguageChange) {
|
||||
Retranslate();
|
||||
}
|
||||
QDockWidget::changeEvent(e);
|
||||
PanelWidget::changeEvent(e);
|
||||
}
|
||||
|
||||
void TimelinePanel::Retranslate()
|
||||
|
||||
@@ -50,7 +50,7 @@ void ToolPanel::changeEvent(QEvent *e)
|
||||
if (e->type() == QEvent::LanguageChange) {
|
||||
Retranslate();
|
||||
}
|
||||
QDockWidget::changeEvent(e);
|
||||
PanelWidget::changeEvent(e);
|
||||
}
|
||||
|
||||
void ToolPanel::Retranslate()
|
||||
|
||||
@@ -122,7 +122,7 @@ void ViewerPanel::changeEvent(QEvent *e)
|
||||
if (e->type() == QEvent::LanguageChange) {
|
||||
Retranslate();
|
||||
}
|
||||
QDockWidget::changeEvent(e);
|
||||
PanelWidget::changeEvent(e);
|
||||
}
|
||||
|
||||
void ViewerPanel::Retranslate()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user