#pragma once #include "SCBufferAdaptor.hpp" #include #include #include #include #include #include #include #include #include #include #include #include namespace fluid { namespace client { template class FluidSCWrapper; namespace impl { // Iterate over kr/ir inputs via callbacks from params object struct FloatControlsIter { FloatControlsIter(float **vals, size_t N) : mValues(vals) , mSize(N) {} float next() { return mCount >= mSize ? 0 : *mValues[mCount++]; } void reset(float **vals) { mValues = vals; mCount = 0; } size_t size() const noexcept { return mSize; } private: float **mValues; size_t mSize; size_t mCount{0}; }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Real Time Processor template class RealTime : public SCUnit { using HostVector = FluidTensorView; using ParamSetType = typename Client::ParamSetType; // using Client = typename Wrapper::ClientType; public: static void setup(InterfaceTable *ft, const char *name) { registerUnit(ft, name); ft->fDefineUnitCmd(name, "latency", doLatency); } 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() << '\n'; ft->fSendNodeReply(&unit->mParent->mNode, -1, ss.str().c_str(), 1, l); } RealTime() : mControlsIterator{mInBuf + mSpecialIndex + 1,static_cast(static_cast(mNumInputs) - mSpecialIndex - 1)} , mParams{Wrapper::Client::getParameterDescriptors()} , mClient{Wrapper::setParams(mParams,mWorld->mVerbosity > 0, mWorld, mControlsIterator,true)} {} void init() { assert(!(mClient.audioChannelsOut() > 0 && mClient.controlChannelsOut() > 0) && "Client can't have both audio and control outputs"); //If we don't the number of arguments we expect, the language side code is probably the wrong version //set plugin to no-op, squawk, and bail; if(mControlsIterator.size() != Client::getParameterDescriptors().count()) { mCalcFunc = Wrapper::getInterfaceTable()->fClearUnitOutputs; std::cout << "ERROR: " << Wrapper::getName() << " wrong number of arguments. Expected " << Client::getParameterDescriptors().count() << ", got " << mControlsIterator.size() << ". Your .sc file and binary plugin might be different versions." << std::endl; return; } mClient.sampleRate(fullSampleRate()); mInputConnections.reserve(mClient.audioChannelsIn()); mOutputConnections.reserve(mClient.audioChannelsOut()); mAudioInputs.reserve(mClient.audioChannelsIn()); mOutputs.reserve(std::max(mClient.audioChannelsOut(), mClient.controlChannelsOut())); for (int i = 0; i < static_cast(mClient.audioChannelsIn()); ++i) { mInputConnections.emplace_back(isAudioRateIn(i)); mAudioInputs.emplace_back(nullptr, 0, 0); } for (int i = 0; i < static_cast(mClient.audioChannelsOut()); ++i) { mOutputConnections.emplace_back(true); mOutputs.emplace_back(nullptr, 0, 0); } for (int i = 0; i < static_cast(mClient.controlChannelsOut()); ++i) { mOutputs.emplace_back(nullptr, 0, 0); } mCalcFunc = make_calc_function(); Wrapper::getInterfaceTable()->fClearUnitOutputs(this, 1); } void next(int) { mControlsIterator.reset(mInBuf + 1); //mClient.audioChannelsIn()); Wrapper::setParams(mParams, mWorld->mVerbosity > 0, mWorld, mControlsIterator); // forward on inputs N + audio inputs as params mParams.constrainParameterValues(); const Unit *unit = this; for (size_t i = 0; i < mClient.audioChannelsIn(); ++i) { if (mInputConnections[i]) mAudioInputs[i].reset(IN(i), 0, fullBufferSize()); } for (size_t i = 0; i < mClient.audioChannelsOut(); ++i) { if (mOutputConnections[i]) mOutputs[i].reset(out(static_cast(i)), 0, fullBufferSize()); } for (size_t i = 0; i < mClient.controlChannelsOut(); ++i) { mOutputs[i].reset(out(static_cast(i)), 0, 1); } mClient.process(mAudioInputs, mOutputs,mContext); } private: std::vector mInputConnections; std::vector mOutputConnections; std::vector mAudioInputs; std::vector mOutputs; FloatControlsIter mControlsIterator; FluidContext mContext; protected: ParamSetType mParams; Client mClient; }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// 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; public: static void setup(InterfaceTable *ft, const char *name) { registerUnit(ft, name); ft->fDefineUnitCmd(name, "cancel", doCancel); } /// Final input is the doneAction, not a param, so we skip it in the controlsIterator NonRealTime() : mControlsIterator{mInBuf,static_cast(static_cast(mNumInputs) - mSpecialIndex - 1)} , mParams{Wrapper::Client::getParameterDescriptors()} , mClient{Wrapper::setParams(mParams,mWorld->mVerbosity > 0, mWorld, mControlsIterator,true)} {} /// 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() { mClient.setSynchronous(false); mFifoMsg.Set(mWorld, initNRTJob, nullptr, this); mWorld->ft->fSendMsgFromRT(mWorld,mFifoMsg); //we want to poll thread roughly every 20ms checkThreadInterval = static_cast(0.02 / controlDur()); set_calc_function(); }; /// The calc function. Checks to see if we've cancelled, spits out progress, launches tidy up when complete void poll(int) { // if(!mClient.done()) // { out0(0) = static_cast(mClient.progress()); // } // else { if(0 == pollCounter++) { mWorld->ft->fDoAsynchronousCommand(mWorld, nullptr, Wrapper::getName(), this, postProcess, exchangeBuffers, tidyUp, destroy, 0, nullptr); } pollCounter %= checkThreadInterval; // if(mClient.state() == kDone) // mDone = true; // mCalcFunc = mWorld->ft->fClearUnitOutputs; // if(!mDone) // } } /// To be called on NRT thread. Validate parameters and commence processing in new thread static void initNRTJob(FifoMsg* f) { auto w = static_cast(f->mData); Result result = validateParameters(w); if (!result.ok()) { std::cout << "ERROR: " << Wrapper::getName() << ": " << result.message().c_str() << std::endl; // w->mDone = true; return; } w->mClient.process(); } /// Check result and report if bad static bool postProcess(World*, void *data) { auto w = static_cast(data); Result r; ProcessState s = w->mClient.checkProgress(r); if(s==ProcessState::kDone || s==ProcessState::kDoneStillProcessing) { if(r.status() == Result::Status::kCancelled) { std::cout << Wrapper::getName() << ": Processing cancelled \n"; return false; } if(!r.ok()) { std::cout << "ERROR: " << Wrapper::getName() << ": " << r.message().c_str() << '\n'; return false; } return true; } return false; } /// swap NRT buffers back to RT-land static bool exchangeBuffers(World *world, void *data) { return static_cast(data)->exchangeBuffers(world); } /// Tidy up any temporary buffers static bool tidyUp(World *world, void *data) { return static_cast(data)->tidyUp(world); } /// Now we're actually properly done, call the UGen's done action (possibly destroying this instance) static void destroy(World*, void*) { // auto w = static_cast(data); // if(w->mDone) // { // int doneAction = static_cast(w->in0(static_cast(w->mNumInputs - 1))); // world->ft->fDoneAction(doneAction,w); // } } static void doCancel(Unit *unit, sc_msg_iter*) { static_cast(unit)->mClient.cancel(); } private: static Result validateParameters(NonRealTime *w) { auto results = w->mParams.constrainParameterValues(); for (auto &r : results) { if (!r.ok()) return r; } return {}; } bool exchangeBuffers(World *world) { mParams.template forEachParamType(world); return true; } bool tidyUp(World *) { mParams.template forEachParamType(); return true; } 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(); } }; FloatControlsIter mControlsIterator; FifoMsg mFifoMsg; char * mCompletionMessage = nullptr; void * mReplyAddr = nullptr; const char *mName = nullptr; size_t checkThreadInterval; size_t pollCounter{0}; protected: ParamSetType mParams; Client mClient; }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// 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); } }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Template Specialisations for NRT/RT template class FluidSCWrapperImpl; template class FluidSCWrapperImpl : public NonRealTime { //public: // FluidSCWrapperImpl(World* w, sc_msg_iter *args): NonRealTime(w,args){}; }; template class FluidSCWrapperImpl : public RealTime {}; //////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Make base class(es), full of CRTP mixin goodness template using FluidSCWrapperBase = FluidSCWrapperImpl, isNonRealTime, isRealTime>; } // namespace impl //////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///The main wrapper template class FluidSCWrapper : public impl::FluidSCWrapperBase { using FloatControlsIter = impl::FloatControlsIter; template struct ParamReader { static auto fromArgs(World *, sc_msg_iter* args, std::string, int) { return std::string(args->gets("")); } static auto fromArgs(World *, FloatControlsIter& args, LongT::type, int) { return args.next(); } static auto fromArgs(World *, FloatControlsIter& args, FloatT::type, int) { return args.next(); } static auto fromArgs(World *, sc_msg_iter* args, LongT::type, int defVal) { return args->geti(defVal); } static auto fromArgs(World *, sc_msg_iter* args, FloatT::type, int) { return args->getf(); } static auto fromArgs(World *w, ArgType args, BufferT::type&, int) { typename LongT::type bufnum = static_cast(fromArgs(w, args, LongT::type(), -1)); return BufferT::type(bufnum >= 0 ? new SCBufferAdaptor(bufnum, w) : nullptr); } static auto fromArgs(World *w, ArgType args, InputBufferT::type&, int) { typename LongT::type bufnum = static_cast(fromArgs(w, args, LongT::type(), -1)); return InputBufferT::type(bufnum >= 0 ? new SCBufferAdaptor(bufnum, w) : nullptr); } }; // Iterate over arguments via callbacks from params object template struct Setter { static constexpr size_t argSize = C::getParameterDescriptors().template get().fixedSize; typename T::type operator()(World *w, ArgType args) { ParamLiteralConvertor a; using LiteralType = typename ParamLiteralConvertor::LiteralType; for (size_t i = 0; i < argSize; i++) a[i] = static_cast(ParamReader::fromArgs(w, 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 size_t allocSize(SCBufferAdaptor*){ return 1; } static size_t allocSize(double){ return 1; } static size_t allocSize(float){ return 1; } static size_t allocSize(intptr_t){ return 1; } static size_t allocSize(std::string s){ return s.size(); } static size_t allocSize(FluidTensor s) { size_t count = 0; for(auto& str: s) count += (str.size() + 1); return count; } template static size_t allocSize(FluidTensor s) { return s.size() ; } static void convert(float* f, SCBufferAdaptor* buf) { f[0] = buf->bufnum(); } static void convert(float* f, double d) { f[0] = static_cast(d); } static void convert(float* f, intptr_t i) { f[0] = i; } static void convert(float* f, std::string s) { std::copy(s.begin(), s.end(), f); } 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); } }; //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; FluidSCWrapper* wrapper; 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::IndexList; launchMessageImpl(x,args,IndexList()); } template static void launchMessageImpl(FluidSCWrapper* x,sc_msg_iter* inArgs,std::index_sequence) { 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; ArgTuple& args = msg->args; (void)std::initializer_list{(std::get(args) = ParamReader::fromArgs(x->mWorld,inArgs,std::get(args),0),0)...}; msg->name = '/' + Client::getMessageDescriptors().template name(); msg->wrapper = x; ft->fDoAsynchronousCommand(x->mWorld, nullptr, getName(), msg, [](World*, void* data) //NRT thread: invocation { MessageData* m = static_cast(data); m->result = ReturnType{invokeImpl(m->wrapper, m->args, IndexList{})}; if(!m->result.ok()) { printResult(m->wrapper, m->result); return false; } return true; }, [](World*, void* data) //RT thread: response { MessageData* m = static_cast(data); messageOutput(m->wrapper,m->name,m->result); return true; } , nullptr, //NRT Thread: No-op [](World* w, void* data) //RT thread: clean up { getInterfaceTable()->fRTFree(w,data); }, 0, nullptr); } template //Call from NRT static decltype(auto) invokeImpl(FluidSCWrapper* x, ArgsTuple& args, std::index_sequence) { return x->mClient.template invoke(x->mClient,std::get(args)...); } template //call from RT static void messageOutput(FluidSCWrapper* x, const std::string& s, MessageResult& result) { auto ft = getInterfaceTable(); //allocate return values size_t numArgs = ToFloatArray::allocSize(static_cast(result)); float* values = static_cast(ft->fRTAlloc(x->mWorld,numArgs * sizeof(float))); //copy return data ToFloatArray::convert(values,static_cast(result)); ft->fSendNodeReply(&x->mParent->mNode, -1, s.c_str(), static_cast(numArgs), values); } static void messageOutput(FluidSCWrapper* x, const std::string& s, MessageResult&) { auto ft = getInterfaceTable(); ft->fSendNodeReply(&x->mParent->mNode, -1, s.c_str(), 0, nullptr); } public: using Client = C; using ParameterSetType = typename C::ParamSetType; FluidSCWrapper() { impl::FluidSCWrapperBase::init(); } static const char *getName(const char *setName = nullptr) { static const char *name = nullptr; return (name = setName ? setName : name); } static InterfaceTable *getInterfaceTable(InterfaceTable *setTable = nullptr) { static InterfaceTable *ft = nullptr; return (ft = setTable ? setTable : ft); } static void setup(InterfaceTable *ft, const char *name) { getName(name); getInterfaceTable(ft); impl::FluidSCWrapperBase::setup(ft, name); Client::getMessageDescriptors().template iterate(); } static auto& setParams(ParameterSetType& p, bool verbose, World* world, FloatControlsIter& inputs, bool constrain = false) { //We won't even try and set params if the arguments don't match if(inputs.size() == C::getParameterDescriptors().count()) { p.template setParameterValues(verbose, world, inputs); if (constrain)p.constrainParameterValues(); } else { std::cout << "ERROR: " << getName() << ": parameter count mismatch. Perhaps your binary plugins and SC sources are different versions\n"; //TODO: work out how to bring any further work to a halt } return p; } static void printResult(FluidSCWrapper* x, Result& r) { if (!x) return; switch (r.status()) { case Result::Status::kWarning: { if(x->mWorld->mVerbosity > 0) std::cout << "WARNING: " << r.message().c_str() << '\n'; break; } case Result::Status::kError: { std::cout << "ERROR: " << r.message().c_str() << '\n'; break; } case Result::Status::kCancelled: { std::cout << "Task cancelled\n" << '\n'; break; } default: { } } } }; template class Client> void makeSCWrapper(const char *name, InterfaceTable *ft) { FluidSCWrapper>::setup(ft, name); } } // namespace client } // namespace fluid