#ifndef FRAMELIB_BLOCK_H #define FRAMELIB_BLOCK_H #include "FrameLib_Types.h" #include "FrameLib_Context.h" #include "FrameLib_Parameters.h" #include #include #include #include /** @defgroup DSP Processing Objects */ /** @class FrameLib_Queueable @brief a template class for items that can be placed on a queue */ template class FrameLib_Queueable { public: FrameLib_Queueable() : mNext(nullptr) {} /** @class Queue @brief a single-threaded queue for non-recursive queuing of items for processing An item can only be in one position in a single queue at a time. */ class Queue { typedef void (T::*Method)(Queue *); public: Queue() : mFirst(nullptr), mTop(nullptr), mTail(nullptr) {} Queue(T *object, Method method) : mFirst(nullptr), mTop(nullptr), mTail(nullptr) { add(object); start(method); } // Non-copyable Queue(const Queue&) = delete; Queue& operator=(const Queue&) = delete; void add(T *object) { // Do not add if nullptr or re-add if already in queue if (!object || object->FrameLib_Queueable::mNext != nullptr) return; // Add to the top/tail of the queue depending on whether the queue is open if (mTop) { mTail->FrameLib_Queueable::mNext = object; mTail = object; } else mTop = mTail = object; } void start(Method method) { assert(!mFirst && "Can't restart queue"); mFirst = mTop; while (mTop) { T *object = mTop; (object->*method)(this); mTop = object->FrameLib_Queueable::mNext; object->FrameLib_Queueable::mNext = nullptr; } mFirst = mTail = nullptr; } T *getFirst() const { return mFirst; } private: T *mFirst; T *mTop; T *mTail; }; private: T *mNext; }; /** @class FrameLib_Connection @ingroup DSP @brief an abstract template class holds the connected object and IO indices for object connections of arbitrary type. @sa FrameLib_Block, FrameLib_DSP FrameLib_Multistream */ template struct FrameLib_Connection { FrameLib_Connection() : mObject(nullptr), mIndex(0) {} FrameLib_Connection(T *object, unsigned long index) : mObject(object), mIndex(index) {} friend bool operator == (const FrameLib_Connection& a, const FrameLib_Connection& b) { return a.mObject == b.mObject && a.mIndex == b.mIndex; } friend bool operator != (const FrameLib_Connection& a, const FrameLib_Connection& b) { return !(a == b); } T *mObject; U mIndex; }; /** @class FrameLib_Object @ingroup DSP @brief an abstract template class providing an interface for FrameLib objects and implementing connectivity. @sa FrameLib_Block, FrameLib_DSP FrameLib_Multistream */ template class FrameLib_Object : public FrameLib_Queueable { public: using Queue = typename FrameLib_Queueable::Queue; using Connection = FrameLib_Connection; // An allocator that you can pass to other objects/code whilst this object exists class Allocator { public: Allocator(FrameLib_Object& object) : mObject(object) {} template U *allocate(size_t N) { return mObject.alloc(N); } template void deallocate(U *& ptr) { mObject.dealloc(ptr); } private: FrameLib_Object &mObject; }; private: // A connector is a thing with one input and many outputs struct Connector { Connector() : mInternal(false) {} void addOut(Connection connection, bool setInternal) { mInternal = setInternal ? true : mInternal; addUniqueItem(mOut, connection); } void deleteOut(Connection connection, bool setInternal) { deleteUniqueItem(mOut, connection); mInternal = setInternal && !mOut.size() ? false : mInternal; } void clearOuts(bool setInternal) { mOut.clear(); mInternal = setInternal ? false : mInternal; } bool mInternal; Connection mIn; std::vector mOut; }; // Connector method typedef and kOrdering definition typedef Connector& (FrameLib_Object::*ConnectorMethod)(unsigned long); const unsigned long kOrdering = -1; public: // Constructor / Destructor FrameLib_Object(ObjectType type, FrameLib_Context context, FrameLib_Proxy *proxy) : mType(type), mContext(context), mAllocator(context), mProxy(proxy), mNumAudioChans(0), mSupportsOrderingConnections(false), mFeedback(false) {} virtual ~FrameLib_Object() { clearConnections(false); } // Object Type ObjectType getType() const { return mType; } // Context FrameLib_Context getContext() const { return mContext; } // Owner FrameLib_Proxy *getProxy() const { return mProxy; } // IO Queries unsigned long getNumIns() const { return static_cast(mInputConnections.size()); } unsigned long getNumOuts() const { return static_cast(mOutputConnections.size()); } unsigned long getNumAudioIns() const { return getType() != kOutput ? mNumAudioChans : 0; } unsigned long getNumAudioOuts() const { return getType() == kOutput ? mNumAudioChans : 0; } unsigned long getNumAudioChans() const { return mNumAudioChans; } // Set / Get Fixed Inputs virtual void setFixedInput(unsigned long idx, const double *input, unsigned long size) = 0; virtual const double *getFixedInput(unsigned long idx, unsigned long *size) = 0; // Audio Processing // Override to handle audio at the block level (reset called with the audio engine resets) virtual void blockUpdate(const double * const *ins, double **outs, unsigned long blockSize) = 0; virtual void reset(double samplingRate, unsigned long maxBlockSize) = 0; // Return to host to request to be passed audio static bool handlesAudio() { return false; } // Info virtual std::string objectInfo(bool verbose = false) { return "No object info available"; } virtual std::string inputInfo(unsigned long idx, bool verbose = false) { return "No input info available"; } virtual std::string outputInfo(unsigned long idx, bool verbose = false) { return "No output info available"; } virtual std::string audioInfo(unsigned long idx, bool verbose = false) { return "No audio channel info available"; } virtual FrameType inputType(unsigned long idx) const = 0; virtual FrameType outputType(unsigned long idx) const = 0; // N.B. Parameter objects can be queried directly for info virtual const FrameLib_Parameters *getParameters() const { return nullptr; } // Connection ConnectionResult addConnection(Connection connection, unsigned long inIdx) { ConnectionResult result = connectionCheck(connection, false); return (result == kConnectSuccess) ? changeConnection(connection, inIdx, true) : result; } void deleteConnection(unsigned long inIdx) { changeConnection(Connection(), inIdx, true); } ConnectionResult addOrderingConnection(Connection connection) { ConnectionResult result = connectionCheck(connection, true); if (result == kConnectSuccess) return addOrderingConnection(connection, true); return result; } void deleteOrderingConnection(Connection connection) { deleteOrderingConnection(connection, true); } void clearOrderingConnections() { clearOrderingConnections(true); } void clearConnections() { clearConnections(true); } // Aliasing ConnectionResult setInputAlias(Connection alias, unsigned long inIdx) { ConnectionResult result = connectionCheck(alias, false); if (result == kConnectSuccess) { changeConnection(Connection(), inIdx, false); changeAlias(&FrameLib_Object::getInputConnector, alias, inIdx, true); } return result; } ConnectionResult setOrderingAlias(T *alias) { ConnectionResult result = connectionCheck(Connection(alias, 0), true); if (result == kConnectSuccess) { clearOrderingConnections(false); changeOrderingAlias(alias, true); } return result; } ConnectionResult setOutputAlias(Connection alias, unsigned long outIdx) { ConnectionResult result = alias.mObject->connectionCheck(thisConnection(outIdx), false); if (result == kConnectSuccess) { clearOutput(outIdx); if (alias.mObject) alias.mObject->changeAlias(&FrameLib_Object::getOutputConnector, thisConnection(outIdx), alias.mIndex, true); } return result; } // Input Connection Queries bool isConnected(unsigned long inIdx) const { return getConnection(inIdx).mObject != nullptr; } Connection getConnection(unsigned long inIdx) const { return getConnection(inIdx, false); } bool supportsOrderingConnections() const { return mSupportsOrderingConnections; } Connection getOrderingConnection(unsigned long idx) const { return getOrderingConnection(idx, false); } unsigned long getNumOrderingConnections() const { return static_cast(traverseOrderingAliases()->mOrderingConnections.size()); } // Automatic Dependency Connections virtual void autoOrderingConnections() = 0; virtual void clearAutoOrderingConnections() = 0; // Connection Update void callConnectionUpdate() { Queue queue(static_cast(this), &T::FrameLib_Object::connectionUpdate); } template void addOutputDependencies(std::vector &dependencies) { for (unsigned long i = 0; i < getNumOuts(); i++) addOutputDependencies(dependencies, i); } template void addOutputDependencies(std::vector &dependencies, unsigned long outIdx) { addOutputDependencies>(dependencies, outIdx); } protected: // IO Connection Queries (protected) Connection getConnectionInternal(unsigned long inIdx) const { return getConnection(inIdx, true); } Connection getOrderingConnectionInternal(unsigned long idx) const { return getOrderingConnection(idx, true); } void addOutputDependencies(Queue *queue) { for (unsigned long i = 0; i < getNumOuts(); i++) addOutputDependencies(queue, i); } void addOutputDependencies(Queue *queue, unsigned long outIdx) { addOutputDependencies(queue, outIdx); } // IO Setup void setIO(unsigned long nIns, unsigned long nOuts, unsigned long nAudioChans = 0) { mNumAudioChans = nAudioChans; mInputConnections.resize((getType() == kScheduler || nIns) ? nIns : 1); mOutputConnections.resize(nOuts); } // Ordering Setup void enableOrderingConnections() { mSupportsOrderingConnections = true; } // Memory Allocation template U *alloc(size_t N) { return reinterpret_cast(mAllocator->alloc(sizeof(U) * N)); } template void dealloc(U *& ptr) { mAllocator->dealloc(ptr); ptr = nullptr; } void clearAllocator() { mAllocator->clear(); } FrameLib_LocalAllocator::Storage *registerStorage(const char *name) { return mAllocator->registerStorage(name); } void releaseStorage(FrameLib_LocalAllocator::Storage *&storage) { mAllocator->releaseStorage(storage->getName()); storage = nullptr; } // Info Helpers static const char *formatInfo(const char *verboseStr, const char *briefStr, bool verbose) { return verbose ? verboseStr : briefStr; } static std::string formatInfo(const char *str, unsigned long idx) { std::string info = str; std::string idxStr = numberedString("", idx + 1); for (size_t pos = info.find("#", 0); pos != std::string::npos; pos = info.find("#", pos + 1)) info.replace(pos, 1, idxStr); return info; } static std::string formatInfo(const char *verboseStr, const char *briefStr, unsigned long idx, bool verbose) { return formatInfo(formatInfo(verboseStr, briefStr, verbose), idx); } static std::string formatInfo(const char *str, const char *replaceStr) { std::string info = str; for (size_t pos = info.find("#", 0); pos != std::string::npos; pos = info.find("#", pos + 1)) info.replace(pos, 1, replaceStr); return info; } static std::string formatInfo(const char *verboseStr, const char *briefStr, const char *replaceStr, bool verbose) { return formatInfo(formatInfo(verboseStr, briefStr, verbose), replaceStr); } static std::string parameterInputInfo(bool verbose) { return formatInfo("Parameter Update - tagged input updates parameters", "Parameter Update", verbose); } // String With Number Helper static std::string numberedString(const char *str, unsigned long idx) { std::ostringstream outStr; outStr << str; outStr << idx; return outStr.str(); } // Unique List Helpers template static bool addUniqueItem(std::vector& list, U item) { if (std::find(list.begin(), list.end(), item) != list.end()) return false; list.push_back(item); return true; } template static bool deleteUniqueItem(std::vector& list, U item) { auto it = std::find(list.begin(), list.end(), item); if (it == list.end()) return false; list.erase(it); return true; } // Input getter helper for empty inputs const double *getEmptyFixedInput(unsigned long idx, unsigned long *size) { *size = 0; return nullptr; } private: // Connection Methods (private) Connector& getInputConnector(unsigned long idx) { return mInputConnections[idx]; } Connector& getOutputConnector(unsigned long idx) { return mOutputConnections[idx]; } Connector& getOrderingConnector(unsigned long idx) { return mOrderingConnector; } static bool isOutput(ConnectorMethod method) { return method == &FrameLib_Object::getOutputConnector; } Connection thisConnection(unsigned long idx) const { return Connection(static_cast(const_cast(this)), idx); } // Add to / Delete from a Connector's Output List void addToConnector(ConnectorMethod method, Connection connection, unsigned long idx, bool setInternal = false) { if (connection.mObject) (connection.mObject->*method)(connection.mIndex).addOut(thisConnection(idx), setInternal); } void deleteFromConnector(ConnectorMethod method, Connection connection, unsigned long idx, bool setInternal = false) { if (connection.mObject) (connection.mObject->*method)(connection.mIndex).deleteOut(thisConnection(idx), setInternal); } // Queue the Dependencies of a Vector of Connectors void queueConnectorVectorDependencies(Queue *queue, const std::vector& connectors) const { for (auto it = connectors.begin(); it != connectors.end(); it++) for (auto jt = it->mOut.begin(); jt != it->mOut.end(); jt++) queue->add(jt->mObject); } // Default Connection Update virtual void connectionUpdate(Queue *queue) { queueConnectorVectorDependencies(queue, mInputConnections); for (auto it = mOutputConnections.begin(); it != mOutputConnections.end(); it++) queue->add(it->mIn.mObject); }; // Input Connection Queries (with and without alias resolution) Connection getConnection(unsigned long inIdx, bool resolveAliases) const { Connection connection = traverseAliases(&FrameLib_Object::getInputConnector, inIdx); connection = connection.mObject->mInputConnections[connection.mIndex].mIn; if (resolveAliases && connection.mObject) connection = connection.mObject->traverseAliases(&FrameLib_Object::getOutputConnector, connection.mIndex); return connection; } Connection getOrderingConnection(unsigned long idx, bool resolveAliases) const { Connection connection = traverseOrderingAliases()->mOrderingConnections[idx]; if (resolveAliases && connection.mObject) connection = connection.mObject->traverseAliases(&FrameLib_Object::getOutputConnector, connection.mIndex); return connection; } // Connection Check ConnectionResult connectionCheck(Connection connection, bool ordering) { if (ordering && !supportsOrderingConnections()) return kConnectNoOrderingSupport; if (connection.mObject == this) return kConnectSelfConnection; if (connection.mObject->mContext != mContext) return kConnectWrongContext; if (detectFeedback(connection.mObject)) return kConnectFeedbackDetected; return kConnectSuccess; } // Notifications void notifySelf(bool notify, Queue *queue = nullptr) { if (notify) { if (queue) queue->add(dynamic_cast(const_cast(this))); else callConnectionUpdate(); } } void notifyConnectionsChanged(Connection connection, Queue *queue = nullptr) { if (connection.mObject) { if (queue) queue->add(connection.mObject); else connection.mObject->callConnectionUpdate(); } } void notifyAliasChanged(ConnectorMethod method, Connection connection, Queue *queue) { if (!connection.mObject) return; if (method == &FrameLib_Object::getOrderingConnector) { FrameLib_Object *object = connection.mObject->traverseOrderingAliases(); std::vector &connections = object->mOrderingConnections; for (auto it = connections.begin(); it != connections.end(); it++) notifyConnectionsChanged(*it, queue); } else { connection = connection.mObject->traverseAliases(method, connection.mIndex); notifyConnectionsChanged((connection.mObject->*method)(connection.mIndex).mIn, queue); } } // Change Input Connection ConnectionResult changeConnection(Connection connection, unsigned long inIdx, bool notify, Queue *queue = nullptr) { if (mInputConnections[inIdx].mIn == connection) return kConnectSuccess; if (mInputConnections[inIdx].mInternal || (connection.mObject && connection.mObject->mOutputConnections[connection.mIndex].mInternal)) return kConnectAliased; // Update all values (note the swap) std::swap(mInputConnections[inIdx].mIn, connection); deleteFromConnector(&FrameLib_Object::getOutputConnector, connection, inIdx); addToConnector(&FrameLib_Object::getOutputConnector, mInputConnections[inIdx].mIn, inIdx); // Notify of updates notifyConnectionsChanged(connection, queue); notifyConnectionsChanged(mInputConnections[inIdx].mIn, queue); notifySelf(notify, queue); return kConnectSuccess; } // Change Ordering Connection typedef bool ListMethod(std::vector&, Connection); typedef void (FrameLib_Object::*AlterMethod)(ConnectorMethod, Connection, unsigned long, bool); ConnectionResult changeOrderingConnection(Connection connection, ListMethod listUpdate, AlterMethod alterConnector, bool notify, Queue *queue) { if (!supportsOrderingConnections()) return kConnectNoOrderingSupport; if (mOrderingConnector.mInternal || connection.mObject->mOutputConnections[connection.mIndex].mInternal) return kConnectAliased; // Add / Delete and update all values if (!listUpdate(mOrderingConnections, connection)) return kConnectSuccess; (this->*alterConnector)(&FrameLib_Object::getOutputConnector, connection, kOrdering, false); // Notify (use queue to minimise calls, and ensure all changes are already complete) notifyConnectionsChanged(connection, queue); notifySelf(notify, queue); return kConnectSuccess; } ConnectionResult addOrderingConnection(Connection connection, bool notify, Queue *queue = nullptr) { return changeOrderingConnection(connection, &addUniqueItem, &FrameLib_Object::addToConnector, notify, queue); } void deleteOrderingConnection(Connection connection, bool notify, Queue *queue = nullptr) { changeOrderingConnection(connection, &deleteUniqueItem, &FrameLib_Object::deleteFromConnector, notify, queue); } // Clear Ordering Connections void clearOrderingConnections(bool notify, Queue *queue = nullptr) { if (!supportsOrderingConnections() || mOrderingConnector.mInternal) return; while (mOrderingConnections.size()) { // Update all values Connection connection = mOrderingConnections.back(); mOrderingConnections.pop_back(); deleteFromConnector(&FrameLib_Object::getOutputConnector, connection, kOrdering); // Notify notifyConnectionsChanged(connection, queue); } // Notify notifySelf(notify, queue); } // Clear output void clearOutput(unsigned long outIdx, Queue *queue = nullptr) { while (!mOutputConnections[outIdx].mInternal && mOutputConnections[outIdx].mOut.size()) { // Update all values Connection connection = mOutputConnections[outIdx].mOut.back(); mOutputConnections[outIdx].mOut.pop_back(); if (connection.mIndex == kOrdering) connection.mObject->deleteOrderingConnection(thisConnection(outIdx), false, queue); else connection.mObject->mInputConnections[connection.mIndex].mIn = Connection(); // Notify notifyConnectionsChanged(connection, queue); } } // Clear All Connections void clearConnections(bool notify) { // Clear input connections Queue queue; for (unsigned long i = 0; i < getNumIns(); i++) { changeConnection(Connection(), i, false, &queue); changeAlias(&FrameLib_Object::getInputConnector, Connection(), i, false, &queue); clearAliases(&FrameLib_Object::getInputConnector, i, &queue); } // Clear ordering connections clearOrderingConnections(false, &queue); changeOrderingAlias(nullptr, false, &queue); clearAliases(&FrameLib_Object::getOrderingConnector, kOrdering, &queue); // Clear outputs for (unsigned long i = 0; i < getNumOuts(); i++) { clearOutput(i, &queue); changeAlias(&FrameLib_Object::getOutputConnector, Connection(), i, false, &queue); clearAliases(&FrameLib_Object::getOutputConnector, i, &queue); } // Notify notifySelf(notify, &queue); queue.start(&T::FrameLib_Object::connectionUpdate); } // Add Output Dependencies void addDependency(Queue *queue) const { queue->add(dynamic_cast(const_cast(this))); } template void addDependency(std::vector& dependencies) const { U *object = dynamic_cast(const_cast(this)); if (object) addUniqueItem(dependencies, object); } template void traverseDependencies(U& dependencies, const Connector& connector, void (FrameLib_Object::*method)(U&, unsigned long) const) const { for (auto it = connector.mOut.begin(); it != connector.mOut.end(); it++) (it->mObject->*method)(dependencies, it->mIndex); } template void addOutputDependencies(U &dependencies, unsigned long outIdx) const { if (mOutputConnections[outIdx].mInternal) traverseDependencies(dependencies, mOutputConnections[outIdx], &FrameLib_Object::addOutputDependencies); else traverseDependencies(dependencies, mOutputConnections[outIdx], &FrameLib_Object::unwrapInputAliases); } template void unwrapInputAliases(U& dependencies, unsigned long inIdx) const { if (inIdx == kOrdering && mOrderingConnector.mOut.size()) traverseDependencies(dependencies, mOrderingConnector, &FrameLib_Object::unwrapInputAliases); else if (inIdx != kOrdering && mInputConnections[inIdx].mOut.size()) traverseDependencies(dependencies, mInputConnections[inIdx], &FrameLib_Object::unwrapInputAliases); else addDependency(dependencies); } // Aliasing Methods Connection traverseAliases(ConnectorMethod method, unsigned long idx) const { const Connector& connector = (const_cast(this)->*method)(idx); if ((!isOutput(method) && connector.mInternal) || (isOutput(method) && connector.mIn.mObject)) return connector.mIn.mObject->traverseAliases(method, connector.mIn.mIndex); return thisConnection(idx); } void changeAlias(ConnectorMethod method, Connection alias, unsigned long idx, bool notify, Queue *queue = nullptr) { Connector& connector = (this->*method)(idx); if (connector.mIn.mObject && !isOutput(method) && !connector.mInternal) return; // Update all values (note the swap) std::swap(connector.mIn, alias); connector.mInternal = isOutput(method) ? connector.mInternal : (bool) connector.mIn.mObject; deleteFromConnector(method, alias, idx, isOutput(method)); addToConnector(method, connector.mIn, idx, isOutput(method)); // Notify of updates notifyAliasChanged(method, alias, queue); notifyAliasChanged(method, connector.mIn, queue); notifySelf(notify, queue); } void clearAliases(ConnectorMethod method, unsigned long idx, Queue *queue) { // N.B. Queue pointer allows the pointer to be passed by reference (as required for the vector versions) Connector& connector = (this->*method)(idx); if (isOutput(method) && !connector.mInternal) return; // Create a list of dependencies if (isOutput(method)) addOutputDependencies(queue, idx); else unwrapInputAliases(queue, idx); // Remove from aliased objects and clear for (auto it = connector.mOut.begin(); it != connector.mOut.end(); it++) (it->mObject->*method)(it->mIndex).mIn = Connection(); connector.clearOuts(isOutput(method)); } // Simpler Ordering Calls FrameLib_Object *traverseOrderingAliases() const { return traverseAliases(&FrameLib_Object::getOrderingConnector, kOrdering).mObject; } void changeOrderingAlias(T *alias, bool notify, Queue *queue = nullptr) { changeAlias(&FrameLib_Object::getOrderingConnector, Connection(alias, kOrdering), kOrdering, notify, queue); } // Detect Potential Feedback in a Network bool detectFeedback(T *object) { object->mFeedback = false; Queue queue(static_cast(this), &T::feedbackProbe); return object->mFeedback; } void feedbackProbe(Queue *queue) { mFeedback = true; queueConnectorVectorDependencies(queue, mOutputConnections); } // Data const ObjectType mType; FrameLib_Context mContext; FrameLib_Context::Allocator mAllocator; FrameLib_Proxy *mProxy; // Audio IO Counts unsigned long mNumAudioChans; // Connections std::vector mInputConnections; std::vector mOutputConnections; std::vector mOrderingConnections; Connector mOrderingConnector; bool mSupportsOrderingConnections; bool mFeedback; }; /** @class FrameLib_Block @ingroup DSP @brief an abstract class that represents either a single FrameLib_DSP object, or a group of connected FrameLib_DSP objects. This abstract class provides a connectivity interface to FrameLib_DSP objects or blocks (groups of FrameLib_DSP objects). Most objects inherit this in the FrameLib_DSP class. Objects that have asynchronous outputs can use this class to host multiple FrameLib_DSP objects and alias the connections correctly. */ class FrameLib_Block : public FrameLib_Object { public: // Constructor / Destructor FrameLib_Block(ObjectType type, FrameLib_Context context, FrameLib_Proxy *proxy) : FrameLib_Object(type, context, proxy) {} virtual ~FrameLib_Block() {} // Stream Awareness virtual void setStream(void *streamOwner, unsigned long stream) {} }; #endif