diff --git a/include/FluidSCWrapper.hpp b/include/FluidSCWrapper.hpp index cc6c8eb..79f7d24 100644 --- a/include/FluidSCWrapper.hpp +++ b/include/FluidSCWrapper.hpp @@ -11,1534 +11,42 @@ under the European Union’s Horizon 2020 research and innovation programme #pragma once #include "SCBufferAdaptor.hpp" -#include -#include -#include -#include -#include -#include -#include +#include "wrapper/DeriveBaseClass.hpp" +#include "wrapper/NonRealtime.hpp" +#include "wrapper/Realtime.hpp" #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace fluid { -namespace client { - -template -class FluidSCWrapper; - - -template -struct WrapperState -{ - typename Client::ParamSetType params; - Client client; - Node* mNode; - std::atomic mCancelled{false}; - std::atomic mJobDone{false}; - std::atomic mHasTriggered{false}; - std::atomic mSynchronous{false}; - std::atomic mInNRT{false}; - std::atomic mNodeAlive{true}; - Result mResult{}; - - ~WrapperState() - { - if(!mJobDone && !mSynchronous && mHasTriggered) - { - std::cout << "Processing Cancelled" << std::endl; - } - } -}; - - -template -using SharedState = std::shared_ptr>; - -/// Named, shared clients already have a lookup table in their adaptor class -template -struct IsPersistent -{ - using type = std::false_type; -}; - -//TODO: make less tied to current implementation -template -struct IsPersistent>> -{ - using type = std::true_type; -}; - -template -using IsPersistent_t = typename IsPersistent::type; - -/// Models don't, but still need to survive CMD-. -template -struct IsModel -{ - using type = std::false_type; -}; - -template -struct IsModel>> -{ - using type = typename ClientWrapper::isModelObject; -}; - - -template -struct IsModel> -{ - using type = typename ClientWrapper::isModelObject; -}; - - -template -using IsModel_t = typename IsModel::type; - -namespace impl { - -template -struct AssignBuffer -{ - void operator()(const typename BufferT::type& p, World* w) - { - if (auto b = static_cast(p.get())) b->assignToRT(w); - } -}; - -template -struct CleanUpBuffer -{ - void operator()(const typename BufferT::type& p) - { - if (auto b = static_cast(p.get())) b->cleanUp(); - } -}; - - -// Iterate over kr/ir inputs via callbacks from params object -struct FloatControlsIter -{ - FloatControlsIter(float** vals, index N) : mValues(vals), mSize(N) {} - - float next() { return mCount >= mSize ? 0 : *mValues[mCount++]; } - - void reset(float** vals) - { - mValues = vals; - mCount = 0; - } - - index size() const noexcept { return mSize; } - index remain() { return mSize - mCount; } - -private: - float** mValues; - index mSize; - index mCount{0}; -}; - -//////////////////////////////////////////////////////////////////////////////// - -// Real Time Processor - -template -class RealTime : public SCUnit -{ - using HostVector = FluidTensorView; - using ParamSetType = typename Client::ParamSetType; - - template - struct doExpectedCount; - - template - struct doExpectedCount - { - static void count(const T& d,FloatControlsIter& c,Result& status) - { - if(!status.ok()) return; - - if(c.remain()) - { - index statedSize = d.fixedSize; - - if(c.remain() < statedSize) - status = {Result::Status::kError,"Ran out of arguments at ", d.name}; - - //fastforward - for(index i=0; i < statedSize; ++i) c.next(); - - } - } - }; - - template - struct doExpectedCount - { - static void count(const T& d,FloatControlsIter& c,Result& status) - { - if(!status.ok()) return; - - if(c.remain()) - { - index statedSize = static_cast(c.next()); - - if(c.remain() < statedSize) - status = {Result::Status::kError,"Ran out of arguments at ", d.name}; - - //fastforward - for(index i=0; i < statedSize; ++i) c.next(); - - } - } - }; - - - template - struct ExpectedCount{ - void operator ()(const T& descriptor,FloatControlsIter& c, Result& status) - { - doExpectedCount::value>::count(descriptor,c,status); - } - }; - - Result expectedSize(FloatControlsIter& controls) - { - if(controls.size() < Client::getParameterDescriptors().count()) - { - return {Result::Status::kError,"Fewer parameters than exepected. Got ", controls.size(), "expect at least", Client::getParameterDescriptors().count()}; - } - - Result countScan; - Client::getParameterDescriptors().template iterate( - std::forward(mWrapper->mControlsIterator), - std::forward(countScan)); - return countScan; - } - -public: - - static index ControlOffset(Unit* unit) { return unit->mSpecialIndex + 1; } - static index ControlSize(Unit* unit) { return static_cast(unit->mNumInputs) - unit->mSpecialIndex - 1 -(IsModel_t::value ? 1 : 0); } - - static void setup(InterfaceTable* ft, const char* name) - { - ft->fDefineUnitCmd(name, "latency", doLatency); - } - - static void doLatency(Unit* unit, sc_msg_iter*) - { - float l[]{ - static_cast(static_cast(unit)->client().latency()) - }; - auto ft = Wrapper::getInterfaceTable(); - - std::stringstream ss; - ss << '/' << Wrapper::getName() << "_latency"; - std::cout << ss.str() << std::endl; - ft->fSendNodeReply(&unit->mParent->mNode, -1, ss.str().c_str(), 1, l); - } - - RealTime() - {} - - void init() - { - - auto& client =static_cast(this)->client(); - assert( - !(client.audioChannelsOut() > 0 && client.controlChannelsOut() > 0) && - "Client can't have both audio and control outputs"); - - Result r; - if(!(r = expectedSize(mWrapper->mControlsIterator)).ok()) - { - mCalcFunc = Wrapper::getInterfaceTable()->fClearUnitOutputs; - std::cout - << "ERROR: " << Wrapper::getName() - << " wrong number of arguments." - << r.message() - << std::endl; - return; - } - - mWrapper->mControlsIterator.reset(mInBuf + mSpecialIndex + 1); - - client.sampleRate(fullSampleRate()); - mInputConnections.reserve(asUnsigned(client.audioChannelsIn())); - mOutputConnections.reserve(asUnsigned(client.audioChannelsOut())); - mAudioInputs.reserve(asUnsigned(client.audioChannelsIn())); - mOutputs.reserve(asUnsigned( - std::max(client.audioChannelsOut(), client.controlChannelsOut()))); - - for (index i = 0; i < client.audioChannelsIn(); ++i) - { - mInputConnections.emplace_back(isAudioRateIn(static_cast(i))); - mAudioInputs.emplace_back(nullptr, 0, 0); - } - - for (index i = 0; i < client.audioChannelsOut(); ++i) - { - mOutputConnections.emplace_back(true); - mOutputs.emplace_back(nullptr, 0, 0); - } - - for (index i = 0; i < client.controlChannelsOut(); ++i) - { mOutputs.emplace_back(nullptr, 0, 0); } - - mCalcFunc = make_calc_function(); - Wrapper::getInterfaceTable()->fClearUnitOutputs(this, 1); - } - - void next(int) - { - - auto& client = mWrapper->client(); - auto& params = mWrapper->params(); - const Unit* unit = this; - bool trig = IsModel_t::value ? !mPrevTrig && in0(0) > 0 : false; - bool shouldProcess = IsModel_t::value ? trig : true; - mPrevTrig = trig; - - if(shouldProcess) - { - mWrapper->mControlsIterator.reset(mInBuf + mSpecialIndex + - 1); // mClient.audioChannelsIn()); - Wrapper::setParams(mWrapper, - params, mWrapper->mControlsIterator); // forward on inputs N + audio inputs as params - params.constrainParameterValues(); - } - for (index i = 0; i < client.audioChannelsIn(); ++i) - { - if (mInputConnections[asUnsigned(i)]) - { mAudioInputs[asUnsigned(i)].reset(IN(i), 0, fullBufferSize()); } - } - for (index i = 0; i < client.audioChannelsOut(); ++i) - { - assert(i <= std::numeric_limits::max()); - if (mOutputConnections[asUnsigned(i)]) - mOutputs[asUnsigned(i)].reset(out(static_cast(i)), 0, - fullBufferSize()); - } - for (index i = 0; i < client.controlChannelsOut(); ++i) - { - assert(i <= std::numeric_limits::max()); - mOutputs[asUnsigned(i)].reset(out(static_cast(i)), 0, 1); - } - client.process(mAudioInputs, mOutputs, mContext); -// } - } -private: - std::vector mInputConnections; - std::vector mOutputConnections; - std::vector mAudioInputs; - std::vector mOutputs; - FluidContext mContext; - Wrapper* mWrapper{static_cast(this)}; - bool mPrevTrig; -}; - -//////////////////////////////////////////////////////////////////////////////// - -/// Non Real Time Processor -/// This is also a UGen, but the main action is delegated off to a worker -/// thread, via the NRT thread. The RT bit is there to allow us (a) to poll our -/// thread and (b) emit a kr progress update -template -class NonRealTime : public SCUnit -{ - using ParamSetType = typename Client::ParamSetType; -// using SharedState = std::shared_ptr>; -public: - - static index ControlOffset(Unit*) { return 0; } - static index ControlSize(Unit* unit) { return index(unit->mNumInputs) - unit->mSpecialIndex - 2 - (IsModel_t::value ? 1 : 0); } - - static void setup(InterfaceTable* ft, const char* name) - { - ft->fDefineUnitCmd(name, "cancel", doCancel); -// ft->fDefineUnitCmd( -// name, "queue_enabled", [](struct Unit* unit, struct sc_msg_iter* args) { -// auto w = static_cast(unit); -// w->mQueueEnabled = args->geti(0); -// w->mFifoMsg.Set( -// w->mWorld, -// [](FifoMsg* f) { -// auto w = static_cast(f->mData); -// w->client().setQueueEnabled(w->mQueueEnabled); -// }, -// nullptr, w); -// Wrapper::getInterfaceTable()->fSendMsgFromRT(w->mWorld, w->mFifoMsg); -// }); -// ft->fDefineUnitCmd( -// name, "synchronous", [](struct Unit* unit, struct sc_msg_iter* args) { -// auto w = static_cast(unit); -// w->mSynchronous = args->geti(0); -// w->mFifoMsg.Set( -// w->mWorld, -// [](FifoMsg* f) { -// auto w = static_cast(f->mData); -// w->client().setSynchronous(w->mSynchronous); -// }, -// nullptr, w); -// Wrapper::getInterfaceTable()->fSendMsgFromRT(w->mWorld, w->mFifoMsg); -// }); - } - - /// Penultimate input is the trigger, final is blocking mode. Neither are - /// params, so we skip them in the controlsIterator - NonRealTime() - : mSynchronous{mNumInputs > 2 ? (in0(int(mNumInputs) - 1) > 0) : false} - {} - - ~NonRealTime() - { - auto state = mWrapper->client().state(); - if (state == ProcessState::kProcessing) - { - std::cout << Wrapper::getName() << ": Processing cancelled" << std::endl; - Wrapper::getInterfaceTable()->fSendNodeReply(&mParent->mNode, 1, "/done", - 0, nullptr); - } - // processing will be cancelled in ~NRTThreadAdaptor() - } - - - /// No option of not using a worker thread for now - /// init() sets up the NRT process via the SC NRT thread, and then sets our - /// UGen calc function going - void init() - { - mWrapper->state()->mSynchronous = mSynchronous; - - mFifoMsg.Set(mWorld, initNRTJob, nullptr, this); - - // we want to poll thread roughly every 20ms - checkThreadInterval = static_cast(0.02 / controlDur()); - set_calc_function(); - Wrapper::getInterfaceTable()->fClearUnitOutputs(this, 1); - }; - - /// The calc function. Checks to see if we've cancelled, spits out progress, - /// launches tidy up when complete - void poll(int) - { - out0(0) = mDone ? 1.0f : static_cast(mWrapper->client().progress()); - - index triggerInput = static_cast(mInBuf[static_cast(mNumInputs) - mSpecialIndex - 2][0]); - bool trigger = (mPreviousTrigger <= 0) && triggerInput > 0; - mPreviousTrigger = triggerInput; - - - - auto& sharedState = mWrapper->state(); - mWrapper->mDone = sharedState->mJobDone; - if(trigger && !sharedState->mInNRT) - { - mWrapper->mControlsIterator.reset(mInBuf + ControlOffset(this)); - Wrapper::setParams(mWrapper, - mWrapper->params(), mWrapper->mControlsIterator); // forward on inputs N + audio inputs as params - mWrapper->params().constrainParameterValues(); - - sharedState->mInNRT = true; -// SharedState* statePtr = static_cast*>(mWorld->ft->fRTAlloc(mWorld, sizeof(SharedState))); - SharedState* statePtr = new SharedState(sharedState); -// statePtr = new (statePtr) SharedState(sharedState); - mFifoMsg.Set(mWorld, initNRTJob, [](FifoMsg* m){ - delete static_cast*>(m->mData); - }, statePtr); - mWorld->ft->fSendMsgFromRT(mWorld, mFifoMsg); - return; - } - - if (0 == pollCounter++ && !sharedState->mInNRT && sharedState->mHasTriggered) - { - sharedState->mInNRT = true; - -// SharedState* statePtr = static_cast*>(mWorld->ft->fRTAlloc(mWorld, sizeof(SharedState))); - SharedState* statePtr = new SharedState(sharedState); -// statePtr = new (statePtr) SharedState(sharedState); - mWorld->ft->fDoAsynchronousCommand(mWorld, nullptr, Wrapper::getName(), - statePtr, postProcess, exchangeBuffers, - tidyUp, destroy, 0, nullptr); - } - pollCounter %= checkThreadInterval; - } - - - /// To be called on NRT thread. Validate parameters and commence processing in - /// new thread - static void initNRTJob(FifoMsg* f) - { - if(!f->mData) return; - auto w = static_cast*>(f->mData); - SharedState& s = *w; - Result result = validateParameters(s->params); - - if (!result.ok()) - { - std::cout << "ERROR: " << Wrapper::getName() << ": " - << result.message().c_str() << std::endl; - } - else - { - s->client.setSynchronous(s->mSynchronous); - result = s->client.enqueue(s->params); - if (!result.ok()) - { - std::cout << "ERROR: " << Wrapper::getName() << ": " - << result.message().c_str() << std::endl; - } - else - { - s->mJobDone = false; - s->mCancelled = false; - s->mResult = s->client.process(); - s->mHasTriggered = true; - } - } - s->mInNRT = false; -// using namespace std; -// w->~shared_ptr>(); -// f->mWorld->ft->fRTFree(f->mWorld,w); -// delete w; - } - - /// Check result and report if bad - static bool postProcess(World*, void* data) - { - - if(!data) return false; - - auto& w = *static_cast*>(data); - Result r; - assert(w->mNode != nullptr) ; - w->mInNRT = true; - ProcessState s = w->client.checkProgress(r); - - if(w->mSynchronous) r = w->mResult; - - if ((s == ProcessState::kDone || s == ProcessState::kDoneStillProcessing) || - (w->mSynchronous && - s == ProcessState::kNoProcess)) // I think this hinges on the fact that - // when mSynchrous = true, this call - // will always be behind process() on - // the command FIFO, so we can assume - // that if the state is kNoProcess, it - // has run (vs never having run) - { - // Given that cancellation from the language now always happens by freeing - // the synth, this block isn't reached normally. HOwever, if someone - // cancels using u_cmd, this is what will fire - if (r.status() == Result::Status::kCancelled) - { - std::cout << Wrapper::getName() << ": Processing cancelled" - << std::endl; - w->mCancelled = true; - w->mHasTriggered = false; - w->mJobDone = true; - return false; - } - - w->client.checkProgress(r); - if(w->mSynchronous) r = w->mResult; - if (!r.ok()) - { - if(!w->mJobDone) Wrapper::printResult(w,r); - if(r.status() == Result::Status::kError) - { - w->mJobDone = true; - w->mHasTriggered = false; - return false; - } - } - w->mJobDone = true; - w->mHasTriggered = false; - return true; - } - return false; - } - - /// swap NRT buffers back to RT-land - static bool exchangeBuffers(World* world, void* data) - { - if(!data) return false; - SharedState& s = *(static_cast*>(data)); - if(!s->mNodeAlive) return false; - s->params.template forEachParamType(world); - // At this point, we can see if we're finished and let the language know (or - // it can wait for the doneAction, but that takes extra time) use replyID to - // convey status (0 = normal completion, 1 = cancelled) - if (s->mJobDone && !s->mCancelled) - world->ft->fSendNodeReply(s->mNode, 0, "/done", 0, nullptr); - if (s->mCancelled) - world->ft->fSendNodeReply(s->mNode, 1, "/done", 0, nullptr); - return true; - - } - - /// Tidy up any temporary buffers - static bool tidyUp(World* , void* data) - { - if(!data) return false; - SharedState& s = *(static_cast*>(data)); - s->params.template forEachParamType(); - return true; - } - - static void destroy(World* /*world*/, void* data) - { - if(!data) return; - auto& s = *static_cast*>(data); - s->mInNRT = false; -// using namespace std; -// s.~shared_ptr>(); -// world->ft->fRTFree(world,data); - delete static_cast*>(data); - } - - static void doCancel(Unit* unit, sc_msg_iter*) - { - static_cast(unit)->client().cancel(); - } - -private: - static Result validateParameters(ParamSetType& p) - { - auto results = p.constrainParameterValues(); - for (auto& r : results) - { - if (!r.ok()) return r; - } - return {}; - } - - template - struct AssignBuffer - { - void operator()(const typename BufferT::type& p, World* w) - { - if (auto b = static_cast(p.get())) b->assignToRT(w); - } - }; - - template - struct CleanUpBuffer - { - void operator()(const typename BufferT::type& p) - { - if (auto b = static_cast(p.get())) b->cleanUp(); - } - }; - - FifoMsg mFifoMsg; - char* mCompletionMessage = nullptr; - void* mReplyAddr = nullptr; - const char* mName = nullptr; - index checkThreadInterval; - index pollCounter{0}; - index mPreviousTrigger{0}; - - bool mSynchronous{true}; - Wrapper* mWrapper{static_cast(this)}; - Result mResult; -}; - -//////////////////////////////////////////////////////////////////////////////// - -/// An impossible monstrosty -template -class NonRealTimeAndRealTime : public RealTime, - public NonRealTime -{ - static void setup(InterfaceTable* ft, const char* name) - { - RealTime::setup(ft, name); - NonRealTime::setup(ft, name); - } -}; - -//////////////////////////////////////////////////////////////////////////////// - -//Discovery for clients that need persistent storage (Dataset and friends) - - - -template -struct LifetimePolicy; - -//template -//struct LifetimePolicy -//{ -//// static_assert(false,"Shared Objecthood and Model Objecthood are not compatible"); -//}; - -/// Default policy -template -struct LifetimePolicy -{ - static void constructClass(Unit* unit) - { - FloatControlsIter controlsReader{unit->mInBuf + Wrapper::ControlOffset(unit),Wrapper::ControlSize(unit)}; - auto params = typename Wrapper::ParamSetType{Client::getParameterDescriptors()}; - Wrapper::setParams(unit, params, controlsReader,true,false); - Client client{params}; - new (static_cast(unit)) Wrapper(std::move(controlsReader), std::move(client), std::move(params)); - } - static void destroyClass(Unit* unit) { static_cast(unit)->~Wrapper(); } - static void setup(InterfaceTable*, const char*){} -}; - -/// Model objects -template -struct LifetimePolicy -{ - - index uid; - - struct CacheRecord - { - typename Client::ParamSetType params{Client::getParameterDescriptors()}; - Client client{params}; - bool leased{false}; - }; - - using Cache = std::unordered_map; - - static void constructClass(Unit* unit) - { - index uid = static_cast(unit->mInBuf[Wrapper::ControlOffset(unit)+Wrapper::ControlSize(unit)][0]); - - FloatControlsIter controlsReader{unit->mInBuf + Wrapper::ControlOffset(unit),Wrapper::ControlSize(unit)}; - auto& entry = mRegistry[uid]; - auto& client = entry.client; - auto& params = entry.params; - if(entry.leased) //if this happens, then the client has probably messed up - { - std::cout << "ERROR: ID " << uid << "is already being used by the cache" << std::endl; - return; - } - Wrapper::setParams(unit, entry.params,controlsReader,true,false); - new (static_cast(unit)) Wrapper{std::move(controlsReader),std::move(client),std::move(params)}; - static_cast(unit)->uid = uid; - entry.leased = true; - } - - static void destroyClass(Unit* unit) - { - auto wrapper = static_cast(unit); - index uid = wrapper->uid; - auto pos = mRegistry.find(uid); - if( pos != mRegistry.end() ) - { - //on cmd-. live to fight another day - auto& entry = *pos; - entry.second.client = std::move(wrapper->client()); - entry.second.params = std::move(wrapper->params()); - entry.second.leased = false; - } - wrapper->~Wrapper(); - } - - static void setup(InterfaceTable* ft, const char* name) - { - auto freeName = std::stringstream(); - freeName << "free" << name; - - ft->fDefinePlugInCmd(freeName.str().c_str(), - [](World*,void*,sc_msg_iter* args, void*/*replyAddr*/) - { - auto objectID = args->geti(); - auto pos = mRegistry.find(objectID); - if(pos != mRegistry.end()) mRegistry.erase(objectID); - }, &mRegistry); - } - -private: - static std::unordered_map mRegistry; -}; - -template -typename LifetimePolicy::Cache - LifetimePolicy::mRegistry{}; - - -/// Shared objects -template -struct LifetimePolicy -{ - - template struct GetSharedType; - - template - struct GetSharedType>> - { - using type = NRTSharedInstanceAdaptor; - }; - - using SharedType = typename GetSharedType::type; - using ClientPointer = typename SharedType::ClientPointer; - using ParamType = typename Client::ParamSetType; - - static void constructClass(Unit* unit) - { - - FloatControlsIter controlsReader{unit->mInBuf + Wrapper::ControlOffset(unit),Wrapper::ControlSize(unit)}; - - auto params = typename Client::ParamSetType{Client::getParameterDescriptors()}; - Wrapper::setParams(unit, params,controlsReader,true,false); - - auto& name = params.template get<0>(); - - mParamsRegistry.emplace(name, ParamType{params}); - mClientRegistry.emplace(name, Client{params}); - - auto client = Client{params}; - new (static_cast(unit)) Wrapper(std::move(controlsReader),std::move(client),std::move(params)); - - } - static void destroyClass(Unit* unit) { - static_cast(unit)->~Wrapper(); - } - - static void setup(InterfaceTable* ft, const char* name) - { - - auto freeName = std::stringstream(); - freeName << "free" << name; - - ft->fDefinePlugInCmd(freeName.str().c_str(), - [](World*,void*,sc_msg_iter* args, void* /*replyAddr*/) - { - auto objectName = std::string(args->gets()); - mClientRegistry.erase(objectName); - mParamsRegistry.erase(objectName); - }, &mClientRegistry); - - } -private: - static ClientPointer getClientPointer(Wrapper* wrapper) - { - auto& params = wrapper->params(); - auto name = params.template get<0>(); - return SharedType::lookup(name); - } - - static std::unordered_map mClientRegistry; - static std::unordered_map mParamsRegistry; -}; - -template -std::unordered_map - LifetimePolicy::mClientRegistry{}; - -template -std::unordered_map::ParamType> - LifetimePolicy::mParamsRegistry{}; -//// Template Specialisations for NRT/RT - -template -class FluidSCWrapperImpl; - -template -class FluidSCWrapperImpl - : public NonRealTime, - public LifetimePolicy, IsPersistent_t> -{ -public: - void init(){ - NonRealTime::init(); - } - static void setup(InterfaceTable* ft, const char* name) - { - NonRealTime::setup(ft,name); - LifetimePolicy, IsPersistent_t>::setup(ft,name); - } -}; - -template -class FluidSCWrapperImpl - : public RealTime, - public LifetimePolicy, IsPersistent_t> -{ -public: - void init(){ - RealTime::init(); - } - static void setup(InterfaceTable* ft, const char* name) - { - RealTime::setup(ft,name); - LifetimePolicy, IsPersistent_t>::setup(ft,name); - } -}; - -//////////////////////////////////////////////////////////////////////////////// - -// Make base class(es), full of CRTP mixin goodness -template -using FluidSCWrapperBase = FluidSCWrapperImpl, - typename Client::isNonRealTime, - typename Client::isRealTime>; - -} // namespace impl - -//////////////////////////////////////////////////////////////////////////////// +namespace fluid { +namespace client { /// The main wrapper template class FluidSCWrapper : public impl::FluidSCWrapperBase { - using FloatControlsIter = impl::FloatControlsIter; - -// using SharedState = std::shared_ptr>; + //I would like to template these to something more scaleable, but baby steps friend class impl::RealTime; friend class impl::NonRealTime; - template - struct ParamReader - { - - static const char* oscTagToString(char tag) - { - switch (tag) - { - case 'i': return "integer"; break; - case 'f': return "float"; break; - case 'd': return "double"; break; - case 's': return "string"; break; - case 'b': return "blob"; break; - case 't': return "time tag"; break; - default: return "unknown type"; - } - } - - static const char* argTypeToString(std::string&) - { - return "string"; - } - - template - static std::enable_if_t::value, const char*> - argTypeToString(T&) - { - return "integer"; - } - - template - static std::enable_if_t::value, const char*> - argTypeToString(T&) - { - return "float"; - } - - static const char* argTypeToString(BufferT::type&) - { - return "buffer"; - } - - static const char* argTypeToString(InputBufferT::type&) - { - return "buffer"; - } - - template - static std::enable_if_t::value,const char*> - argTypeToString(P&) - { - return "shared_object"; //not ideal - } - - static bool argTypeOK(std::string&, char tag) - { - return tag == 's'; - } - - template - static std::enable_if_t::value - || std::is_floating_point::value, bool> - argTypeOK(T&, char tag) - { - return tag == 'i' || tag == 'f' || tag == 'd'; - } - - static bool argTypeOK(BufferT::type&, char tag) - { - return tag == 'i'; - } - - static bool argTypeOK(InputBufferT::type&, char tag) - { - return tag == 'i'; - } - - template - static std::enable_if_t::value,bool> - argTypeOK(P&, char tag) - { - return tag == 's'; - } - - static auto fromArgs(Unit*, sc_msg_iter* args, std::string, int) - { - const char* recv = args->gets(""); - - return std::string(recv ? recv : ""); - } - - static auto fromArgs(Unit* /*x*/, FloatControlsIter& args, std::string, int) - { - // first is string size, then chars - index size = static_cast(args.next()); - -// auto ft = FluidSCWrapper::getInterfaceTable(); -// auto w = x->mWorld; -// char* chunk = -// static_cast(ft->fRTAlloc(w, asUnsigned(size + 1))); - std::string res; - res.resize(asUnsigned(size)); - -// if (!chunk) -// { -// std::cout << "ERROR: " << FluidSCWrapper::getName() -// << ": RT memory allocation failed\n"; -// return std::string{""}; -// } - - for (index i = 0; i < size; ++i) - res[asUnsigned(i)] = static_cast(args.next()); - -// res[size] = 0; // terminate string -// auto res = std::string{chunk}; -// ft->fRTFree(w,chunk); - return res; - } - - static auto fromArgs(Unit*, FloatControlsIter& args,typename LongArrayT::type&, int) - { - //first is array size, then items - - using Container = typename LongArrayT::type; - using Value = typename Container::type; - - index size = static_cast(args.next()); - - Container res(size); - - for (index i = 0; i < size; ++i) - res[i] = static_cast(args.next()); - - return res; - } - - - template - static std::enable_if_t::value, T> - fromArgs(Unit*, FloatControlsIter& args, T, int) - { - return static_cast(args.next()); - } - - template - static std::enable_if_t::value, T> - fromArgs(Unit*, FloatControlsIter& args, T, int) - { - return args.next(); - } - - template - static std::enable_if_t::value, T> - fromArgs(Unit*, sc_msg_iter* args, T, int defVal) - { - return args->geti(defVal); - } - - template - static std::enable_if_t::value, T> - fromArgs(Unit*, sc_msg_iter* args, T, int) - { - return args->getf(); - } - - static SCBufferAdaptor* fetchBuffer(Unit* x, index bufnum) - { - if(bufnum >= x->mWorld->mNumSndBufs) - { - index localBufNum = bufnum - x->mWorld->mNumSndBufs; - - Graph* parent = x->mParent; - - return localBufNum <= parent->localMaxBufNum ? - new SCBufferAdaptor(parent->mLocalSndBufs + localBufNum,x->mWorld,true) - : nullptr; - } - else - return bufnum >= 0 ? new SCBufferAdaptor(bufnum, x->mWorld) : nullptr; - } - - static auto fromArgs(Unit* x, ArgType args, BufferT::type&, int) - { - typename LongT::type bufnum = static_cast( - ParamReader::fromArgs(x, args, typename LongT::type(), -1)); - return BufferT::type(fetchBuffer(x, bufnum)); - } - - static auto fromArgs(Unit* x, ArgType args, InputBufferT::type&, int) - { - typename LongT::type bufnum = - static_cast(fromArgs(x, args, LongT::type(), -1)); - return InputBufferT::type(fetchBuffer(x, bufnum)); - } - - template - static std::enable_if_t::value, P> - fromArgs(Unit* x, ArgType args, P&, int) - { - return {fromArgs(x, args, std::string{}, 0).c_str()}; - } - }; - - - // Iterate over arguments via callbacks from params object - template - struct Setter - { - static constexpr index argSize = - C::getParameterDescriptors().template get().fixedSize; - - typename T::type operator()(Unit* x, ArgType args) - { - // Just return default if there's nothing left to grab - if (args.remain() == 0) - { - std::cout << "WARNING: " << getName() - << " received fewer parameters than expected\n"; - return C::getParameterDescriptors().template makeValue(); - } - - ParamLiteralConvertor a; - using LiteralType = - typename ParamLiteralConvertor::LiteralType; - - for (index i = 0; i < argSize; i++) - a[i] = static_cast( - ParamReader::fromArgs(x, args, a[0], 0)); - - return a.value(); - } - }; - - template - using ArgumentSetter = Setter; - - template - using ControlSetter = Setter; - - // CryingEmoji.png: SC API hides all the useful functions for sending - // replies back to the language with things like, uh, strings and stuff. - // We have Node_SendReply, which assumes you are sending an array of float, - // and must be called only from the RT thread. Thanks. - // So, we do in reverse what the SendReply Ugen does, and parse - // an array of floats as characters in the language. VomitEmoji.png - - struct ToFloatArray - { - static index allocSize(typename BufferT::type) { return 1; } - - template - static std::enable_if_t< - std::is_integral::value || std::is_floating_point::value, index> - allocSize(T) - { - return 1; - } - - static index allocSize(std::string s) - { - return asSigned(s.size()) + 1; - } // put null char at end when we send - - static index allocSize(FluidTensor s) - { - index count = 0; - for (auto& str : s) count += (str.size() + 1); - return count; - } - - template - static index allocSize(FluidTensor s) - { - return s.size(); - } - - template - static std::tuple, index> - allocSize(std::tuple&& t) - { - return allocSizeImpl(std::forward(t), - std::index_sequence_for()); - }; - - template - static std::tuple, index> - allocSizeImpl(std::tuple&& t, std::index_sequence) - { - index size{0}; - std::array res; - (void) std::initializer_list{ - (res[Is] = size, size += ToFloatArray::allocSize(std::get(t)), - 0)...}; - return std::make_tuple(res, - size); // array of offsets into allocated buffer & - // total number of floats to alloc - }; - - static void convert(float* f, typename BufferT::type buf) - { - f[0] = static_cast(buf.get())->bufnum(); - } - - template - static std::enable_if_t::value || - std::is_floating_point::value> - convert(float* f, T x) - { - f[0] = static_cast(x); - } - - static void convert(float* f, std::string s) - { - std::copy(s.begin(), s.end(), f); - f[s.size()] = 0; // terminate - } - static void convert(float* f, FluidTensor s) - { - for (auto& str : s) - { - std::copy(str.begin(), str.end(), f); - f += str.size(); - *f++ = 0; - } - } - template - static void convert(float* f, FluidTensor s) - { - static_assert(std::is_convertible::value, - "Can't convert this to float output"); - std::copy(s.begin(), s.end(), f); - } - - template - static void convert(float* f, std::tuple&& t, - std::array offsets, - std::index_sequence) - { - (void) std::initializer_list{ - (convert(f + offsets[Is], std::get(t)), 0)...}; - } - }; - - - // So, to handle a message to a plugin we will need to - // (1) Launch the invovation of the message on the SC NRT Queue using FIFO - // Message (2) Run the actual function (maybe asynchronously, in our own - // thread) (3) Launch an asynchronous command to send the reply back (in Stage - // 3) - - template - struct MessageDispatch - { - static constexpr size_t Message = N; - SharedState state; - ArgTuple args; - Ret result; - std::string name; - }; - - // Sets up a single /u_cmd - template - struct SetupMessage - { - void operator()(const T& message) - { - auto ft = getInterfaceTable(); - ft->fDefineUnitCmd(getName(), message.name, launchMessage); - } - }; - - template - static void launchMessage(Unit* u, sc_msg_iter* args) - { - FluidSCWrapper* x = static_cast(u); - using IndexList = - typename Client::MessageSetType::template MessageDescriptorAt< - N>::IndexList; - if(!x->init) - { - std::cout << "ERROR: Synth constructor not yet called" << std::endl; - return; - } - - launchMessageImpl(x, args, IndexList()); - } - - template - static void launchMessageImpl(FluidSCWrapper* x, sc_msg_iter* inArgs, - std::index_sequence) - { - x->mControlsIterator.reset(x->mInBuf + FluidSCWrapper::ControlOffset(x)); - setParams(x, x->params(),x->mControlsIterator, true); - - using MessageDescriptor = - typename Client::MessageSetType::template MessageDescriptorAt; - using ArgTuple = typename MessageDescriptor::ArgumentTypes; - using ReturnType = typename MessageDescriptor::ReturnType; - using IndexList = typename MessageDescriptor::IndexList; - using MessageData = MessageDispatch; - auto ft = getInterfaceTable(); -// void* msgptr = ft->fRTAlloc(x->mWorld, sizeof(MessageData)); -// MessageData* msg = new (msgptr) MessageData; - MessageData* msg = new MessageData(); - msg->name = '/' + Client::getMessageDescriptors().template name(); - msg->state = x->state(); - ArgTuple& args = msg->args; - // type check OSC message - - std::string tags(inArgs->tags + inArgs->count);//evidently this needs commenting: construct string at pointer offset by tag count, to pick up args - bool willContinue = true; - bool typesMatch = true; - - constexpr size_t expectedArgCount = std::tuple_size::value; - - if(tags.size() > expectedArgCount) - { - std::cout << "WARNING: " << msg->name << " received more arguments than expected (got " - << tags.size() << ", expect " << expectedArgCount << ")\n"; - } - - if(tags.size() < expectedArgCount) - { - std::cout << "ERROR: " << msg->name << " received fewer arguments than expected (got " - << tags.size() << ", expect " << expectedArgCount << ")\n"; - willContinue = false; - } - - auto tagsIter = tags.begin(); - auto tagsEnd = tags.end(); - ForEach(args,[&typesMatch,&tagsIter,&tagsEnd](auto& arg){ - if(tagsIter == tagsEnd) - { - typesMatch = false; - return; - } - char t = *(tagsIter++); - typesMatch = typesMatch && ParamReader::argTypeOK(arg,t); - }); - - willContinue = willContinue && typesMatch; - - if(!typesMatch) - { - auto& report = std::cout; - report << "ERROR: " << msg->name << " type signature incorrect.\nExpect: ("; - size_t i{0}; - ForEach(args, [&i](auto& x){ - std::cout << ParamReader::argTypeToString(x); - if(i < (std::tuple_size::value - 1 ) ) - { - std::cout << " ,"; - } - i++; - }); - report << ")\nReceived: ("; - i = 0; - for(auto t: tags) - { - report << ParamReader::oscTagToString(t); - if( i < ( tags.size() - 1 ) ) - { - report << ", "; - } - i++; - } - report << ")\n"; - } - - if(!willContinue) - { -// msg->~MessageData(); -// ft->fRTFree(x->mWorld, msgptr); - delete msg; - return; - } - ForEach(args,[x,&inArgs](auto& arg){ - arg = ParamReader::fromArgs(x, inArgs,arg,0); - }); - - x->client().setParams(x->params()); - - ft->fDoAsynchronousCommand( - x->mWorld, nullptr, getName(), msg, - [](World*, void* data) // NRT thread: invocation - { - MessageData* m = static_cast(data); - m->result = - ReturnType{invokeImpl(m->state, m->args, IndexList{})}; - - if (!m->result.ok()) - printResult(m->state, m->result); - - return true; - }, - [](World* world, void* data) // RT thread: response - { - MessageData* m = static_cast(data); - MessageDescriptor::template forEachArg(m->args, - world); - - if(m->result.status() != Result::Status::kError) - messageOutput(m->state, m->name, m->result); - else - { - auto ft = getInterfaceTable(); - if(m->state->mNodeAlive) - ft->fSendNodeReply(m->state->mNode, - -1, m->name.c_str(),0, nullptr); - } - return true; - }, - nullptr, // NRT Thread: No-op - [](World* /*w*/, void* data) // RT thread: clean up - { - MessageData* m = static_cast(data); -// m->~MessageData(); -// getInterfaceTable()->fRTFree(w, data); - delete m; - }, - 0, nullptr); - } - - template // Call from NRT - static decltype(auto) invokeImpl(SharedState& x, ArgsTuple& args, - std::index_sequence) - { - return x->client.template invoke(x->client, std::get(args)...); - } - - template // call from RT - static void messageOutput(SharedState& x, const std::string& s, - MessageResult& result) - { - auto ft = getInterfaceTable(); - if(!x->mNodeAlive) return; - - // allocate return values - index numArgs = ToFloatArray::allocSize(static_cast(result)); - - if(numArgs > 2048) - { - std::cout << "ERROR: Message response too big to send (" << asUnsigned(numArgs) * sizeof(float) << " bytes)." << std::endl; - return; - } - -// float* values = static_cast( -// ft->fRTAlloc(x->mNode->mWorld, asUnsigned(numArgs) * sizeof(float))); - - float* values = new float[asUnsigned(numArgs)]; - - // copy return data - ToFloatArray::convert(values, static_cast(result)); - - ft->fSendNodeReply(x->mNode, -1, s.c_str(), - static_cast(numArgs), values); - -// ft->fRTFree(x->mNode->mWorld,values); - delete[] values; - } - - static void messageOutput(SharedState& x, const std::string& s, - MessageResult&) - { - auto ft = getInterfaceTable(); - if(!x->mNodeAlive) return; - ft->fSendNodeReply(x->mNode, -1, s.c_str(), 0, nullptr); - } - - template - static void messageOutput(SharedState& x, const std::string& s, - MessageResult>& result) - { - auto ft = getInterfaceTable(); - std::array offsets; - index numArgs; - if(!x->mNodeAlive) return; - - std::tie(offsets, numArgs) = - ToFloatArray::allocSize(static_cast>(result)); - - if(numArgs > 2048) - { - std::cout << "ERROR: Message response too big to send (" << asUnsigned(numArgs) * sizeof(float) << " bytes)." << std::endl; - return; - } - -// float* values = static_cast( -// ft->fRTAlloc(x->mNode->mWorld, asUnsigned(numArgs) * sizeof(float))); - - float* values = new float[asUnsigned(numArgs)]; - ToFloatArray::convert(values, std::tuple(result), offsets, - std::index_sequence_for()); - - ft->fSendNodeReply(x->mNode, -1, s.c_str(), - static_cast(numArgs), values); - -// ft->fRTFree(x->mNode->mWorld,values); - delete[] values; - } - - static void doVersion(Unit*, sc_msg_iter*) { std::cout << "Fluid Corpus Manipualtion Toolkit version " << fluidVersion() << std::endl; } - bool init{false}; + bool mInit{false}; public: - using Client = C; - using ParamSetType = typename C::ParamSetType; - - FluidSCWrapper(FloatControlsIter&& i, Client&& c, ParamSetType&& p): - mControlsIterator{std::move(i)}, - mState{new WrapperState{std::move(p),std::move(c),&SCUnit::mParent->mNode}} - { - client().setParams(params()); //<-IMPORTANT: client's ref to params is by address, and this has just changed - impl::FluidSCWrapperBase::init(); - init = true; - } - - ~FluidSCWrapper() - { - mState->mNodeAlive = false; - } + template + using ArgumentSetter = typename ClientParams::template Setter; - SharedState& state() { return mState; } + template + using ControlSetter = typename ClientParams::template Setter; + using Client = C; + using ParamSetType = typename C::ParamSetType; static const char* getName(const char* setName = nullptr) { @@ -1556,9 +64,7 @@ public: { getName(name); getInterfaceTable(ft); - registerUnit(ft, name); impl::FluidSCWrapperBase::setup(ft, name); - Client::getMessageDescriptors().template iterate(); ft->fDefineUnitCmd(name, "version", doVersion); } @@ -1577,26 +83,27 @@ public: { for(auto& r:*reportage) { - if(!r.ok()) printResult(&x->mParent->mNode, r); + if(!r.ok()) printResult(x->mParent->mNode.mWorld, r); } } if(!initialized) delete reportage; return p; } - static void printResult(SharedState& x, Result& r) - { - if (!x.get() || !x->mNodeAlive) return; - FluidSCWrapper::printResult(x->mNode, r); - } +// static void printResult(SharedState& x, Result& r) +// { +// if (!x.get() || !x->mNodeAlive) return; +// FluidSCWrapper::printResult(x->mNode->mWorld, r); +// } - static void printResult(Node* x, Result& r) + static void printResult(World* w,Result& r) { + switch (r.status()) { case Result::Status::kWarning: { - if (x->mWorld->mVerbosity > 0) + if (!w || w->mVerbosity > 0) std::cout << "WARNING: " << r.message().c_str() << '\n'; break; } @@ -1615,20 +122,9 @@ public: } } } - - auto& client() { return mState->client; } - auto& params() { return mState->params; } private: - - static void registerUnit(InterfaceTable* ft, const char* name) { - UnitCtorFunc ctor =impl::FluidSCWrapperBase::constructClass; - UnitDtorFunc dtor = impl::FluidSCWrapperBase::destroyClass; - (*ft->fDefineUnit)(name, sizeof(FluidSCWrapper), ctor, dtor, 0); - } std::array mReportage; - FloatControlsIter mControlsIterator; - std::shared_ptr> mState; }; template diff --git a/include/wrapper/ArgsFromClient.hpp b/include/wrapper/ArgsFromClient.hpp new file mode 100644 index 0000000..88ae458 --- /dev/null +++ b/include/wrapper/ArgsFromClient.hpp @@ -0,0 +1,337 @@ +#pragma once + +#include "Meta.hpp" + +namespace fluid { +namespace client { + +namespace impl { + // Iterate over kr/ir inputs via callbacks from params object + struct FloatControlsIter + { + FloatControlsIter(float** vals, index N) : mValues(vals), mSize(N) {} + + float next() { return mCount >= mSize ? 0 : *mValues[mCount++]; } + + void reset(float** vals) + { + mValues = vals; + mCount = 0; + } + + index size() const noexcept { return mSize; } + index remain() { return mSize - mCount; } + + private: + float** mValues; + index mSize; + index mCount{0}; + }; +} //impl + +//Specializations of param reader for RT and NRT cases (data encoded differently, buffer semantics differ cause of local bufs) +template struct ParamReader; + +// RT case: we're decoding data from float**, there will be a Unit, we can have LocalBufs +// TODO: All the allocations should be using SC RT allocator, but this won't work reliably until it propagates down through the param set +template<> +struct ParamReader +{ + + using Controls = impl::FloatControlsIter; + + static auto fromArgs(Unit* /*x*/, Controls& args, std::string, int) + { + // first is string size, then chars + index size = static_cast(args.next()); + std::string res; + res.resize(asUnsigned(size)); + for (index i = 0; i < size; ++i) + res[asUnsigned(i)] = static_cast(args.next()); + return res; + } + + static auto fromArgs(Unit*, Controls& args,typename LongArrayT::type&, int) + { + //first is array size, then items + using Container = typename LongArrayT::type; + using Value = typename Container::type; + index size = static_cast(args.next()); + Container res(size); + for (index i = 0; i < size; ++i) + res[i] = static_cast(args.next()); + return res; + } + + template + static std::enable_if_t::value, T> + fromArgs(Unit*, Controls& args, T, int) + { + return static_cast(args.next()); + } + + template + static std::enable_if_t::value, T> + fromArgs(Unit*, Controls& args, T, int) + { + return args.next(); + } + + static SCBufferAdaptor* fetchBuffer(Unit* x, index bufnum) + { + if(bufnum >= x->mWorld->mNumSndBufs) + { + index localBufNum = bufnum - x->mWorld->mNumSndBufs; + + Graph* parent = x->mParent; + + return localBufNum <= parent->localMaxBufNum ? + new SCBufferAdaptor(parent->mLocalSndBufs + localBufNum,x->mWorld,true) + : nullptr; + } + else + return bufnum >= 0 ? new SCBufferAdaptor(bufnum, x->mWorld) : nullptr; + } + + static auto fromArgs(Unit* x, Controls& args, BufferT::type&, int) + { + typename LongT::type bufnum = static_cast( + ParamReader::fromArgs(x, args, typename LongT::type(), -1)); + return BufferT::type(fetchBuffer(x, bufnum)); + } + + static auto fromArgs(Unit* x, Controls& args, InputBufferT::type&, int) + { + typename LongT::type bufnum = + static_cast(fromArgs(x, args, LongT::type(), -1)); + return InputBufferT::type(fetchBuffer(x, bufnum)); + } + + template + static std::enable_if_t::value, P> + fromArgs(Unit* x, Controls& args, P&, int) + { + auto id = fromArgs(x, args, index{}, 0); + return {id >= 0 ? std::to_string(id).c_str() : "" }; + } +}; + +// NRT case: we're decoding data from sc_msg_iter*, there will be a World*, we can't have LocalBufs +// TODO: All the allocations should be using SC RT allocator (I guess: this will probably always run on the RT thread), but this won't work reliably until it propagates down through the param set +template<> +struct ParamReader +{ + static const char* oscTagToString(char tag) + { + switch (tag) + { + case 'i': return "integer"; break; + case 'f': return "float"; break; + case 'd': return "double"; break; + case 's': return "string"; break; + case 'b': return "blob"; break; + case 't': return "time tag"; break; + default: return "unknown type"; + } + } + + static const char* argTypeToString(std::string&) + { + return "string"; + } + + template + static std::enable_if_t::value, const char*> + argTypeToString(T&) + { + return "integer"; + } + + template + static std::enable_if_t::value, const char*> + argTypeToString(T&) + { + return "float"; + } + + static const char* argTypeToString(BufferT::type&) + { + return "buffer"; + } + + static const char* argTypeToString(InputBufferT::type&) + { + return "buffer"; + } + + template + static std::enable_if_t::value,const char*> + argTypeToString(P&) + { + return "shared_object"; //not ideal + } + + static bool argTypeOK(std::string&, char tag) + { + return tag == 's'; + } + + template + static std::enable_if_t::value + || std::is_floating_point::value, bool> + argTypeOK(T&, char tag) + { + return tag == 'i' || tag == 'f' || tag == 'd'; + } + + static bool argTypeOK(BufferT::type&, char tag) + { + return tag == 'i'; + } + + static bool argTypeOK(InputBufferT::type&, char tag) + { + return tag == 'i'; + } + + template + static std::enable_if_t::value,bool> + argTypeOK(P&, char tag) + { + return tag == 'i'; + } + + static auto fromArgs(World*, sc_msg_iter& args, std::string, int) + { + const char* recv = args.gets(""); + + return std::string(recv ? recv : ""); + } + + template + static std::enable_if_t::value, T> + fromArgs(World*, sc_msg_iter& args, T, int defVal) + { + return args.geti(defVal); + } + + template + static std::enable_if_t::value, T> + fromArgs(World*, sc_msg_iter& args, T, int) + { + return args.getf(); + } + + static SCBufferAdaptor* fetchBuffer(World* x, index bufnum) + { + if(bufnum >= x->mNumSndBufs) + { + std::cout << "ERROR: bufnum " << bufnum << " is invalid for global buffers\n"; + return nullptr; + } + else + return bufnum >= 0 ? new SCBufferAdaptor(bufnum, x) : nullptr; + } + + static auto fromArgs(World* x, sc_msg_iter& args, BufferT::type&, int) + { + typename LongT::type bufnum = static_cast( + ParamReader::fromArgs(x, args, typename LongT::type(), -1)); + return BufferT::type(fetchBuffer(x, bufnum)); + } + + static auto fromArgs(World* x, sc_msg_iter& args, InputBufferT::type&, int) + { + typename LongT::type bufnum = + static_cast(fromArgs(x, args, LongT::type(), -1)); + return InputBufferT::type(fetchBuffer(x, bufnum)); + } + + template + static std::enable_if_t::value, P> + fromArgs(World* x, sc_msg_iter& args, P&, int) + { + auto id = fromArgs(x, args, index{}, 0); + return {id >= 0 ? std::to_string(id).c_str() : ""}; + } + + static auto fromArgs(World*, sc_msg_iter& args,typename LongArrayT::type&, int) + { + //first is array size, then items + using Container = typename LongArrayT::type; + using Value = typename Container::type; + index size = static_cast(args.geti()); + Container res(size); + for (index i = 0; i < size; ++i) + res[i] = static_cast(args.geti()); + return res; + } +}; + + +template +struct ClientParams{ +// Iterate over arguments via callbacks from params object + template + struct Setter + { + static constexpr index argSize = + Wrapper::Client::getParameterDescriptors().template get().fixedSize; + + + /// Grizzly enable_if hackage coming up. Need to brute force an int from incoming data into a string param for FluidDataSet / FluidLabelSet. + /// This will go away one day + + template + std::enable_if_t || Number!=0, typename T::type> + operator()(Context* x, ArgType& args) + { + // Just return default if there's nothing left to grab + if (args.remain() == 0) + { + std::cout << "WARNING: " << Wrapper::getName() + << " received fewer parameters than expected\n"; + return Wrapper::Client::getParameterDescriptors().template makeValue(); + } + + ParamLiteralConvertor a; + using LiteralType = + typename ParamLiteralConvertor::LiteralType; + + for (index i = 0; i < argSize; i++) + a[i] = static_cast( + ParamReader::fromArgs(x, args, a[0], 0)); + + return a.value(); + } + + template + std::enable_if_t && Number==0, typename T::type> + operator()(Context* x, ArgType& args) + { + // Just return default if there's nothing left to grab + if (args.remain() == 0) + { + std::cout << "WARNING: " << Wrapper::getName() + << " received fewer parameters than expected\n"; + return Wrapper::Client::getParameterDescriptors().template makeValue(); + } + + index id = ParamReader::fromArgs(x,args,index{},0); + return std::to_string(id); + } + }; + + template + struct Getter + { + static constexpr index argSize = + Wrapper::Client::getParameterDescriptors().template get().fixedSize; + + }; + + +}; + +} +} diff --git a/include/wrapper/ArgsToClient.hpp b/include/wrapper/ArgsToClient.hpp new file mode 100644 index 0000000..4be724d --- /dev/null +++ b/include/wrapper/ArgsToClient.hpp @@ -0,0 +1,103 @@ +#pragma once + +namespace fluid { +namespace client { + + struct ToFloatArray + { + static index allocSize(typename BufferT::type) { return 1; } + + template + static std::enable_if_t< + std::is_integral::value || std::is_floating_point::value, index> + allocSize(T) + { + return 1; + } + + static index allocSize(std::string s) + { + return asSigned(s.size()) + 1; + } // put null char at end when we send + + static index allocSize(FluidTensor s) + { + index count = 0; + for (auto& str : s) count += (str.size() + 1); + return count; + } + + template + static index allocSize(FluidTensor s) + { + return s.size(); + } + + template + static std::tuple, index> + allocSize(std::tuple&& t) + { + return allocSizeImpl(std::forward(t), + std::index_sequence_for()); + }; + + template + static std::tuple, index> + allocSizeImpl(std::tuple&& t, std::index_sequence) + { + index size{0}; + std::array res; + (void) std::initializer_list{ + (res[Is] = size, size += ToFloatArray::allocSize(std::get(t)), + 0)...}; + return std::make_tuple(res, + size); // array of offsets into allocated buffer & + // total number of floats to alloc + }; + + static void convert(float* f, typename BufferT::type buf) + { + f[0] = static_cast(buf.get())->bufnum(); + } + + template + static std::enable_if_t::value || + std::is_floating_point::value> + convert(float* f, T x) + { + f[0] = static_cast(x); + } + + static void convert(float* f, std::string s) + { + std::copy(s.begin(), s.end(), f); + f[s.size()] = 0; // terminate + } + static void convert(float* f, FluidTensor s) + { + for (auto& str : s) + { + std::copy(str.begin(), str.end(), f); + f += str.size(); + *f++ = 0; + } + } + template + static void convert(float* f, FluidTensor s) + { + static_assert(std::is_convertible::value, + "Can't convert this to float output"); + std::copy(s.begin(), s.end(), f); + } + + template + static void convert(float* f, std::tuple&& t, + std::array offsets, + std::index_sequence) + { + (void) std::initializer_list{ + (convert(f + offsets[Is], std::get(t)), 0)...}; + } + }; +} +} diff --git a/include/wrapper/BufferFuncs.hpp b/include/wrapper/BufferFuncs.hpp new file mode 100644 index 0000000..303e96f --- /dev/null +++ b/include/wrapper/BufferFuncs.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "SCBufferAdaptor.hpp" + +namespace fluid { +namespace client { +namespace impl { + + template + struct AssignBuffer + { + void operator()(const typename BufferT::type& p, World* w) + { + if (auto b = static_cast(p.get())) b->assignToRT(w); + } + }; + + template + struct CleanUpBuffer + { + void operator()(const typename BufferT::type& p) + { + if (auto b = static_cast(p.get())) b->cleanUp(); + } + }; + +} +} +} diff --git a/include/wrapper/CopyReplyAddress.hpp b/include/wrapper/CopyReplyAddress.hpp new file mode 100644 index 0000000..6506779 --- /dev/null +++ b/include/wrapper/CopyReplyAddress.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include + +namespace fluid{ +namespace client{ + +void* copyReplyAddress(InterfaceTable* ft, World* inWorld, void* inreply) +{ + + if(! inreply) return nullptr; + + ReplyAddress* reply = (ReplyAddress*)ft->fRTAlloc(inWorld, sizeof(ReplyAddress)); + + *reply = *(static_cast(inreply)); + + return reply; +} + +void deleteReplyAddress(InterfaceTable* ft, World* inWorld, void* inreply) +{ + if(! inreply) return; + ft->fRTFree(inWorld,(ReplyAddress*)inreply); +} + +void* copyReplyAddress(void* inreply) +{ + + if(! inreply) return nullptr; + + ReplyAddress* reply = new ReplyAddress(); + + *reply = *(static_cast(inreply)); + + return reply; +} + +void deleteReplyAddress(void* inreply) +{ + if(! inreply) return; + delete (ReplyAddress*)inreply; +} + + + +} +} diff --git a/include/wrapper/DeriveBaseClass.hpp b/include/wrapper/DeriveBaseClass.hpp new file mode 100644 index 0000000..9f59348 --- /dev/null +++ b/include/wrapper/DeriveBaseClass.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "NonRealtime.hpp" +#include "Realtime.hpp" + +namespace fluid { +namespace client { + +template class FluidSCWrapper; + +namespace impl { + +template +struct BaseChooser +{ + templatestruct Choose + { + using type = NonRealTime; + }; + + template<> + struct Choose + { + using type = RealTime; + }; + + using RT = typename Client::isRealTime; + + static constexpr bool UseRealTime = RT::value && !IsModel_t::value; + + using type = typename Choose::type; +}; + +template +using BaseChooser_t = typename BaseChooser::type; + + +template +using FluidSCWrapperBase = BaseChooser_t>; +} +} +} diff --git a/include/wrapper/Messaging.hpp b/include/wrapper/Messaging.hpp new file mode 100644 index 0000000..47881b9 --- /dev/null +++ b/include/wrapper/Messaging.hpp @@ -0,0 +1,261 @@ +#pragma once + +#include "ArgsFromClient.hpp" +#include "ArgsToClient.hpp" + +namespace fluid { +namespace client { + +template +struct FluidSCMessaging{ + + static auto getInterfaceTable(){ return FluidSCWrapper::getInterfaceTable(); } + static auto getName(){ return FluidSCWrapper::getName(); } + + + template + struct MessageDispatchCmd + { + using Descriptor = typename Client::MessageSetType::template MessageDescriptorAt; + using ArgTuple = typename Descriptor::ArgumentTypes; + using ReturnType = typename Descriptor::ReturnType; + using IndexList = typename Descriptor::IndexList; + + static constexpr size_t Message = N; + index id; + ArgTuple args; + ReturnType result; + std::string name; + IndexList argIndices; + }; + + + template + struct SetupMessageCmd + { + + void operator()(const T& message) + { + static std::string messageName = std::string{getName()} + '/' + message.name; + auto ft = getInterfaceTable(); + ft->fDefinePlugInCmd(messageName.c_str(), doMessage,(void*)messageName.c_str()); + } + }; + + + template + static bool validateMessageArgs(Message* msg, sc_msg_iter* inArgs) + { + using ArgTuple = decltype(msg->args); + + std::string tags(inArgs->tags + inArgs->count);//evidently this needs commenting: construct string at pointer offset by tag count, to pick up args + bool willContinue = true; + bool typesMatch = true; + + auto& args = msg->args; + + constexpr size_t expectedArgCount = std::tuple_size::value; + + /// TODO this squawks if we have a completion message, so maybe we can check if extra arg is a 'b' and squawk if not? +// if(tags.size() > expectedArgCount) +// { +// std::cout << "WARNING: " << msg->name << " received more arguments than expected (got " +// << tags.size() << ", expect " << expectedArgCount << ")\n"; +// } + + if(tags.size() < expectedArgCount) + { + std::cout << "ERROR: " << msg->name << " received fewer arguments than expected (got " + << tags.size() << ", expect " << expectedArgCount << ")\n"; + willContinue = false; + } + + auto tagsIter = tags.begin(); + auto tagsEnd = tags.end(); + ForEach(args,[&typesMatch,&tagsIter,&tagsEnd](auto& arg){ + if(tagsIter == tagsEnd) + { + typesMatch = false; + return; + } + char t = *(tagsIter++); + typesMatch = typesMatch && ParamReader::argTypeOK(arg,t); + }); + + willContinue = willContinue && typesMatch; + + if(!typesMatch) + { + auto& report = std::cout; + report << "ERROR: " << msg->name << " type signature incorrect.\nExpect: ("; + size_t i{0}; + ForEach(args, [&i](auto& x){ + std::cout << ParamReader::argTypeToString(x); + if(i < (std::tuple_size::value - 1 ) ) + { + std::cout << " ,"; + } + i++; + }); + report << ")\nReceived: ("; + i = 0; + for(auto t: tags) + { + report << ParamReader::oscTagToString(t); + if( i < ( tags.size() - 1 ) ) + { + report << ", "; + } + i++; + } + report << ")\n"; + } + + return willContinue; + } + + + template + static void doMessage(World* inWorld, void* inUserData, struct sc_msg_iter* args, void* replyAddr) + { + using MessageData = MessageDispatchCmd; + + auto msg = new MessageData(); + + msg->id = args->geti(); + ///TODO make this step contingent on verbosity or something, in the name of effieciency + bool willContinue = validateMessageArgs(msg, args); + + if(!willContinue) + { + delete msg; + return; + } + + + msg->name = std::string{'/'} + (const char*)(inUserData); + + ForEach(msg-> args,[inWorld,&args](auto& thisarg) + { + thisarg = ParamReader::fromArgs(inWorld, *args,thisarg,0); + }); + + size_t completionMsgSize{args ? args->getbsize() : 0}; + assert(completionMsgSize <= std::numeric_limits::max()); + char* completionMsgData = nullptr; + + if (completionMsgSize) { + completionMsgData = (char*)getInterfaceTable()->fRTAlloc(inWorld, completionMsgSize); + args->getb(completionMsgData, completionMsgSize); + } + + + getInterfaceTable()->fDoAsynchronousCommand(inWorld, replyAddr, getName(), msg, + [](World* world, void* data) // NRT thread: invocation + { + MessageData* m = static_cast(data); + using ReturnType = typename MessageData::ReturnType; + + if(auto ptr = FluidSCWrapper::get(m->id).lock()) + { + m->result = + ReturnType{invokeImpl(ptr->mClient, m->args,m->argIndices)}; + + if (!m->result.ok()) + FluidSCWrapper::printResult(world, m->result); + } else FluidSCWrapper::printNotFound(m->id); + + return true; + }, + [](World* world, void* data) // RT thread: response + { + MessageData* m = static_cast(data); + MessageData::Descriptor::template forEachArg(m->args, + world); + + if(m->result.status() != Result::Status::kError) + messageOutput(m->name, m->id, m->result, world); + else + { + auto ft = getInterfaceTable(); + ft->fSendNodeReply(ft->fGetNode(world,0),-1, m->name.c_str(),0, nullptr); + } + return true; + }, + nullptr, // NRT Thread: No-op + [](World* /*w*/, void* data) // RT thread: clean up + { + MessageData* m = static_cast(data); + delete m; + }, + static_cast(completionMsgSize), completionMsgData); + } + + template // Call from NRT + static decltype(auto) invokeImpl(Client& x, ArgsTuple& args, + std::index_sequence) + { + return x.template invoke(x, std::get(args)...); + } + + template // call from RT + static void messageOutput(const std::string& s, index id, MessageResult& result, World* world) + { + auto ft = getInterfaceTable(); + + // allocate return values + index numArgs = ToFloatArray::allocSize(static_cast(result)); + + if(numArgs > 2048) + { + std::cout << "ERROR: Message response too big to send (" << asUnsigned(numArgs) * sizeof(float) << " bytes)." << std::endl; + return; + } + + float* values = new float[asUnsigned(numArgs)]; + + // copy return data + ToFloatArray::convert(values, static_cast(result)); + + ft->fSendNodeReply(ft->fGetNode(world,0), static_cast(id), s.c_str(), + static_cast(numArgs), values); + + delete[] values; + } + + static void messageOutput(const std::string& s,index id, MessageResult&, World* world) + { + auto ft = getInterfaceTable(); + ft->fSendNodeReply(ft->fGetNode(world,0), static_cast(id), s.c_str(), 0, nullptr); + } + + template + static void messageOutput(const std::string& s, index id, MessageResult>& result, World* world) + { + std::array offsets; + + index numArgs; + + std::tie(offsets, numArgs) = + ToFloatArray::allocSize(static_cast>(result)); + + if(numArgs > 2048) + { + std::cout << "ERROR: Message response too big to send (" << asUnsigned(numArgs) * sizeof(float) << " bytes)." << std::endl; + return; + } + + float* values = new float[asUnsigned(numArgs)]; + ToFloatArray::convert(values, std::tuple(result), offsets, + std::index_sequence_for()); + + auto ft = getInterfaceTable(); + ft->fSendNodeReply(ft->fGetNode(world,0), id, s.c_str(), + static_cast(numArgs), values); + + delete[] values; + } +}; +} +} diff --git a/include/wrapper/Meta.hpp b/include/wrapper/Meta.hpp new file mode 100644 index 0000000..8b375cf --- /dev/null +++ b/include/wrapper/Meta.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include + +namespace fluid { +namespace client { +namespace impl { + /// Named, shared clients already have a lookup table in their adaptor class + template + struct IsNamedShared + { + using type = std::false_type; + }; + + //TODO: make less tied to current implementation + template + struct IsNamedShared>> + { + using type = std::true_type; + }; + + template + using IsNamedShared_t = typename IsNamedShared::type; + + template + constexpr bool IsNamedShared_v = IsNamedShared_t::value; + + /// Models don't, but still need to survive CMD-. + template + struct IsModel + { + using type = std::false_type; + }; + + template + struct IsModel>> + { + using type = typename ClientWrapper::isModelObject; + }; + + template + struct IsModel> + { + using type = typename ClientWrapper::isModelObject; + }; + + template + using IsModel_t = typename IsModel::type; + + template + constexpr bool IsModel_v = IsModel_t::value; + + + +} +} +} diff --git a/include/wrapper/NonRealtime.hpp b/include/wrapper/NonRealtime.hpp new file mode 100644 index 0000000..4440924 --- /dev/null +++ b/include/wrapper/NonRealtime.hpp @@ -0,0 +1,940 @@ +#pragma once + +#include "BufferFuncs.hpp" +#include "Meta.hpp" +#include "SCBufferAdaptor.hpp" +#include "CopyReplyAddress.hpp" +#include "Messaging.hpp" +#include "RealTimeBase.hpp" + +#include +#include +#include +#include + +namespace fluid { +namespace client { +namespace impl { + + /// Non Real Time Processor + + template + class NonRealTime : public SCUnit + { + using Params = typename Client::ParamSetType; + + template + static T* rtalloc(World* world,Args&&...args) + { + void* space = getInterfaceTable()->fRTAlloc(world, sizeof(T)); + return new (space) T{std::forward(args)...}; + } + + /// Instance cache + struct CacheEntry + { + + CacheEntry(Params& p):mParams{p},mClient{mParams} + {} + + Params mParams; + Client mClient; + }; + + using CacheEntryPointer = std::shared_ptr; + using WeakCacheEntryPointer = std::weak_ptr; //could use weak_type in 17 + using Cache = std::map; + + static bool isNull(WeakCacheEntryPointer const& weak) { + return !weak.owner_before(WeakCacheEntryPointer{}) && !WeakCacheEntryPointer{}.owner_before(weak); + } + + static Cache mCache; + + public: + static WeakCacheEntryPointer get(index id) + { + auto lookup = mCache.find(id); + return lookup == mCache.end() ? WeakCacheEntryPointer() : lookup->second; + } + + static WeakCacheEntryPointer add(index id, CacheEntry&& entry) + { + if(isNull(get(id))) + { + auto result = mCache.emplace(id, + std::make_shared(std::move(entry))); + + return result.second ? (result.first)->second : WeakCacheEntryPointer(); //sob + } + else //client has screwed up + { + std::cout << "ERROR: ID " << id << " already in use\n"; + return {}; + } + } + + static void remove(index id) + { + mCache.erase(id); + } + + static void printNotFound(index id) + { + std::cout << "ERROR: " << Wrapper::getName() << " no instance with ID " << id << std::endl; + } + + private: + static InterfaceTable* getInterfaceTable() { return Wrapper::getInterfaceTable() ;} + + template + using ParamsFromOSC = typename ClientParams::template Setter; + + template + using ParamsFromSynth = typename ClientParams::template Setter; + + + struct NRTCommand + { + NRTCommand(World*, sc_msg_iter* args, bool consumeID = true) + { + auto count = args->count; + auto pos = args->rdpos; + + mID = args->geti(); + + if(!consumeID) + { + args->count = count; + args->rdpos = pos; + } + } + + NRTCommand(){} + + explicit NRTCommand(index id):mID{id}{} + + bool stage2(World*) { std::cout << "Nope\n"; return true; } //nrt + bool stage3(World*) { return true; } //rt + bool stage4(World*) { return true; } //nrt + void cleanup(World*) {} //rt + + void sendReply(World* world, const char* name,bool success) + { + if(mID < 0) return; + auto ft = getInterfaceTable(); + auto root = ft->fGetNode(world,0); + float res[2] = {static_cast(success),static_cast(mID)}; + std::string slashname = std::string{'/'} + name; + ft->fSendNodeReply(root,0, slashname.c_str(), 2, res); + } +// protected: + index mID; + }; + + struct CommandNew : public NRTCommand + { + CommandNew(World* world, sc_msg_iter* args) + : NRTCommand{world,args, !IsNamedShared_v}, + mParams{Client::getParameterDescriptors()} + { + mParams.template setParameterValuesRT(nullptr, world, *args); + } + + CommandNew(index id, World*, FloatControlsIter& args, Unit* x) + :NRTCommand{id}, + mParams{Client::getParameterDescriptors()} + { + mParams.template setParameterValuesRT(nullptr, x, args); + } + + static const char* name() + { + static std::string cmd = std::string(Wrapper::getName()) + "/new"; + return cmd.c_str(); + } + + bool stage2(World*) + { +// auto entry = ; + mResult = (!isNull(add(NRTCommand::mID, CacheEntry{mParams}))); + + //Sigh. The cache entry above has both the client instance and main params instance. + // The client is linked to the params by reference; I've not got the in-place constrction + // working properly so that params are in their final resting place by the time we make the client + // so (for) now we need to manually repoint the client to the correct place. Or badness. + if(mResult) + { + auto ptr = get(NRTCommand::mID).lock(); + ptr->mClient.setParams(ptr->mParams); + } + return mResult; + } + + bool stage3(World* w) + { + NRTCommand::sendReply(w, name(), mResult); + return true; + } + + private: + bool mResult; + Params mParams; + }; + + struct CommandFree: public NRTCommand + { + using NRTCommand::NRTCommand; + + + template + struct CancelCheck{ + void operator()(index id) + { + if(auto ptr = get(id).lock()) + { + auto& client = ptr->mClient; + if(!client.synchronous() && client.state() == ProcessState::kProcessing) + std::cout << Wrapper::getName() + << ": Processing cancelled" + << std::endl; + } + } + }; + + template<> + struct CancelCheck{ + void operator()(index) + {} + }; + + static const char* name() + { + static std::string cmd = std::string(Wrapper::getName()) + "/free"; + return cmd.c_str(); + } + + bool stage2(World*) + { + CancelCheck()(NRTCommand::mID); + remove(NRTCommand::mID); + return true; + } + + bool stage3(World* w) + { + NRTCommand::sendReply(w, name(), true); + return true; + } + }; + + + /// Not registered as a PlugInCmd. Triggered by worker thread callback + struct CommandAsyncComplete: public NRTCommand + { + CommandAsyncComplete(World*, index id, void* replyAddress) + { + NRTCommand::mID = id; + mReplyAddress = replyAddress; + } + + static const char* name() { return CommandProcess::name(); } + + bool stage2(World* world) + { + + std::cout << "In Async completion\n"; + if(auto ptr = get(NRTCommand::mID).lock()) + { + Result r; + auto& client = ptr->mClient; + ProcessState s = client.checkProgress(r); + if (s == ProcessState::kDone || s == ProcessState::kDoneStillProcessing) + { + if (r.status() == Result::Status::kCancelled) + { + std::cout << Wrapper::getName() + << ": Processing cancelled" + << std::endl; + return false; + } + + client.checkProgress(r); + mSuccess = !(r.status() == Result::Status::kError); + if (!r.ok()) + { + Wrapper::printResult(world,r); + if(r.status() == Result::Status::kError) return false; + } + return true; + } + } + return false; + } + + bool stage3(World* world) + { + if(auto ptr = get(NRTCommand::mID).lock()) + { + auto& params = ptr->mParams; + params.template forEachParamType(world); + //NRTCommand::sendReply(world, name(), true); + return true; + } + return false; + } + + bool stage4(World*) //nrt + { + if(auto ptr = get(NRTCommand::mID).lock()) + { + ptr->mParams.template forEachParamType(); + return true; + } + return false; + } + + void cleanup(World* world) { + if(auto ptr = get(NRTCommand::mID).lock() && NRTCommand::mID >= 0) + NRTCommand::sendReply(world, name(), mSuccess); + if(mReplyAddress) + { + ReplyAddress* r = static_cast(mReplyAddress); + delete r; + } + } //rt + + bool mSuccess; + void* mReplyAddress{nullptr}; + + }; + + static void doProcessCallback(World* world, index id,size_t completionMsgSize,char* completionMessage,void* replyAddress) + { + std::cout << "In callback\n"; + auto ft = getInterfaceTable(); + + struct Context{ + World* mWorld; + index mID; + size_t mCompletionMsgSize; + char* mCompletionMessage; + void* mReplyAddress; + }; + + Context* c = new Context{world,id,completionMsgSize,completionMessage,replyAddress}; + + auto runCompletion = [](FifoMsg* msg){ + std::cout << "In FIFOMsg\n"; + Context* c = static_cast(msg->mData); + World* world = c->mWorld; + index id = c->mID; + auto ft = getInterfaceTable(); + void* space = ft->fRTAlloc(world,sizeof(CommandAsyncComplete)); + CommandAsyncComplete* cmd = new (space) CommandAsyncComplete(world, id,c->mReplyAddress); + runAsyncCommand(world, cmd, c->mReplyAddress, c->mCompletionMsgSize, c->mCompletionMessage); + }; + + auto tidyup = [](FifoMsg* msg) + { + Context* c = static_cast(msg->mData); + delete c; + }; + + FifoMsg msg; + msg.Set(world, runCompletion, tidyup, c); + ft->fSendMsgToRT(world,msg); + } + + struct CommandProcess: public NRTCommand + { + CommandProcess(World* world, sc_msg_iter* args): NRTCommand{world, args} + { + auto& ar = *args; + + if(auto ptr = get(NRTCommand::mID).lock()) + { + ptr->mParams.template setParameterValuesRT(nullptr, world, ar); + mSynchronous = static_cast(ar.geti()); + } //if this fails, we'll hear about it in stage2 anyway + } + + explicit CommandProcess(index id,bool synchronous):NRTCommand{id},mSynchronous(synchronous) + {} + + + static const char* name() + { + static std::string cmd = std::string(Wrapper::getName()) + "/process"; + return cmd.c_str(); + } + + bool stage2(World* world) + { + if(auto ptr = get(NRTCommand::mID).lock()) + { + auto& params = ptr->mParams; + auto& client = ptr->mClient; + +// if(mOSCData) +// { +// params.template setParameterValuesRT(nullptr, world, *mOSCData); +// mSynchronous = static_cast(mOSCData->geti()); +// } + + Result result = validateParameters(params); + Wrapper::printResult(world, result); + if (result.status() != Result::Status::kError) + { +// client.done() + client.setSynchronous(mSynchronous); + index id = NRTCommand::mID; + size_t completionMsgSize = mCompletionMsgSize; + char* completionMessage = mCompletionMessage; + void* replyAddress = copyReplyAddress(mReplyAddr); + + auto callback = [world,id,completionMsgSize,completionMessage,replyAddress](){ + doProcessCallback(world,id,completionMsgSize,completionMessage,replyAddress); + }; + + result = mSynchronous ? client.enqueue(params) : client.enqueue(params,callback); + Wrapper::printResult(world, result); + + if(result.ok()) + { + mResult = client.process(); + Wrapper::printResult(world,mResult); + return mSynchronous && mResult.ok(); + } + } + } + else + { + mResult = Result{Result::Status::kError, "No ", Wrapper::getName(), " with ID ", NRTCommand::mID}; + Wrapper::printResult(world,mResult); + } + return false; + } + + //Only for blocking execution + bool stage3(World* world) //rt + { + if(auto ptr = get(NRTCommand::mID).lock()) + { + ptr->mParams.template forEachParamType(world); +// NRTCommand::sendReply(world, name(), mResult.ok()); + return true; + } + std::cout << "Ohno\n"; + return false; + } + + //Only for blocking execution + bool stage4(World*) //nrt + { + if(auto ptr = get(NRTCommand::mID).lock()) + { + ptr->mParams.template forEachParamType(); + return true; + } + return false; + } + + void cleanup(World* world) { + if(auto ptr = get(NRTCommand::mID).lock() && NRTCommand::mID >= 0 && mSynchronous) + NRTCommand::sendReply(world, name(), mResult.ok()); + if(mReplyAddr) + deleteReplyAddress(mReplyAddr); +// getInterfaceTable()->fRTFree(world,mReplyAddr); + } //rt + + bool synchronous() + { + return mSynchronous; + } + + void addCompletionMessage(size_t size, char* message, void* addr) + { + mCompletionMsgSize = size; + mCompletionMessage = message; + mReplyAddr = copyReplyAddress(addr); +// ReplyAddress* rr = static_cast(addr); +// if(addr)mReplyAddr = *rr; + } + +// private: + Result mResult; + bool mSynchronous; + size_t mCompletionMsgSize{0}; + char* mCompletionMessage{nullptr}; + void* mReplyAddr{nullptr}; +// sc_msg_iter* mOSCData; + }; + + struct CommandProcessNew: public NRTCommand + { + CommandProcessNew(World* world, sc_msg_iter* args) + : mNew{world,args}, + mProcess{mNew.mID,false} + { + mProcess.mSynchronous = args->geti(); + } + + CommandProcessNew(index id, World* world, FloatControlsIter& args, Unit* x) + : mNew{id, world, args, x}, + mProcess{id} + {} + + static const char* name() + { + static std::string cmd = std::string(Wrapper::getName()) + "/processNew"; + return cmd.c_str(); + } + + bool stage2(World* world) + { + return mNew.stage2(world) ? mProcess.stage2(world) : false; + } + + bool stage3(World* world) //rt + { + return mProcess.stage3(world); + } + + bool stage4(World* world) //rt + { + return mProcess.stage4(world); + } + + void cleanup(World* world) + { + mProcess.cleanup(world); + } + + bool synchronous() + { + return mProcess.synchronous(); + } + + void addCompletionMessage(size_t size, char* message,void* addr) + { + mProcess.addCompletionMessage(size, message,addr); + } + + private: + CommandNew mNew; + CommandProcess mProcess; + }; + + + struct CommandCancel: public NRTCommand + { + CommandCancel(World* world, sc_msg_iter* args): NRTCommand{world, args} + {} + + static const char* name() + { + static std::string cmd = std::string(Wrapper::getName()) + "/cancel"; + return cmd.c_str(); + } + + bool stage2(World*) + { + if(auto ptr = get(NRTCommand::mID).lock()) + { + auto& client = ptr->mClient; + if(!client.synchronous()) + { + client.cancel(); + return true; + } + } + return false; + } + }; + + struct CommandSetParams: public NRTCommand + { + CommandSetParams(World* world, sc_msg_iter* args) + : NRTCommand{world, args} + { + auto& ar = *args; + if(auto ptr = get(NRTCommand::mID).lock()) + { + ptr->mParams.template setParameterValuesRT(nullptr, world, ar); + Result result = validateParameters(ptr->mParams); + ptr->mClient.setParams(ptr->mParams); + NRTCommand::sendReply(world, name(), result.ok()); + } else printNotFound(NRTCommand::mID); + } + + static const char* name() + { + static std::string cmd = std::string(Wrapper::getName()) + "/setParams"; + return cmd.c_str(); + } + }; + + + template + static auto runAsyncCommand(World* world, Command* cmd, void* replyAddr, + size_t completionMsgSize, char* completionMsgData) + { + auto ft = getInterfaceTable(); + + return ft->fDoAsynchronousCommand(world, replyAddr,Command::name(),cmd, + [](World* w, void* d) { return static_cast(d)->stage2(w); }, + [](World* w, void* d) { return static_cast(d)->stage3(w); }, + [](World* w, void* d) { return static_cast(d)->stage4(w); }, + [](World* w, void* d) + { + auto cmd = static_cast(d); + cmd->cleanup(w); + cmd->~Command(); + getInterfaceTable()->fRTFree(w,d); + }, + static_cast(completionMsgSize), completionMsgData); + } + + + static auto runAsyncCommand(World* world, CommandProcess* cmd, void* replyAddr, + size_t completionMsgSize, char* completionMsgData) + { + if(!cmd->synchronous()) + { + cmd->addCompletionMessage(completionMsgSize,completionMsgData,replyAddr); + return runAsyncCommand(world, cmd, replyAddr, 0, nullptr); + } + else return runAsyncCommand(world, cmd, replyAddr, completionMsgSize, completionMsgData); + } + + static auto runAsyncCommand(World* world, CommandProcessNew* cmd, void* replyAddr, + size_t completionMsgSize, char* completionMsgData) + { + if(!cmd->synchronous()) + { + cmd->addCompletionMessage(completionMsgSize,completionMsgData,replyAddr); + return runAsyncCommand(world, cmd, replyAddr, 0, nullptr); + } + else return runAsyncCommand(world, cmd, replyAddr, completionMsgSize, completionMsgData); + } + + + template + static void defineNRTCommand() + { + auto ft = getInterfaceTable(); + auto commandRunner = [](World* world, void*, struct sc_msg_iter* args, void* replyAddr) + { + + auto ft = getInterfaceTable(); + void* space = ft->fRTAlloc(world,sizeof(Command)); + Command* cmd = new (space) Command(world, args); + //This is brittle, but can't think of something better offhand + //This is the only place we can check for a completion message at the end of the OSC packet + //beause it has to be passed on to DoAsynhronousCommand at this point. However, detecting correctly + //relies on the Command type having fully consumed arguments from the args iterator in the constructor for cmd + size_t completionMsgSize{args ? args->getbsize() : 0}; + assert(completionMsgSize <= std::numeric_limits::max()); + char* completionMsgData = nullptr; + + if (completionMsgSize) { + completionMsgData = (char*)ft->fRTAlloc(world, completionMsgSize); + args->getb(completionMsgData, completionMsgSize); + } + runAsyncCommand(world, cmd, replyAddr, completionMsgSize, completionMsgData); + }; + ft->fDefinePlugInCmd(Command::name(),commandRunner,nullptr); + } + + + + struct NRTProgressUnit: SCUnit + { + + static const char* name() + { + static std::string n = std::string(Wrapper::getName()) + "Monitor"; + return n.c_str(); + } + + NRTProgressUnit() + { + mInterval = static_cast(0.02 / controlDur()); + set_calc_function(); + Wrapper::getInterfaceTable()->fClearUnitOutputs(this, 1); + } + + void next(int) + { + if (0 == mCounter++) + { + index id = static_cast(mInBuf[0][0]); + if(auto ptr = get(id).lock()) + { + if(ptr->mClient.done()) mDone = 1; + out0(0) = static_cast(ptr->mClient.progress()); + } + else + std::cout << "WARNING: No " << Wrapper::getName() << " with ID " << id << std::endl; + } + mCounter %= mInterval; + } + + private: + index mInterval; + index mCounter{0}; + }; + + + struct NRTTriggerUnit: SCUnit + { + + static index count(){ + static index counter = -1; + return counter--; + } + + index ControlOffset() { return mSpecialIndex + 1; } + + index ControlSize() + { + return index(mNumInputs) + - mSpecialIndex //used for oddball cases + - 3; //id + trig + blocking; + } + + static const char* name() + { + static std::string n = std::string(Wrapper::getName()) + "Trigger"; + return n.c_str(); + } + + NRTTriggerUnit() + : mControlsIterator{mInBuf + ControlOffset(),ControlSize()} + { + mID = static_cast(mInBuf[0][0]); + if(mID == -1) mID = count(); + auto cmd = NonRealTime::rtalloc(mWorld,mID,mWorld, mControlsIterator, this); + runAsyncCommand(mWorld, cmd, nullptr, 0, nullptr); + set_calc_function(); + Wrapper::getInterfaceTable()->fClearUnitOutputs(this, 1); + } + + ~NRTTriggerUnit() + { + if(auto ptr = get(mID).lock()) + { + auto cmd = NonRealTime::rtalloc(mWorld,mID); + runAsyncCommand(mWorld, cmd, nullptr, 0, nullptr); + } + } + + void next(int) + { + index triggerInput = static_cast(mInBuf[static_cast(mNumInputs) - 2][0]); + mTrigger = mTrigger || triggerInput; + + if(auto ptr = get(mID).lock()) + { + bool trigger = (mPreviousTrigger <= 0) && mTrigger > 0; + mPreviousTrigger = mTrigger; + mTrigger = 0; + auto& client = ptr->mClient; + + if(trigger) + { + mDone = 0; + client.resetDone(); + mControlsIterator.reset(1 + mInBuf); //add one for ID + auto& params = ptr->mParams; + Wrapper::setParams(this,params,mControlsIterator,true,false); + bool blocking = mInBuf[mNumInputs - 1][0] > 0; + CommandProcess* cmd = rtalloc(mWorld,mID,blocking); + runAsyncCommand(mWorld,cmd, nullptr,0, nullptr); + mRunCount++; + } + else + { + mDone = mRunCount && client.done() ; + out0(0) = mDone ? 1 : static_cast(client.progress()); + } + } +// else printNotFound(id); + } + + private: + bool mPreviousTrigger{0}; + bool mTrigger{0}; + Result mResult; + impl::FloatControlsIter mControlsIterator; + index mID; + index mRunCount{0}; + }; + + struct NRTModelQueryUnit: SCUnit + { + using Delegate = impl::RealTimeBase; + + index ControlOffset() { return mSpecialIndex + 2; } + index ControlSize() + { + return index(mNumInputs) + - mSpecialIndex //used for oddball cases + - 2; // trig + id + } + + static const char* name() + { + static std::string n = std::string(Wrapper::getName()) + "/query"; + return n.c_str(); + } + + NRTModelQueryUnit() + //Offset controls by 1 to account for ID + : mControls{mInBuf + ControlOffset(),ControlSize()} + { + index id = static_cast(in0(1)); + if(auto ptr = get(id).lock()) + { + auto& client = ptr->mClient; + mDelegate.init(*this,client,mControls); + set_calc_function(); + Wrapper::getInterfaceTable()->fClearUnitOutputs(this, 1); + }else printNotFound(id); + } + + void next(int) + { + index id = static_cast(in0(1)); + if(auto ptr = get(id).lock()) + { + auto& client = ptr->mClient; + auto& params = ptr->mParams; + mControls.reset(mInBuf + ControlOffset()); + mDelegate.next(*this,client,params,mControls); + }else printNotFound(id); + } + + private: + Delegate mDelegate; + FloatControlsIter mControls; + index mID; + }; + + + using ParamSetType = typename Client::ParamSetType; + + template + using SetupMessageCmd = typename FluidSCMessaging::template SetupMessageCmd; + + + template + struct DefineCommandIf + { + void operator()() { } + }; + + + template + struct DefineCommandIf + { + void operator()() { + std::cout << CommandType::name() << std::endl; + defineNRTCommand(); + } + }; + + template + struct RegisterUnitIf + { + void operator()(InterfaceTable*) {} + }; + + template + struct RegisterUnitIf + { + void operator()(InterfaceTable* ft) { registerUnit(ft,UnitType::name()); } + }; + + + using IsRTQueryModel_t = typename Client::isRealTime; + static constexpr bool IsRTQueryModel = IsRTQueryModel_t::value; + + static constexpr bool IsModel = Client::isModelObject::value; + + + public: + static void setup(InterfaceTable* ft, const char* name) + { + defineNRTCommand(); + DefineCommandIf()(); + DefineCommandIf()(); + DefineCommandIf()(); + + DefineCommandIf()(); +// DefineCommandIf()(); + + defineNRTCommand(); + RegisterUnitIf()(ft); + RegisterUnitIf()(ft); + + RegisterUnitIf()(ft); + Client::getMessageDescriptors().template iterate(); + } + + + void init(){}; + + private: + static Result validateParameters(ParamSetType& p) + { + auto results = p.constrainParameterValues(); + for (auto& r : results) + { + if (!r.ok()) return r; + } + return {}; + } + + template + struct AssignBuffer + { + void operator()(const typename BufferT::type& p, World* w) + { + if (auto b = static_cast(p.get())) b->assignToRT(w); + } + }; + + template + struct CleanUpBuffer + { + void operator()(const typename BufferT::type& p) + { + if (auto b = static_cast(p.get())) b->cleanUp(); + } + }; + + FifoMsg mFifoMsg; + char* mCompletionMessage = nullptr; + void* mReplyAddr = nullptr; + const char* mName = nullptr; + index checkThreadInterval; + index pollCounter{0}; + index mPreviousTrigger{0}; + + bool mSynchronous{true}; + Wrapper* mWrapper{static_cast(this)}; + Result mResult; + }; + + //initialize static cache + template + using Cache = typename NonRealTime::Cache; + + template + Cache NonRealTime::mCache{}; + +} +} +} diff --git a/include/wrapper/RealTimeBase.hpp b/include/wrapper/RealTimeBase.hpp new file mode 100644 index 0000000..3c04dd6 --- /dev/null +++ b/include/wrapper/RealTimeBase.hpp @@ -0,0 +1,171 @@ +#pragma once + +#include + +namespace fluid{ +namespace client{ +namespace impl{ + template + struct RealTimeBase + { + using HostVector = FluidTensorView; + using Params = typename Client::ParamSetType; + template + struct doExpectedCount; + + template + struct doExpectedCount + { + static void count(const T& d,FloatControlsIter& c,Result& status) + { + if(!status.ok()) return; + + if(c.remain()) + { + index statedSize = d.fixedSize; + + if(c.remain() < statedSize) + status = {Result::Status::kError,"Ran out of arguments at ", d.name}; + + //fastforward + for(index i=0; i < statedSize; ++i) c.next(); + + } + } + }; + + template + struct doExpectedCount + { + static void count(const T& d,FloatControlsIter& c,Result& status) + { + if(!status.ok()) return; + + if(c.remain()) + { + index statedSize = static_cast(c.next()); + + if(c.remain() < statedSize) + status = {Result::Status::kError,"Ran out of arguments at ", d.name}; + + //fastforward + for(index i=0; i < statedSize; ++i) c.next(); + + } + } + }; + template + struct ExpectedCount{ + void operator ()(const T& descriptor,FloatControlsIter& c, Result& status) + { + doExpectedCount::value>::count(descriptor,c,status); + } + }; + + Result expectedSize(FloatControlsIter& controls) + { + if(controls.size() < Client::getParameterDescriptors().count()) + { + return {Result::Status::kError,"Fewer parameters than exepected. Got ", controls.size(), "expect at least", Client::getParameterDescriptors().count()}; + } + + Result countScan; + Client::getParameterDescriptors().template iterate( + std::forward(controls), + std::forward(countScan)); + return countScan; + } + + // static index ControlOffset(Unit* unit) { return unit->mSpecialIndex + 1; } + // static index ControlSize(Unit* unit) { return static_cast(unit->mNumInputs) - unit->mSpecialIndex - 1 -(IsModel_t::value ? 1 : 0); } + + void init(SCUnit& unit, Client& client, FloatControlsIter& controls) + { + assert(!(client.audioChannelsOut() > 0 && client.controlChannelsOut() > 0) &&"Client can't have both audio and control outputs"); + // consoltr.reset(unit.mInBuf + unit.mSpecialIndex + 1); + client.sampleRate(unit.fullSampleRate()); + mInputConnections.reserve(asUnsigned(client.audioChannelsIn())); + mOutputConnections.reserve(asUnsigned(client.audioChannelsOut())); + mAudioInputs.reserve(asUnsigned(client.audioChannelsIn())); + mOutputs.reserve(asUnsigned( + std::max(client.audioChannelsOut(), client.controlChannelsOut()))); + + + Result r; + if(!(r = expectedSize(controls)).ok()) + { +// mCalcFunc = Wrapper::getInterfaceTable()->fClearUnitOutputs; + std::cout + << "ERROR: " << Wrapper::getName() + << " wrong number of arguments." + << r.message() + << std::endl; + return; + } + + + for (index i = 0; i < client.audioChannelsIn(); ++i) + { + mInputConnections.emplace_back(unit.isAudioRateIn(static_cast(i))); + mAudioInputs.emplace_back(nullptr, 0, 0); + } + + for (index i = 0; i < client.audioChannelsOut(); ++i) + { + mOutputConnections.emplace_back(true); + mOutputs.emplace_back(nullptr, 0, 0); + } + + for (index i = 0; i < client.controlChannelsOut(); ++i) + { + mOutputs.emplace_back(nullptr, 0, 0); + } + } + + void next(SCUnit& unit, Client& client,Params& params,FloatControlsIter& controls) + { + bool trig = IsModel_t::value ? !mPrevTrig && unit.in0(0) > 0 : false; + bool shouldProcess = IsModel_t::value ? trig : true; + mPrevTrig = trig; + + if(shouldProcess) + { + // controls.reset(unit.mInBuf + unit.mSpecialIndex + 1); + Wrapper::setParams(&unit, params, controls); + params.constrainParameterValues(); + } + + for (index i = 0; i < client.audioChannelsIn(); ++i) + { + assert(i <= std::numeric_limits::max()); + if (mInputConnections[asUnsigned(i)]) + mAudioInputs[asUnsigned(i)].reset(const_cast(unit.in(static_cast(i))), 0, + unit.fullBufferSize()); + } + + for (index i = 0; i < client.audioChannelsOut(); ++i) + { + assert(i <= std::numeric_limits::max()); + if (mOutputConnections[asUnsigned(i)]) + mOutputs[asUnsigned(i)].reset(unit.out(static_cast(i)), 0, + unit.fullBufferSize()); + } + + for (index i = 0; i < client.controlChannelsOut(); ++i) + { + assert(i <= std::numeric_limits::max()); + mOutputs[asUnsigned(i)].reset(unit.out(static_cast(i)), 0, 1); + } + client.process(mAudioInputs, mOutputs, mContext); + } + private: + std::vector mInputConnections; + std::vector mOutputConnections; + std::vector mAudioInputs; + std::vector mOutputs; + FluidContext mContext; + bool mPrevTrig; + }; +} +} +} diff --git a/include/wrapper/Realtime.hpp b/include/wrapper/Realtime.hpp new file mode 100644 index 0000000..cb70677 --- /dev/null +++ b/include/wrapper/Realtime.hpp @@ -0,0 +1,168 @@ +#pragma once + +#include "ArgsFromClient.hpp" +#include "Meta.hpp" +#include "RealTimeBase.hpp" +#include +#include + +// Real Time Processor +namespace fluid { +namespace client { +namespace impl { + +template +class RealTime : public SCUnit +{ + + using Delegate = impl::RealTimeBase; + using Params = typename Client::ParamSetType; + +public: + + // static index ControlOffset(Unit* unit) { return Delegate::ControlOffset(unit); } + // static index ControlSize(Unit* unit) { return Delegate::ControlSize(unit); } + + static index ControlOffset(Unit* unit) { return unit->mSpecialIndex + 1; } + static index ControlSize(Unit* unit) + { + return static_cast(unit->mNumInputs) + - unit->mSpecialIndex + - 1 + - (IsModel_t::value ? 1 : 0); + } + + static void setup(InterfaceTable* ft, const char* name) + { + ft->fDefineUnitCmd(name, "latency", doLatency); + registerUnit(ft,name); + } + + static void doLatency(Unit* unit, sc_msg_iter*) + { + float l[]{ + static_cast(static_cast(unit)->mClient.latency()) + }; + auto ft = Wrapper::getInterfaceTable(); + + std::stringstream ss; + ss << '/' << Wrapper::getName() << "_latency"; + std::cout << ss.str() << std::endl; + ft->fSendNodeReply(&unit->mParent->mNode, -1, ss.str().c_str(), 1, l); + } + + RealTime() + : mControls{mInBuf + ControlOffset(this),ControlSize(this)}, + mClient{Wrapper::setParams(this, mParams, mControls)} + { + init(); + } + + void init() + { +// auto& client = mClient; + mDelegate.init(*this,mClient,mControls); + mCalcFunc = make_calc_function(); + Wrapper::getInterfaceTable()->fClearUnitOutputs(this, 1); + + // assert( + // !(client.audioChannelsOut() > 0 && client.controlChannelsOut() > 0) && + // "Client can't have both audio and control outputs"); + // + // Result r; + // if(!(r = expectedSize(mWrapper->mControlsIterator)).ok()) + // { + // mCalcFunc = Wrapper::getInterfaceTable()->fClearUnitOutputs; + // std::cout + // << "ERROR: " << Wrapper::getName() + // << " wrong number of arguments." + // << r.message() + // << std::endl; + // return; + // } + // + // mWrapper->mControlsIterator.reset(mInBuf + mSpecialIndex + 1); + // + // client.sampleRate(fullSampleRate()); + // mInputConnections.reserve(asUnsigned(client.audioChannelsIn())); + // mOutputConnections.reserve(asUnsigned(client.audioChannelsOut())); + // mAudioInputs.reserve(asUnsigned(client.audioChannelsIn())); + // mOutputs.reserve(asUnsigned( + // std::max(client.audioChannelsOut(), client.controlChannelsOut()))); + // + // for (index i = 0; i < client.audioChannelsIn(); ++i) + // { + // mInputConnections.emplace_back(isAudioRateIn(static_cast(i))); + // mAudioInputs.emplace_back(nullptr, 0, 0); + // } + // + // for (index i = 0; i < client.audioChannelsOut(); ++i) + // { + // mOutputConnections.emplace_back(true); + // mOutputs.emplace_back(nullptr, 0, 0); + // } + // + // for (index i = 0; i < client.controlChannelsOut(); ++i) + // { mOutputs.emplace_back(nullptr, 0, 0); } + // + // mCalcFunc = make_calc_function(); + // Wrapper::getInterfaceTable()->fClearUnitOutputs(this, 1); + } + + void next(int) + { +// auto& client = mWrapper->client(); +// auto& params = mWrapper->params(); +// const Unit* unit = this; + mControls.reset(mInBuf + ControlOffset(this)); + mDelegate.next(*this,mClient,mParams,mControls); +// bool trig = IsModel_t::value ? !mPrevTrig && in0(0) > 0 : false; +// bool shouldProcess = IsModel_t::value ? trig : true; +// mPrevTrig = trig; +// +// if(shouldProcess) +// { +// mWrapper->mControlsIterator.reset(mInBuf + mSpecialIndex + +// 1); // mClient.audioChannelsIn()); +// Wrapper::setParams(mWrapper, +// params, mWrapper->mControlsIterator); // forward on inputs N + audio inputs as params +// params.constrainParameterValues(); +// } +// for (index i = 0; i < client.audioChannelsIn(); ++i) +// { +// if (mInputConnections[asUnsigned(i)]) +// { mAudioInputs[asUnsigned(i)].reset(IN(i), 0, fullBufferSize()); } +// } +// for (index i = 0; i < client.audioChannelsOut(); ++i) +// { +// assert(i <= std::numeric_limits::max()); +// if (mOutputConnections[asUnsigned(i)]) +// mOutputs[asUnsigned(i)].reset(out(static_cast(i)), 0, +// fullBufferSize()); +// } +// for (index i = 0; i < client.controlChannelsOut(); ++i) +// { +// assert(i <= std::numeric_limits::max()); +// mOutputs[asUnsigned(i)].reset(out(static_cast(i)), 0, 1); +// } +// client.process(mAudioInputs, mOutputs, mContext); +// // } + } +private: + Delegate mDelegate; + FloatControlsIter mControls; + Params mParams{Client::getParameterDescriptors()}; + Client mClient; + // std::vector mInputConnections; + // std::vector mOutputConnections; + // std::vector mAudioInputs; + // std::vector mOutputs; + // FluidContext mContext; + + Wrapper* mWrapper{static_cast(this)}; + // bool mPrevTrig; +}; + +} +} +}