nodeobject contents, twiddly knobs, gain/balance gadget!
parent
b6922e4f75
commit
0427e25467
6
notes
6
notes
|
@ -45,7 +45,7 @@ project data {
|
|||
|
||||
TODO {
|
||||
immediate frontburner {
|
||||
...
|
||||
probably move the "process all nodes" part of tick processing into its own function?
|
||||
|
||||
multithreaded audio
|
||||
^ audio engine invokes workers, then QThread::wait()s on them
|
||||
|
@ -63,7 +63,7 @@ TODO {
|
|||
at *least* js plugin support, with lua+lv2 highly preferable
|
||||
SAMPLES and SAMPLING
|
||||
|
||||
gadget widgets (w/container) - at least a knob with nice range and such
|
||||
- gadget widgets (w/container) - at least a knob with nice range and such
|
||||
|
||||
add metadata and pattern properties (artist, song title, project bpm; pattern name, length etc.)
|
||||
pattern cut+copy+paste
|
||||
|
@ -89,7 +89,7 @@ TODO {
|
|||
|
||||
gadgets and bundled things {
|
||||
(the simple things:)
|
||||
gain and panning gadget
|
||||
- gain and panning gadget
|
||||
note transpose
|
||||
volume meter
|
||||
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
#include "gainbalance.h"
|
||||
using Xybrid::Gadgets::GainBalance;
|
||||
using namespace Xybrid::Data;
|
||||
|
||||
#include "data/porttypes.h"
|
||||
|
||||
#include "config/pluginregistry.h"
|
||||
using namespace Xybrid::Config;
|
||||
|
||||
#include "audio/audioengine.h"
|
||||
using namespace Xybrid::Audio;
|
||||
|
||||
#include "ui/patchboard/nodeobject.h"
|
||||
#include "ui/gadgets/knobgadget.h"
|
||||
using namespace Xybrid::UI;
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <QCborMap>
|
||||
|
||||
namespace {
|
||||
bool _ = PluginRegistry::enqueueRegistration([] {
|
||||
auto i = std::make_shared<PluginInfo>();
|
||||
i->id = "gadget:gainbalance";
|
||||
i->displayName = "Gain/Balance";
|
||||
i->category = "Gadget";
|
||||
//i->hidden = true;
|
||||
i->createInstance = []{ return std::make_shared<GainBalance>(); };
|
||||
PluginRegistry::registerPlugin(i);
|
||||
//inf = i;
|
||||
});
|
||||
}
|
||||
|
||||
GainBalance::GainBalance() {
|
||||
|
||||
}
|
||||
|
||||
void GainBalance::init() {
|
||||
addPort(Port::Input, Port::Audio, 0);
|
||||
addPort(Port::Output, Port::Audio, 0);
|
||||
}
|
||||
|
||||
void GainBalance::process() { // TODO: lerp from tick to tick?
|
||||
const double PI = std::atan(1)*4;
|
||||
const double M = 1.0 / std::cos(PI * 0.25);
|
||||
|
||||
double g = gain.load();
|
||||
double b = balance.load();
|
||||
|
||||
// calculate multipliers
|
||||
double gm = std::pow(10.0, g / 20.0); // dBFS
|
||||
double s = (b+1.0) * PI * 0.25;
|
||||
double lm = std::cos(s) * M;
|
||||
double rm = std::sin(s) * M;
|
||||
|
||||
auto in = std::static_pointer_cast<AudioPort>(port(Port::Input, Port::Audio, 0));
|
||||
auto out = std::static_pointer_cast<AudioPort>(port(Port::Output, Port::Audio, 0));
|
||||
in->pull();
|
||||
out->pull();
|
||||
|
||||
size_t ts = audioEngine->curTickSize();
|
||||
|
||||
for (size_t s = 0; s < ts; s++) {
|
||||
out->bufL[s] = static_cast<float>(static_cast<double>(in->bufL[s]) * gm * lm);
|
||||
out->bufR[s] = static_cast<float>(static_cast<double>(in->bufR[s]) * gm * rm);
|
||||
}
|
||||
}
|
||||
|
||||
void GainBalance::saveData(QCborMap& m) {
|
||||
m.insert(QString("gain"), QCborValue(gain));
|
||||
m.insert(QString("balance"), QCborValue(balance));
|
||||
}
|
||||
|
||||
void GainBalance::loadData(QCborMap& m) {
|
||||
gain = m.value("gain").toDouble(0.0);
|
||||
balance = m.value("balance").toDouble(0.0);
|
||||
}
|
||||
|
||||
void GainBalance::onGadgetCreated() {
|
||||
if (!obj) return;
|
||||
|
||||
//obj->showName = false;
|
||||
//obj->canRename = false;
|
||||
obj->showPluginName = false;
|
||||
obj->setGadgetSize(8+8*2+32*2, 64);
|
||||
|
||||
{
|
||||
auto k = new KnobGadget(obj->contents);
|
||||
k->setPos(8, 16);
|
||||
k->min = -60;
|
||||
k->max = 6;
|
||||
k->step = .1;
|
||||
k->bind(gain);
|
||||
|
||||
k->setLabel("Gain");
|
||||
k->fText = [](double d) { return QString("%1dB").arg(d); };
|
||||
}
|
||||
|
||||
{
|
||||
auto k = new KnobGadget(obj->contents);
|
||||
k->setPos(32+8+8, 16);
|
||||
k->min = -1;
|
||||
k->max = 1;
|
||||
k->bind(balance);
|
||||
|
||||
k->setLabel("Balance");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "data/node.h"
|
||||
|
||||
namespace Xybrid::Gadgets {
|
||||
class GainBalance : public Data::Node {
|
||||
std::atomic<double> gain = 0.0;
|
||||
std::atomic<double> balance = 0.0;
|
||||
public:
|
||||
GainBalance();
|
||||
~GainBalance() override = default;
|
||||
|
||||
void init() override;
|
||||
//void reset() override;
|
||||
void process() override;
|
||||
|
||||
//void onRename() override;
|
||||
|
||||
void saveData(QCborMap&) override;
|
||||
void loadData(QCborMap&) override;
|
||||
|
||||
//void onUnparent(std::shared_ptr<Data::Graph>) override;
|
||||
//void onParent(std::shared_ptr<Data::Graph>) override;
|
||||
|
||||
void onGadgetCreated() override;
|
||||
|
||||
//void drawCustomChrome(QPainter*, const QStyleOptionGraphicsItem*) override;
|
||||
};
|
||||
}
|
|
@ -10,6 +10,10 @@ using namespace Xybrid::Config;
|
|||
#include "audio/audioengine.h"
|
||||
using namespace Xybrid::Audio;
|
||||
|
||||
#include "ui/patchboard/nodeobject.h"
|
||||
#include "ui/gadgets/knobgadget.h"
|
||||
using namespace Xybrid::UI;
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <QDebug>
|
||||
|
@ -91,3 +95,11 @@ void TestSynth::process() {
|
|||
}
|
||||
//audioEngine->curSampleRate()
|
||||
}
|
||||
|
||||
void TestSynth::onGadgetCreated() {
|
||||
/*if (!obj) return;
|
||||
auto k = new KnobGadget(obj);
|
||||
k->setPos(16, 16);
|
||||
static double dbl = 0.5;
|
||||
k->bind(dbl);*/
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace Xybrid::Gadgets {
|
|||
//void onUnparent(std::shared_ptr<Data::Graph>) override;
|
||||
//void onParent(std::shared_ptr<Data::Graph>) override;
|
||||
|
||||
//void onGadgetCreated() override;
|
||||
void onGadgetCreated() override;
|
||||
|
||||
//void drawCustomChrome(QPainter*, const QStyleOptionGraphicsItem*) override;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
#include "knobgadget.h"
|
||||
using namespace Xybrid::UI;
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QPainter>
|
||||
#include <QRadialGradient>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
|
||||
double KnobGadget::get() {
|
||||
double v = fGet();
|
||||
if (v != lastVal) {
|
||||
lastVal = v;
|
||||
dirty = true;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
KnobGadget::KnobGadget(QGraphicsItem* parent) : QGraphicsObject(parent) {
|
||||
label = new QGraphicsSimpleTextItem(this);
|
||||
valLabel = new QGraphicsSimpleTextItem(this);
|
||||
label->setBrush(QColor(255, 255, 255));
|
||||
valLabel->setBrush(QColor(255, 255, 255));
|
||||
label->setScale(0.8);
|
||||
valLabel->setScale(0.8);
|
||||
}
|
||||
|
||||
QRectF KnobGadget::boundingRect() const {
|
||||
return QRectF(0, 0, size, size);
|
||||
}
|
||||
|
||||
QPainterPath KnobGadget::shape() const {
|
||||
QPainterPath p;
|
||||
p.addEllipse(boundingRect());
|
||||
return p;
|
||||
}
|
||||
|
||||
void KnobGadget::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) {
|
||||
auto val = get();
|
||||
auto proportion = (val - min) / (max - min);
|
||||
|
||||
auto r = boundingRect();
|
||||
auto ir = r - QMarginsF(2, 2, 2, 2);
|
||||
|
||||
if (dirty) {
|
||||
dirty = false;
|
||||
|
||||
valLabel->setText(fText(val));
|
||||
|
||||
const qreal pad = 2;
|
||||
|
||||
auto ls = label->boundingRect().size() * label->scale();
|
||||
label->setPos(r.center().x() - ls.width() / 2.0, r.height() + pad);
|
||||
auto vls = valLabel->boundingRect().size() * valLabel->scale();
|
||||
valLabel->setPos(r.center().x() - vls.width() / 2.0, -vls.height() - pad);
|
||||
}
|
||||
|
||||
QColor outline = QColor(31, 31, 31);
|
||||
if (highlighted) outline = QColor(127, 127, 255);
|
||||
QColor needle = QColor(127, 127, 127);
|
||||
|
||||
// base circle
|
||||
painter->setPen(Qt::NoPen);
|
||||
painter->setBrush(outline);
|
||||
painter->drawEllipse(r);
|
||||
|
||||
// range
|
||||
painter->setBrush(QColor(63, 63, 63));
|
||||
painter->drawPie(ir, 250*16, -320*16);
|
||||
|
||||
// fill
|
||||
painter->setBrush(QColor(95, 95, 95));
|
||||
painter->drawPie(ir, 250*16, static_cast<int>(-320.0*16*proportion));
|
||||
|
||||
// needle
|
||||
QRadialGradient gr = QRadialGradient(ir.center(), ir.width() / 2.0, ir.center());
|
||||
gr.setColorAt(0.0, QColor(0, 0, 0, 0));
|
||||
gr.setColorAt(1.0, needle);
|
||||
|
||||
painter->setPen(QPen(gr, 1.25));
|
||||
painter->setBrush(Qt::NoBrush);
|
||||
painter->drawPie(ir, 250*16 + static_cast<int>(-320.0*16*proportion), 0);
|
||||
}
|
||||
|
||||
void KnobGadget::mousePressEvent(QGraphicsSceneMouseEvent* e) {
|
||||
if (e->button() == Qt::LeftButton) {
|
||||
highlighted = true;
|
||||
startVal = get();
|
||||
if (step != 0.0) {
|
||||
startVal = std::round(startVal / step) * step;
|
||||
}
|
||||
} else if (e->button() == Qt::RightButton) {
|
||||
fSet(defaultVal);
|
||||
e->accept();
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void KnobGadget::mouseReleaseEvent(QGraphicsSceneMouseEvent* e) {
|
||||
if (e->button() == Qt::LeftButton) {
|
||||
highlighted = false;
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void KnobGadget::mouseMoveEvent(QGraphicsSceneMouseEvent* e) {
|
||||
if (highlighted) {
|
||||
auto tdelta = -(e->screenPos().y() - e->buttonDownScreenPos(Qt::LeftButton).y());
|
||||
fSet(std::clamp(startVal + tdelta * step, min, max));
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void KnobGadget::contextMenuEvent(QGraphicsSceneContextMenuEvent* e) {
|
||||
e->accept();
|
||||
}
|
||||
|
||||
void KnobGadget::setLabel(const QString& s) {
|
||||
label->setText(s);
|
||||
dirty = true;
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <atomic>
|
||||
|
||||
#include <QGraphicsObject>
|
||||
|
||||
namespace Xybrid::UI {
|
||||
class KnobGadget : public QGraphicsObject {
|
||||
Q_OBJECT
|
||||
|
||||
bool highlighted = false;
|
||||
double startVal;
|
||||
double lastVal = 0.0;
|
||||
bool dirty = true;
|
||||
|
||||
QGraphicsSimpleTextItem* label;
|
||||
QGraphicsSimpleTextItem* valLabel;
|
||||
|
||||
double get();
|
||||
public:
|
||||
std::function<double()> fGet = [] { return 0.0; };
|
||||
std::function<void(double)> fSet;
|
||||
std::function<QString(double)> fText = [](double d) { return QString::number(d); };
|
||||
|
||||
double min = 0.0;
|
||||
double max = 1.0;
|
||||
double step = 0.01;
|
||||
double defaultVal = 0.0;
|
||||
|
||||
qreal size = 32;
|
||||
|
||||
KnobGadget(QGraphicsItem* parent = nullptr);
|
||||
~KnobGadget() override = default;
|
||||
|
||||
QRectF boundingRect() const override;
|
||||
QPainterPath shape() const override;
|
||||
void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) override;
|
||||
|
||||
void mousePressEvent(QGraphicsSceneMouseEvent*) override;
|
||||
void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override;
|
||||
void mouseMoveEvent(QGraphicsSceneMouseEvent*) override;
|
||||
void contextMenuEvent(QGraphicsSceneContextMenuEvent*) override;
|
||||
|
||||
void setLabel(const QString&);
|
||||
|
||||
// and binding templates
|
||||
template<typename T> void bind(T& ref) {
|
||||
auto p = &ref; // pointerize
|
||||
fGet = [p] { return static_cast<double>(*p); };
|
||||
fSet = [p](double d) { *p = static_cast<T>(d); };
|
||||
}
|
||||
template<typename T> void bind(std::atomic<T>& atm) {
|
||||
auto p = &atm;
|
||||
fGet = [p] { return p->load(); };
|
||||
fSet = [p](double d) { p->store(static_cast<T>(d)); };
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -29,6 +29,8 @@ namespace {
|
|||
QColor(95, 191, 163), // MIDI
|
||||
QColor(127, 127, 255), // Parameter
|
||||
};
|
||||
|
||||
const constexpr qreal edgePad = 3;
|
||||
}
|
||||
|
||||
void PortObject::connectTo(PortObject* o) {
|
||||
|
@ -177,6 +179,10 @@ NodeObject::NodeObject(const std::shared_ptr<Data::Node>& n) {
|
|||
|
||||
//setToolTip(QString::fromStdString(node->name));
|
||||
|
||||
contents = new QGraphicsRectItem(this);
|
||||
contents->setPen(Qt::NoPen);
|
||||
contents->setBrush(Qt::NoBrush);
|
||||
|
||||
createPorts();
|
||||
|
||||
node->onGadgetCreated();
|
||||
|
@ -235,6 +241,8 @@ void NodeObject::contextMenuEvent(QGraphicsSceneContextMenuEvent* e) {
|
|||
//setToolTip(n);
|
||||
node->name = n.toStdString();
|
||||
node->onRename();
|
||||
updateGeometry();
|
||||
update();
|
||||
});
|
||||
}
|
||||
m->addAction("Delete node", this, &NodeObject::promptDelete);
|
||||
|
@ -286,11 +294,28 @@ void NodeObject::createPorts() {
|
|||
}
|
||||
|
||||
void NodeObject::updateGeometry() {
|
||||
contents->setRect(QRectF(QPointF(0, 0), gadgetSize_));
|
||||
if (showName) contents->setPos(edgePad, edgePad + nameSize());
|
||||
else contents->setPos(edgePad, edgePad);
|
||||
|
||||
qreal pm = PortObject::portSize * .5 + PortObject::portSpacing;
|
||||
if (inputPortContainer) inputPortContainer->setPos(QPointF(-pm, PortObject::portSize));
|
||||
if (outputPortContainer) outputPortContainer->setPos(QPointF(boundingRect().width() + pm, PortObject::portSize));
|
||||
}
|
||||
|
||||
qreal NodeObject::nameSize() const {
|
||||
qreal n = 0;
|
||||
if (showName) {
|
||||
if (showPluginName) n++;
|
||||
if (!node->name.empty()) n++;
|
||||
}
|
||||
if (n > 0) {
|
||||
n *= QFontMetrics(QFont()).height();
|
||||
n--; // decreased top padding
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
void NodeObject::onMoved() {
|
||||
if (x() < 0) setX(0);
|
||||
else setX(std::round(x()));
|
||||
|
@ -331,19 +356,25 @@ void NodeObject::paint(QPainter* painter, const QStyleOptionGraphicsItem* opt, Q
|
|||
painter->setPen(QPen(QBrush(outline), 2));
|
||||
painter->drawRoundedRect(r, 8, 8);
|
||||
|
||||
QRectF tr = r - QMargins(3, 2, 3, 0);
|
||||
if (!node->name.empty()) {
|
||||
painter->setPen(QColor(222, 222, 222));
|
||||
painter->drawText(tr, Qt::AlignLeft, QString::fromStdString(node->name));
|
||||
tr -= QMarginsF(0, painter->fontMetrics().height(), 0, 0);
|
||||
if (showName) {
|
||||
QRectF tr = r - QMarginsF(edgePad, edgePad - 1, edgePad, 0);
|
||||
if (!node->name.empty()) {
|
||||
painter->setPen(QColor(222, 222, 222));
|
||||
painter->drawText(tr, Qt::AlignLeft, QString::fromStdString(node->name));
|
||||
tr -= QMarginsF(0, painter->fontMetrics().height(), 0, 0);
|
||||
}
|
||||
if (showPluginName) {
|
||||
painter->setPen(QColor(171, 171, 171));
|
||||
painter->drawText(tr, Qt::AlignLeft, QString::fromStdString(node->pluginName()));
|
||||
}
|
||||
}
|
||||
painter->setPen(QColor(171, 171, 171));
|
||||
painter->drawText(tr, Qt::AlignLeft, QString::fromStdString(node->pluginName()));
|
||||
}
|
||||
|
||||
QRectF NodeObject::boundingRect() const {
|
||||
if (customChrome) return QRectF(QPointF(0, 0), gadgetSize_);
|
||||
return QRectF(0, 0, 192, 36);// + QMarginsF(8, 8, 8, 8);
|
||||
if (gadgetSize_.isNull()) return QRectF(0, 0, 192, 36);// + QMarginsF(8, 8, 8, 8);
|
||||
if (showName) return QRectF(QPointF(), gadgetSize_ + QPointF(edgePad * 2, edgePad * 2 + nameSize()));
|
||||
return QRectF(QPointF(), gadgetSize_ + QPointF(edgePad * 2, edgePad * 2));
|
||||
}
|
||||
|
||||
PortConnectionObject::PortConnectionObject(PortObject* in, PortObject* out) {
|
||||
|
|
|
@ -82,6 +82,8 @@ namespace Xybrid::UI {
|
|||
|
||||
QPointF gadgetSize_{0, 0};
|
||||
|
||||
qreal nameSize() const;
|
||||
|
||||
void onMoved();
|
||||
void bringToTop(bool force = false);
|
||||
|
||||
|
@ -100,6 +102,10 @@ namespace Xybrid::UI {
|
|||
|
||||
bool customChrome = false;
|
||||
bool canRename = true;
|
||||
bool showName = true;
|
||||
bool showPluginName = true;
|
||||
|
||||
QGraphicsRectItem* contents;
|
||||
|
||||
NodeObject(const std::shared_ptr<Data::Node>&);
|
||||
|
||||
|
|
|
@ -55,7 +55,9 @@ SOURCES += \
|
|||
ui/breadcrumbview.cpp \
|
||||
gadgets/ioport.cpp \
|
||||
gadgets/testsynth.cpp \
|
||||
util/keys.cpp
|
||||
util/keys.cpp \
|
||||
ui/gadgets/knobgadget.cpp \
|
||||
gadgets/gainbalance.cpp
|
||||
|
||||
HEADERS += \
|
||||
mainwindow.h \
|
||||
|
@ -85,7 +87,9 @@ HEADERS += \
|
|||
ui/breadcrumbview.h \
|
||||
gadgets/ioport.h \
|
||||
gadgets/testsynth.h \
|
||||
util/keys.h
|
||||
util/keys.h \
|
||||
ui/gadgets/knobgadget.h \
|
||||
gadgets/gainbalance.h
|
||||
|
||||
FORMS += \
|
||||
mainwindow.ui
|
||||
|
|
Loading…
Reference in New Issue