GUI M8e
This commit is contained in:
parent
a07f94c93d
commit
ecec82c70e
10 changed files with 809 additions and 21 deletions
90
gui/AudioLevelMeter.cpp
Normal file
90
gui/AudioLevelMeter.cpp
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
#include "AudioLevelMeter.h"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
AudioLevelMeter::AudioLevelMeter(QWidget *parent) : QWidget(parent) {
|
||||
setAutoFillBackground(false);
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
}
|
||||
|
||||
void AudioLevelMeter::setLevel(float level) {
|
||||
m_level = std::clamp(level, 0.0f, 1.0f);
|
||||
|
||||
if (m_level >= m_peakHold) {
|
||||
m_peakHold = m_level;
|
||||
m_peakHoldFrames = 0;
|
||||
} else {
|
||||
++m_peakHoldFrames;
|
||||
if (m_peakHoldFrames > kPeakHoldDuration) {
|
||||
m_peakHold = std::max(0.0f, m_peakHold - kPeakDecayRate);
|
||||
}
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
float AudioLevelMeter::level() const { return m_level; }
|
||||
|
||||
float AudioLevelMeter::peakHold() const { return m_peakHold; }
|
||||
|
||||
void AudioLevelMeter::resetPeakHold() {
|
||||
m_peakHold = 0.0f;
|
||||
m_peakHoldFrames = 0;
|
||||
update();
|
||||
}
|
||||
|
||||
QSize AudioLevelMeter::sizeHint() const { return {40, 160}; }
|
||||
|
||||
QSize AudioLevelMeter::minimumSizeHint() const { return {12, 40}; }
|
||||
|
||||
void AudioLevelMeter::paintEvent(QPaintEvent *) {
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing, false);
|
||||
|
||||
QRect r = rect();
|
||||
painter.fillRect(r, QColor(24, 24, 28));
|
||||
|
||||
if (m_level <= 0.0f && m_peakHold <= 0.0f)
|
||||
return;
|
||||
|
||||
int barHeight = static_cast<int>(m_level * r.height());
|
||||
int barTop = r.height() - barHeight;
|
||||
|
||||
if (barHeight > 0) {
|
||||
float greenEnd = 0.7f * r.height();
|
||||
float yellowEnd = 0.9f * r.height();
|
||||
|
||||
int greenH = std::min(barHeight, static_cast<int>(greenEnd));
|
||||
if (greenH > 0) {
|
||||
painter.fillRect(r.left(), r.bottom() - greenH + 1, r.width(), greenH,
|
||||
QColor(76, 175, 80));
|
||||
}
|
||||
|
||||
if (barHeight > static_cast<int>(greenEnd)) {
|
||||
int yellowH =
|
||||
std::min(barHeight - static_cast<int>(greenEnd),
|
||||
static_cast<int>(yellowEnd) - static_cast<int>(greenEnd));
|
||||
if (yellowH > 0) {
|
||||
painter.fillRect(r.left(), r.bottom() - static_cast<int>(greenEnd) - yellowH + 1,
|
||||
r.width(), yellowH, QColor(255, 193, 7));
|
||||
}
|
||||
}
|
||||
|
||||
if (barHeight > static_cast<int>(yellowEnd)) {
|
||||
int redH = barHeight - static_cast<int>(yellowEnd);
|
||||
if (redH > 0) {
|
||||
painter.fillRect(r.left(), barTop, r.width(), redH,
|
||||
QColor(244, 67, 54));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_peakHold > 0.0f) {
|
||||
int peakY = r.height() - static_cast<int>(m_peakHold * r.height());
|
||||
peakY = std::clamp(peakY, r.top(), r.bottom());
|
||||
painter.setPen(QColor(255, 255, 255));
|
||||
painter.drawLine(r.left(), peakY, r.right(), peakY);
|
||||
}
|
||||
}
|
||||
28
gui/AudioLevelMeter.h
Normal file
28
gui/AudioLevelMeter.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class AudioLevelMeter : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AudioLevelMeter(QWidget *parent = nullptr);
|
||||
|
||||
void setLevel(float level);
|
||||
float level() const;
|
||||
float peakHold() const;
|
||||
void resetPeakHold();
|
||||
|
||||
QSize sizeHint() const override;
|
||||
QSize minimumSizeHint() const override;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
private:
|
||||
float m_level = 0.0f;
|
||||
float m_peakHold = 0.0f;
|
||||
int m_peakHoldFrames = 0;
|
||||
|
||||
static constexpr int kPeakHoldDuration = 6;
|
||||
static constexpr float kPeakDecayRate = 0.02f;
|
||||
};
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
#include "AudioLevelMeter.h"
|
||||
#include "GraphEditorWidget.h"
|
||||
#include "PresetManager.h"
|
||||
#include "VolumeWidgets.h"
|
||||
|
|
@ -217,7 +218,55 @@ GraphEditorWidget::GraphEditorWidget(warppipe::Client *client,
|
|||
presetsLayout->addWidget(loadPresetBtn);
|
||||
presetsLayout->addStretch();
|
||||
|
||||
m_sidebar->addTab(presetsTab, QStringLiteral("PRESETS"));
|
||||
auto *metersTab = new QWidget();
|
||||
auto *metersLayout = new QVBoxLayout(metersTab);
|
||||
metersLayout->setContentsMargins(8, 8, 8, 8);
|
||||
metersLayout->setSpacing(8);
|
||||
|
||||
auto *masterLabel = new QLabel(QStringLiteral("MASTER OUTPUT"));
|
||||
masterLabel->setStyleSheet(QStringLiteral(
|
||||
"QLabel { color: #a0a8b6; font-size: 11px; font-weight: bold;"
|
||||
" background: transparent; }"));
|
||||
metersLayout->addWidget(masterLabel);
|
||||
|
||||
auto *masterRow = new QWidget();
|
||||
auto *masterRowLayout = new QHBoxLayout(masterRow);
|
||||
masterRowLayout->setContentsMargins(0, 0, 0, 0);
|
||||
masterRowLayout->setSpacing(4);
|
||||
m_masterMeterL = new AudioLevelMeter();
|
||||
m_masterMeterL->setFixedWidth(18);
|
||||
m_masterMeterL->setMinimumHeight(100);
|
||||
m_masterMeterR = new AudioLevelMeter();
|
||||
m_masterMeterR->setFixedWidth(18);
|
||||
m_masterMeterR->setMinimumHeight(100);
|
||||
masterRowLayout->addStretch();
|
||||
masterRowLayout->addWidget(m_masterMeterL);
|
||||
masterRowLayout->addWidget(m_masterMeterR);
|
||||
masterRowLayout->addStretch();
|
||||
metersLayout->addWidget(masterRow);
|
||||
|
||||
auto *nodeMetersLabel = new QLabel(QStringLiteral("NODE METERS"));
|
||||
nodeMetersLabel->setStyleSheet(masterLabel->styleSheet());
|
||||
metersLayout->addWidget(nodeMetersLabel);
|
||||
|
||||
m_nodeMeterScroll = new QScrollArea();
|
||||
m_nodeMeterScroll->setWidgetResizable(true);
|
||||
m_nodeMeterScroll->setStyleSheet(QStringLiteral(
|
||||
"QScrollArea { background: transparent; border: none; }"
|
||||
"QScrollBar:vertical { background: #1a1a1e; width: 8px; }"
|
||||
"QScrollBar::handle:vertical { background: #3a3a44; border-radius: 4px; }"
|
||||
"QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0; }"));
|
||||
m_nodeMeterContainer = new QWidget();
|
||||
m_nodeMeterContainer->setStyleSheet(QStringLiteral("background: transparent;"));
|
||||
auto *nodeMeterLayout = new QVBoxLayout(m_nodeMeterContainer);
|
||||
nodeMeterLayout->setContentsMargins(0, 0, 0, 0);
|
||||
nodeMeterLayout->setSpacing(2);
|
||||
nodeMeterLayout->addStretch();
|
||||
m_nodeMeterScroll->setWidget(m_nodeMeterContainer);
|
||||
metersLayout->addWidget(m_nodeMeterScroll, 1);
|
||||
|
||||
metersTab->setStyleSheet(QStringLiteral("background: #1a1a1e;"));
|
||||
m_sidebar->addTab(metersTab, QStringLiteral("METERS"));
|
||||
|
||||
m_mixerScroll = new QScrollArea();
|
||||
m_mixerScroll->setWidgetResizable(true);
|
||||
|
|
@ -234,6 +283,7 @@ GraphEditorWidget::GraphEditorWidget(warppipe::Client *client,
|
|||
mixerLayout->addStretch();
|
||||
m_mixerScroll->setWidget(m_mixerContainer);
|
||||
m_sidebar->addTab(m_mixerScroll, QStringLiteral("MIXER"));
|
||||
m_sidebar->addTab(presetsTab, QStringLiteral("PRESETS"));
|
||||
|
||||
m_splitter = new QSplitter(Qt::Horizontal);
|
||||
m_splitter->addWidget(m_view);
|
||||
|
|
@ -354,11 +404,14 @@ GraphEditorWidget::GraphEditorWidget(warppipe::Client *client,
|
|||
[this](QtNodes::NodeId nodeId) {
|
||||
wireVolumeWidget(nodeId);
|
||||
rebuildMixerStrips();
|
||||
rebuildNodeMeters();
|
||||
});
|
||||
connect(m_model, &QtNodes::AbstractGraphModel::nodeDeleted, this,
|
||||
[this](QtNodes::NodeId nodeId) {
|
||||
m_mixerStrips.erase(nodeId);
|
||||
m_nodeMeters.erase(nodeId);
|
||||
rebuildMixerStrips();
|
||||
rebuildNodeMeters();
|
||||
});
|
||||
|
||||
m_saveTimer = new QTimer(this);
|
||||
|
|
@ -386,6 +439,12 @@ GraphEditorWidget::GraphEditorWidget(warppipe::Client *client,
|
|||
connect(m_refreshTimer, &QTimer::timeout, this,
|
||||
&GraphEditorWidget::onRefreshTimer);
|
||||
m_refreshTimer->start(500);
|
||||
|
||||
m_meterTimer = new QTimer(this);
|
||||
m_meterTimer->setTimerType(Qt::PreciseTimer);
|
||||
connect(m_meterTimer, &QTimer::timeout, this,
|
||||
&GraphEditorWidget::updateMeters);
|
||||
m_meterTimer->start(33);
|
||||
}
|
||||
|
||||
void GraphEditorWidget::onRefreshTimer() {
|
||||
|
|
@ -1212,3 +1271,105 @@ void GraphEditorWidget::rebuildMixerStrips() {
|
|||
|
||||
static_cast<QVBoxLayout *>(layout)->addStretch();
|
||||
}
|
||||
|
||||
void GraphEditorWidget::updateMeters() {
|
||||
if (!m_client)
|
||||
return;
|
||||
|
||||
auto master = m_client->MeterPeak();
|
||||
if (master.ok()) {
|
||||
m_masterMeterL->setLevel(master.value.peak_left);
|
||||
m_masterMeterR->setLevel(master.value.peak_right);
|
||||
}
|
||||
|
||||
for (auto &[nodeId, row] : m_nodeMeters) {
|
||||
const WarpNodeData *data = m_model->warpNodeData(nodeId);
|
||||
if (!data || !row.meter)
|
||||
continue;
|
||||
auto peak = m_client->NodeMeterPeak(data->info.id);
|
||||
if (peak.ok()) {
|
||||
row.meter->setLevel(
|
||||
std::max(peak.value.peak_left, peak.value.peak_right));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GraphEditorWidget::rebuildNodeMeters() {
|
||||
if (!m_nodeMeterContainer || !m_client)
|
||||
return;
|
||||
|
||||
auto *layout = m_nodeMeterContainer->layout();
|
||||
if (!layout)
|
||||
return;
|
||||
|
||||
std::unordered_map<uint32_t, bool> old_pw_ids;
|
||||
for (const auto &[nid, row] : m_nodeMeters) {
|
||||
const WarpNodeData *d = m_model->warpNodeData(nid);
|
||||
if (d)
|
||||
old_pw_ids[d->info.id.value] = true;
|
||||
}
|
||||
|
||||
while (layout->count() > 0) {
|
||||
auto *item = layout->takeAt(0);
|
||||
if (item->widget())
|
||||
item->widget()->deleteLater();
|
||||
delete item;
|
||||
}
|
||||
m_nodeMeters.clear();
|
||||
|
||||
auto nodeIds = m_model->allNodeIds();
|
||||
std::vector<QtNodes::NodeId> sorted(nodeIds.begin(), nodeIds.end());
|
||||
std::sort(sorted.begin(), sorted.end());
|
||||
|
||||
std::unordered_map<uint32_t, bool> new_pw_ids;
|
||||
for (auto nodeId : sorted) {
|
||||
const WarpNodeData *data = m_model->warpNodeData(nodeId);
|
||||
if (!data)
|
||||
continue;
|
||||
|
||||
new_pw_ids[data->info.id.value] = true;
|
||||
m_client->EnsureNodeMeter(data->info.id);
|
||||
|
||||
auto *row = new QWidget();
|
||||
auto *rowLayout = new QHBoxLayout(row);
|
||||
rowLayout->setContentsMargins(0, 0, 0, 0);
|
||||
rowLayout->setSpacing(6);
|
||||
|
||||
auto *label = new QLabel(
|
||||
WarpGraphModel::classifyNode(data->info) == WarpNodeType::kApplication
|
||||
? QString::fromStdString(
|
||||
data->info.application_name.empty()
|
||||
? data->info.name
|
||||
: data->info.application_name)
|
||||
: QString::fromStdString(
|
||||
data->info.description.empty()
|
||||
? data->info.name
|
||||
: data->info.description));
|
||||
label->setStyleSheet(QStringLiteral(
|
||||
"QLabel { color: #a0a8b6; font-size: 11px; background: transparent; }"));
|
||||
label->setToolTip(QString::fromStdString(data->info.name));
|
||||
|
||||
auto *meter = new AudioLevelMeter();
|
||||
meter->setFixedWidth(26);
|
||||
meter->setMinimumHeight(70);
|
||||
|
||||
rowLayout->addWidget(label, 1);
|
||||
rowLayout->addWidget(meter);
|
||||
|
||||
layout->addWidget(row);
|
||||
|
||||
NodeMeterRow meterRow;
|
||||
meterRow.widget = row;
|
||||
meterRow.meter = meter;
|
||||
meterRow.label = label;
|
||||
m_nodeMeters[nodeId] = meterRow;
|
||||
}
|
||||
|
||||
static_cast<QVBoxLayout *>(layout)->addStretch();
|
||||
|
||||
for (const auto &[pw_id, _] : old_pw_ids) {
|
||||
if (new_pw_ids.find(pw_id) == new_pw_ids.end()) {
|
||||
m_client->DisableNodeMeter(warppipe::NodeId{pw_id});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ class BasicGraphicsScene;
|
|||
class GraphicsView;
|
||||
} // namespace QtNodes
|
||||
|
||||
class AudioLevelMeter;
|
||||
class WarpGraphModel;
|
||||
class NodeVolumeWidget;
|
||||
class QLabel;
|
||||
|
|
@ -69,6 +70,8 @@ private:
|
|||
void loadPreset();
|
||||
void wireVolumeWidget(QtNodes::NodeId nodeId);
|
||||
void rebuildMixerStrips();
|
||||
void updateMeters();
|
||||
void rebuildNodeMeters();
|
||||
|
||||
struct PendingPasteLink {
|
||||
std::string outNodeName;
|
||||
|
|
@ -95,4 +98,16 @@ private:
|
|||
QWidget *m_mixerContainer = nullptr;
|
||||
QScrollArea *m_mixerScroll = nullptr;
|
||||
std::unordered_map<QtNodes::NodeId, QWidget *> m_mixerStrips;
|
||||
|
||||
QTimer *m_meterTimer = nullptr;
|
||||
AudioLevelMeter *m_masterMeterL = nullptr;
|
||||
AudioLevelMeter *m_masterMeterR = nullptr;
|
||||
QWidget *m_nodeMeterContainer = nullptr;
|
||||
QScrollArea *m_nodeMeterScroll = nullptr;
|
||||
struct NodeMeterRow {
|
||||
QWidget *widget = nullptr;
|
||||
AudioLevelMeter *meter = nullptr;
|
||||
QLabel *label = nullptr;
|
||||
};
|
||||
std::unordered_map<QtNodes::NodeId, NodeMeterRow> m_nodeMeters;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue