From e5f16157247035193bb50345cae9f5d5f21a7392 Mon Sep 17 00:00:00 2001 From: zetaPRIME Date: Fri, 28 Dec 2018 12:19:32 -0500 Subject: [PATCH] it actually makes music now! --- asset-work/xybrid-logo-banner480.png | Bin 0 -> 51942 bytes notes | 29 +++-- readme.md | 5 +- xybrid/audio/audioengine.cpp | 187 ++++++++++++++++++++++++++- xybrid/audio/audioengine.h | 33 ++++- xybrid/config/pluginregistry.cpp | 6 + xybrid/config/pluginregistry.h | 4 +- xybrid/data/graph.cpp | 3 + xybrid/data/graph.h | 1 + xybrid/data/node.cpp | 37 ++++++ xybrid/data/node.h | 11 +- xybrid/data/porttypes.cpp | 43 +++++- xybrid/data/porttypes.h | 5 + xybrid/gadgets/testsynth.cpp | 93 +++++++++++++ xybrid/gadgets/testsynth.h | 37 ++++++ xybrid/mainwindow.cpp | 4 - xybrid/ui/patchboard/nodeobject.cpp | 6 +- xybrid/xybrid.pro | 6 +- 18 files changed, 474 insertions(+), 36 deletions(-) create mode 100644 asset-work/xybrid-logo-banner480.png create mode 100644 xybrid/gadgets/testsynth.cpp create mode 100644 xybrid/gadgets/testsynth.h diff --git a/asset-work/xybrid-logo-banner480.png b/asset-work/xybrid-logo-banner480.png new file mode 100644 index 0000000000000000000000000000000000000000..4d2e469a5c4e82141aa2da327e5d9808558f73ec GIT binary patch literal 51942 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 literal 0 HcmV?d00001 diff --git a/notes b/notes index bda3856..c496718 100644 --- a/notes +++ b/notes @@ -46,16 +46,16 @@ project data { TODO { immediate frontburner { neeeeext { - hook the graph up to the audio engine! { - recursive dependency queue resolution - done/readiness test (process() wrapper?) + - hook the graph up to the audio engine! { + - (not so-)recursive dependency queue resolution + - done/readiness test (process() wrapper?) } - hook up commands to the graph { - figure out how to do note numbers - ^ vector sized by channel count rounded up to next 16 - ^^ on switching to new graph... map of (hashes of) named channels?? - assemble wire command queues (probaby some minor trickiness) and push into ports - NOTE! note number and port have to be combined in tracking (have to know what port to send the note-offs to) + - hook up commands to the graph { + - figure out how to do note numbers + - ^ vector sized by channel count rounded up to next 16 + - ^^ on switching to new graph... map of (hashes of) named channels?? + - assemble wire command queues (probaby some minor trickiness) and push into ports + - NOTE! note number and port have to be combined in tracking (have to know what port to send the note-offs to) } then implement multithreading! :D } @@ -85,6 +85,7 @@ TODO { import/export subgraph as file (*.xyg) proper playback controls and indicators + play from current pattern instrument previewing pattern editor cells can have (dynamic) tool tips; set this up with port names, etc. @@ -167,11 +168,11 @@ graph+node+port system { } on-the-wire command format { - ushort noteId // for sending commands to the same note - short note // note number >= 0, -1 for none, -2 note off, -3 hard cut - unsigned char numParams * { - unsigned char cmd - unsigned char amount + uint16_t noteId // for sending commands to the same note + int16_t note // note number >= 0, -1 for none, -2 note off, -3 hard cut + uint8_t numParams x { + uint8_t cmd + uint8_t amount } } diff --git a/readme.md b/readme.md index 2c57252..f6bcf02 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,5 @@ -# Xybrid - -something something, actual readme coming later +![Xybrid logo](asset-work/xybrid-logo-banner480.png) +Xybrid: deeply modular tracker ## Build dependencies: - Qt 5.12 or later diff --git a/xybrid/audio/audioengine.cpp b/xybrid/audio/audioengine.cpp index 6b12daa..2b835d1 100644 --- a/xybrid/audio/audioengine.cpp +++ b/xybrid/audio/audioengine.cpp @@ -2,6 +2,8 @@ #include "data/project.h" using namespace Xybrid::Audio; using namespace Xybrid::Data; +#include "data/graph.h" +#include "data/porttypes.h" #include "mainwindow.h" #include "uisocket.h" @@ -36,14 +38,18 @@ void AudioEngine::postInit() { open(QIODevice::ReadOnly); // set up buffer for per-tick allocation - tickBuf = std::make_unique(tickBufSize/sizeof(int)); // aligned to int, which we assume is the native word size + tickBuf = std::make_unique(tickBufSize/sizeof(size_t)); // aligned to size_t tickBufPtr = tickBuf.get(); tickBufEnd = tickBufPtr+tickBufSize; + buf.reserve(1024); // 1kb isn't much to make sure it's super unlikely to have to reallocate + chTrack.reserve(256); + noteEndQueue.reserve(256); + nameTrack.reserve(64+1); // +1 to make extra sure it doesn't rehash later } void* AudioEngine::tickAlloc(size_t size) { - if (auto r = size % sizeof(int); r != 0) size += sizeof(int) - r; // pad to word + if (auto r = size % sizeof(size_t); r != 0) size += sizeof(size_t) - r; // pad auto n = tickBufPtr.fetch_add(static_cast(size)); if (n + size > tickBufEnd) qWarning() << "Tick buffer overrun!"; return n; @@ -88,7 +94,12 @@ void AudioEngine::play(std::shared_ptr p) { QMetaObject::invokeMethod(this, [this, p]() { if (!p) return; // nope project = p; + // stop and reset, then init playback + queueValid = false; + queue.clear(); + portLastNoteId.fill(0); + project->rootGraph->reset(); initAudio(); for (auto& b : buffer) { @@ -112,12 +123,57 @@ void AudioEngine::play(std::shared_ptr p) { void AudioEngine::stop() { QMetaObject::invokeMethod(this, [this]() { project = nullptr; + queueValid = false; + queue.clear(); deinitAudio(); mode = Stopped; emit this->playbackModeChanged(); }, Qt::QueuedConnection); } +void AudioEngine::buildQueue() { + queue.clear(); + // stuff + std::deque> q1, q2; + auto* qCurrent = &q1; + auto* qNext = &q2; + + if (auto p = project->rootGraph->port(Port::Output, Port::Audio, 0); p) + if (auto pt = p->passthroughTo.lock(); pt) + if (auto ptn = pt->owner.lock(); ptn) + qCurrent->push_back(ptn); + + // T_ODO: make this not process things the weird way around + // oh, it's working properly... it just processing subgraph before its internally-connected *inputs* + while (!qCurrent->empty()) { + // ... this could be made more efficient with some redundancy checking, but whatever + for (auto n : *qCurrent) { + queue.push_front(n); // add to actual queue + for (auto p1 : n->inputs) { // data types... + for (auto p2 : p1.second) { // ports... + for (auto p3 : p2.second->connections) { // connected ports! + auto pc = p3.lock(); + if (!pc) continue; + auto pcn = pc->owner.lock(); + if (!pcn) continue; + qNext->push_back(pcn); + if (auto pp = pc->passthroughTo.lock(); pp) { + // if it has a passthrough, also place passthrough's owner after (before) + if (auto ppp = pp->owner.lock(); ppp) qNext->push_back(ppp); + } + + } + } + } + } + + qCurrent->clear(); + std::swap(qCurrent, qNext); + } + + queueValid = true; +} + qint64 AudioEngine::readData(char *data, qint64 maxlen) { const constexpr qint64 smp = 2; const constexpr qint64 stride = smp*2; @@ -166,14 +222,20 @@ void AudioEngine::nextTick() { buffer[0].clear(); buffer[1].clear(); + if (!queueValid) buildQueue(); + Pattern* p = nullptr; + Pattern* pOld = nullptr; auto setP = [&] { if (seqPos >= 0 && seqPos < static_cast(project->sequence.size())) p = project->sequence[static_cast(seqPos)]; else p = nullptr; }; setP(); + bool newRow = false; + bool newPattern = false; auto advanceSeq = [&] { + pOld = p; p = nullptr; int tries = 0; while (!p) { @@ -185,6 +247,8 @@ void AudioEngine::nextTick() { // set pattern things if (p->tempo > 0) tempo = p->tempo; + + newPattern = true; }; auto advanceRow = [&] { curTick = 0; @@ -202,7 +266,110 @@ void AudioEngine::nextTick() { } } - // TODO then assemble command buffers + newRow = true; + + // assemble command buffers + + noteEndQueue.clear(); + if (newPattern) { // notes on named channels carry over to their matching channel on the new pattern (if present); everything else is note-offed + if (pOld) { + size_t cs = pOld->channels.size(); + for (size_t c = 0; c < cs; c++) { + auto& ch = pOld->channels[c]; + if (!chTrack[c].valid) continue; // skip notes that aren't actually playing + if (ch.name.empty()) noteEndQueue.push_back(chTrack[c]); // end notes in unnamed channels right away + else nameTrack[&ch.name] = chTrack[c]; // otherwise keep track for later + } + } + chTrack.clear(); // clear and prepare channel note tracking + chTrack.resize(p->channels.size()); + if (nameTrack.size() > 0) { // if there were any + size_t cs = p->channels.size(); + for (size_t c = 0; c < cs; c++) { + auto& ch = p->channels[c]; + if (ch.name.empty()) continue; + if (auto nt = nameTrack.find(&ch.name); nt != nameTrack.end() && nt->second.valid) { + chTrack[c] = nt->second; // carry over + nt->second.valid = false; // and invalidate + } + } + // dump remainder into note end + for (auto nt : nameTrack) if (nt.second.valid) noteEndQueue.push_back(nt.second); + } + nameTrack.clear(); + } + + int chs = static_cast(p->channels.size()); + for (int c = 0; c < chs; c++) { + auto& ct = chTrack[static_cast(c)]; + if (!ct.valid) continue; // no saved note + auto& r = p->rowAt(c, curRow); + if (r.note != -1 && r.port >= 0 && r.port != ct.port) { // if explicitly specified for a different port... + noteEndQueue.push_back(ct); // old note overwritten + ct.valid = false; + } + } + + auto& cpm = project->rootGraph->inputs[Port::Command]; + for (auto p_ : cpm) { + auto* pt = static_cast(p_.second.get()); + //if (pt->passthroughTo.lock()->connections.empty()) continue; // port isn't hooked up to anything + uint8_t idx = pt->index; + buf.clear(); + + for (auto& ne : noteEndQueue) { + if (ne.valid && ne.port == idx) { + size_t bi = buf.size(); + buf.resize(bi+5, 0); + reinterpret_cast(buf[bi]) = ne.noteId; // trigger on note id... + reinterpret_cast(buf[bi+2]) = -2; // note off + } + } + + for (int c = 0; c < chs; c++) { + auto& r = p->rowAt(c, curRow); + auto& ct = chTrack[static_cast(c)]; + int16_t port = r.port; + if (port < 0 && ct.valid) port = ct.port; // assume last port used on channel if not specified + if (port != idx) continue; + + NoteInfo rpl; // default initialization, invalid + + if (r.note >= 0) { + if (ct.valid) rpl = ct; // replace + ct = NoteInfo(idx, portLastNoteId[idx]++); + } else if (r.note <= -2 && ct.valid) { + ct.valid = false; // invalidate it here but leave note id intact + // this condition will allow you to note-off the same note id multiple times but anything + // that takes offense to that is a bug anyway + } + + size_t bi = buf.size(); + buf.resize(bi+5, 0); + reinterpret_cast(buf[bi]) = ct.noteId; // either new note, or note-off on old one + reinterpret_cast(buf[bi+2]) = r.note; // shove note into vector + auto& np = buf[bi+4]; // number of params + + if (r.params) { + for (auto& p : *r.params) { + if (p[0] == ' ') continue; // ignore struts + buf.push_back(p[0]); + buf.push_back(p[1]); + np++; + } + } + + if (rpl.valid) { // replacing old note on the same port and channel + bi = buf.size(); + buf.resize(bi+5, 0); + reinterpret_cast(buf[bi]) = rpl.noteId; // trigger on note id... + reinterpret_cast(buf[bi+2]) = -2; // note off + } + } + + //qDebug() << "port" << idx << "data of size" << buf.size(); + pt->push(buf); + } }; curTick++; @@ -219,8 +386,18 @@ void AudioEngine::nextTick() { buffer[1].resize(ts); //qDebug() << "tick" << tickId << "contains"<"; + for (auto n : queue) if (!n->try_process()) qWarning() << "Dependency check failed in single threaded mode!"; + if (auto p = std::static_pointer_cast(project->rootGraph->port(Port::Output, Port::Audio, 0)); p) { + p->pull(); + size_t bufs = ts * sizeof(float); + memcpy(buffer[0].data(), p->bufL, bufs); + memcpy(buffer[1].data(), p->bufR, bufs); + //p->bufL + } + //buffer[0].data() // test - const double PI = std::atan(1)*4; + /*const double PI = std::atan(1)*4; const double SEMI = std::pow(2.0, 1.0/12.0); double time = 0; int note = curRow % 4; @@ -229,7 +406,7 @@ void AudioEngine::nextTick() { buffer[0][i] = static_cast(std::sin(time * PI*2 * 440 * std::pow(SEMI, -6 + note * 5)) * .25); buffer[1][i] = buffer[0][i]; time += 1.0/sampleRate; - } + }*/ } diff --git a/xybrid/audio/audioengine.h b/xybrid/audio/audioengine.h index ced110c..d2b7629 100644 --- a/xybrid/audio/audioengine.h +++ b/xybrid/audio/audioengine.h @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include #include @@ -11,8 +13,20 @@ class QThread; namespace Xybrid::Data { class Project; + class Node; } namespace Xybrid::Audio { + template struct PointerCompare { + bool operator()(T* a, T* b) const { return *a == *b; } + size_t operator()(T* a) const { return std::hash()(*a); } + }; + struct NoteInfo { + bool valid = false; + uint8_t port = 0; + uint16_t noteId = 0; + NoteInfo() = default; + NoteInfo(uint8_t p, uint16_t nId) { valid = true; port = p; noteId = nId; } + }; class AudioEngine : public QIODevice { Q_OBJECT explicit AudioEngine(QObject *parent = nullptr); @@ -33,14 +47,24 @@ namespace Xybrid::Audio { size_t bufPos = 0; static const constexpr size_t tickBufSize = (1024*1024*5); // 5mb should be enough - std::unique_ptr tickBuf; - std::atomic tickBufPtr; - int* tickBufEnd; + std::unique_ptr tickBuf; + std::atomic tickBufPtr; + size_t* tickBufEnd; PlaybackMode mode = Stopped; size_t tickId = 0; std::shared_ptr project; + std::deque> queue; + bool queueValid; + void buildQueue(); + + std::array portLastNoteId; + std::vector chTrack; + std::vector noteEndQueue; + std::unordered_map, PointerCompare> nameTrack; + std::vector buf; /// preallocated buffer for building commands + // playback timing and position float tempo = 140.0; int seqPos; @@ -59,9 +83,12 @@ namespace Xybrid::Audio { void play(std::shared_ptr); void stop(); + inline void invalidateQueue(Data::Project* p) { if (p == project.get()) queueValid = false; } + void* tickAlloc(size_t size); inline size_t curTickId() const { return tickId; } inline size_t curTickSize() const { return buffer[0].size(); } + inline int curSampleRate() const { return sampleRate; } // QIODevice functions qint64 readData(char* data, qint64 maxlen) override; diff --git a/xybrid/config/pluginregistry.cpp b/xybrid/config/pluginregistry.cpp index 222c70c..8ba5ee1 100644 --- a/xybrid/config/pluginregistry.cpp +++ b/xybrid/config/pluginregistry.cpp @@ -48,6 +48,8 @@ void PluginRegistry::registerPlugin(std::shared_ptr pi) { if (pi->id.empty()) return; if (plugins.find(pi->id) != plugins.end()) return; plugins[pi->id] = pi; + // there might be a better way to do this? + for (auto& id : pi->oldIds) plugins[id] = pi; } std::shared_ptr PluginRegistry::createInstance(const std::string& id) { @@ -55,6 +57,7 @@ std::shared_ptr PluginRegistry::createInstance(const std::string& id) { if (f == plugins.end()) return nullptr; auto n = f->second->createInstance(); n->plugin = f->second; + n->init(); return n; } @@ -114,6 +117,7 @@ void PluginRegistry::populatePluginMenu(QMenu* m, std::functionaddAction(QString::fromStdString(i.second->displayName), [f, pi = i.second] { auto n = pi->createInstance(); n->plugin = pi; + n->init(); f(n); }); } @@ -129,6 +133,7 @@ void PluginRegistry::populatePluginMenu(QMenu* m, std::functionaddAction(QString::fromStdString(i.second->displayName), [f, pi = i.second] { auto n = pi->createInstance(); n->plugin = pi; + n->init(); f(n); }); } @@ -139,6 +144,7 @@ void PluginRegistry::populatePluginMenu(QMenu* m, std::functionaddAction(QString::fromStdString(i.second->displayName), [f, pi = i.second] { auto n = pi->createInstance(); n->plugin = pi; + n->init(); f(n); }); diff --git a/xybrid/config/pluginregistry.h b/xybrid/config/pluginregistry.h index cce6c8f..196f358 100644 --- a/xybrid/config/pluginregistry.h +++ b/xybrid/config/pluginregistry.h @@ -1,7 +1,8 @@ #pragma once -#include #include +#include +#include #include class QMenu; @@ -15,6 +16,7 @@ namespace Xybrid::Config { class PluginInfo { public: std::string id; + std::vector oldIds; std::string displayName; std::string category; std::function()> createInstance; diff --git a/xybrid/data/graph.cpp b/xybrid/data/graph.cpp index b145677..2242a66 100644 --- a/xybrid/data/graph.cpp +++ b/xybrid/data/graph.cpp @@ -33,6 +33,9 @@ Graph::Graph() { plugin = inf; // harder bind } +// propagate +void Graph::reset() { for (auto c : children) c->reset(); } + void Graph::saveData(QCborMap& m) { // graph properties // ... maybe there will be some at some point diff --git a/xybrid/data/graph.h b/xybrid/data/graph.h index 87971f6..9b1939c 100644 --- a/xybrid/data/graph.h +++ b/xybrid/data/graph.h @@ -13,6 +13,7 @@ namespace Xybrid::Data { // position of viewport within graph (not serialized) int viewX{}, viewY{}; + void reset() override; void saveData(QCborMap&) override; void loadData(QCborMap&) override; diff --git a/xybrid/data/node.cpp b/xybrid/data/node.cpp index 73198ea..2dd55de 100644 --- a/xybrid/data/node.cpp +++ b/xybrid/data/node.cpp @@ -6,6 +6,9 @@ using namespace Xybrid::Data; #include "config/pluginregistry.h" +#include "audio/audioengine.h" +using namespace Xybrid::Audio; + #include #include @@ -33,6 +36,7 @@ bool Port::connect(std::shared_ptr p) { // actually hook up connections.emplace_back(p); p->connections.emplace_back(shared_from_this()); + if (auto o = owner.lock(); o) audioEngine->invalidateQueue(o->project); return true; } @@ -41,6 +45,7 @@ void Port::disconnect(std::shared_ptr p) { auto t = shared_from_this(); connections.erase(std::remove_if(connections.begin(), connections.end(), [p](auto w) { return w.lock() == p; }), connections.end()); p->connections.erase(std::remove_if(p->connections.begin(), p->connections.end(), [t](auto w) { return w.lock() == t; }), p->connections.end()); + if (auto o = owner.lock(); o) audioEngine->invalidateQueue(o->project); } void Port::cleanConnections() { @@ -66,6 +71,7 @@ void Node::parentTo(std::shared_ptr graph) { graph->children.push_back(t); onParent(graph); } + audioEngine->invalidateQueue(project); // just to be safe } std::shared_ptr Node::port(Port::Type t, Port::DataType dt, uint8_t idx, bool addIfNeeded) { @@ -120,4 +126,35 @@ bool Node::dependsOn(std::shared_ptr o) { return false; } +bool Node::try_process(bool checkDependencies) { + size_t tick_this = audioEngine->curTickId(); + if (tick_last == tick_this) return true; // already processed + + if (checkDependencies) { // check if dependencies are done + for (auto& t : inputs) { + for (auto& p : t.second) { + for (auto& c : p.second->connections) { + // if connection still exists, *and its owner* still exists... + if (auto cp = c.lock(); cp) { + if (auto n = cp->owner.lock(); n) { + if (auto cpp = cp->passthroughTo.lock(); cpp) { // passthrough... + if (auto np = cpp->owner.lock(); np && np->tick_last != tick_this) return false; + } + if (n->tick_last != tick_this) return false; + } + } + } + } + } + } + + /*auto qd = qDebug() << "processing" << QString::fromStdString(pluginName()); + if (!name.empty()) qd << "named" << QString::fromStdString(name); + if (auto p = parent.lock(); p && !p->name.empty()) qd << "within" << QString::fromStdString(p->name);*/ + process(); + + tick_last = tick_this; + return true; +} + std::string Node::pluginName() const { if (!plugin) return "(unknown plugin)"; return plugin->displayName; } diff --git a/xybrid/data/node.h b/xybrid/data/node.h index 3db9ab0..ee991b0 100644 --- a/xybrid/data/node.h +++ b/xybrid/data/node.h @@ -21,6 +21,10 @@ namespace Xybrid::Config { class PluginInfo; } +namespace Xybrid::Audio { + class AudioEngine; +} + namespace Xybrid::Data { class Project; @@ -41,7 +45,7 @@ namespace Xybrid::Data { std::weak_ptr owner; std::vector> connections; std::weak_ptr passthroughTo; - Type type; // TODO: figure out passthrough? + Type type; uint8_t index; size_t tickUpdatedOn = static_cast(-1); @@ -64,6 +68,9 @@ namespace Xybrid::Data { }; class Node : public std::enable_shared_from_this { + friend class Audio::AudioEngine; + size_t tick_last = 0; + bool try_process(bool checkDependencies = true); public: Project* project; std::weak_ptr parent; @@ -87,6 +94,8 @@ namespace Xybrid::Data { std::unordered_set> dependencies() const; bool dependsOn(std::shared_ptr); + virtual void init() { } + virtual void reset() { } virtual void saveData(QCborMap&) { } virtual void loadData(QCborMap&) { } diff --git a/xybrid/data/porttypes.cpp b/xybrid/data/porttypes.cpp index 4f43a6d..129084a 100644 --- a/xybrid/data/porttypes.cpp +++ b/xybrid/data/porttypes.cpp @@ -14,8 +14,17 @@ void AudioPort::pull() { size_t s = sizeof(float) * ts; if (type == Input) { + if (connections.size() == 1) { + // if this is a single connection, just repoint to source audio + if (auto p = std::static_pointer_cast(connections[0].lock()); p && p->dataType() == Audio) { + p->pull(); + bufL = p->bufL; + bufR = p->bufR; + return; + } + } bufL = static_cast(audioEngine->tickAlloc(s*2)); - bufR = bufL + s; + bufR = &bufL[ts]; // for some reason just adding the size wonks out memset(bufL, 0, s*2); // clear buffers for (auto c : connections) { // mix @@ -34,7 +43,37 @@ void AudioPort::pull() { bufR = pt->bufR; } else { // output without valid passthrough, just clear and prepare a blank buffer bufL = static_cast(audioEngine->tickAlloc(s*2)); - bufR = bufL + s; + bufR = &bufL[ts]; memset(bufL, 0, s*2); // clear buffers } } + +void CommandPort::pull() { + auto t = audioEngine->curTickId(); + if (tickUpdatedOn == t) return; + tickUpdatedOn = t; + + dataSize = 0; + if (type == Input) { + for (auto c : connections) { + if (auto p = std::static_pointer_cast(c.lock()); p && p->dataType() == Command) { + p->pull(); + data = p->data; // just repoint to input's buffer + dataSize = p->dataSize; + break; + } + } + } else if (auto pt = std::static_pointer_cast(passthroughTo.lock()); pt && pt->dataType() == Command) { + // valid passthrough + pt->pull(); + data = pt->data; // again, just repoint + dataSize = pt->dataSize; + } // don't need an else case, size is already zero +} + +void CommandPort::push(std::vector v) { + tickUpdatedOn = audioEngine->curTickId(); + dataSize = v.size(); + data = static_cast(audioEngine->tickAlloc(dataSize)); + memcpy(data, v.data(), dataSize); +} diff --git a/xybrid/data/porttypes.h b/xybrid/data/porttypes.h index 3a18bb4..ddb5262 100644 --- a/xybrid/data/porttypes.h +++ b/xybrid/data/porttypes.h @@ -28,5 +28,10 @@ namespace Xybrid::Data { Port::DataType dataType() const override { return Port::Command; } bool singleInput() const override { return true; } + + void pull() override; + + /// Push a data buffer + void push(std::vector); }; } diff --git a/xybrid/gadgets/testsynth.cpp b/xybrid/gadgets/testsynth.cpp new file mode 100644 index 0000000..b16c296 --- /dev/null +++ b/xybrid/gadgets/testsynth.cpp @@ -0,0 +1,93 @@ +#include "testsynth.h" +using Xybrid::Gadgets::TestSynth; +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 + +#include + +namespace { + bool _ = PluginRegistry::enqueueRegistration([] { + auto i = std::make_shared(); + i->id = "plug:testsynth"; + i->displayName = "The Testron"; + i->category = "Instrument"; + //i->hidden = true; + i->createInstance = []{ return std::make_shared(); }; + PluginRegistry::registerPlugin(i); + //inf = i; + }); +} + +TestSynth::TestSynth() { + // +} + +void TestSynth::init() { + addPort(Port::Input, Port::Command, 0); + addPort(Port::Output, Port::Audio, 0); +} + +void TestSynth::reset() { + osc = 0.0; + osc2 = 0.0; + cvol = 0.0; + tvol = 0.0; + noteId = 0; +} + +void TestSynth::process() { + auto cp = std::static_pointer_cast(port(Port::Input, Port::Command, 0)); + cp->pull(); + auto p = std::static_pointer_cast(port(Port::Output, Port::Audio, 0)); + p->pull(); + + size_t mi = 0; + while (cp->dataSize >= mi+5) { + uint16_t id = reinterpret_cast(cp->data[mi]); + int16_t n = reinterpret_cast(cp->data[mi+2]); + if (n > -1) { + noteId = id; + note = n; + tvol = 1.0; + } else if (n < -1 && id == noteId) { // note off + tvol = 0.0; + } + mi += 5 + cp->data[mi+4]; + } + + const double PI = std::atan(1)*4; + const double SEMI = std::pow(2.0, 1.0/12.0); + + size_t ts = audioEngine->curTickSize(); + + for (size_t s = 0; s < ts; s++) { + if (tvol > cvol) cvol += 64.0 / audioEngine->curSampleRate(); + else if (tvol < cvol) cvol -= 16.0 / audioEngine->curSampleRate(); + cvol = std::clamp(cvol, 0.0, 1.0); + if (cvol == 0.0) { osc = osc2 = 0.0; } + float oscV = static_cast((std::sin(osc * PI*2) + std::sin(osc2 * PI*2) * std::pow(.75, 4)) * std::pow(cvol*.5, 4)); + + double enote = note + std::sin(lfo * PI*2) * 0.1; + double freq = 440.0 * std::pow(SEMI, enote - (45+12)); + osc += freq / audioEngine->curSampleRate(); + osc = std::fmod(osc, 1.0); + osc2 += (freq * .5) / audioEngine->curSampleRate(); + osc2 = std::fmod(osc2, 1.0); + + lfo += 3.0 / audioEngine->curSampleRate(); + lfo = std::fmod(lfo, 1.0); + + p->bufL[s] = oscV; + p->bufR[s] = oscV; + } + //audioEngine->curSampleRate() +} diff --git a/xybrid/gadgets/testsynth.h b/xybrid/gadgets/testsynth.h new file mode 100644 index 0000000..8c67a72 --- /dev/null +++ b/xybrid/gadgets/testsynth.h @@ -0,0 +1,37 @@ +#pragma once + +#include "data/node.h" + +namespace Xybrid::Gadgets { + class TestSynth : public Data::Node { + // + double osc = 0; + double osc2 = 0; + double note = 45+12; + double lfo = 0; + + uint16_t noteId = 0; + double cvol = 0; + double tvol = 0; + public: + TestSynth(); + ~TestSynth() 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) override; + //void onParent(std::shared_ptr) override; + + //void onGadgetCreated() override; + + //void drawCustomChrome(QPainter*, const QStyleOptionGraphicsItem*) override; + }; +} + diff --git a/xybrid/mainwindow.cpp b/xybrid/mainwindow.cpp index b1cd99c..6cad668 100644 --- a/xybrid/mainwindow.cpp +++ b/xybrid/mainwindow.cpp @@ -320,10 +320,6 @@ MainWindow::MainWindow(QWidget *parent) : // and start with a new project menuFileNew(); - - //auto q = QJsonObject(); - //q.insert(QMetaObject::, "frenk"); - qDebug() << QVariant::fromValue(Data::Port::Audio).toString(); } MainWindow::~MainWindow() { diff --git a/xybrid/ui/patchboard/nodeobject.cpp b/xybrid/ui/patchboard/nodeobject.cpp index 97e8580..72e7600 100644 --- a/xybrid/ui/patchboard/nodeobject.cpp +++ b/xybrid/ui/patchboard/nodeobject.cpp @@ -31,7 +31,7 @@ namespace { }; } -void PortObject::connectTo(Xybrid::UI::PortObject* o) { +void PortObject::connectTo(PortObject* o) { if (!o) return; if (connections.find(o) != connections.end()) return; if (port->type == o->port->type) return; @@ -331,6 +331,10 @@ PortConnectionObject::PortConnectionObject(PortObject* in, PortObject* out) { this->in = in; this->out = out; + // remove dupes + if (in->connections[out]) delete in->connections[out]; + if (out->connections[in]) delete out->connections[in]; + // and hook up in->connections[out] = this; out->connections[in] = this; diff --git a/xybrid/xybrid.pro b/xybrid/xybrid.pro index a7f377d..f43495b 100644 --- a/xybrid/xybrid.pro +++ b/xybrid/xybrid.pro @@ -53,7 +53,8 @@ SOURCES += \ config/pluginregistry.cpp \ data/porttypes.cpp \ ui/breadcrumbview.cpp \ - gadgets/ioport.cpp + gadgets/ioport.cpp \ + gadgets/testsynth.cpp HEADERS += \ mainwindow.h \ @@ -81,7 +82,8 @@ HEADERS += \ data/porttypes.h \ config/pluginregistry.h \ ui/breadcrumbview.h \ - gadgets/ioport.h + gadgets/ioport.h \ + gadgets/testsynth.h FORMS += \ mainwindow.ui