Proper audio through connection
This commit is contained in:
parent
2e0cb27987
commit
f4f5a69531
9 changed files with 199 additions and 5 deletions
|
|
@ -96,6 +96,7 @@ if(WARPPIPE_BUILD_GUI)
|
||||||
gui/VolumeWidgets.cpp
|
gui/VolumeWidgets.cpp
|
||||||
gui/AudioLevelMeter.cpp
|
gui/AudioLevelMeter.cpp
|
||||||
gui/SquareConnectionPainter.cpp
|
gui/SquareConnectionPainter.cpp
|
||||||
|
gui/WarpBezierConnectionPainter.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(warppipe-gui PRIVATE
|
target_link_libraries(warppipe-gui PRIVATE
|
||||||
|
|
@ -114,6 +115,7 @@ if(WARPPIPE_BUILD_GUI)
|
||||||
gui/VolumeWidgets.cpp
|
gui/VolumeWidgets.cpp
|
||||||
gui/AudioLevelMeter.cpp
|
gui/AudioLevelMeter.cpp
|
||||||
gui/SquareConnectionPainter.cpp
|
gui/SquareConnectionPainter.cpp
|
||||||
|
gui/WarpBezierConnectionPainter.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_compile_definitions(warppipe-gui-tests PRIVATE WARPPIPE_TESTING)
|
target_compile_definitions(warppipe-gui-tests PRIVATE WARPPIPE_TESTING)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
#include <QtNodes/BasicGraphicsScene>
|
#include <QtNodes/BasicGraphicsScene>
|
||||||
#include <QtNodes/ConnectionStyle>
|
#include <QtNodes/ConnectionStyle>
|
||||||
#include <QtNodes/GraphicsView>
|
#include <QtNodes/GraphicsView>
|
||||||
#include <QtNodes/internal/DefaultConnectionPainter.hpp>
|
#include "WarpBezierConnectionPainter.h"
|
||||||
#include <QtNodes/internal/NodeGraphicsObject.hpp>
|
#include <QtNodes/internal/NodeGraphicsObject.hpp>
|
||||||
#include <QtNodes/internal/ConnectionGraphicsObject.hpp>
|
#include <QtNodes/internal/ConnectionGraphicsObject.hpp>
|
||||||
#include <QtNodes/internal/UndoCommands.hpp>
|
#include <QtNodes/internal/UndoCommands.hpp>
|
||||||
|
|
@ -1927,7 +1927,7 @@ void GraphEditorWidget::setConnectionStyle(ConnectionStyleType style) {
|
||||||
m_scene->setConnectionPainter(std::make_unique<SquareConnectionPainter>());
|
m_scene->setConnectionPainter(std::make_unique<SquareConnectionPainter>());
|
||||||
} else {
|
} else {
|
||||||
m_scene->setConnectionPainter(
|
m_scene->setConnectionPainter(
|
||||||
std::make_unique<QtNodes::DefaultConnectionPainter>());
|
std::make_unique<WarpBezierConnectionPainter>());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto *item : m_scene->items()) {
|
for (auto *item : m_scene->items()) {
|
||||||
|
|
|
||||||
|
|
@ -205,8 +205,7 @@ void SquareConnectionPainter::paint(
|
||||||
auto *model = dynamic_cast<WarpGraphModel *>(&scene->graphModel());
|
auto *model = dynamic_cast<WarpGraphModel *>(&scene->graphModel());
|
||||||
if (model) {
|
if (model) {
|
||||||
auto cId = cgo.connectionId();
|
auto cId = cgo.connectionId();
|
||||||
peakLevel = std::max(model->nodePeakLevel(cId.outNodeId),
|
peakLevel = model->connectionPeakLevel(cId);
|
||||||
model->nodePeakLevel(cId.inNodeId));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
117
gui/WarpBezierConnectionPainter.cpp
Normal file
117
gui/WarpBezierConnectionPainter.cpp
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
#include "WarpBezierConnectionPainter.h"
|
||||||
|
#include "WarpGraphModel.h"
|
||||||
|
|
||||||
|
#include <QtNodes/internal/BasicGraphicsScene.hpp>
|
||||||
|
#include <QtNodes/internal/ConnectionGraphicsObject.hpp>
|
||||||
|
#include <QtNodes/internal/ConnectionState.hpp>
|
||||||
|
#include <QtNodes/internal/Definitions.hpp>
|
||||||
|
#include <QtNodes/StyleCollection>
|
||||||
|
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QPainterPath>
|
||||||
|
#include <QPainterPathStroker>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
QPainterPath WarpBezierConnectionPainter::cubicPath(
|
||||||
|
QtNodes::ConnectionGraphicsObject const &cgo) const {
|
||||||
|
QPointF const &in = cgo.endPoint(QtNodes::PortType::In);
|
||||||
|
QPointF const &out = cgo.endPoint(QtNodes::PortType::Out);
|
||||||
|
auto const c1c2 = cgo.pointsC1C2();
|
||||||
|
|
||||||
|
QPainterPath cubic(out);
|
||||||
|
cubic.cubicTo(c1c2.first, c1c2.second, in);
|
||||||
|
return cubic;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WarpBezierConnectionPainter::paint(
|
||||||
|
QPainter *painter,
|
||||||
|
QtNodes::ConnectionGraphicsObject const &cgo) const {
|
||||||
|
auto const &style = QtNodes::StyleCollection::connectionStyle();
|
||||||
|
|
||||||
|
bool const hovered = cgo.connectionState().hovered();
|
||||||
|
bool const selected = cgo.isSelected();
|
||||||
|
bool const sketch = cgo.connectionState().requiresPort();
|
||||||
|
|
||||||
|
auto path = cubicPath(cgo);
|
||||||
|
|
||||||
|
float peakLevel = 0.0f;
|
||||||
|
auto *scene = cgo.nodeScene();
|
||||||
|
if (scene) {
|
||||||
|
auto *model = dynamic_cast<WarpGraphModel *>(&scene->graphModel());
|
||||||
|
if (model) {
|
||||||
|
auto cId = cgo.connectionId();
|
||||||
|
peakLevel = model->connectionPeakLevel(cId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto activeColor = [&](QColor base) -> QColor {
|
||||||
|
if (peakLevel < 0.005f)
|
||||||
|
return base;
|
||||||
|
float t = std::min(peakLevel * 2.0f, 1.0f);
|
||||||
|
int r = static_cast<int>(base.red() + t * (60 - base.red()));
|
||||||
|
int g = static_cast<int>(base.green() + t * (210 - base.green()));
|
||||||
|
int b = static_cast<int>(base.blue() + t * (80 - base.blue()));
|
||||||
|
return QColor(std::clamp(r, 0, 255),
|
||||||
|
std::clamp(g, 0, 255),
|
||||||
|
std::clamp(b, 0, 255),
|
||||||
|
base.alpha());
|
||||||
|
};
|
||||||
|
|
||||||
|
if (hovered || selected) {
|
||||||
|
QPen pen;
|
||||||
|
pen.setWidth(static_cast<int>(2 * style.lineWidth()));
|
||||||
|
pen.setColor(selected ? style.selectedHaloColor() : style.hoveredColor());
|
||||||
|
painter->setPen(pen);
|
||||||
|
painter->setBrush(Qt::NoBrush);
|
||||||
|
painter->drawPath(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sketch) {
|
||||||
|
QPen pen;
|
||||||
|
pen.setWidth(static_cast<int>(style.constructionLineWidth()));
|
||||||
|
pen.setColor(style.constructionColor());
|
||||||
|
pen.setStyle(Qt::DashLine);
|
||||||
|
painter->setPen(pen);
|
||||||
|
painter->setBrush(Qt::NoBrush);
|
||||||
|
painter->drawPath(path);
|
||||||
|
} else {
|
||||||
|
QColor base = selected ? style.selectedColor() : style.normalColor();
|
||||||
|
QColor color = selected ? base : activeColor(base);
|
||||||
|
float width = style.lineWidth();
|
||||||
|
if (!selected && peakLevel > 0.005f)
|
||||||
|
width += peakLevel * 1.5f;
|
||||||
|
|
||||||
|
QPen pen;
|
||||||
|
pen.setWidthF(width);
|
||||||
|
pen.setColor(color);
|
||||||
|
painter->setPen(pen);
|
||||||
|
painter->setBrush(Qt::NoBrush);
|
||||||
|
painter->drawPath(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
double const pointRadius = style.pointDiameter() / 2.0;
|
||||||
|
painter->setPen(style.constructionColor());
|
||||||
|
painter->setBrush(style.constructionColor());
|
||||||
|
painter->drawEllipse(cgo.out(), pointRadius, pointRadius);
|
||||||
|
painter->drawEllipse(cgo.in(), pointRadius, pointRadius);
|
||||||
|
}
|
||||||
|
|
||||||
|
QPainterPath WarpBezierConnectionPainter::getPainterStroke(
|
||||||
|
QtNodes::ConnectionGraphicsObject const &cgo) const {
|
||||||
|
auto cubic = cubicPath(cgo);
|
||||||
|
|
||||||
|
QPointF const &out = cgo.endPoint(QtNodes::PortType::Out);
|
||||||
|
QPainterPath result(out);
|
||||||
|
|
||||||
|
unsigned int constexpr segments = 20;
|
||||||
|
for (unsigned int i = 0; i < segments; ++i) {
|
||||||
|
double ratio = double(i + 1) / segments;
|
||||||
|
result.lineTo(cubic.pointAtPercent(ratio));
|
||||||
|
}
|
||||||
|
|
||||||
|
QPainterPathStroker stroker;
|
||||||
|
stroker.setWidth(10.0);
|
||||||
|
return stroker.createStroke(result);
|
||||||
|
}
|
||||||
15
gui/WarpBezierConnectionPainter.h
Normal file
15
gui/WarpBezierConnectionPainter.h
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QtNodes/internal/AbstractConnectionPainter.hpp>
|
||||||
|
|
||||||
|
class WarpBezierConnectionPainter : public QtNodes::AbstractConnectionPainter {
|
||||||
|
public:
|
||||||
|
void paint(QPainter *painter,
|
||||||
|
QtNodes::ConnectionGraphicsObject const &cgo) const override;
|
||||||
|
QPainterPath
|
||||||
|
getPainterStroke(QtNodes::ConnectionGraphicsObject const &cgo) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QPainterPath
|
||||||
|
cubicPath(QtNodes::ConnectionGraphicsObject const &cgo) const;
|
||||||
|
};
|
||||||
|
|
@ -1473,6 +1473,23 @@ float WarpGraphModel::nodePeakLevel(QtNodes::NodeId nodeId) const {
|
||||||
return it != m_peakLevels.end() ? it->second : 0.0f;
|
return it != m_peakLevels.end() ? it->second : 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float WarpGraphModel::connectionPeakLevel(QtNodes::ConnectionId cId) const {
|
||||||
|
constexpr float kSourceReliableThreshold = 0.005f;
|
||||||
|
|
||||||
|
float outPeak = nodePeakLevel(cId.outNodeId);
|
||||||
|
if (outPeak >= kSourceReliableThreshold)
|
||||||
|
return outPeak;
|
||||||
|
|
||||||
|
auto outNodeIt = m_nodes.find(cId.outNodeId);
|
||||||
|
if (outNodeIt == m_nodes.end())
|
||||||
|
return outPeak;
|
||||||
|
|
||||||
|
if (classifyNode(outNodeIt->second.info) != WarpNodeType::kApplication)
|
||||||
|
return outPeak;
|
||||||
|
|
||||||
|
return std::max(outPeak, nodePeakLevel(cId.inNodeId));
|
||||||
|
}
|
||||||
|
|
||||||
void WarpGraphModel::recomputeConnectionChannels() {
|
void WarpGraphModel::recomputeConnectionChannels() {
|
||||||
m_connectionChannels.clear();
|
m_connectionChannels.clear();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,7 @@ public:
|
||||||
|
|
||||||
void setNodePeakLevel(QtNodes::NodeId nodeId, float level);
|
void setNodePeakLevel(QtNodes::NodeId nodeId, float level);
|
||||||
float nodePeakLevel(QtNodes::NodeId nodeId) const;
|
float nodePeakLevel(QtNodes::NodeId nodeId) const;
|
||||||
|
float connectionPeakLevel(QtNodes::ConnectionId cId) const;
|
||||||
|
|
||||||
struct ConnectionChannel {
|
struct ConnectionChannel {
|
||||||
int index = 0;
|
int index = 0;
|
||||||
|
|
|
||||||
|
|
@ -697,7 +697,14 @@ void Client::Impl::RegistryGlobalRemove(void* data, uint32_t id) {
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(impl->cache_mutex);
|
std::lock_guard<std::mutex> lock(impl->cache_mutex);
|
||||||
impl->virtual_streams.erase(id);
|
impl->virtual_streams.erase(id);
|
||||||
impl->link_proxies.erase(id);
|
auto link_it = impl->link_proxies.find(id);
|
||||||
|
if (link_it != impl->link_proxies.end()) {
|
||||||
|
if (link_it->second && link_it->second->proxy) {
|
||||||
|
spa_hook_remove(&link_it->second->listener);
|
||||||
|
link_it->second->proxy = nullptr;
|
||||||
|
}
|
||||||
|
impl->link_proxies.erase(link_it);
|
||||||
|
}
|
||||||
auto node_it = impl->nodes.find(id);
|
auto node_it = impl->nodes.find(id);
|
||||||
if (node_it != impl->nodes.end()) {
|
if (node_it != impl->nodes.end()) {
|
||||||
impl->nodes.erase(node_it);
|
impl->nodes.erase(node_it);
|
||||||
|
|
|
||||||
|
|
@ -1671,6 +1671,42 @@ TEST_CASE("findPwNodeIdByName returns 0 for ghost nodes without pw mapping") {
|
||||||
REQUIRE(model.findPwNodeIdByName("ghost-lookup") == 100220);
|
REQUIRE(model.findPwNodeIdByName("ghost-lookup") == 100220);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("connectionPeakLevel uses source node activity, with application fallback") {
|
||||||
|
auto tc = TestClient::Create();
|
||||||
|
if (!tc.available()) { SUCCEED("PipeWire unavailable"); return; }
|
||||||
|
ensureApp();
|
||||||
|
|
||||||
|
REQUIRE(tc.client->Test_InsertNode(
|
||||||
|
MakeNode(100240, "app-out", "Stream/Output/Audio", "Firefox")).ok());
|
||||||
|
REQUIRE(tc.client->Test_InsertNode(
|
||||||
|
MakeNode(100241, "sink-out", "Audio/Sink")).ok());
|
||||||
|
REQUIRE(tc.client->Test_InsertNode(
|
||||||
|
MakeNode(100242, "hw-in", "Audio/Sink")).ok());
|
||||||
|
|
||||||
|
WarpGraphModel model(tc.client.get());
|
||||||
|
model.refreshFromClient();
|
||||||
|
|
||||||
|
auto appQt = model.qtNodeIdForPw(100240);
|
||||||
|
auto sinkQt = model.qtNodeIdForPw(100241);
|
||||||
|
auto hwQt = model.qtNodeIdForPw(100242);
|
||||||
|
REQUIRE(appQt != 0);
|
||||||
|
REQUIRE(sinkQt != 0);
|
||||||
|
REQUIRE(hwQt != 0);
|
||||||
|
|
||||||
|
model.setNodePeakLevel(appQt, 0.0f);
|
||||||
|
model.setNodePeakLevel(sinkQt, 0.0f);
|
||||||
|
model.setNodePeakLevel(hwQt, 0.8f);
|
||||||
|
|
||||||
|
REQUIRE(model.connectionPeakLevel(QtNodes::ConnectionId{sinkQt, 0u, hwQt, 0u}) ==
|
||||||
|
Catch::Approx(0.0f));
|
||||||
|
REQUIRE(model.connectionPeakLevel(QtNodes::ConnectionId{appQt, 0u, hwQt, 0u}) ==
|
||||||
|
Catch::Approx(0.8f));
|
||||||
|
|
||||||
|
model.setNodePeakLevel(appQt, 0.4f);
|
||||||
|
REQUIRE(model.connectionPeakLevel(QtNodes::ConnectionId{appQt, 0u, hwQt, 0u}) ==
|
||||||
|
Catch::Approx(0.4f));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("saveLayout stores and loadLayout restores view state") {
|
TEST_CASE("saveLayout stores and loadLayout restores view state") {
|
||||||
auto tc = TestClient::Create();
|
auto tc = TestClient::Create();
|
||||||
if (!tc.available()) { SUCCEED("PipeWire unavailable"); return; }
|
if (!tc.available()) { SUCCEED("PipeWire unavailable"); return; }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue