#include "node.h" using namespace Xybrid::Data; #include "data/graph.h" #include "data/porttypes.h" #include "config/pluginregistry.h" #include "audio/audioengine.h" using namespace Xybrid::Audio; #include #include Port::~Port() { // clean up others' connection lists while (connections.size() > 0) { if (auto cc = connections.back().lock(); cc) cc->cleanConnections(); connections.pop_back(); } } bool Port::canConnectTo(DataType d) const { return d == dataType(); } bool Port::connect(std::shared_ptr p) { if (!p) return false; // no blank pointers pls // actual processing is always done on the input port, since that's where any limits are if (type == Output) return p->type == Input && p->connect(shared_from_this()); if (!canConnectTo(p->dataType())) return false; // can't hook up to an incompatible data type for (auto c : connections) if (c.lock() == p) return true; // I guess report success if already connected? if (singleInput() && connections.size() > 0) return false; // reject multiple connections on single-input ports if (auto o = owner.lock(), po = p->owner.lock(); !o || !po || po->dependsOn(o)) return false; // no dependency loops! // 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; } void Port::disconnect(std::shared_ptr p) { if (!p) return; 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() { connections.erase(std::remove_if(connections.begin(), connections.end(), [](auto w) { return !w.lock(); }), connections.end()); } std::shared_ptr Port::makePort(DataType dt) { if (dt == Audio) return std::make_shared(); if (dt == Command) return std::make_shared(); // fallback return std::make_shared(); } void Node::parentTo(std::shared_ptr graph) { auto t = shared_from_this(); // keep alive during reparenting if (auto p = parent.lock(); p) { onUnparent(p); p->children.erase(std::remove(p->children.begin(), p->children.end(), t), p->children.end()); } parent = graph; if (graph) { project = graph->project; 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) { auto& m = t == Port::Input ? inputs : outputs; if (auto mdt = m.find(dt); mdt != m.end()) { if (auto it = mdt->second.find(idx); it != mdt->second.end()) return it->second; } return addIfNeeded ? addPort(t, dt, idx) : nullptr; } std::shared_ptr Node::addPort(Port::Type t, Port::DataType dt, uint8_t idx) { auto& m = t == Port::Input ? inputs : outputs; m.try_emplace(dt); auto mdt = m.find(dt); if (mdt->second.find(idx) == mdt->second.end()) { auto p = Port::makePort(dt); p->owner = shared_from_this(); p->type = t; mdt->second.insert({idx, p}); p->index = idx; return p; } return nullptr; } void Node::removePort(Port::Type t, Port::DataType dt, uint8_t idx) { auto& m = t == Port::Input ? inputs : outputs; if (auto mdt = m.find(dt); mdt != m.end()) { mdt->second.erase(idx); } } std::unordered_set> Node::dependencies() const { std::unordered_set> set; 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) set.insert(n); } } } return set; } bool Node::dependsOn(std::shared_ptr o) { auto deps = dependencies(); for (auto d : deps) { if (d == o) return true; if (d->dependsOn(o)) return true; } 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; }