nodeobject contents, twiddly knobs, gain/balance gadget!

portability/boost
zetaPRIME 2019-01-02 02:02:03 -05:00
parent b6922e4f75
commit 0427e25467
10 changed files with 389 additions and 14 deletions

6
notes
View File

@ -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

View File

@ -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");
}
}

View File

@ -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;
};
}

View File

@ -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);*/
}

View File

@ -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;
};

View File

@ -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;
}

View File

@ -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)); };
}
};
}

View File

@ -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) {

View File

@ -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>&);

View File

@ -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