Merge branch 'ParametersWithTuples' into refactor

nix
Owen Green 7 years ago
commit 2fef8a0a07

@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.3)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -mavx -msse -msse2 -msse3 -msse4")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -stdlib=libc++")
@ -30,6 +30,8 @@ MACRO(SUBDIRLIST result curdir)
ENDMACRO()
set(FLUID_PATH ~/fluid_decomposition CACHE PATH "The top level of the fluid_decomposition repo")
set(LOCAL_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/include)
get_filename_component(FLUID_ABS_PATH ${FLUID_PATH} ABSOLUTE)
message(${FLUID_ABS_PATH})
@ -59,6 +61,13 @@ if(APPLE OR WIN32)
set(CMAKE_SHARED_MODULE_SUFFIX ".scx")
endif()
add_library(FLUID_SC_WRAPPER INTERFACE)
target_sources(FLUID_SC_WRAPPER
INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/include/FluidSCWrapper.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/SCBufferAdaptor.hpp
)
SUBDIRLIST(PROJECT_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/src)
foreach (project_dir ${PROJECT_DIRS})
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/${project_dir}/CMakeLists.txt")

@ -0,0 +1,462 @@
#pragma once
#include "SCBufferAdaptor.hpp"
#include <clients/common/FluidBaseClient.hpp>
#include <clients/common/Result.hpp>
#include <data/FluidTensor.hpp>
#include <data/TensorTypes.hpp>
#include <SC_PlugIn.hpp>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
namespace fluid {
namespace client {
template <typename Client, typename Params> class FluidSCWrapper;
namespace impl {
template <typename Client, typename T, size_t N> struct Setter;
template <size_t N, typename T> struct ArgumentGetter;
template <size_t N, typename T> struct ControlGetter;
template <typename T> using msg_iter_method = T (sc_msg_iter::*)(T);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Iterate over kr/ir inputs via callbacks from params object
struct FloatControlsIter
{
FloatControlsIter(float** vals, size_t N):mValues(vals), mSize(N) {}
float next()
{
assert(mCount < mSize && "Boundary error fail horror");
float f = *mValues[mCount++];
return f;
}
// float operator[](size_t i)
// {
// assert(i < mSize);
// return *mValues[i];
// }
void reset(float** vals)
{
mValues = vals;
mCount = 0;
}
private:
float** mValues;
size_t mSize;
size_t mCount{0};
};
//General case
template <size_t N, typename T> struct GetControl
{
T operator()(World*, FloatControlsIter& controls) { return controls.next(); }
};
template <size_t N, typename T> struct ControlGetter : public GetControl<N, typename T::type>
{};
//Specializations
template <size_t N> struct ControlGetter<N, BufferT>
{
auto operator() (World* w, FloatControlsIter& iter)
{
typename LongT::type bufnum = iter.next();
return std::unique_ptr<BufferAdaptor>(bufnum >= 0 ? new SCBufferAdaptor(bufnum,w): nullptr);
}
};
template<size_t N>
struct ControlGetter<N,FloatPairsArrayT>
{
typename FloatPairsArrayT::type operator()(World*, FloatControlsIter& iter)
{
return {{iter.next(),iter.next()},{iter.next(),iter.next()}};
}
};
template<size_t N>
struct ControlGetter<N,FFTParamsT>
{
typename FFTParamsT::type operator()(World*, FloatControlsIter& iter)
{
return {static_cast<long>(iter.next()),static_cast<long>(iter.next()),static_cast<long>(iter.next())};
}
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// Iterate over arguments in sc_msg_iter, via callbacks from params object
template <size_t N, typename T, msg_iter_method<T> Method> struct GetArgument
{
T operator()(World* w, sc_msg_iter *args)
{
T r = (args->*Method)(T{0});
return r;
}
};
//General cases
template <size_t N> struct ArgumentGetter<N, FloatT> : public GetArgument<N, float, &sc_msg_iter::getf>
{};
template <size_t N> struct ArgumentGetter<N, LongT> : public GetArgument<N, int32, &sc_msg_iter::geti>
{};
template <size_t N> struct ArgumentGetter<N, EnumT> : public GetArgument<N, int32, &sc_msg_iter::geti>
{};
//Specializations
template <size_t N> struct ArgumentGetter<N, BufferT>
{
auto operator() (World* w, sc_msg_iter *args)
{
typename LongT::type bufnum = args->geti(-1);
return std::unique_ptr<BufferAdaptor>(bufnum >= 0 ? new SCBufferAdaptor(bufnum,w) : nullptr);
}
};
template <size_t N> struct ArgumentGetter<N, FloatPairsArrayT>
{
typename FloatPairsArrayT::type operator()(World* w, sc_msg_iter *args)
{
return {{args->getf(),args->getf()},{args->getf(),args->getf()}};
}
};
template <size_t N> struct ArgumentGetter<N, FFTParamsT>
{
typename FFTParamsT::type operator()(World* w, sc_msg_iter *args)
{
return {args->geti(),args->geti(),args->geti()};
}
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Real Time Processor
template <typename Client,class Wrapper, class Params> class RealTime : public SCUnit
{
using HostVector = FluidTensorView<float, 1>;
// using Client = typename Wrapper::ClientType;
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 *args)
{
float l[] {static_cast<float>(static_cast<Wrapper*>(unit)->mClient.latency())};
auto ft = Wrapper::getInterfaceTable();
ft->fSendNodeReply(&unit->mParent->mNode,-1,Wrapper::getName(), 1, l);
}
RealTime():
mControlsIterator{mInBuf + mSpecialIndex + 1,mNumInputs - mSpecialIndex - 1},
mParams{*Wrapper::getParamDescriptors()},
mClient{Wrapper::setParams(mParams,mWorld->mVerbosity > 0, mWorld, mControlsIterator)}
{}
void init()
{
assert(!(mClient.audioChannelsOut() > 0 && mClient.controlChannelsOut() > 0) && "Client can't have both audio and control outputs");
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 < mClient.audioChannelsIn(); ++i)
{
mInputConnections.emplace_back(isAudioRateIn(i));
mAudioInputs.emplace_back(nullptr, 0, 0);
}
for (int i = 0; i < mClient.audioChannelsOut(); ++i)
{
mOutputConnections.emplace_back(true);
mOutputs.emplace_back(nullptr, 0, 0);
}
for (int i = 0; i < mClient.controlChannelsOut(); ++i)
{
mOutputs.emplace_back(nullptr, 0, 0);
}
set_calc_function<RealTime, &RealTime::next>();
Wrapper::getInterfaceTable()->fClearUnitOutputs(this, 1);
}
void next(int n)
{
mControlsIterator.reset(mInBuf + 1); //mClient.audioChannelsIn());
Wrapper::setParams(mParams,mWorld->mVerbosity > 0, mWorld,mControlsIterator); // forward on inputs N + audio inputs as params
const Unit *unit = this;
for (int i = 0; i < mClient.audioChannelsIn(); ++i)
{
if (mInputConnections[i]) mAudioInputs[i].reset(IN(i), 0, fullBufferSize());
}
for (int i = 0; i < mClient.audioChannelsOut(); ++i)
{
if (mOutputConnections[i]) mOutputs[i].reset(out(i), 0, fullBufferSize());
}
for(int i = 0; i < mClient.controlChannelsOut();++i)
{
mOutputs[i].reset(out(i),0,1);
}
mClient.process(mAudioInputs, mOutputs);
}
private:
std::vector<bool> mInputConnections;
std::vector<bool> mOutputConnections;
std::vector<HostVector> mAudioInputs;
std::vector<HostVector> mOutputs;
FloatControlsIter mControlsIterator;
protected:
ParameterSet<Params> mParams;
Client mClient;
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// Non Real Time Processor
template <typename Client, typename Wrapper, typename Params> class NonRealTime
{
public:
static void setup(InterfaceTable *ft, const char *name) { DefinePlugInCmd(name, launch, nullptr); }
NonRealTime(World *world,sc_msg_iter *args):
mParams{*Wrapper::getParamDescriptors()},
mClient{mParams}
{}
void init(){};
static void launch(World *world, void *inUserData, struct sc_msg_iter *args, void *replyAddr)
{
Wrapper *w = new Wrapper(world,args); //this has to be on the heap, because it doesn't get destoryed until the async command is done
int argsPosition = args->count;
auto argsRdPos = args->rdpos;
Result result = validateParameters(w, world, args);
if (!result.ok())
{
std::cout << "FluCoMa Error " << Wrapper::getName() << ": " << result.message().c_str();
delete w;
return;
}
args->count = argsPosition;
args->rdpos = argsRdPos;
Wrapper::setParams(w->mParams,false, world, args);
size_t msgSize = args->getbsize();
std::vector<char> completionMessage(msgSize);
// char * completionMsgData = 0;
if (msgSize)
{
args->getb(completionMessage.data(), msgSize);
}
world->ft->fDoAsynchronousCommand(world, replyAddr, Wrapper::getName(), w, process, exchangeBuffers, tidyUp, destroy,msgSize, completionMessage.data());
}
static bool process(World *world, void *data) { return static_cast<Wrapper *>(data)->process(world); }
static bool exchangeBuffers(World *world, void *data) { return static_cast<Wrapper *>(data)->exchangeBuffers(world); }
static bool tidyUp(World *world, void *data) { return static_cast<Wrapper *>(data)->tidyUp(world); }
static void destroy(World *world, void *data)
{
// void* c = static_cast<Wrapper *>(data)->mCompletionMessage;
// if(c) world->ft->fRTFree(world,c);
delete static_cast<Wrapper *>(data);
}
protected:
ParameterSet<Params> mParams;
Client mClient;
private:
static Result validateParameters(NonRealTime *w, World* world, sc_msg_iter *args)
{
auto results = w->mParams.template checkParameterValues<ArgumentGetter>(world, args);
for (auto &r : results)
{
std::cout << r.message() << '\n';
if (!r.ok()) return r;
}
return {};
}
bool process(World *world)
{
Result r = mClient.process();///mInputs, mOutputs);
if(!r.ok())
{
std::cout << "FluCoMa Error " << Wrapper::getName() << ": " << r.message().c_str();
return false;
}
return true;
}
bool exchangeBuffers(World *world)
{
mParams.template forEachParamType<BufferT,AssignBuffer>(world);
// for (auto &b : mBuffersOut) b.assignToRT(world);
return true;
}
bool tidyUp(World *world)
{
// for (auto &b : mBuffersIn) b.cleanUp();
// for (auto &b : mBuffersOut) b.cleanUp()
mParams.template forEachParamType<BufferT,CleanUpBuffer>();
return true;
}
template<size_t N,typename T>
struct AssignBuffer
{
void operator()(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()(typename BufferT::type& p)
{
if(auto b = static_cast<SCBufferAdaptor*>(p.get()))
b->cleanUp();
}
};
// std::vector<SCBufferAdaptor> mBuffersIn;
// std::vector<SCBufferAdaptor> mBuffersOut;
// std::vector<BufferProcessSpec> mInputs;
// std::vector<BufferProcessSpec> mOutputs;
char * mCompletionMessage = nullptr;
void * mReplyAddr = nullptr;
const char * mName = nullptr;
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// An impossible monstrosty
template <typename Client, typename Wrapper, typename Params> class NonRealTimeAndRealTime : public RealTime<Client,Wrapper, Params>, public NonRealTime<Client,Wrapper, Params>
{
static void setup(InterfaceTable *ft, const char *name)
{
RealTime<Client,Wrapper,Params >::setup(ft, name);
NonRealTime<Client,Wrapper, Params>::setup(ft, name);
}
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Template Specialisations for NRT/RT
template <typename Client, typename Wrapper, typename Params, typename NRT, typename RT> class FluidSCWrapperImpl;
template <typename Client, typename Wrapper, typename Params> class FluidSCWrapperImpl<Client, Wrapper, Params, std::true_type, std::false_type> : public NonRealTime<Client, Wrapper, Params>
{
public:
FluidSCWrapperImpl(World* w, sc_msg_iter *args): NonRealTime<Client, Wrapper, Params>(w,args){};
};
template <typename Client, typename Wrapper, typename Params> class FluidSCWrapperImpl<Client, Wrapper,Params, std::false_type, std::true_type> : public RealTime<Client, Wrapper, Params>
{};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Make base class(es), full of CRTP mixin goodness
template <typename Client,typename Params>
using FluidSCWrapperBase = FluidSCWrapperImpl<Client, FluidSCWrapper<Client, Params>,Params, isNonRealTime<Client>, isRealTime<Client>>;
} // namespace impl
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///The main wrapper
template <typename C, typename P> class FluidSCWrapper : public impl::FluidSCWrapperBase<C,P>
{
public:
using Client = C;
using Params = P;
FluidSCWrapper() //mParams{*getParamDescriptors()}, //impl::FluidSCWrapperBase<Client,Params>()
{ impl::FluidSCWrapperBase<Client,Params>::init(); }
FluidSCWrapper(World* w, sc_msg_iter *args): impl::FluidSCWrapperBase<Client, Params>(w,args)
{ impl::FluidSCWrapperBase<Client, Params>::init(); }
static const char *getName(const char *setName = nullptr)
{
static const char *name = nullptr;
return (name = setName ? setName : name);
}
static Params *getParamDescriptors(Params *setParams = nullptr)
{
static Params* descriptors = nullptr;
return (descriptors = setParams ? setParams : descriptors);
}
static InterfaceTable *getInterfaceTable(InterfaceTable *setTable = nullptr)
{
static InterfaceTable *ft = nullptr;
return (ft = setTable ? setTable : ft);
}
static void setup(Params& p, InterfaceTable *ft, const char *name)
{
getName(name);
getInterfaceTable(ft);
getParamDescriptors(&p);
impl::FluidSCWrapperBase<Client, Params>::setup(ft, name);
}
template<typename ParameterSet>
static auto& setParams(ParameterSet& p, bool verbose, World* world, impl::FloatControlsIter& inputs)
{
p.template setParameterValues<impl::ControlGetter>(verbose, world, inputs);
return p;
}
template<typename ParameterSet>
static auto& setParams(ParameterSet& p, bool verbose, World* world, sc_msg_iter *args)
{
p.template setParameterValues<impl::ArgumentGetter>(verbose,world, args);
return p;
}
// impl::ParameterSet<Params> mParams;
// Client &client() { return mClient; }
//
//private:
// Client mClient;
};
template <template <typename...> class Client,typename...Rest,typename Params>
void makeSCWrapper(const char *name, Params& params, InterfaceTable *ft)
{
FluidSCWrapper<Client<ParameterSet<Params>,Rest...>, Params>::setup(params, ft, name);
}
} // namespace client
} // namespace fluid

@ -0,0 +1,250 @@
#pragma once
#include <SC_PlugIn.h>
#include <boost/align/aligned_alloc.hpp>
#include <cctype>
//#include <clients/common/FluidParams.hpp>
#include <data/FluidTensor.hpp>
#include <clients/common/BufferAdaptor.hpp>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
// static InterfaceTable *ft;
namespace fluid
{
namespace client
{
/**
A descendent of SndBuf that will populate itself
from the NRT mirror buffers given a world and a bufnum
**/
struct NRTBuf {
NRTBuf(SndBuf *b)
: mBuffer(b)
{
}
NRTBuf(World *world, long bufnum, bool rt = false)
: NRTBuf(rt ? World_GetBuf(world, bufnum)
: World_GetNRTBuf(world, bufnum))
{
if (mBuffer && !mBuffer->samplerate)
mBuffer->samplerate = world->mFullRate.mSampleRate;
}
protected:
SndBuf *mBuffer;
};
/**
A combination of SndBuf and client::BufferAdaptor (which, in turn, exposes
FluidTensorView<float,2>), for simple transfer of data
Given a World* and a buffer number, this will populate its SndBuf stuff
from the NRT mirror buffers, and create a FluidTensorView wrapper of
appropriate dimensions.
The SndBuf can then be 'transferred' back to the RT buffers once we're done
with it, and SC notified of the update. (In the context of SequencedCommands,
in which this is meant to be used, this would happen at Stage3() on the
real-time thread)
nSamps = rows
nChans = columns
**/
class SCBufferAdaptor : public NRTBuf, public client::BufferAdaptor
{
public:
// SCBufferAdaptor() = delete;
SCBufferAdaptor(SCBufferAdaptor &) = delete;
SCBufferAdaptor operator=(SCBufferAdaptor &) = delete;
SCBufferAdaptor(SCBufferAdaptor&&) = default;
SCBufferAdaptor& operator=(SCBufferAdaptor&&) = default;
SCBufferAdaptor(long bufnum,World *world, bool rt = false)
: NRTBuf(world, bufnum, rt)
, mBufnum(bufnum)
, mWorld(world)
{
}
SCBufferAdaptor() = default;
~SCBufferAdaptor(){ cleanUp(); }
void assignToRT(World *rtWorld)
{
SndBuf *rtBuf = World_GetBuf(rtWorld, mBufnum);
*rtBuf = *mBuffer;
rtWorld->mSndBufUpdates[mBufnum].writes++;
}
void cleanUp()
{
if (mOldData)
{
boost::alignment::aligned_free(mOldData);
mOldData = nullptr;
}
}
// No locks in (vanilla) SC, so no-ops for these
void acquire() override {}
void release() override {}
// Validity is based on whether this buffer is within the range the server
// knows about
bool valid() const override
{
return (mBuffer && mBufnum >= 0 && mBufnum < mWorld->mNumSndBufs);
}
bool exists() const override
{
return true;
}
FluidTensorView<float, 1> samps(size_t channel, size_t rankIdx = 0) override
{
FluidTensorView<float, 2> v{mBuffer->data, 0,
static_cast<size_t>(mBuffer->frames),
static_cast<size_t>(mBuffer->channels)};
return v.col(rankIdx + channel * mRank);
}
// Return a 2D chunk
FluidTensorView<float, 1> samps(size_t offset, size_t nframes,
size_t chanoffset) override
{
FluidTensorView<float, 2> v{mBuffer->data, 0,
static_cast<size_t>(mBuffer->frames),
static_cast<size_t>(mBuffer->channels)};
return v(fluid::Slice(offset, nframes), fluid::Slice(chanoffset, 1)).col(0);
}
size_t numFrames() const override
{
return valid() ? this->mBuffer->frames : 0;
}
size_t numChans() const override
{
return valid() ? this->mBuffer->channels / mRank : 0;
}
size_t rank() const override { return valid() ? mRank : 0; }
void resize(size_t frames, size_t channels, size_t rank) override
{
SndBuf *thisThing = mBuffer;
mOldData = thisThing->data;
mRank = rank;
mWorld->ft->fBufAlloc(mBuffer, channels * rank, frames,
thisThing->samplerate);
}
int bufnum() { return mBufnum; }
void realTime(bool rt) { mRealTime = rt; }
protected:
bool equal(BufferAdaptor *rhs) const override
{
SCBufferAdaptor *x = dynamic_cast<SCBufferAdaptor *>(rhs);
if (x) { return mBufnum == x->mBufnum; }
return false;
}
bool mRealTime{false};
float *mOldData{0};
long mBufnum;
World *mWorld;
size_t mRank{1};
};
class RTBufferView : public client::BufferAdaptor
{
public:
RTBufferView(World *world, int bufnum)
: mWorld(world)
, mBufnum(bufnum)
{
}
void acquire() override { mBuffer = World_GetBuf(mWorld, mBufnum); }
void release() override {}
// Validity is based on whether this buffer is within the range the server
// knows about
bool valid() const override
{
return (mBuffer && mBufnum >= 0 && mBufnum < mWorld->mNumSndBufs);
}
FluidTensorView<float, 1> samps(size_t channel, size_t rankIdx = 0) override
{
FluidTensorView<float, 2> v{mBuffer->data, 0,
static_cast<size_t>(mBuffer->frames),
static_cast<size_t>(mBuffer->channels)};
return v.col(rankIdx + channel * mRank);
}
FluidTensorView<float, 1> samps(size_t offset, size_t nframes,
size_t chanoffset) override
{
FluidTensorView<float, 2> v{mBuffer->data, 0,
static_cast<size_t>(mBuffer->frames),
static_cast<size_t>(mBuffer->channels)};
return v(fluid::Slice(offset, nframes), fluid::Slice(chanoffset, 1)).col(0);
}
size_t numFrames() const override
{
return valid() ? this->mBuffer->frames : 0;
}
size_t numChans() const override
{
return valid() ? this->mBuffer->channels / mRank : 0;
}
size_t rank() const override { return valid() ? mRank : 0; }
void resize(size_t frames, size_t channels, size_t rank) override
{
assert(false && "Don't try and resize real-time buffers");
}
int bufnum() { return mBufnum; }
private:
bool equal(BufferAdaptor *rhs) const override
{
RTBufferView *x = dynamic_cast<RTBufferView *>(rhs);
if (x) { return mBufnum == x->mBufnum; }
return false;
}
size_t mRank = 1;
World * mWorld;
int mBufnum = -1;
SndBuf *mBuffer = nullptr;
};
std::ostream& operator <<(std::ostream& os, SCBufferAdaptor& b)
{
return os << b.bufnum();
}
} // namespace client
} // namespace fluid

@ -1,402 +0,0 @@
#pragma once
#include "data/FluidTensor.hpp"
#include "clients/common/FluidParams.hpp"
#include "SC_PlugIn.h"
#include <boost/align/aligned_alloc.hpp>
#include <cctype>
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <vector>
//static InterfaceTable *ft;
namespace fluid {
namespace wrapper{
/**
A descendent of SndBuf that will populate itself
from the NRT mirror buffers given a world and a bufnum
**/
struct NRTBuf//: public SndBuf
{
NRTBuf(SndBuf* b):mBuffer(b){}
NRTBuf(World* world,long bufnum, bool rt=false):
NRTBuf(rt?World_GetBuf(world, bufnum):World_GetNRTBuf(world,bufnum))
{
if(mBuffer && !mBuffer->samplerate)
mBuffer->samplerate = world->mFullRate.mSampleRate;
}
protected:
SndBuf* mBuffer;
};
/**
A combination of SndBuf and client::BufferAdaptor (which, in turn, exposes FluidTensorView<float,2>), for simple transfer of data
Given a World* and a buffer number, this will populate its SndBuf stuff
from the NRT mirror buffers, and create a FluidTensorView wrapper of
appropriate dimensions.
The SndBuf can then be 'transferred' back to the RT buffers once we're done with it,
and SC notified of the update. (In the context of SequencedCommands, in which this is meant
to be used, this would happen at Stage3() on the real-time thread)
nSamps = rows
nChans = columns
**/
class SCBufferView: public NRTBuf, public client::BufferAdaptor
{
public:
SCBufferView() = delete;
SCBufferView(SCBufferView&) = delete;
SCBufferView operator=(SCBufferView&) = delete;
SCBufferView(long bufnum,World* world,bool rt=false):
NRTBuf(world,bufnum,rt), mBufnum(bufnum), mWorld(world)
{
}
~SCBufferView() = default;
void assignToRT(World* rtWorld)
{
SndBuf* rtBuf = World_GetBuf(rtWorld,mBufnum);
*rtBuf = *mBuffer;
rtWorld->mSndBufUpdates[mBufnum].writes++;
}
void cleanUp()
{
if(mOldData)
boost::alignment::aligned_free(mOldData);
}
//No locks in (vanilla) SC, so no-ops for these
void acquire() override {
// NRTLock(mWorld);
}
void release() override {
// NRTUnlock(mWorld);
}
//Validity is based on whether this buffer is within the range the server knows about
bool valid() const override {
return (mBuffer && mBufnum >=0 && mBufnum < mWorld->mNumSndBufs);
}
FluidTensorView<float,1> samps(size_t channel, size_t rankIdx = 0) override
{
FluidTensorView<float,2> v{mBuffer->data,0, static_cast<size_t>(mBuffer->frames),static_cast<size_t>(mBuffer->channels)};
return v.col(rankIdx + channel * mRank );
}
// //Return a view of all the data
// FluidTensorView<float,2> samps() override
// {
// return {mBuffer->data,0, static_cast<size_t>(mBuffer->frames), static_cast<size_t>(mBuffer->channels)};
// }
//Return a 2D chunk
FluidTensorView<float,1> samps(size_t offset, size_t nframes, size_t chanoffset) override
{
FluidTensorView<float,2> v{mBuffer->data,0, static_cast<size_t>(mBuffer->frames), static_cast<size_t>(mBuffer->channels)};
return v(fluid::Slice(offset, nframes), fluid::Slice(chanoffset, 1))
.col(0);
}
size_t numFrames() const override
{
return valid() ? this->mBuffer->frames : 0 ;
}
size_t numChans() const override
{
return valid() ? this->mBuffer->channels / mRank : 0;
}
size_t rank() const override
{
return valid() ? mRank :0;
}
void resize(size_t frames, size_t channels, size_t rank) override {
SndBuf* thisThing = mBuffer;
mOldData = thisThing->data;
mRank = rank;
mWorld->ft->fBufAlloc(mBuffer, channels * rank, frames, thisThing->samplerate);
}
protected:
bool equal(BufferAdaptor* rhs) const override
{
SCBufferView* x = dynamic_cast<SCBufferView*>(rhs);
if(x)
{
return mBufnum == x->mBufnum;
}
return false;
}
float* mOldData = 0;
long mBufnum;
World* mWorld;
size_t mRank = 1;
};
class RTBufferView: public client::BufferAdaptor
{
public:
RTBufferView(World* world, int bufnum): mWorld(world), mBufnum(bufnum) {}
void acquire() override {
mBuffer = World_GetBuf(mWorld, mBufnum);
}
void release() override {
// NRTUnlock(mWorld);
}
//Validity is based on whether this buffer is within the range the server knows about
bool valid() const override {
return (mBuffer && mBufnum >=0 && mBufnum < mWorld->mNumSndBufs);
}
FluidTensorView<float,1> samps(size_t channel, size_t rankIdx = 0) override
{
FluidTensorView<float,2> v{mBuffer->data,0, static_cast<size_t>(mBuffer->frames),static_cast<size_t>(mBuffer->channels)};
return v.col(rankIdx + channel * mRank );
}
FluidTensorView<float,1> samps(size_t offset, size_t nframes, size_t chanoffset) override
{
FluidTensorView<float,2> v{mBuffer->data,0, static_cast<size_t>(mBuffer->frames), static_cast<size_t>(mBuffer->channels)};
return v(fluid::Slice(offset, nframes), fluid::Slice(chanoffset, 1))
.col(0);
}
size_t numFrames() const override
{
return valid() ? this->mBuffer->frames : 0 ;
}
size_t numChans() const override
{
return valid() ? this->mBuffer->channels / mRank : 0;
}
size_t rank() const override
{
return valid() ? mRank :0;
}
void resize(size_t frames, size_t channels, size_t rank) override {
assert(false && "Don't try and resize real-time buffers");
// SndBuf* thisThing = mBuffer;
// mOldData = thisThing->data;
// mRank = rank;
// mWorld->ft->fBufAlloc(mBuffer, channels * rank, frames, thisThing->samplerate);
}
int bufnum() {
return mBufnum;
}
private:
bool equal(BufferAdaptor* rhs) const override
{
RTBufferView* x = dynamic_cast<RTBufferView*>(rhs);
if(x)
{
return mBufnum == x->mBufnum;
}
return false;
}
size_t mRank = 1;
World* mWorld;
int mBufnum = -1;
SndBuf* mBuffer = nullptr;
};
class NRTCommandBase{
using param_type = fluid::client::Instance;
public:
NRTCommandBase() = delete;
NRTCommandBase(NRTCommandBase&) = delete;
NRTCommandBase& operator=(NRTCommandBase&) = delete;
NRTCommandBase(void* inUserData)
// mWorld(inWorld),mReplyAddr(replyAddr), mCompletionMsgData(completionMsgData), mCompletionMsgSize(completionMsgSize),
{}
~NRTCommandBase() = default;
template <typename T> using AsyncFn = bool (T::*)(World* w);
template <typename T> using AsyncCleanup = void (T::*)();
template <typename T, AsyncFn<T> F>
static bool call(World* w,void* x){return (static_cast<T*>(x)->*F)(w);}
template<typename T>
static void call(World*, void* x){delete static_cast<T*>(x);}
template<typename T, AsyncFn<T> Stage2, AsyncFn<T> Stage3, AsyncFn<T> Stage4>
void cmd(World* world, std::string name, void* replyAddr, char* completionMsgData, size_t completionMsgSize)
{
(world->ft->fDoAsynchronousCommand)(world, replyAddr,name.c_str(),this,
call<T,Stage2>, call<T,Stage3>, call<T,Stage4>,call<T>,
completionMsgSize,completionMsgData);
}
protected:
// World * mWorld;
// InterfaceTable *ft;
long bufNUm;
void* mReplyAddr;
const char* cmdName;
void *cmdData;
char* mCompletionMsgData;
size_t mCompletionMsgSize;
// std::vector<param_type> mParams;
};
template<typename Client>
static void printCmd(InterfaceTable* ft, const char* name, const char* classname)
{
// std::string filepath(__FILE__);
// size_t path_sep = filepath.rfind('/');
// size_t extdot = filepath.rfind('.');
// filepath.erase(filepath.begin() + extdot,filepath.end());
// filepath.erase(filepath.begin(),filepath.begin() + path_sep + 1);
// std::for_each(filepath.begin(), filepath.begin() + 2, [](char& c){
// c = std::toupper(c);
// });
std::string filepath("/tmp/");
filepath.append(classname);
filepath.append(".sc");
std::ofstream ss(filepath);
ss << classname << "{\n";
ss << "\t\t*process { arg server";
std::ostringstream cmd;
cmd << "\t\t\tserver.sendMsg(\\cmd, \\" << name;
size_t count = 0;
for(auto&& d: Client::getParamDescriptors())
{
ss << ", " << d.getName();
if(d.hasDefault())
{
ss << " = " << d.getDefault();
}
cmd << ", ";
if (d.getType() == client::Type::kBuffer) {
if (count == 0)
cmd << d.getName() << ".bufnum";
else
cmd << "\nif( " << d.getName() << ".isNil, -1, {" << d.getName() << ".bufnum})";
}
else
cmd << d.getName();
count++;
}
cmd << ");\n\n";
ss << ";\n\n\t\tserver = server ? Server.default\n;" ;
if (Client::getParamDescriptors()[0].getType() ==
client::Type::kBuffer) {
ss << "if("<<Client::getParamDescriptors()[0].getName()
<< ".bufnum.isNil) {Error(\"Invalid Buffer\").format(thisMethod.name, this.class.name).throw};\n\n";
}
ss << cmd.str() << "\n\n}\n}";
// Print(ss.str().c_str());
}
//This wraps a class instance in a function call to pass to SC
template<typename NRT_Plug>
void command(World *inWorld, void* inUserData, struct sc_msg_iter *args, void *replyAddr)
{
NRT_Plug* cmd = new NRT_Plug(inUserData);
//Iterate over parameter descriptions associated with this client object, fill with data from language side
// std::vector<client::Instance> params = NRT_Plug::client_type::newParameterSet();
for (auto&& p: cmd->parameters())
{
switch(p.getDescriptor().getType())
{
case client::Type::kBuffer: {
long bufnum = static_cast<long>(args->geti());
if (bufnum >= 0) {
SCBufferView *buf = new SCBufferView(bufnum, inWorld);
p.setBuffer(buf);
}
break;
}
case client::Type::kLong: {
p.setLong(static_cast<long>(args->geti()));
break;
}
case client::Type::kFloat: {
p.setFloat(args->getf());
break;
}
default:
{
p.setLong(static_cast<long>(args->geti()));
}
}
}
//Deal with the completion message at the end, if any
size_t completionMsgSize = args->getbsize();
char* completionMsgData = 0;
if(completionMsgSize)
{
//allocate string
completionMsgData = (char*)inWorld->ft->fRTAlloc(inWorld,completionMsgSize);
args->getb(completionMsgData,completionMsgSize);
}
//Make a new pointer for our plugin, and set it going
cmd->runCommand(inWorld, replyAddr, completionMsgData, completionMsgSize);
}
} //namespace wrapper
}//namespace fluid
template <typename NRT_Plug,typename NRT_Client>
void registerCommand(InterfaceTable* ft, const char* name)
{
PlugInCmdFunc cmd = fluid::wrapper::command<NRT_Plug>;
(*ft->fDefinePlugInCmd)(name,cmd,nullptr);
}

@ -1,14 +1,11 @@
FluidBufCompose{
*process { arg server, srcBufNumA, startAtA = 0, nFramesA = -1, startChanA = 0, nChansA = -1, srcGainA = 1, dstStartAtA = 0, dstStartChanA = 0, srcBufNumB, startAtB = 0, nFramesB = -1, startChanB = 0, nChansB = -1, srcGainB = 1, dstStartAtB = 0, dstStartChanB = 0, dstBufNum;
*process { arg server, srcBufNum, startAt = 0, nFrames = -1, startChan = 0, nChans = -1, srcGain = 1, dstBufNum, dstStartAt = 0, dstStartChan = 0;
if(srcBufNumA.isNil) {Error("Invalid Buffer").format(thisMethod.name, this.class.name).throw};
if(srcBufNumB.isNil) {Error("Invalid Buffer").format(thisMethod.name, this.class.name).throw};
if(srcBufNum.isNil) {Error("Invalid Buffer").format(thisMethod.name, this.class.name).throw};
if(srcBufNum.isNil) {Error("Invalid Buffer").format(thisMethod.name, this.class.name).throw};
if(dstBufNum.isNil) {Error("Invalid Buffer").format(thisMethod.name, this.class.name).throw};
server = server ? Server.default;
server.sendMsg(\cmd, \BufCompose, srcBufNumA, startAtA, nFramesA, startChanA, nChansA, srcGainA, dstStartAtA, dstStartChanA,
srcBufNumB, startAtB, nFramesB, startChanB, nChansB, srcGainB, dstStartAtB, dstStartChanB,
dstBufNum);
server.sendMsg(\cmd, \BufCompose, srcBufNum, startAt, nFrames, startChan, nChans, srcGain, dstBufNum,dstStartAt, dstStartChan);
}
}

@ -7,6 +7,9 @@ FluidBufHPSS{
harmBufNum = harmBufNum ? -1;
percBufNum = percBufNum ? -1;
server.sendMsg(\cmd, \BufHPSS, srcBufNum, startAt, nFrames, startChan, nChans, harmBufNum, percBufNum, resBufNum, percFiltSize, harmFiltSize, modeFlag, htf1, hta1, htf2, hta2, ptf1, pta1, ptf2, pta2, winSize, hopSize, fftSize);
//For wrapped RT clients, send maximal param values as aliases of the ones that are passed
harmFiltSize.postln;
server.sendMsg(\cmd, \BufHPSS, srcBufNum, startAt, nFrames, startChan, nChans, harmBufNum, percBufNum, resBufNum, harmFiltSize,percFiltSize, modeFlag, htf1, hta1, htf2, hta2, ptf1, pta1, ptf2, pta2, winSize, hopSize, fftSize, fftSize,harmFiltSize, percFiltSize);
}
}

@ -1,5 +1,5 @@
FluidBufNMF {
*process { arg server, srcBufNum, startAt = 0, nFrames = -1, startChan = 0, nChans = -1, dstBufNum, dictBufNum, dictFlag = 0, actBufNum, actFlag = 0, rank = 1, nIter = 100, sortFlag = 0, winSize = 1024, hopSize = 256, fftSize = -1, winType = 0, randSeed = -1;
*process { arg server, srcBufNum, startAt = 0, nFrames = -1, startChan = 0, nChans = -1, dstBufNum, dictBufNum, dictFlag = 0, actBufNum, actFlag = 0, rank = 1, nIter = 100, sortFlag = 0, winSize = 1024, hopSize = 512, fftSize = -1, winType = 0, randSeed = -1;
if(srcBufNum.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw};

@ -7,6 +7,9 @@ FluidBufSines{
sineBufNum = sineBufNum ? -1;
resBufNum = resBufNum ? -1;
server.sendMsg(\cmd, \BufSines, srcBufNum, startAt, nFrames, startChan, nChans, sineBufNum, resBufNum, bandwidth, thresh, minTrackLen, magWeight, freqWeight, winSize, hopSize, fftSize);
//NB For wrapped versions of NRT classes, we set the params for maxima to
//whatever has been passed in language-side (e.g maxFFTSize still exists as a parameter for the server plugin, but makes less sense here: it just needs to be set to a legal value)
server.sendMsg(\cmd, \BufSines, srcBufNum, startAt, nFrames, startChan, nChans, sineBufNum, resBufNum, bandwidth, thresh, minTrackLen, magWeight, freqWeight, winSize, hopSize, fftSize, fftSize);
}
}

@ -1,11 +1,11 @@
FluidBufTransientSlice{
*process { arg server, srcBufNum, startAt = 0, nFrames = -1, startChan = 0, nChans = -1, transBufNum, order = 200, blockSize = 2048, padSize = 1024, skew = 0, threshFwd = 3, threshBack = 1.1, winSize = 14, debounce = 25;
*process { arg server, srcBufNum, startAt = 0, nFrames = -1, startChan = 0, nChans = -1, transBufNum, order = 200, blockSize = 2048, padSize = 1024, skew = 0, threshFwd = 3, threshBack = 1.1, winSize = 14, debounce = 25, minSlice = 1000;
if(srcBufNum.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw};
if(transBufNum.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw};
server = server ? Server.default;
server.sendMsg(\cmd, \BufTransientSlice, srcBufNum, startAt, nFrames, startChan, nChans, transBufNum, order, blockSize, padSize, skew, threshFwd, threshBack, winSize, debounce);
server.sendMsg(\cmd, \BufTransientSlice, srcBufNum, startAt, nFrames, startChan, nChans, transBufNum, order, blockSize, padSize, skew, threshFwd, threshBack, winSize, debounce, minSlice);
}
}

@ -7,6 +7,9 @@ FluidBufTransients {
transBufNum = transBufNum ? -1;
resBufNum = resBufNum ? -1;
("Source" + srcBufNum).postln;
("Trans" + transBufNum).postln;
("Res" + resBufNum).postln;
server.sendMsg(\cmd, \BufTransients, srcBufNum, startAt, nFrames, startChan, nChans, transBufNum, resBufNum, order, blockSize, padSize, skew, threshFwd, threshBack, winSize, debounce);
}
}

@ -1,6 +1,6 @@
FluidGain : UGen {
*ar { arg in = 0, frameSize=64, gain=1.0;
^this.multiNew('audio', in.asAudioRateInput(this),frameSize, gain)
*ar { arg in = 0, gain=1.0;
^this.multiNew('audio', in.asAudioRateInput(this), gain)
}
}

@ -1,6 +1,6 @@
FluidHPSS : MultiOutUGen {
*ar { arg in = 0, harmFiltSize=17, percFiltSize = 17, modeFlag=0, htf1 = 0.1, hta1 = 0, htf2 = 0.5, hta2 = 0, ptf1 = 0.1, pta1 = 0, ptf2 = 0.5, pta2 = 0, winSize= 1024, hopSize= 256, fftSize= -1;
^this.multiNew('audio', in.asAudioRateInput(this), percFiltSize, harmFiltSize, modeFlag, htf1, hta1, htf2, hta2, ptf1, pta1, ptf2, pta2, winSize, hopSize, fftSize)
*ar { arg in = 0, harmFiltSize=17, percFiltSize = 17, modeFlag=0, htf1 = 0.1, hta1 = 0, htf2 = 0.5, hta2 = 0, ptf1 = 0.1, pta1 = 0, ptf2 = 0.5, pta2 = 0, winSize= 1024, hopSize= 256, fftSize= -1, maxFFTSize = 16384, maxHSize = 101, maxPSize = 101;
^this.multiNew('audio', in.asAudioRateInput(this), percFiltSize, harmFiltSize, modeFlag, htf1, hta1, htf2, hta2, ptf1, pta1, ptf2, pta2, winSize, hopSize, fftSize, maxFFTSize, maxHSize, maxPSize)
}
init { arg ... theInputs;
inputs = theInputs;

@ -1,7 +1,7 @@
FluidNMFMatch : MultiOutUGen {
*kr { arg in = 0, dictBufNum, rank = 1, nIter = 10, winSize = 1024, hopSize = 256, fftSize = -1;
^this.multiNew('control', in, dictBufNum, rank, nIter, winSize, hopSize, fftSize);
*kr { arg in = 0, dictBufNum, maxRank = 1, nIter = 10, winSize = 1024, hopSize = 512, fftSize = -1, maxFFTSize = 16384;
^this.multiNew('control', in, dictBufNum, maxRank, nIter, winSize, hopSize, fftSize, maxFFTSize);
}
init {arg ...theInputs;

@ -0,0 +1,5 @@
FluidOnsetSlice : UGen {
*ar { arg in = 0, function = 0, threshold = 0.1, debounce = 2, filterSize = 5, winSize = 1024, hopSize = 256, frameDelta = 0, fftSize = 1024, maFFTSize = 16384;
^this.multiNew('audio', in.asAudioRateInput(this), function, threshold, debounce, filterSize, winSize, hopSize, frameDelta, fftSize, maFFTSize)
}
}

@ -1,5 +1,5 @@
FluidSTFTPass : UGen {
*ar { arg in = 0, windowSize= 1024, hopSize= 256, fftSize= -1;
^this.multiNew('audio', in.asAudioRateInput(this),windowSize, hopSize, fftSize)
*ar { arg in = 0, windowSize= 1024, hopSize= 256, fftSize= -1, maxFFTSize = 16384;
^this.multiNew('audio', in.asAudioRateInput(this),windowSize, hopSize, fftSize, maxFFTSize)
}
}

@ -1,6 +1,6 @@
FluidSines : MultiOutUGen {
*ar { arg in = 0, bandwidth = 76, thresh = 0.7, minTrackLen = 15, magWeight = 0.1, freqWeight = 1.0, winSize= 2048, hopSize= 512, fftSize= 8192;
^this.multiNew('audio', in.asAudioRateInput(this), bandwidth, thresh, minTrackLen, magWeight,freqWeight ,winSize, hopSize, fftSize)
*ar { arg in = 0, bandwidth = 76, thresh = 0.7, minTrackLen = 15, magWeight = 0.1, freqWeight = 1.0, winSize= 2048, hopSize= 512, fftSize= 8192, maxFFTSize=16384;
^this.multiNew('audio', in.asAudioRateInput(this), bandwidth, thresh, minTrackLen, magWeight,freqWeight ,winSize, hopSize, fftSize, maxFFTSize)
}
init { arg ... theInputs;
inputs = theInputs;

@ -1,5 +1,5 @@
FluidTransientSlice : UGen {
*ar { arg in = 0, order = 20, blockSize = 256, padSize = 128, skew = 0.0, threshFwd = 3.0, threshBack = 1.1, winSize=14, debounce=25;
^this.multiNew('audio', in.asAudioRateInput(this), order, blockSize, padSize, skew, threshFwd ,threshBack, winSize, debounce)
*ar { arg in = 0, order = 20, blockSize = 256, padSize = 128, skew = 0.0, threshFwd = 3.0, threshBack = 1.1, winSize=14, debounce=25, minSlice = 1000;
^this.multiNew('audio', in.asAudioRateInput(this), order, blockSize, padSize, skew, threshFwd ,threshBack, winSize, debounce, minSlice)
}
}

@ -166,4 +166,3 @@ code::
//still nullsumming
{PlayBuf.ar(1,c.bufnum) + PlayBuf.ar(1,d.bufnum) + PlayBuf.ar(1,e.bufnum) - PlayBuf.ar(1,b.bufnum,doneAction:2)}.play;
::

@ -31,10 +31,7 @@ The whole process can be related to a channel vocoder where, instead of fixed ba
More information on possible musicianly uses of NMF are availabe in LINK::Guides/FluCoMa:: overview file.
FluidBufNMF is part of the Fluid Decomposition Toolkit of the FluCoMa project. footnote::
This was made possible thanks to the FluCoMa project ( http://www.flucoma.org/ ) funded by the European Research Council ( https://erc.europa.eu/ ) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899).
::
This was made possible thanks to the FluCoMa project ( http://www.flucoma.org/ ) funded by the European Research Council ( https://erc.europa.eu/ ) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899). ::
CLASSMETHODS::
@ -113,6 +110,58 @@ RETURNS::
EXAMPLES::
STRONG::A didactic example::
CODE::
(
// create buffers
b = Buffer.alloc(s,44100);
c = Buffer.alloc(s, 44100);
d = Buffer.new(s);
e = Buffer.new(s);
f = Buffer.new(s);
g = Buffer.new(s);
)
(
// fill them with 2 clearly segregated sine waves and composite a buffer where they are consecutive
Routine {
b.sine2([500],[1], false, false);
c.sine2([5000],[1],false, false);
s.sync;
FluidBufCompose.process(s,srcBufNumA:b.bufnum, srcBufNumB:c.bufnum,dstStartAtB:44100,dstBufNum:d.bufnum);
s.sync;
d.query;
}.play;
)
// check
d.plot
d.play //////(beware !!!! loud!!!)
(
// separate them in 2 ranks
Routine {
FluidBufNMF.process(s, d.bufnum, dstBufNum:e.bufnum, dictBufNum: f.bufnum, actBufNum:g.bufnum, rank:2);
s.sync;
e.query;
f.query;
g.query;
}.play
)
// look at the resynthesised separated signal
e.plot;
// look at the dictionaries signal for 2 spikes
f.plot;
// look at the activations
g.plot;
//trying running the same process on superimposed sinewaves instead of consecutive in the source and see how it fails.
::
STRONG::Basic musical examples::
code::
// set some buffers and parameters
@ -210,18 +259,19 @@ Routine {
// find the rank that has the picking sound by changing which channel to listen to
(
~element = 0;
~element = 9;
{PlayBuf.ar(10,c.bufnum)[~element]}.play
)
// copy all the other ranks on itself and the picking dictionnary as the sole component of the 1st channel
(
Routine{
(0..9).remove(~element).do({|chan|FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA:chan, nChansA: 1, srcBufNumB: e.bufnum, dstBufNum: e.bufnum)});
z = (0..9);
FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA: z.removeAt(~element), nChansA: 1, srcBufNumB: e.bufnum, dstBufNum: e.bufnum);
s.sync;
e.query;
s.sync;
FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA: ~element, nChansA: 1, srcBufNumB: e.bufnum, dstStartChanB: 1, dstBufNum: e.bufnum);
z.do({|chan|FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA:chan, nChansA: 1, dstStartChanA: 1, srcBufNumB: e.bufnum, dstBufNum: e.bufnum)});
s.sync;
e.query;
}.play;
@ -243,3 +293,85 @@ c.play
{(PlayBuf.ar(2,c.bufnum,doneAction:2).sum)-(PlayBuf.ar(1,b.bufnum,doneAction:2))}.play
::
STRONG::Updating Dictionnaries:: The process can update dictionaries provided as seed.
CODE::
(
// create buffers
b = Buffer.alloc(s,44100);
c = Buffer.alloc(s, 44100);
d = Buffer.new(s);
e = Buffer.alloc(s,513,3);
f = Buffer.new(s);
g = Buffer.new(s);
)
(
// fill them with 2 clearly segregated sine waves and composite a buffer where they are consecutive
Routine {
b.sine2([500],[1], false, false);
c.sine2([5000],[1],false, false);
s.sync;
FluidBufCompose.process(s,srcBufNumA:b.bufnum, srcBufNumB:c.bufnum,dstStartAtB:44100,dstBufNum:d.bufnum);
s.sync;
d.query;
}.play;
)
// check
d.plot
d.play //////(beware !!!! loud!!!)
(
//make a seeding dictionary of 3 ranks:
var highpass, lowpass, direct;
highpass = Array.fill(513,{|i| (i < 50).asInteger});
lowpass = 1 - highpass;
direct = Array.fill(513,0.1);
e.setn(0,[highpass, lowpass, direct].flop.flat);
)
//check the dictionary: a steep lowpass, a steep highpass, and a small DC
e.plot
e.query
(
// use the seeding dictionary, without updating
Routine {
FluidBufNMF.process(s, d.bufnum, dstBufNum:f.bufnum, dictBufNum: e.bufnum, dictFlag: 2, actBufNum:g.bufnum, rank:3);
s.sync;
e.query;
f.query;
g.query;
}.play
)
// look at the resynthesised separated signal
f.plot;
// look at the dictionaries that have not changed
e.plot;
// look at the activations
g.plot;
(
// use the seeding dictionary, with updating this time
Routine {
FluidBufNMF.process(s, d.bufnum, dstBufNum:f.bufnum, dictBufNum: e.bufnum, dictFlag: 1, actBufNum:g.bufnum, rank:3);
s.sync;
e.query;
f.query;
g.query;
}.play
)
// look at the resynthesised separated signal
f.plot;
// look at the dictionaries that have now updated in place (with the 3rd channel being more focused
e.plot;
// look at the activations (sharper 3rd rank at transitions)
g.plot;
::

@ -40,7 +40,10 @@ ARGUMENT:: kernelSize
The granularity of the window in which the algorithm looks for change, in samples. A small number will be sensitive to short term changes, and a large number should look for long term changes.
ARGUMENT:: thresh
The normalised threshold, between 0 an 1, to consider a peak as a sinusoidal component from the in the novelty curve.
The normalised threshold, between 0 an 1, on the novelty curve to consider it a segmentation point.
ARGUMENT:: filterSize
The size of a smoothing filter that is applied on the novelty curve. A larger filter filter size allows for cleaner cuts on very sharp changes.
ARGUMENT:: winSize
The window size. As novelty estimation relies on spectral frames, we need to decide what precision we give it spectrally and temporally, in line with Gabor Uncertainty principles. http://www.subsurfwiki.org/wiki/Gabor_uncertainty
@ -91,3 +94,39 @@ c.query;
}.play;
)
::
STRONG::Examples of the impact of the filterSize::
CODE::
// load some buffers
(
b = Buffer.read(s,File.realpath(FluidBufNoveltySlice.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-AcousticStrums-M.wav");
c = Buffer.new(s);
)
// process with a given filterSize
FluidBufNoveltySlice.process(s,b.bufnum, transBufNum: c.bufnum, kernelSize:31, thresh:0.35, filterSize:0)
//check the number of slices: it is the number of frames in the transBuf minus the boundary index.
c.query;
//play slice number 2
(
{
BufRd.ar(1, b.bufnum,
Line.ar(
BufRd.kr(1, c.bufnum, DC.kr(2), 0, 1),
BufRd.kr(1, c.bufnum, DC.kr(3), 0, 1),
(BufRd.kr(1, c.bufnum, DC.kr(3)) - BufRd.kr(1, c.bufnum, DC.kr(2), 0, 1) + 1) / s.sampleRate),
0,1);
}.play;
)
// change the filterSize in the code above to 4. Then to 8. Listen in between to the differences.
// What's happening? In the first instance (filterSize = 0), the novelty line is jittery and therefore overtriggers on the arpegiated guitar. We also can hear attacks at the end of the segment. Setting the threshold higher (like in the 'Basic Example' pane) misses some more subtle variations.
// So in the second settings (filterSize = 4), we smooth the novelty line a little, which allows us to catch small differences that are not jittery. It also corrects the ending cutting by the same trick: the averaging of the sharp pick is sliding up, crossing the threshold slightly earlier.
// If we smooth too much, like the third settings (filterSize = 8), we start to loose precision. Have fun with different values of theshold then will allow you to find the perfect segment for your signal.
::

@ -58,7 +58,10 @@ ARGUMENT:: winSize
The averaging window of the error detection function. It needs smoothing as it is very jittery. The longer the window, the less precise, but the less false positives.
ARGUMENT:: debounce
The window size in sample within which positive detections will be clumped together to avoid overdetecting in time. No slice will be shorter than this duration.
The window size in sample within which positive detections will be clumped together to avoid overdetecting in time.
ARGUMENT:: minSlice
The minimum duration of a slice in samples.
RETURNS::
Nothing, as the destination buffer is declared in the function call.
@ -77,7 +80,7 @@ c = Buffer.new(s);
(
Routine{
t = Main.elapsedTime;
FluidBufTransientSlice.process(s,b.bufnum, transBufNum:c.bufnum, order:80, debounce:4410);
FluidBufTransientSlice.process(s,b.bufnum, transBufNum:c.bufnum, order:80, minSlice:4410);
s.sync;
(Main.elapsedTime - t).postln;
}.play
@ -105,7 +108,7 @@ c.query;
(
Routine{
t = Main.elapsedTime;
FluidBufTransients.process(s,b.bufnum, 44100, 44100, 0, 0, c.bufnum, d.bufnum, 100, 512,256,1,2,1,12,20);
FluidBufTransients.process(s,b.bufnum, 44100, 44100, 0, 0, c.bufnum, d.bufnum, 100, 512,256,1,2,1,12,20,441);
s.sync;
(Main.elapsedTime - t).postln;
}.play

@ -109,4 +109,3 @@ CODE::
// null test (the process add a latency of ((harmFiltSize + (winSize / hopSize) - 1) * hopSize) samples
{var sig = PlayBuf.ar(1,b.bufnum,loop:1); [FluidHPSS.ar(sig,17,31, winSize:1024,hopSize:512,fftSize:2048).sum - DelayN.ar(sig, 1, ((31 + 1) * 512 / s.sampleRate))]}.play
::

@ -1,55 +1,313 @@
TITLE:: FluidNMFMatch
SUMMARY:: Real-Time Non-Negative Matrix Factorisation on Buffered Dictionaries
SUMMARY:: Real-Time Non-Negative Matrix Factorisation with Fixed Dictionaries
CATEGORIES:: Libraries>FluidDecomposition
RELATED:: Guides/FluCoMa, Guides/FluidDecomposition, Classes/FluidBufNMF
DESCRIPTION::
The FluidBufNMF object provides the activation (linked to amplitude) for each pre-defined dictionaries (similar to spectra) predefined in a buffer. These dictionaries would have usually be computed through an offline Non-Negative Matrix Factorisation (NMF) footnote:: Lee, Daniel D., and H. Sebastian Seung. 1999. Learning the Parts of Objects by Non-Negative Matrix Factorization. Nature 401 (6755): 78891. https://doi.org/10.1038/44565 :: with the link::Classes/FluidBufNMF:: UGen. NMF has been a popular technique in signal processing research for things like source separation and transcription footnote:: Smaragdis and Brown, Non-Negative Matrix Factorization for Polyphonic Music Transcription.::, although its creative potential is so far relatively unexplored.
The FluidNMFMatch object matches an incoming audio signal against a set of spectral templates using an slimmed-down version of Nonnegative Matrix Factorisation (NMF) footnote:: Lee, Daniel D., and H. Sebastian Seung. 1999. Learning the Parts of Objects by Non-Negative Matrix Factorization. Nature 401 (6755): 78891. https://doi.org/10.1038/44565. ::
The algorithm takes a buffer in which provides a spectral definition of a number of components, determined by the rank argument and the dictionary buffer channel count. It works iteratively, by trying to find a combination of amplitudes ('activations') that yield the original magnitude spectrogram of the audio input when added together. By and large, there is no unique answer to this question (i.e. there are different ways of accounting for an evolving spectrum in terms of some set of templates and envelopes). In its basic form, NMF is a form of unsupervised learning: it starts with some random data and then converges towards something that minimizes the distance between its generated data and the original:it tends to converge very quickly at first and then level out. Fewer iterations mean less processing, but also less predictable results.
It outputs at kr the degree of detected match for each template (the activation amount, in NMF-terms). The spectral templates are presumed to have been produced by the offline NMF process (link::Classes/FluidBufNMF::), and must be the correct size with respect to the FFT settings being used (FFT size / 2 + 1 frames long). The rank of the decomposition is determined by the number of channels in the supplied buffer of templates, up to a maximum set by the STRONG::maxRank:: parameter.
NMF has been a popular technique in signal processing research for things like source separation and transcription footnote:: Smaragdis and Brown, Non-Negative Matrix Factorization for Polyphonic Music Transcription.::, although its creative potential is so far relatively unexplored. It works iteratively, by trying to find a combination of amplitudes ('activations') that yield the original magnitude spectrogram of the audio input when added together. By and large, there is no unique answer to this question (i.e. there are different ways of accounting for an evolving spectrum in terms of some set of templates and envelopes). In its basic form, NMF is a form of unsupervised learning: it starts with some random data and then converges towards something that minimizes the distance between its generated data and the original:it tends to converge very quickly at first and then level out. Fewer iterations mean less processing, but also less predictable results.
The whole process can be related to a channel vocoder where, instead of fixed bandpass filters, we get more complex filter shapes and the activations correspond to channel envelopes.
More information on possible musicianly uses of NMF are availabe in LINK::Guides/FluCoMa:: overview file.
FluidBufNMF is part of the Fluid Decomposition Toolkit of the FluCoMa project. footnote::
This was made possible thanks to the FluCoMa project ( http://www.flucoma.org/ ) funded by the European Research Council ( https://erc.europa.eu/ ) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899).
::
FluidBufNMF is part of the Fluid Decomposition Toolkit of the FluCoMa project. footnote::This was made possible thanks to the FluCoMa project ( http://www.flucoma.org/ ) funded by the European Research Council ( https://erc.europa.eu/ ) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899). ::
CLASSMETHODS::
METHOD:: kr
The real-time processing method. It takes an audio or control input, and will yield a control stream in the form of a multichannel array of size STRONG::rank::.
The real-time processing method. It takes an audio or control input, and will yield a control stream in the form of a multichannel array of size STRONG::maxRank:: . If the dictionary buffer has fewer than maxRank channels, the remaining outputs will be zeroed.
ARGUMENT:: in
The input to the factorisation process.
The signal input to the factorisation process.
ARGUMENT:: dictBufNum
The index of the buffer where the different dictionaries will be matched against. Dictionaries must be STRONG::(fft size / 2) + 1:: frames and STRONG::rank:: channels
The server index of the buffer containing the different dictionaries that the input signal will be matched against. Dictionaries must be STRONG::(fft size / 2) + 1:: frames. If the buffer has more than STRONG::maxRank:: channels, the excess will be ignored.
ARGUMENT:: rank
The number of elements the NMF algorithm will try to divide the spectrogram of the source in. This should match the number of channels of the dictBuf defined above.
ARGUMENT::maxRank
The maximum number of elements the NMF algorithm will try to divide the spectrogram of the source in. This dictates the number of output channelsfor the ugen.
ARGUMENT:: nIter
The NMF process is iterative, trying to converge to the smallest error in its factorisation. The number of iterations will decide how many times it tries to adjust its estimates. Higher numbers here will be more CPU expensive, lower numbers will be more unpredictable in quality.
The NMF process is iterative, trying to converge to the smallest error in its factorisation. The number of iterations will decide how many times it tries to adjust its estimates. Higher numbers here will be more CPU intensive, lower numbers will be more unpredictable in quality.
ARGUMENT:: winSize
The window size. As NMF relies on spectral frames, we need to decide what precision we give it spectrally and temporally, in line with Gabor Uncertainty principles. http://www.subsurfwiki.org/wiki/Gabor_uncertainty
The number of samples that are analysed at a time. A lower number yields greater temporal resolution, at the expense of spectral resoultion, and vice-versa.
ARGUMENT:: hopSize
The window hope size. As NMF relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts.
The window hope size. As NMF relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts. Default = winSize / 2
ARGUMENT:: fftSize
The inner FFT/IFFT size. It should be at least 4 samples long, at least the size of the window, and a power of 2. Making it larger allows an oversampling of the spectral precision.
The FFT/IFFT size. It should be at least 4 samples long, at least the size of the window, and a power of 2. Making it larger allows an oversampling of the spectral precision. Default = winSize
returns::
A multichannel array, giving for each dictionary the activation value.
RETURNS::
A multichannel kr output, giving for each dictionary component the activation amount.
EXAMPLES::
yes
STRONG::A didactic example::
CODE::
(
// create buffers
b= Buffer.alloc(s,44100);
c = Buffer.alloc(s, 44100);
d = Buffer.new(s);
e= Buffer.new(s);
)
(
// fill them with 2 clearly segregated sine waves and composite a buffer where they are consecutive
Routine {
b.sine2([500],[1], false, false);
c.sine2([5000],[1],false, false);
s.sync;
FluidBufCompose.process(s,srcBufNumA:b.bufnum, srcBufNumB:c.bufnum,dstStartAtB:44100,dstBufNum:d.bufnum);
s.sync;
d.query;
}.play;
)
// check
d.plot
d.play //////(beware !!!! loud!!!)
(
// separate them in 2 ranks
Routine {
FluidBufNMF.process(s, d.bufnum, dictBufNum: e.bufnum, rank:2);
s.sync;
e.query;
}.play
)
// check for 2 spikes in the spectra
e.query
e.plot
// test the activations values with test one, another, or both ideal material
{FluidNMFMatch.kr(SinOsc.ar(500),e.bufnum,2, hopSize:512)}.plot(1)
{FluidNMFMatch.kr(SinOsc.ar(5000),e.bufnum,2, hopSize:512)}.plot(1)
{FluidNMFMatch.kr(SinOsc.ar([500,5000]).sum,e.bufnum,2, hopSize:512)}.plot(1)
::
STRONG::A pick compressor::
CODE::
//set some buffers
(
b = Buffer.read(s,File.realpath(FluidNMFMatch.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-AcousticStrums-M.wav");
c = Buffer.new(s);
x = Buffer.new(s);
e = Buffer.alloc(s,1,1);
)
// train only 2 seconds
(
Routine {
FluidBufNMF.process(s,b.bufnum,0,88200,0,1, c.bufnum, x.bufnum, rank:10,fftSize:2048);
s.sync;
c.query;
}.play;
)
// wait for the query to print
// then find the rank that has the picking sound by changing which channel to listen to
(
~element = 8;
{PlayBuf.ar(10,c.bufnum)[~element]}.play
)
// copy all the other ranks on itself and the picking dictionnary as the sole component of the 1st channel
(
Routine{
z = (0..9);
FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA: z.removeAt(~element), nChansA: 1, srcBufNumB: e.bufnum, dstBufNum: e.bufnum);
s.sync;
e.query;
s.sync;
z.do({|chan|FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA:chan, nChansA: 1, dstStartChanA: 1, srcBufNumB: e.bufnum, dstBufNum: e.bufnum)});
s.sync;
e.query;
}.play;
)
e.plot;
//using this trained dictionary we can see the envelop (activations) of each rank
{FluidNMFMatch.kr(PlayBuf.ar(1,b.bufnum),e.bufnum,2,fftSize:2048)}.plot(1);
// the left/top activations are before, the pick before the sustain.
//we can then use the activation value to sidechain a compression patch that is sent in a delay
(
{
var source, todelay, delay1, delay2, delay3, feedback, mod1, mod2, mod3, mod4;
//read the source
source = PlayBuf.ar(1, b.bufnum);
// generate modulators that are coprime in frequency
mod1 = SinOsc.ar(1, 0, 0.001);
mod2 = SinOsc.ar(((617 * 181) / (461 * 991)), 0, 0.001);
mod3 = SinOsc.ar(((607 * 193) / (491 * 701)), 0, 0.001);
mod4 = SinOsc.ar(((613 * 191) / (463 * 601)), 0, 0.001);
// compress the signal to send to the delays
todelay = DelayN.ar(source,0.1, 800/44100, //delaying it to compensate for FluidNMFMatch's latency
LagUD.ar(K2A.ar(FluidNMFMatch.kr(source,e.bufnum,2,fftSize:2048)[0]), //reading the channel of the activations on the pick dictionary
80/44100, // lag uptime (compressor's attack)
1000/44100, // lag downtime (compressor's decay)
(1/(2.dbamp) // compressor's threshold inverted
)).clip(1,1000).pow((8.reciprocal)-1)); //clipping it so we only affect above threshold, then ratio(8) becomes the exponent of that base
// delay network
feedback = LocalIn.ar(3);// take the feedback in for the delays
delay1 = DelayC.ar(BPF.ar(todelay+feedback[1]+(feedback[2] * 0.3), 987, 6.7,0.8),0.123,0.122+(mod1*mod2));
delay2 = DelayC.ar(BPF.ar(todelay+feedback[0]+(feedback[2] * 0.3), 1987, 6.7,0.8),0.345,0.344+(mod3*mod4));
delay3 = DelayC.ar(BPF.ar(todelay+feedback[1], 1456, 6.7,0.8),0.567,0.566+(mod1*mod3),0.6);
LocalOut.ar([delay1,delay2, delay3]); // write the feedback for the delays
//listen to the delays only by uncommenting the following line
// [delay1+delay3,delay2+delay3]
source.dup + ([delay1+delay3,delay2+delay3]*(-3.dbamp))
}.play;
)
::
STRONG::Object finder::
CODE::
/set some buffers
(
b = Buffer.read(s,File.realpath(FluidNMFMatch.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-BaB-SoundscapeGolcarWithDog.wav");
c = Buffer.new(s);
x = Buffer.new(s);
e = Buffer.alloc(s,1,1);
)
// train where all objects are present
(
Routine {
FluidBufNMF.process(s,b.bufnum,130000,150000,0,1, c.bufnum, x.bufnum, rank:10);
s.sync;
c.query;
}.play;
)
// wait for the query to print
// then find a rank for each item you want to find. You could also sum them. Try to find a rank with a good object-to-rest ratio
(
~dog =0;
{PlayBuf.ar(10,c.bufnum)[~dog]}.play
)
(
~bird = 5;
{PlayBuf.ar(10,c.bufnum)[~bird]}.play
)
// copy at least one other rank to a third rank, a sort of left-over channel
(
Routine{
FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA:~dog, nChansA: 1, srcBufNumB: e.bufnum, dstBufNum: e.bufnum);
FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA:~bird, nChansA: 1, dstStartChanA: 1, srcBufNumB: e.bufnum, dstBufNum: e.bufnum);
s.sync;
(0..9).removeAll([~dog,~bird]).do({|chan|FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA:chan, nChansA: 1, dstStartChanA: 2, srcBufNumB: e.bufnum, dstBufNum: e.bufnum)});
s.sync;
e.query;
}.play;
)
e.plot;
//using this trained dictionary we can then see the activation...
(
{
var source, blips;
//read the source
source = PlayBuf.ar(2, b.bufnum);
blips = FluidNMFMatch.kr(source.sum,e.bufnum,3);
}.plot(10);
)
// ...and use some threshold to 'find' objects...
(
{
var source, blips;
//read the source
source = PlayBuf.ar(2, b.bufnum);
blips = Schmidt.kr(FluidNMFMatch.kr(source.sum,e.bufnum,3),0.5,[10,1,1000]);
}.plot(10);
)
// ...and use these to sonify them
(
{
var source, blips, dogs, birds;
//read the source
source = PlayBuf.ar(2, b.bufnum);
blips = Schmidt.kr(FluidNMFMatch.kr(source.sum,e.bufnum,3),0.5,[10,1,1000]);
dogs = SinOsc.ar(100,0,Lag.kr(blips[0],0.05,0.15));
birds = SinOsc.ar(1000,0,Lag.kr(blips[1],0.05,0.05));
[dogs, birds] + source;
}.play;
)
::
STRONG::Pretrained piano::
CODE::
//load in the sound in and a pretrained dictionary
(
b = Buffer.read(s,File.realpath(FluidNMFMatch.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-SA-UprightPianoPedalWide.wav");
c = Buffer.read(s,File.realpath(FluidNMFMatch.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/filters/piano-dicts.wav");
)
b.play
c.query
//use the pretrained dictionary to compute activations of each notes to drive the amplitude of a resynth
(
{
var source, resynth;
source = PlayBuf.ar(2, b.bufnum,loop:1).sum;
resynth = SinOsc.ar((21..108).midicps, 0, FluidNMFMatch.kr(source,c.bufnum,88,10,4096).madd(0.002)).sum;
[source, resynth]
}.play
)
//now sample and hold the same stream to get notes identified, played and sent back via osc
(
{
var source, resynth, chain, trig, acts;
source = PlayBuf.ar(2,b.bufnum,loop:1).sum;
// built in attack detection, delayed until the stable part of the sound
chain = FFT(LocalBuf(256), source);
trig = TDelay.kr(Onsets.kr(chain, 0.5),0.1);
// samples and holds activation values that are scaled and capped, in effect thresholding them
acts = Latch.kr(FluidNMFMatch.kr(source,c.bufnum,88,10,4096).linlin(15,20,0,0.1),trig);
// resynths as in the previous example, with the values sent back to the language
resynth = SinOsc.ar((21..108).midicps, 0, acts).sum;
SendReply.kr(trig, '/activations', acts);
[source, resynth]
// [source, T2A.ar(trig)]
// resynth
}.play
)
// define a receiver for the activations
(
OSCdef(\listener, {|msg|
var data = msg[3..];
// removes the silent and spits out the indicies as midinote number
data.collect({arg item, i; if (item > 0.01, {i + 21})}).reject({arg item; item.isNil}).postln;
}, '/activations');
)
::
STRONG::Strange Resonators::
CODE::
//indeed
//to be completed
::

@ -38,7 +38,10 @@ ARGUMENT:: winSize
The averaging window of the error detection function. It needs smoothing as it is very jittery. The longer the window, the less precise, but the less false positives.
ARGUMENT:: debounce
The window size in sample within with positive detections will be clumped together to avoid overdetecting in time. No slice will be shorter than this duration.
The window size in sample within with positive detections will be clumped together to avoid overdetecting in time.
ARGUMENT:: minSlice
The minimum duration of a slice in samples.
RETURNS::
An audio stream with impulses at detected transients. The latency between the input and the output is (blockSize + padSize - order) samples.
@ -54,18 +57,17 @@ b = Buffer.read(s,File.realpath(FluidTransientSlice.class.filenameSymbol).dirnam
{var sig = PlayBuf.ar(1,b.bufnum,loop:1); [FluidTransientSlice.ar(sig)*0.5, DelayN.ar(sig, 1, ((256 + 128 - 20)/ s.sampleRate))]}.play
// other parameters
{var sig = PlayBuf.ar(1,b.bufnum,loop:1); [FluidTransientSlice.ar(sig,order:80,debounce:2205)*0.5, DelayN.ar(sig, 1, ((256 + 128 - 80)/ s.sampleRate))]}.play
{var sig = PlayBuf.ar(1,b.bufnum,loop:1); [FluidTransientSlice.ar(sig,order:80,minSlice:2205)*0.5, DelayN.ar(sig, 1, ((256 + 128 - 80)/ s.sampleRate))]}.play
// more musical trans-trigged autopan
(
{
var sig, trig, syncd, pan;
sig = PlayBuf.ar(1,b.bufnum,loop:1);
trig = FluidTransientSlice.ar(sig,order:10,debounce:2205);
trig = FluidTransientSlice.ar(sig,order:10,minSlice:4410);
syncd = DelayN.ar(sig, 1, ((256 + 128 - 10)/ s.sampleRate));
pan = TRand.ar(-1,1,trig);
Pan2.ar(syncd,pan);
}.play
)
::

@ -1,5 +1,15 @@
target_link_libraries(
${PLUGIN}
PRIVATE
FLUID_DECOMPOSITION
FLUID_SC_WRAPPER
)
target_include_directories(
${PLUGIN}
PRIVATE
${LOCAL_INCLUDES}
SYSTEM PRIVATE
${SC_PATH}/include/plugin_interface
${SC_PATH}/include/common
@ -7,12 +17,15 @@ target_include_directories(
${SC_PATH}/external_libraries/boost #we need boost::align for deallocating buffer memory :-(
)
get_property(HEADERS TARGET FLUID_DECOMPOSITION PROPERTY INTERFACE_SOURCES)
source_group(TREE ${FLUID_PATH}/include FILES ${HEADERS})
if (SUPERNOVA)
target_include_directories(
${PLUGIN}
SYSTEM PRIVATE
${SC_PATH}/external_libraries/nova-tt
${SC_PATH}/external_libraries/boost_lockfree
${SC_PATH}/external_libraries/boost-lockfree
)

@ -1,74 +1,14 @@
// FD_BufNMF, an NRT buffer NMF Processor
// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899)
#include "clients/nrt/BufferComposeNRT.hpp"
#include "fdNRTBase.hpp"
#include "data/FluidTensor.hpp"
#include "clients/common/FluidParams.hpp"
#include "SC_PlugIn.h"
#include <unordered_set>
#include <vector>
#include <clients/nrt/BufferComposeNRT.hpp>
#include <FluidSCWrapper.hpp>
static InterfaceTable *ft;
namespace fluid {
namespace wrapper{
class BufCompose: public NRTCommandBase
{
public:
using client_type = client::BufferComposeClient;
using NRTCommandBase::NRTCommandBase;
~BufCompose() {}
void runCommand(World* world, void* replyAddr, char* completionMsgData, size_t completionMsgSize)
{
cmd<BufCompose, &BufCompose::process, &BufCompose::postProcess, &BufCompose::postComplete>(world, "/BufCompose", replyAddr, completionMsgData, completionMsgSize);
}
bool process(World* world)
{
//sanity check the parameters
bool parametersOk;
client_type::ProcessModel processModel;
std::string whatHappened;//this will give us a message to pass back if param check fails
std::tie(parametersOk,whatHappened,processModel) = bufferCompose.sanityCheck();
if(!parametersOk)
{
Print("fdCompose: %s \n", whatHappened.c_str());
return false;
}
bufferCompose.process(processModel);
mModel = processModel;
return true;
}
bool postProcess(World* world)
{
static_cast<SCBufferView*>(mModel.dst)->assignToRT(world);
return true;
}
bool postComplete(World* w) {
static_cast<SCBufferView*>(mModel.dst)->cleanUp();
return true;
}
std::vector<client::Instance>& parameters()
{
return bufferCompose.getParams();
}
private:
client_type bufferCompose;
client_type::ProcessModel mModel;
};//class
} //namespace wrapper
}//namespace fluid
PluginLoad(OfflineFluidDecompositionUGens) {
ft = inTable;
registerCommand<fluid::wrapper::BufCompose,fluid:: client::BufferComposeClient>(ft, "BufCompose");
fluid::wrapper::printCmd<fluid::client::BufferComposeClient>(ft,"BufCompose","FDCompose");
using namespace fluid::client;
makeSCWrapper<BufferComposeClient, float,float>("BufCompose", BufComposeParams, ft);
// registerCommand<fluid::wrapper::BufCompose,fluid:: client::BufferComposeClient>(ft, "BufCompose");
// fluid::wrapper::printCmd<fluid::client::BufferComposeClient>(ft,"BufCompose","FDCompose");
}

@ -1,101 +1,13 @@
// FD_BufHPSS, an NRT buffer HPSS Processor
// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899)
#include "fdNRTBase.hpp"
#include "algorithms/STFT.hpp"
#include "data/FluidTensor.hpp"
#include "clients/nrt/HPSS.hpp"
#include "clients/common/FluidParams.hpp"
#include "SC_PlugIn.h"
#include <unordered_set>
#include <vector>
#include <clients/rt/HPSSClient.hpp>
#include <FluidSCWrapper.hpp>
static InterfaceTable *ft;
//Using statements for fluidtensor
using fluid::FluidTensor;
using fluid::FluidTensorView;
namespace fluid {
namespace wrapper{
class BufHPSS: public NRTCommandBase
{
/*
- srcbuf num
 src start frame
- src numframes
src start chan
 src num chans
 harms dst
 perc dst
 window size
 hop size
 fft size
*/
public:
using client_type = client::HPSSClient;
using NRTCommandBase::NRTCommandBase;
~BufHPSS() {}
void runCommand(World* world, void* replyAddr, char* completionMsgData, size_t completionMsgSize)
{
cmd<BufHPSS, &BufHPSS::process, &BufHPSS::postProcess, &BufHPSS::postComplete>(world, "/BufHPSS", replyAddr, completionMsgData, completionMsgSize);
}
bool process(World* world)
{
//sanity check the parameters
bool parametersOk;
client::HPSSClient::ProcessModel processModel;
std::string whatHappened;//this will give us a message to pass back if param check fails
std::tie(parametersOk,whatHappened,processModel) = processor.sanityCheck();
if(!parametersOk)
{
Print("fdHPSS: %s \n", whatHappened.c_str());
return false;
}
//Now, we can proceed
processor.process(processModel);
mModel = processModel;
return true;
}
bool postProcess(World* world)
{
static_cast<SCBufferView*>(mModel.harm)->assignToRT(world);
static_cast<SCBufferView*>(mModel.perc)->assignToRT(world);
if(mModel.res)
static_cast<SCBufferView*>(mModel.res)->assignToRT(world);
return true;
}
bool postComplete(World*) {
static_cast<SCBufferView*>(mModel.harm)->cleanUp();
static_cast<SCBufferView*>(mModel.perc)->cleanUp();
if(mModel.res)
static_cast<SCBufferView*>(mModel.res)->cleanUp();
return true;
}
std::vector<client::Instance>& parameters()
{
return processor.getParams();
}
private:
client::HPSSClient processor;
client::HPSSClient::ProcessModel mModel;
};//class
} //namespace wrapper
}//namespace fluid
PluginLoad(OfflineFluidDecompositionUGens) {
ft = inTable;
registerCommand<fluid::wrapper::BufHPSS,fluid::client::HPSSClient>(ft, "BufHPSS");
fluid::wrapper::printCmd<fluid::client::HPSSClient>(ft,"BufHPSS","FDHPSS");
using namespace fluid::client;
makeSCWrapper<NRTHPSS,double,float>("BufHPSS",NRTHPSSParams,ft);
}

@ -1,135 +1,10 @@
// FD_BufNMF, an NRT buffer NMF Processor
// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899)
#include "fdNRTBase.hpp"
#include "algorithms/STFT.hpp"
#include "data/FluidTensor.hpp"
#include "clients/nrt/NMFClient.hpp"
#include "clients/common/FluidParams.hpp"
#include "SC_PlugIn.h"
#include <unordered_set>
#include <vector>
#include <clients/nrt/NMFClient.hpp>
#include <FluidSCWrapper.hpp>
static InterfaceTable *ft;
//Using statements for fluidtensor
using fluid::FluidTensor;
using fluid::FluidTensorView;
using fluid::client::NMFClient;
namespace fluid {
namespace wrapper{
class BufNMF: public NRTCommandBase
{
/*
- srcbuf num
 src start frame
- src numframes
src start chan
 src num chans
 resynths dst
 dicts dst
 acts dst
- 'overwrite' flag [-1:1]
- rank
- iterations
 window size
 hop size
 fft size
- boundary flag
- rand seed
*/
public:
using client_type = NMFClient;
using NRTCommandBase::NRTCommandBase;
~BufNMF()
{
// if(src) delete src;
// if(resynth) delete resynth;
// if(dict) delete dict;
// if(act) delete act;
}
void runCommand(World* world, void* replyAddr, char* completionMsgData, size_t completionMsgSize)
{
cmd<BufNMF, &BufNMF::process, &BufNMF::postProcess, &BufNMF::postComplete>(world, "/BufNMF", replyAddr, completionMsgData, completionMsgSize);
}
bool process(World* world)
{
//sanity check the parameters
bool parametersOk;
NMFClient::ProcessModel processModel;
std::string whatHappened;//this will give us a message to pass back if param check fails
std::tie(parametersOk,whatHappened,processModel) = nmf.sanityCheck();
if(!parametersOk)
{
Print("fdNMF: %s \n", whatHappened.c_str());
return false;
}
//Now, we can proceed
nmf.process(processModel);
mModel = processModel;
src = static_cast<SCBufferView*>(client::lookupParam("src", nmf.getParams()).getBuffer());
resynth = static_cast<SCBufferView*>(client::lookupParam("resynthbuf", nmf.getParams()).getBuffer());
dict = static_cast<SCBufferView*>(client::lookupParam("filterbuf", nmf.getParams()).getBuffer());
act = static_cast<SCBufferView*>(client::lookupParam("envbuf", nmf.getParams()).getBuffer());
return true;
}
bool postProcess(World* world)
{
if(mModel.resynthesise)
resynth->assignToRT(world);
if(mModel.returnDictionaries)
dict->assignToRT(world);
if(mModel.returnActivations)
act->assignToRT(world);
return true;
}
bool postComplete(World*) {
if(mModel.resynthesise)
resynth->cleanUp();
if(mModel.returnDictionaries)
dict->cleanUp();
if(mModel.returnActivations)
act->cleanUp();
return true;
}
std::vector<client::Instance>& parameters()
{
return nmf.getParams();
}
private:
NMFClient nmf;
NMFClient::ProcessModel mModel;
SCBufferView* src;
SCBufferView* resynth;
SCBufferView* dict;
SCBufferView* act;
};//class
} //namespace wrapper
}//namespace fluid
PluginLoad(OfflineFluidDecompositionUGens) {
ft = inTable;
registerCommand<fluid::wrapper::BufNMF,fluid::client::NMFClient>(ft, "BufNMF");
using namespace fluid::client;
makeSCWrapper<NMFClient,double,float>("BufNMF",NMFParams, ft);
}

@ -2,74 +2,13 @@
// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899)
#include "clients/nrt/NoveltyClient.hpp"
#include "fdNRTBase.hpp"
#include "data/FluidTensor.hpp"
#include "clients/common/FluidParams.hpp"
//#include "SC_PlugIn.h"
//#include <unordered_set>
//#include <vector>
#include <clients/nrt/NoveltyClient.hpp>
#include <FluidSCWrapper.hpp>
static InterfaceTable* ft;
namespace fluid {
namespace wrapper{
class BufNoveltySlice: public NRTCommandBase
{
public:
using client_type = client::NoveltyClient;
using NRTCommandBase::NRTCommandBase;
~BufNoveltySlice() {}
void runCommand(World* world, void* replyAddr, char* completionMsgData, size_t completionMsgSize)
{
cmd<BufNoveltySlice, &BufNoveltySlice::process, &BufNoveltySlice::postProcess, &BufNoveltySlice::postComplete>(world, "/BufNoveltySlice", replyAddr, completionMsgData, completionMsgSize);
}
bool process(World* world)
{
//sanity check the parameters
bool parametersOk;
client_type::ProcessModel processModel;
std::string whatHappened;//this will give us a message to pass back if param check fails
std::tie(parametersOk,whatHappened,processModel) = trans.sanityCheck();
if(!parametersOk)
{
Print("FluidBufNovletySlice: %s \n", whatHappened.c_str());
return false;
}
trans.process(processModel);
mModel = processModel;
return true;
}
bool postProcess(World* world)
{
static_cast<SCBufferView*>(mModel.indices)->assignToRT(world);
return true;
}
bool postComplete(World*) {
static_cast<SCBufferView*>(mModel.indices)->cleanUp();
return true;
}
std::vector<client::Instance>& parameters()
{
return trans.getParams();
}
private:
client_type trans;
client_type::ProcessModel mModel;
};//class
} //namespace wrapper
}//namespace fluid
PluginLoad(OfflineFluidDecompositionUGens) {
ft = inTable;
registerCommand<fluid::wrapper::BufNoveltySlice,fluid::client::NoveltyClient>(ft, "BufNoveltySlice");
fluid::wrapper::printCmd<fluid::client::NoveltyClient>(ft,"BufNoveltySlice","FluidBufNoveltySlice");
using namespace fluid::client;
makeSCWrapper<NoveltyClient,double,float>("BufNoveltySlice",NoveltyParams,ft);
}

@ -1,99 +1,15 @@
// FD_BufSines, an NRT buffer Sinusoidal Modelling Processor
// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899)
#include "fdNRTBase.hpp"
#include "algorithms/STFT.hpp"
#include "data/FluidTensor.hpp"
#include "clients/nrt/Sines.hpp"
#include "clients/common/FluidParams.hpp"
#include "SC_PlugIn.h"
#include <unordered_set>
#include <vector>
#include <clients/rt/SinesClient.hpp>
#include <clients/nrt/FluidNRTClientWrapper.hpp>
#include <FluidSCWrapper.hpp>
static InterfaceTable *ft;
//Using statements for fluidtensor
using fluid::FluidTensor;
using fluid::FluidTensorView;
namespace fluid {
namespace wrapper{
class BufSines: public NRTCommandBase
{
/*
- srcbuf num
 src start frame
- src numframes
src start chan
 src num chans
 sines dst
 residual dst
 bandwidth (bins)
 threshold (0-1)
 minTrackLen (frames, 0 for no tracking)
- magnitude weight(0-1)
- freq weight (0-1)
 window size
 hop size
 fft size
*/
public:
using client_type = client::SinesClient;
using NRTCommandBase::NRTCommandBase;
~BufSines() {}
void runCommand(World* world, void* replyAddr, char* completionMsgData, size_t completionMsgSize)
{
cmd<BufSines, &BufSines::process, &BufSines::postProcess, &BufSines::postComplete>(world, "/BufSines", replyAddr, completionMsgData, completionMsgSize);
}
bool process(World* world)
{
//sanity check the parameters
bool parametersOk;
client::SinesClient::ProcessModel processModel;
std::string whatHappened;//this will give us a message to pass back if param check fails
std::tie(parametersOk,whatHappened,processModel) = processor.sanityCheck();
if(!parametersOk)
{
Print("fdNMF: %s \n", whatHappened.c_str());
return false;
}
//Now, we can proceed
processor.process(processModel);
mModel = processModel;
return true;
}
bool postProcess(World* world)
{
static_cast<SCBufferView*>(mModel.sine)->assignToRT(world);
static_cast<SCBufferView*>(mModel.res)->assignToRT(world);
return true;
}
bool postComplete(World* w) {
static_cast<SCBufferView*>(mModel.sine)->cleanUp();
static_cast<SCBufferView*>(mModel.res)->cleanUp();
return true;
}
std::vector<client::Instance>& parameters()
{
return processor.getParams();
}
private:
client::SinesClient processor;
client::SinesClient::ProcessModel mModel;
};//class
} //namespace wrapper
}//namespace fluid
PluginLoad(OfflineFluidDecompositionUGens) {
ft = inTable;
registerCommand<fluid::wrapper::BufSines,fluid::client::SinesClient>(ft, "BufSines");
fluid::wrapper::printCmd<fluid::client::SinesClient>(ft,"BufSines","FDSines");
using namespace fluid::client;
makeSCWrapper<NRTSines,double,float>("BufSines",NRTSineParams,ft);
}

@ -1,77 +1,14 @@
// FD_BufNMF, an NRT buffer NMF Processor
// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899)
#define EIGEN_USE_BLAS
#include "clients/nrt/TransientSliceNRT.hpp"
#include "fdNRTBase.hpp"
#include "data/FluidTensor.hpp"
#include "clients/common/FluidParams.hpp"
#include "SC_PlugIn.h"
#include <unordered_set>
#include <vector>
#include <clients/rt/TransientSlice.hpp>
#include <clients/nrt/FluidNRTClientWrapper.hpp>
#include <FluidSCWrapper.hpp>
static InterfaceTable* ft;
namespace fluid {
namespace wrapper{
class BufTransientsSlice: public NRTCommandBase
{
public:
using client_type = client::TransientSliceNRT;
using NRTCommandBase::NRTCommandBase;
~BufTransientsSlice() {}
void runCommand(World* world, void* replyAddr, char* completionMsgData, size_t completionMsgSize)
{
cmd<BufTransientsSlice, &BufTransientsSlice::process, &BufTransientsSlice::postProcess, &BufTransientsSlice::postComplete>(world, "/BufTransientSlice", replyAddr, completionMsgData, completionMsgSize);
}
bool process(World* world)
{
//sanity check the parameters
bool parametersOk;
client_type::ProcessModel processModel;
std::string whatHappened;//this will give us a message to pass back if param check fails
std::tie(parametersOk,whatHappened,processModel) = trans.sanityCheck();
if(!parametersOk)
{
Print("FluidBufTransientSlice: %s \n", whatHappened.c_str());
return false;
}
trans.process(processModel);
mModel = processModel;
return true;
}
bool postProcess(World* world)
{
static_cast<SCBufferView*>(mModel.trans)->assignToRT(world);
return true;
}
bool postComplete(World*)
{
static_cast<SCBufferView*>(mModel.trans)->cleanUp();
return true;
}
std::vector<client::Instance>& parameters()
{
return trans.getParams();
}
private:
client_type trans;
client_type::ProcessModel mModel;
};//class
} //namespace wrapper
}//namespace fluid
PluginLoad(OfflineFluidDecompositionUGens) {
ft = inTable;
registerCommand<fluid::wrapper::BufTransientsSlice,fluid::client::TransientSliceNRT>(ft, "BufTransientSlice");
fluid::wrapper::printCmd<fluid::client::TransientSliceNRT>(ft,"BufTransientsSlice","FluidBufTransientSlice");
using namespace fluid::client;
makeSCWrapper<NRTTransientSlice,double,float>("BufTransientSlice",NRTTransientSliceParams, ft);
}

@ -1,81 +1,11 @@
// FD_BufNMF, an NRT buffer NMF Processor
// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899)
#include "clients/nrt/TransientNRTClient.hpp"
#include "fdNRTBase.hpp"
#include "data/FluidTensor.hpp"
#include "clients/common/FluidParams.hpp"
#include "SC_PlugIn.h"
#include <unordered_set>
#include <vector>
#include <clients/nrt/FluidNRTClientWrapper.hpp>
#include <clients/rt/TransientClient.hpp>
#include <FluidSCWrapper.hpp>
static InterfaceTable *ft;
namespace fluid {
namespace wrapper{
class BufTransients: public NRTCommandBase
{
public:
using client_type = client::TransientNRTClient;
using NRTCommandBase::NRTCommandBase;
~BufTransients() {}
void runCommand(World* world, void* replyAddr, char* completionMsgData, size_t completionMsgSize)
{
cmd<BufTransients, &BufTransients::process, &BufTransients::postProcess, &BufTransients::postComplete>(world, "/BufTransients", replyAddr, completionMsgData, completionMsgSize);
}
bool process(World* world)
{
//sanity check the parameters
bool parametersOk;
client_type::ProcessModel processModel;
std::string whatHappened;//this will give us a message to pass back if param check fails
std::tie(parametersOk,whatHappened,processModel) = trans.sanityCheck();
if(!parametersOk)
{
Print("fdTransients: %s \n", whatHappened.c_str());
return false;
}
trans.process(processModel);
mModel = processModel;
return true;
}
bool postProcess(World* world)
{
if(mModel.returnTransients)
static_cast<SCBufferView*>(mModel.trans)->assignToRT(world);
if(mModel.returnResidual)
static_cast<SCBufferView*>(mModel.res)->assignToRT(world);
return true;
}
bool postComplete(World*)
{
if(mModel.returnTransients)
static_cast<SCBufferView*>(mModel.trans)->cleanUp();
if(mModel.returnResidual)
static_cast<SCBufferView*>(mModel.res)->cleanUp();
return true;
}
std::vector<client::Instance>& parameters()
{
return trans.getParams();
}
private:
client_type trans;
client_type::ProcessModel mModel;
};//class
} //namespace wrapper
}//namespace fluid
PluginLoad(OfflineFluidDecompositionUGens) {
ft = inTable;
registerCommand<fluid::wrapper::BufTransients,fluid::client::TransientNRTClient>(ft, "BufTransients");
fluid::wrapper::printCmd<fluid::client::TransientNRTClient>(ft,"BufTransients","FDTransients");
using namespace fluid::client;
makeSCWrapper<NRTTransients,double,float>("BufTransients",NRTTransientParams,ft);
}

@ -14,7 +14,7 @@ target_include_directories(
)
target_link_libraries(
${PLUGIN} PRIVATE FLUID_DECOMPOSITION
${PLUGIN} PRIVATE FLUID_DECOMPOSITION FLUID_SC_WRAPPER
)
include(${CMAKE_CURRENT_LIST_DIR}/../../scripts/target_post.cmake)

@ -1,66 +1,13 @@
// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899)
#include "SC_PlugIn.hpp"
#include "data/FluidTensor.hpp"
#include "clients/rt/GainClient.hpp"
#include "clients/common/FluidParams.hpp"
#include <FluidSCWrapper.hpp>
#include <clients/rt/GainClient.hpp>
#include <SC_PlugIn.hpp>
static InterfaceTable *ft;
namespace fluid {
class FDGain: public SCUnit
{
using AudioClient = fluid::client::GainAudioClient<double, float>;
using ClientPointer = std::unique_ptr<AudioClient>;
using SignalWrapper = AudioClient::Signal;
using AudioSignal= AudioClient::AudioSignal;
using ControlSignal = AudioClient::ScalarSignal;
using SignalPointer = std::unique_ptr<SignalWrapper>;
template <size_t N>
using SignalArray = std::array<SignalPointer, N>;
public:
FDGain()
{
mClient = ClientPointer(new AudioClient(65536));
std::vector<client::Instance>& params = mClient->getParams();
client::lookupParam("winsize", params).setLong(in0(1));
client::lookupParam("hopsize", params).setLong(in0(1));
mClient->setHostBufferSize(bufferSize());
mClient->reset();
inputSignals[0] = SignalPointer(new AudioSignal());
outputSignals[0] = SignalPointer(new AudioSignal());
if(isAudioRateIn(2))
inputSignals[1] = SignalPointer(new AudioSignal());
else
inputSignals[1] = SignalPointer(new ControlSignal());
mCalcFunc = make_calc_function<FDGain,&FDGain::next>();
Unit* unit = this;
ClearUnitOutputs(unit,1);
}
~FDGain() {}
private:
void next(int numsamples)
{
//TODO: Remove const_cast code smell by making input_signal type for const
inputSignals[0]->set(const_cast<float*>(in(0)), in0(0));
inputSignals[1]->set(const_cast<float*>(in(2)), in0(2));
outputSignals[0]->set(out(0), out0(0));
mClient->doProcess(inputSignals.begin(),inputSignals.end(),outputSignals.begin(),outputSignals.end(),numsamples,2,1);
}
ClientPointer mClient;
SignalArray<2> inputSignals;
SignalArray<1> outputSignals;
};
}
PluginLoad(BoringMixer2UGens) {
PluginLoad(FluidGainUgen) {
ft = inTable;
registerUnit<fluid::FDGain>(ft, "FluidGain");
using namespace fluid::client;
makeSCWrapper<GainClient,double,float>("FluidGain", GainParams,ft);
}

@ -1,140 +1,14 @@
// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899)
#include "SC_PlugIn.hpp"
#include "data/FluidTensor.hpp"
#include "clients/rt/HPSSClient.hpp"
#include <clients/rt/HPSSClient.hpp>
#include <FluidSCWrapper.hpp>
static InterfaceTable *ft;
namespace fluid {
namespace wrapper{
class FDRTHPSS: public SCUnit
{
using AudioSignalWrapper = client::HPSSClient<double, float>::AudioSignal;
using SignalWrapper = client::HPSSClient<double, float>::Signal<float>;
using SignalPointer = std::unique_ptr<SignalWrapper>;
using ClientPointer = std::unique_ptr<client::HPSSClient<double,float>>;
template <size_t N>
using SignalArray = std::array<SignalPointer,N>;
public:
FDRTHPSS()
{
//Order of args
//psize hszie pthresh hthresh Window size, Hop size, FFT Size
//Oh NO! Heap allocation! Make client object
mClient = ClientPointer(new client::HPSSClient<double,float>(65536));
setParams(true);
bool isOK;
std::string feedback;
std::tie(isOK, feedback) = mClient->sanityCheck();
if(!isOK)
{
Print("fdRTHPSS Error: %s",feedback.c_str());
return;
}
mClient->setHostBufferSize(bufferSize());
mClient->reset();
//Work out what signals we need. For now keep it simple:
inputSignals[0] = SignalPointer(new AudioSignalWrapper());
outputSignals[0] = SignalPointer(new AudioSignalWrapper());
outputSignals[1] = SignalPointer(new AudioSignalWrapper());
outputSignals[2] = SignalPointer(new AudioSignalWrapper());
mCalcFunc = make_calc_function<FDRTHPSS,&FDRTHPSS::next>();
Unit* unit = this;
ClearUnitOutputs(unit,1);
}
~FDRTHPSS() {}
private:
void setParams(bool instantiation)
{
assert(mClient);
for(size_t i = 0; i < mClient->getParams().size(); ++i)
{
client::Instance& p = mClient->getParams()[i];
if(!instantiation && p.getDescriptor().instantiation())
continue;
switch(p.getDescriptor().getType())
{
case client::Type::kLong:
p.setLong(in0(i + 1));
p.checkRange();
break;
case client::Type::kFloat: {
// We need to constrain threshold (normalised) frequency pairs at
// runtime.
std::string attrname = p.getDescriptor().getName();
auto constraint = paramConstraints.find(attrname);
if (constraint != paramConstraints.end()) {
double limit = client::lookupParam(constraint->second.param,
mClient->getParams())
.getFloat();
if (!constraint->second.condition(in0(i + 1), limit)) {
return;
}
}
p.setFloat(in0(i + 1));
p.checkRange();
}
break;
case client::Type::kBuffer:
// p.setBuffer( in0(i+1));
break;
default:
break;
}
}
}
void next(int numsamples)
{
setParams(false);
const float* input = in(0);
const float inscalar = in0(0);
inputSignals[0]->set(const_cast<float*>(input), inscalar);
outputSignals[0]->set(out(0), out0(0));
outputSignals[1]->set(out(1), out0(1));
outputSignals[2]->set(out(2), out0(2));
mClient->doProcess(std::begin(inputSignals),std::end(inputSignals),std::begin(outputSignals), std::end(outputSignals),numsamples,1,3);
}
struct Constraint{
std::string param;
std::function<bool(double, double)> condition;
};
std::map<std::string, Constraint> paramConstraints{
{"ptf1",{"ptf2", std::less<double>()}},
{"htf1",{"htf2", std::less<double>()}},
{"ptf2",{"ptf1", std::greater<double>()}},
{"htf2",{"htf1", std::greater<double>()}}
};
ClientPointer mClient;
SignalArray<1> inputSignals;
SignalArray<3> outputSignals;
};
}
}
PluginLoad(FluidSTFTUGen) {
ft = inTable;
registerUnit<fluid::wrapper::FDRTHPSS>(ft, "FluidHPSS");
using namespace fluid::client;
makeSCWrapper<HPSSClient,double,float>("FluidHPSS",HPSSParams,ft);
}

@ -1,134 +1,15 @@
// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899)
#include "SC_PlugIn.hpp"
#include "data/FluidTensor.hpp"
#include "fdNRTBase.hpp"
#include "clients/rt/NMFMatch.hpp"
#include <clients/rt/NMFMatch.hpp>
#include <FluidSCWrapper.hpp>
static InterfaceTable *ft;
namespace fluid {
namespace nmf{
class FDNMFMatch: public SCUnit
{
using Client = client::NMFMatch<double,float>;
using AudioSignalWrapper = Client::AudioSignal;
using SignalWrapper = Client::Signal<float>;
using SignalPointer = std::unique_ptr<SignalWrapper>;
using ClientPointer = std::unique_ptr<Client>;
template <size_t N>
using SignalArray = std::array<SignalPointer,N>;
using SignalVector = std::vector<SignalPointer>;
public:
FDNMFMatch()
{
//Order of args
//psize hszie pthresh hthresh Window size, Hop size, FFT Size
mClient = ClientPointer(new Client(65536));
setParams(true);
bool isOK;
std::string feedback;
std::tie(isOK, feedback) = mClient->sanityCheck();
if(!isOK)
{
std::cout << "FluidNMFMatch Error: " << feedback << '\n';
mCalcFunc = ClearUnitOutputs;
return;
}
mRank = client::lookupParam("rank", mClient->getParams()).getLong();
mClient->setHostBufferSize(bufferSize());
mClient->reset();
inputSignals[0] = SignalPointer(new AudioSignalWrapper());
outputSignals.resize(mRank);
for(size_t i = 0; i < mRank; ++i)
outputSignals[i].reset(new Client::ScalarSignal());
mCalcFunc = make_calc_function<FDNMFMatch,&FDNMFMatch::next>();
Unit* unit = this;
ClearUnitOutputs(unit,1);
}
~FDNMFMatch() {}
private:
void setParams(bool instantiation)
{
assert(mClient);
for(size_t i = 0; i < mClient->getParams().size(); ++i)
{
client::Instance& p = mClient->getParams()[i];
if(!instantiation && p.getDescriptor().instantiation())
continue;
switch(p.getDescriptor().getType())
{
case client::Type::kLong:
p.setLong(in0(i + 1));
p.checkRange();
break;
case client::Type::kFloat: {
p.setFloat(in0(i + 1));
p.checkRange();
}
break;
case client::Type::kBuffer: {
long bufnum = static_cast<long>(in0(i+1));
wrapper::RTBufferView* currentBuf = static_cast<wrapper::RTBufferView*>(p.getBuffer());
if(bufnum >= 0 && (currentBuf? (currentBuf->bufnum() != bufnum) : true)){
wrapper::RTBufferView* buf = new wrapper::RTBufferView(mWorld,bufnum);
p.setBuffer(buf);
}
break;
}
default:
break;
}
}
}
void next(int numsamples)
{
setParams(false);
const float* input = zin(0);
const float inscalar = in0(0);
inputSignals[0]->set(const_cast<float*>(input), inscalar);
for(size_t i = 0; i < mRank; ++i)
outputSignals[i]->set(out(i),out0(i));
mClient->doProcessNoOla(inputSignals.begin(),inputSignals.end(), outputSignals.begin(), outputSignals.end(), mWorld->mFullRate.mBufLength ,1,mRank);
for(size_t i = 0; i < mRank; ++i)
out0(i) = outputSignals[i]->next();
}
size_t mRank;
ClientPointer mClient;
SignalArray<1> inputSignals;
SignalVector outputSignals;
};
}
}
PluginLoad(FluidSTFTUGen) {
ft = inTable;
registerUnit<fluid::nmf::FDNMFMatch>(ft, "FluidNMFMatch");
using namespace fluid::client;
makeSCWrapper<NMFMatch,double, float>("FluidNMFMatch",NMFMatchParams,ft);
}

@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 3.3)
get_filename_component(PLUGIN ${CMAKE_CURRENT_LIST_DIR} NAME_WE)
message("Configuring ${PLUGIN}")
set(FILENAME ${PLUGIN}.cpp)
add_library(
${PLUGIN}
MODULE
${FILENAME}
)
target_include_directories(
${PLUGIN} PRIVATE ${CMAKE_CURRENT_LIST_DIR}/../../include
)
target_link_libraries(
${PLUGIN} PRIVATE FLUID_DECOMPOSITION
)
include(${CMAKE_CURRENT_LIST_DIR}/../../scripts/target_post.cmake)

@ -0,0 +1,13 @@
// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899)
#include <clients/rt/OnsetSlice.hpp>
#include <FluidSCWrapper.hpp>
static InterfaceTable *ft;
PluginLoad(FluidSTFTUGen) {
ft = inTable;
using namespace fluid::client;
makeSCWrapper<OnsetSlice<double,float>>(ft, "FluidOnsetSlice");
}

@ -0,0 +1,21 @@
s.reboot;
b = Buffer.read(s,"../../release-packaging/AudioFiles/Tremblay-AaS-SynthTwoVoices-M.wav".resolveRelative);
// basic param (the process add a latency of (blockSize + padding - order) samples
{var sig = PlayBuf.ar(1,b.bufnum,loop:1); [FluidTransientSlice.ar(sig)*0.5, DelayN.ar(sig, 1, ((256 + 128 - 20)/ s.sampleRate))]}.play
// sexier params
{var sig = PlayBuf.ar(1,b.bufnum,loop:1); [FluidTransientSlice.ar(sig,order:80,debounce:2205)*0.5, DelayN.ar(sig, 1, ((256 + 128 - 80)/ s.sampleRate))]}.play
// more musical trans-trigged autopan
(
{
var sig, trig, syncd, pan;
sig = PlayBuf.ar(1,b.bufnum,loop:1);
trig = FluidTransientSlice.ar(sig,order:10,debounce:2205);
syncd = DelayN.ar(sig, 1, ((256 + 128 - 10)/ s.sampleRate));
pan = TRand.ar(-1,1,trig);
Pan2.ar(syncd,pan);
}.play
)

@ -1,88 +1,13 @@
// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899)
#include "SC_PlugIn.hpp"
#include "data/FluidTensor.hpp"
#include "clients/rt/BaseSTFTClient.hpp"
#include <FluidSCWrapper.hpp>
#include <clients/rt/BaseSTFTClient.hpp>
static InterfaceTable *ft;
namespace fluid {
namespace stft{
class FDSTFTPass: public SCUnit
{
using AudioSignalWrapper = client::BaseSTFTClient<double, float>::AudioSignal;
using SignalWrapper = client::BaseSTFTClient<double, float>::Signal<float>;
// using SignalPointer = std::unique_ptr<signal_wrapper>;
public:
FDSTFTPass()
{
//Order of args
//Window size, Hop size, FFT Size
//Get the window size
const float windowSize = in0(1);
const float hopSize = in0(2);
const float fftSize = in0(3);
//Oh NO! Heap allocation! Make client object
mClient = new client::BaseSTFTClient<double,float>(65536);
mClient->getParams()[0].setLong(windowSize);
mClient->getParams()[1].setLong(hopSize);
mClient->getParams()[2].setLong(fftSize);
bool isOK;
std::string feedback;
std::tie(isOK, feedback) = mClient->sanityCheck();
if(!isOK)
{
Print("fdSTFTPass Error: %s",feedback.c_str());
return;
}
mClient->setHostBufferSize(bufferSize());
mClient->reset();
//Work out what signals we need. For now keep it simple:
//in 0 => only audio
//out 0 => only audio
inputSignals[0] = new AudioSignalWrapper();
outputSignals[0] = new AudioSignalWrapper();
mCalcFunc = make_calc_function<FDSTFTPass,&FDSTFTPass::next>();
Unit* unit = this;
ClearUnitOutputs(unit,1);
}
~FDSTFTPass()
{
delete inputSignals[0];
delete outputSignals[0];
delete mClient;
}
private:
void next(int numsamples)
{
const float* input = in(0);
const float inscalar = in0(0);
inputSignals[0]->set(const_cast<float*>(input), inscalar);
outputSignals[0]->set(out(0), out0(0));
mClient->doProcess(std::begin(inputSignals),std::end(inputSignals),std::begin(outputSignals), std::end(outputSignals),numsamples,1,1);
}
client::BaseSTFTClient<double, float> *mClient;
SignalWrapper *inputSignals[1];
SignalWrapper *outputSignals[1];
};
}
}
PluginLoad(FluidSTFTUGen) {
ft = inTable;
registerUnit<fluid::stft::FDSTFTPass>(ft, "FluidSTFTPass");
using namespace fluid::client;
makeSCWrapper<BaseSTFTClient,double,float>("FluidSTFTPass",STFTParams,ft);
}

@ -1,131 +1,14 @@
// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899)
#include "SC_PlugIn.hpp"
#include "data/FluidTensor.hpp"
#include "clients/rt/SinesClient.hpp"
#include <clients/rt/SinesClient.hpp>
#include <FluidSCWrapper.hpp>
static InterfaceTable *ft;
namespace fluid {
namespace wrapper{
class FDRTSines: public SCUnit
{
using AudioSignalWrapper = client::SinesClient<double, float>::AudioSignal;
using SignalWrapper = client::SinesClient<double, float>::Signal<float>;
// using SignalPointer = std::unique_ptr<signal_wrapper>;
public:
FDRTSines()
{
//Order of args
//Window size, Hop size, FFT Size
//Get the window size
// const float hfilter_size = in0(1);
// const float pfilter_size = in0(2);
// const float window_size = in0(3);
// const float hop_size = in0(4);
// const float fft_size = in0(5);
//
//Oh NO! Heap allocation! Make client object
mClient = new client::SinesClient<double,float>(65536);
setParams(true);
// m_client->getParams()[0].setLong(pfilter_size);
// m_client->getParams()[1].setLong(hfilter_size);
// m_client->getParams()[2].setLong(window_size);
// m_client->getParams()[3].setLong(hop_size);
// m_client->getParams()[4].setLong(fft_size);
bool isOK;
std::string feedback;
std::tie(isOK, feedback) = mClient->sanityCheck();
if(!isOK)
{
Print("fdRTHPSS Error: %s",feedback.c_str());
return;
}
mClient->setHostBufferSize(bufferSize());
mClient->reset();
//Work out what signals we need. For now keep it simple:
//in 0 => only audio
//out 0 => only audio
inputSignals[0] = new AudioSignalWrapper();
outputSignals[0] = new AudioSignalWrapper();
outputSignals[1] = new AudioSignalWrapper();
mCalcFunc = make_calc_function<FDRTSines,&FDRTSines::next>();
Unit* unit = this;
ClearUnitOutputs(unit,1);
}
~FDRTSines()
{
delete inputSignals[0];
delete outputSignals[0];
delete outputSignals[1];
delete mClient;
}
private:
void setParams(bool instantiation)
{
assert(mClient);
for(size_t i = 0; i < mClient->getParams().size(); ++i)
{
client::Instance& p = mClient->getParams()[i];
if(!instantiation && p.getDescriptor().instantiation())
continue;
switch(p.getDescriptor().getType())
{
case client::Type::kLong:
p.setLong(in0(i + 1));
p.checkRange();
break;
case client::Type::kFloat:
p.setFloat(in0(i + 1));
p.checkRange();
break;
case client::Type::kBuffer:
// p.setBuffer( in0(i+1));
break;
default:
break;
}
}
}
void next(int numsamples)
{
setParams(false);
const float* input = in(0);
const float inscalar = in0(0);
inputSignals[0]->set(const_cast<float*>(input), inscalar);
outputSignals[0]->set(out(0), out0(0));
outputSignals[1]->set(out(1), out0(1));
mClient->doProcess(std::begin(inputSignals),std::end(inputSignals),std::begin(outputSignals), std::end(outputSignals),numsamples,1,2);
}
client::SinesClient<double, float> *mClient;
SignalWrapper *inputSignals[1];
SignalWrapper *outputSignals[2];
};
}
}
PluginLoad(FluidSTFTUGen) {
ft = inTable;
registerUnit<fluid::wrapper::FDRTSines>(ft, "FluidSines");
using namespace fluid::client;
makeSCWrapper<SinesClient,double,float>("FluidSines",SinesParams,ft);
}

@ -1,107 +1,13 @@
// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899)
#include "SC_PlugIn.hpp"
#include "data/FluidTensor.hpp"
#include "clients/rt/TransientSlice.hpp"
#include <clients/rt/TransientSlice.hpp>
#include <FluidSCWrapper.hpp>
static InterfaceTable *ft;
namespace fluid {
namespace segmentation{
class FluidSliceTransients: public SCUnit
{
using audio_client = client::TransientsSlice<double, float>;
using AudioSignalWrapper = audio_client::AudioSignal;
using SignalWrapper = audio_client::Signal<float>;
// using SignalPointer = std::unique_ptr<signal_wrapper>;
public:
FluidSliceTransients()
{
mClient = new audio_client(65536);
setParams(true);
bool isOK;
std::string feedback;
std::tie(isOK, feedback) = mClient->sanityCheck();
if(!isOK)
{
Print("FluidSliceTransients Error: %s",feedback.c_str());
return;
}
mClient->setHostBufferSize(bufferSize());
mClient->reset();
//Work out what signals we need. For now keep it simple:
//in 0 => only audio
//out 0 => only audio
inputSignals[0] = new AudioSignalWrapper();
outputSignals[0] = new AudioSignalWrapper();
mCalcFunc = make_calc_function<FluidSliceTransients,&FluidSliceTransients::next>();
Unit* unit = this;
ClearUnitOutputs(unit,1);
}
~FluidSliceTransients()
{
delete inputSignals[0];
delete outputSignals[0];
delete mClient;
}
private:
void setParams(bool instantiation)
{
assert(mClient);
for(size_t i = 0; i < mClient->getParams().size(); ++i)
{
client::Instance& p = mClient->getParams()[i];
if(!instantiation && p.getDescriptor().instantiation())
continue;
switch(p.getDescriptor().getType())
{
case client::Type::kLong:
p.setLong(in0(i + 1));
p.checkRange();
break;
case client::Type::kFloat:
p.setFloat(in0(i + 1));
p.checkRange();
break;
case client::Type::kBuffer:
// p.setBuffer( in0(i+1));
break;
default:
break;
}
}
}
void next(int numsamples)
{
setParams(false);
const float* input = in(0);
const float inscalar = in0(0);
inputSignals[0]->set(const_cast<float*>(input), inscalar);
outputSignals[0]->set(out(0), out0(0));
mClient->doProcess(std::begin(inputSignals),std::end(inputSignals),std::begin(outputSignals), std::end(outputSignals),numsamples,1,1);
}
audio_client *mClient;
SignalWrapper *inputSignals[1];
SignalWrapper *outputSignals[1];
};
}
}
PluginLoad(FluidSTFTUGen) {
ft = inTable;
registerUnit<fluid::segmentation::FluidSliceTransients>(ft, "FluidTransientSlice");
using namespace fluid::client;
makeSCWrapper<TransientsSlice,double,float>("FluidTransientSlice",TransientParams,ft);
}

@ -9,12 +9,4 @@ add_library(
${FILENAME}
)
target_include_directories(
${PLUGIN} PRIVATE ${CMAKE_CURRENT_LIST_DIR}/../../include
)
target_link_libraries(
${PLUGIN} PRIVATE FLUID_DECOMPOSITION
)
include(${CMAKE_CURRENT_LIST_DIR}/../../scripts/target_post.cmake)

@ -1,129 +1,13 @@
// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899)
#include "SC_PlugIn.hpp"
#include "data/FluidTensor.hpp"
#include "clients/rt/TransientClient.hpp"
#include <clients/rt/TransientClient.hpp>
#include <FluidSCWrapper.hpp>
static InterfaceTable *ft;
namespace fluid {
namespace wrapper{
class FluidTransients: public SCUnit
{
using AudioSignalWrapper = client::TransientsClient<double, float>::AudioSignal;
using SignalWrapper = client::TransientsClient<double, float>::Signal<float>;
// using SignalPointer = std::unique_ptr<signal_wrapper>;
public:
FluidTransients()
{
//Order of args
//Window size, Hop size, FFT Size
//Get the window size
// const float hfilter_size = in0(1);
// const float pfilter_size = in0(2);
// const float window_size = in0(3);
// const float hop_size = in0(4);
// const float fft_size = in0(5);
//
//Oh NO! Heap allocation! Make client object
mClient = new client::TransientsClient<double,float>(65536);
setParams(true);
// mClient->geParams()[0].setLong(pfilter_size);
// mClient->geParams()[1].setLong(hfilter_size);
// mClient->geParams()[2].setLong(window_size);
// mClient->geParams()[3].setLong(hop_size);
// mClient->geParams()[4].setLong(fft_size);
bool isOK;
std::string feedback;
std::tie(isOK, feedback) = mClient->sanityCheck();
if(!isOK)
{
Print("fdRTHPSS Error: %s",feedback.c_str());
return;
}
mClient->setHostBufferSize(bufferSize());
mClient->reset();
//Work out what signals we need. For now keep it simple:
//in 0 => only audio
//out 0 => only audio
inputSignals[0] = new AudioSignalWrapper();
outputSignals[0] = new AudioSignalWrapper();
outputSignals[1] = new AudioSignalWrapper();
mCalcFunc = make_calc_function<FluidTransients,&FluidTransients::next>();
Unit* unit = this;
ClearUnitOutputs(unit,1);
}
~FluidTransients()
{
delete inputSignals[0];
delete outputSignals[0];
delete outputSignals[1];
delete mClient;
}
private:
void setParams(bool instantiation)
{
assert(mClient);
for(size_t i = 0; i < mClient->getParams().size(); ++i)
{
client::Instance& p = mClient->getParams()[i];
if(!instantiation && p.getDescriptor().instantiation())
continue;
switch(p.getDescriptor().getType())
{
case client::Type::kLong:
p.setLong(in0(i + 1));
p.checkRange();
break;
case client::Type::kFloat:
p.setFloat(in0(i + 1));
p.checkRange();
break;
case client::Type::kBuffer:
// p.setBuffer( in0(i+1));
break;
default:
break;
}
}
}
void next(int numsamples)
{
setParams(false);
const float* input = in(0);
const float inscalar = in0(0);
inputSignals[0]->set(const_cast<float*>(input), inscalar);
outputSignals[0]->set(out(0), out0(0));
outputSignals[1]->set(out(1), out0(1));
mClient->doProcess(std::begin(inputSignals),std::end(inputSignals),std::begin(outputSignals), std::end(outputSignals),numsamples,1,2);
}
client::TransientsClient<double, float> *mClient;
SignalWrapper *inputSignals[1];
SignalWrapper *outputSignals[2];
};
}
}
PluginLoad(FluidSTFTUGen) {
ft = inTable;
registerUnit<fluid::wrapper::FluidTransients>(ft, "FluidTransients");
using namespace fluid::client;
makeSCWrapper<TransientClient, double, float>("FluidTransients", TransientParams, ft);
}

Loading…
Cancel
Save