You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

596 lines
19 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
Part of the Fluid Corpus Manipulation Project (http://www.flucoma.org/)
Copyright 2017-2019 University of Huddersfield.
Licensed under the BSD-3 License.
See license.md file in the project root for full license information.
This project has received funding from the European Research Council (ERC)
under the European Unions Horizon 2020 research and innovation programme
(grant agreement No 725899).
*/
#pragma once
#include "SCBufferAdaptor.hpp"
#include <clients/common/FluidBaseClient.hpp>
#include <clients/common/Result.hpp>
#include <data/FluidTensor.hpp>
#include <data/TensorTypes.hpp>
#include <FluidVersion.hpp>
#include <SC_PlugIn.hpp>
#include <algorithm>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
namespace fluid {
namespace client {
template <typename Client>
class FluidSCWrapper;
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; }
private:
float** mValues;
index mSize;
index mCount{0};
};
////////////////////////////////////////////////////////////////////////////////
// Real Time Processor
template <typename Client, class Wrapper>
class RealTime : public SCUnit
{
using HostVector = FluidTensorView<float, 1>;
using ParamSetType = typename Client::ParamSetType;
public:
static void setup(InterfaceTable* ft, const char* name)
{
registerUnit<Wrapper>(ft, name);
ft->fDefineUnitCmd(name, "latency", doLatency);
}
static void doLatency(Unit* unit, sc_msg_iter*)
{
float l[]{
static_cast<float>(static_cast<Wrapper*>(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()
: mControlsIterator{mInBuf + mSpecialIndex + 1,
static_cast<index>(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(asUnsigned(mClient.audioChannelsIn()));
mOutputConnections.reserve(asUnsigned(mClient.audioChannelsOut()));
mAudioInputs.reserve(asUnsigned(mClient.audioChannelsIn()));
mOutputs.reserve(asUnsigned(
std::max(mClient.audioChannelsOut(), mClient.controlChannelsOut())));
for (index i = 0; i < mClient.audioChannelsIn(); ++i)
{
mInputConnections.emplace_back(isAudioRateIn(static_cast<int>(i)));
mAudioInputs.emplace_back(nullptr, 0, 0);
}
for (index i = 0; i < mClient.audioChannelsOut(); ++i)
{
mOutputConnections.emplace_back(true);
mOutputs.emplace_back(nullptr, 0, 0);
}
for (index i = 0; i < mClient.controlChannelsOut(); ++i)
{ mOutputs.emplace_back(nullptr, 0, 0); }
mCalcFunc = make_calc_function<RealTime, &RealTime::next>();
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 (index i = 0; i < mClient.audioChannelsIn(); ++i)
{
if (mInputConnections[asUnsigned(i)])
{ mAudioInputs[asUnsigned(i)].reset(IN(i), 0, fullBufferSize()); }
}
for (index i = 0; i < mClient.audioChannelsOut(); ++i)
{
assert(i <= std::numeric_limits<int>::max());
if (mOutputConnections[asUnsigned(i)])
mOutputs[asUnsigned(i)].reset(out(static_cast<int>(i)), 0,
fullBufferSize());
}
for (index i = 0; i < mClient.controlChannelsOut(); ++i)
{
assert(i <= std::numeric_limits<int>::max());
mOutputs[asUnsigned(i)].reset(out(static_cast<int>(i)), 0, 1);
}
mClient.process(mAudioInputs, mOutputs, mContext);
}
private:
std::vector<bool> mInputConnections;
std::vector<bool> mOutputConnections;
std::vector<HostVector> mAudioInputs;
std::vector<HostVector> 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 <typename Client, typename Wrapper>
class NonRealTime : public SCUnit
{
using ParamSetType = typename Client::ParamSetType;
public:
static void setup(InterfaceTable* ft, const char* name)
{
registerUnit<Wrapper>(ft, name);
ft->fDefineUnitCmd(name, "cancel", doCancel);
ft->fDefineUnitCmd(
name, "queue_enabled", [](struct Unit* unit, struct sc_msg_iter* args) {
auto w = static_cast<Wrapper*>(unit);
w->mQueueEnabled = args->geti(0);
w->mFifoMsg.Set(
w->mWorld,
[](FifoMsg* f) {
auto w = static_cast<Wrapper*>(f->mData);
w->mClient.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<Wrapper*>(unit);
w->mSynchronous = args->geti(0);
w->mFifoMsg.Set(
w->mWorld,
[](FifoMsg* f) {
auto w = static_cast<Wrapper*>(f->mData);
w->mClient.setSynchronous(w->mSynchronous);
},
nullptr, w);
Wrapper::getInterfaceTable()->fSendMsgFromRT(w->mWorld, w->mFifoMsg);
});
}
/// Penultimate input is the doneAction, final is blocking mode. Neither are
/// params, so we skip them in the controlsIterator
NonRealTime()
: mControlsIterator{mInBuf, index(mNumInputs) - mSpecialIndex - 2},
mParams{Wrapper::Client::getParameterDescriptors()},
mClient{Wrapper::setParams(mParams, mWorld->mVerbosity > 0, mWorld,
mControlsIterator, true)},
mSynchronous{mNumInputs > 2 ? (in0(int(mNumInputs) - 1) > 0) : false}
{}
~NonRealTime()
{
if (mClient.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()
{
mFifoMsg.Set(mWorld, initNRTJob, nullptr, this);
mWorld->ft->fSendMsgFromRT(mWorld, mFifoMsg);
// we want to poll thread roughly every 20ms
checkThreadInterval = static_cast<index>(0.02 / controlDur());
set_calc_function<NonRealTime, &NonRealTime::poll>();
};
/// 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<float>(mClient.progress());
if (0 == pollCounter++ && !mCheckingForDone)
{
mCheckingForDone = true;
mWorld->ft->fDoAsynchronousCommand(mWorld, nullptr, Wrapper::getName(),
this, 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)
{
auto w = static_cast<Wrapper*>(f->mData);
w->mDone = false;
w->mCancelled = false;
Result result = validateParameters(w);
if (!result.ok())
{
std::cout << "ERROR: " << Wrapper::getName() << ": "
<< result.message().c_str() << std::endl;
return;
}
w->mClient.setSynchronous(w->mSynchronous);
w->mClient.enqueue(w->mParams);
w->mClient.process();
}
/// Check result and report if bad
static bool postProcess(World*, void* data)
{
auto w = static_cast<Wrapper*>(data);
Result r;
ProcessState s = w->mClient.checkProgress(r);
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;
return false;
}
if (!r.ok())
{
std::cout << "ERROR: " << Wrapper::getName() << ": "
<< r.message().c_str() << std::endl;
return false;
}
w->mDone = true;
return true;
}
return false;
}
/// swap NRT buffers back to RT-land
static bool exchangeBuffers(World* world, void* data)
{
return static_cast<Wrapper*>(data)->exchangeBuffers(world);
}
/// Tidy up any temporary buffers
static bool tidyUp(World* world, void* data)
{
return static_cast<Wrapper*>(data)->tidyUp(world);
}
/// Now we're actually properly done, call the UGen's done action (possibly
/// destroying this instance)
static void destroy(World* world, void* data)
{
auto w = static_cast<Wrapper*>(data);
if (w->mDone &&
w->mNumInputs >
2) // don't check for doneAction if UGen has no ins (there should be
// 3 minimum -> sig, doneAction, blocking mode)
{
int doneAction = static_cast<int>(
w->in0(int(w->mNumInputs) -
2)); // doneAction is penultimate input; THIS IS THE LAW
world->ft->fDoneAction(doneAction, w);
return;
}
w->mCheckingForDone = false;
}
static void doCancel(Unit* unit, sc_msg_iter*)
{
static_cast<Wrapper*>(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) // RT thread
{
mParams.template forEachParamType<BufferT, AssignBuffer>(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 (mDone)
world->ft->fSendNodeReply(&mParent->mNode, 0, "/done", 0, nullptr);
if (mCancelled)
world->ft->fSendNodeReply(&mParent->mNode, 1, "/done", 0, nullptr);
return true;
}
bool tidyUp(World*) // NRT thread
{
mParams.template forEachParamType<BufferT, CleanUpBuffer>();
return true;
}
template <size_t N, typename T>
struct AssignBuffer
{
void operator()(const typename BufferT::type& p, World* w)
{
if (auto b = static_cast<SCBufferAdaptor*>(p.get())) b->assignToRT(w);
}
};
template <size_t N, typename T>
struct CleanUpBuffer
{
void operator()(const typename BufferT::type& p)
{
if (auto b = static_cast<SCBufferAdaptor*>(p.get())) b->cleanUp();
}
};
FloatControlsIter mControlsIterator;
FifoMsg mFifoMsg;
char* mCompletionMessage = nullptr;
void* mReplyAddr = nullptr;
const char* mName = nullptr;
index checkThreadInterval;
index pollCounter{0};
protected:
ParamSetType mParams;
Client mClient;
bool mSynchronous{true};
bool mQueueEnabled{false};
bool mCheckingForDone{false}; // only write to this from RT thread kthx
bool mCancelled{false};
};
////////////////////////////////////////////////////////////////////////////////
/// An impossible monstrosty
template <typename Client, typename Wrapper>
class NonRealTimeAndRealTime : public RealTime<Client, Wrapper>,
public NonRealTime<Client, Wrapper>
{
static void setup(InterfaceTable* ft, const char* name)
{
RealTime<Client, Wrapper>::setup(ft, name);
NonRealTime<Client, Wrapper>::setup(ft, name);
}
};
////////////////////////////////////////////////////////////////////////////////
// Template Specialisations for NRT/RT
template <typename Client, typename Wrapper, typename NRT, typename RT>
class FluidSCWrapperImpl;
template <typename Client, typename Wrapper>
class FluidSCWrapperImpl<Client, Wrapper, std::true_type, std::false_type>
: public NonRealTime<Client, Wrapper>
{};
template <typename Client, typename Wrapper>
class FluidSCWrapperImpl<Client, Wrapper, std::false_type, std::true_type>
: public RealTime<Client, Wrapper>
{};
////////////////////////////////////////////////////////////////////////////////
// Make base class(es), full of CRTP mixin goodness
template <typename Client>
using FluidSCWrapperBase =
FluidSCWrapperImpl<Client, FluidSCWrapper<Client>, isNonRealTime<Client>,
isRealTime<Client>>;
} // namespace impl
////////////////////////////////////////////////////////////////////////////////
/// The main wrapper
template <typename C>
class FluidSCWrapper : public impl::FluidSCWrapperBase<C>
{
using FloatControlsIter = impl::FloatControlsIter;
// Iterate over arguments via callbacks from params object
template <typename ArgType, size_t N, typename T>
struct Setter
{
static constexpr index argSize =
C::getParameterDescriptors().template get<N>().fixedSize;
auto fromArgs(World*, FloatControlsIter& args, LongT::type, int)
{
return args.next();
}
auto fromArgs(World*, FloatControlsIter& args, FloatT::type, int)
{
return args.next();
}
auto fromArgs(World* w, ArgType args, BufferT::type, int)
{
typename LongT::type bufnum =
static_cast<LongT::type>(fromArgs(w, args, LongT::type(), -1));
return BufferT::type(bufnum >= 0 ? new SCBufferAdaptor(bufnum, w)
: nullptr);
}
auto fromArgs(World* w, ArgType args, InputBufferT::type, int)
{
typename LongT::type bufnum =
static_cast<LongT::type>(fromArgs(w, args, LongT::type(), -1));
return InputBufferT::type(bufnum >= 0 ? new SCBufferAdaptor(bufnum, w)
: nullptr);
}
typename T::type operator()(World* w, ArgType args)
{
ParamLiteralConvertor<T, argSize> a;
using LiteralType =
typename ParamLiteralConvertor<T, argSize>::LiteralType;
for (index i = 0; i < argSize; i++)
a[i] = static_cast<LiteralType>(fromArgs(w, args, a[0], 0));
return a.value();
}
};
template <size_t N, typename T>
using ControlSetter = Setter<FloatControlsIter&, N, T>;
static void doVersion(Unit*, sc_msg_iter*)
{
std::cout << "Fluid Corpus Manipualtion Toolkit version " << fluidVersion()
<< std::endl;
}
public:
using Client = C;
using ParameterSetType = typename C::ParamSetType;
FluidSCWrapper() { impl::FluidSCWrapperBase<Client>::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<Client>::setup(ft, name);
ft->fDefineUnitCmd(name, "version", doVersion);
}
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<ControlSetter>(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"
<< std::endl;
// TODO: work out how to bring any further work to a halt
}
return p;
}
};
template <template <typename T> class Client>
void makeSCWrapper(const char* name, InterfaceTable* ft)
{
FluidSCWrapper<Client<float>>::setup(ft, name);
}
} // namespace client
} // namespace fluid