Merge branch 'master' into refactor-windows

# Conflicts:
#	CMakeLists.txt
nix
Owen Green 7 years ago
commit 34ee65ab77

@ -40,7 +40,7 @@ message("${FLUID_ABS_PATH}")
# endif()
if (NOT (EXISTS "${FLUID_ABS_PATH}/build/fluid_decomposition-exports.cmake"))
message(FATAL_ERROR "Can't find the fluid_decomposition CMake targets file at ${FLUID_ABS_PATH}/build/fluid_decomposition-expors.cmake. Please go to ${FLUID_ABS_PATH}/build and run CMake")
message(FATAL_ERROR "Can't find the fluid_decomposition CMake targets file at ${FLUID_ABS_PATH}/build/fluid_decomposition-exports.cmake. Please go to ${FLUID_ABS_PATH}/build and run CMake")
endif()
if (NOT (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/release-packaging/AudioFiles/"))
@ -61,6 +61,17 @@ if(APPLE OR WIN32)
set(CMAKE_SHARED_MODULE_SUFFIX ".scx")
endif()
get_property(FFT_SOURCES TARGET HISSTools_FFT PROPERTY INTERFACE_SOURCES)
get_property(FFT_LINK TARGET HISSTools_FFT PROPERTY INTERFACE_LINK_LIBRARIES)
add_library(FFTLIB STATIC ${FFT_SOURCES})
target_link_libraries(
FFTLIB PRIVATE ${FFT_LINK}
)
target_compile_options(
FFTLIB PRIVATE $<$<NOT:$<CONFIG:DEBUG>>:-mavx -msse -msse2 -msse3 -msse4>
)
add_library(FLUID_SC_WRAPPER INTERFACE)
target_sources(FLUID_SC_WRAPPER
INTERFACE

@ -1,4 +1,4 @@
#pragma once
#pragma once
#include "SCBufferAdaptor.hpp"
#include <clients/common/FluidBaseClient.hpp>
@ -16,299 +16,209 @@
namespace fluid {
namespace client {
template <typename Client, typename Params> class FluidSCWrapper;
template <typename Client>
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
// Iterate over kr/ir inputs via callbacks from params object
struct FloatControlsIter
{
FloatControlsIter(float** vals, size_t N):mValues(vals), mSize(N) {}
float next()
{
return mCount >= mSize ? 0 : *mValues[mCount++];
}
void reset(float** vals)
FloatControlsIter(float **vals, size_t N)
: mValues(vals)
, mSize(N)
{}
float next() { return mCount >= mSize ? 0 : *mValues[mCount++]; }
void reset(float **vals)
{
mValues = vals;
mCount = 0;
mCount = 0;
}
size_t size() const noexcept { return mSize; }
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()};
}
private:
float **mValues;
size_t mSize;
size_t mCount{0};
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Real Time Processor
// Real Time Processor
template <typename Client,class Wrapper, class Params> class RealTime : public SCUnit
template <typename Client, class Wrapper>
class RealTime : public SCUnit
{
using HostVector = FluidTensorView<float, 1>;
using ParamSetType = typename Client::ParamSetType;
// using Client = typename Wrapper::ClientType;
public:
static void setup(InterfaceTable *ft, const char *name)
{
registerUnit<Wrapper>(ft, name);
ft->fDefineUnitCmd(name,"latency",doLatency);
ft->fDefineUnitCmd(name, "latency", doLatency);
}
static void doLatency(Unit *unit, sc_msg_iter *args)
static void doLatency(Unit *unit, sc_msg_iter*)
{
float l[] {static_cast<float>(static_cast<Wrapper*>(unit)->mClient.latency())};
auto ft = Wrapper::getInterfaceTable();
float l[]{static_cast<float>(static_cast<Wrapper *>(unit)->mClient.latency())};
auto ft = Wrapper::getInterfaceTable();
std::stringstream ss;
ss << '/' << Wrapper::getName() << "_latency";
std::cout << ss.str() << '\n';
ft->fSendNodeReply(&unit->mParent->mNode,-1,ss.str().c_str() , 1, l);
ft->fSendNodeReply(&unit->mParent->mNode, -1, ss.str().c_str(), 1, l);
}
RealTime():
mControlsIterator{mInBuf + mSpecialIndex + 1,mNumInputs - mSpecialIndex - 1},
mParams{*Wrapper::getParamDescriptors()},
mClient{Wrapper::setParams(mParams,mWorld->mVerbosity > 0, mWorld, mControlsIterator)}
RealTime()
: mControlsIterator{mInBuf + mSpecialIndex + 1,static_cast<size_t>(static_cast<ptrdiff_t>(mNumInputs) - mSpecialIndex - 1)}
, mParams{Wrapper::Client::getParameterDescriptors()}
, mClient{Wrapper::setParams(mParams,mWorld->mVerbosity > 0, mWorld, mControlsIterator,true)}
{}
void init()
{
assert(!(mClient.audioChannelsOut() > 0 && mClient.controlChannelsOut() > 0) && "Client can't have both audio and control outputs");
assert(!(mClient.audioChannelsOut() > 0 && mClient.controlChannelsOut() > 0) &&
"Client can't have both audio and control outputs");
//If we don't the number of arguments we expect, the language side code is probably the wrong version
//set plugin to no-op, squawk, and bail;
if(mControlsIterator.size() != Wrapper::getParamDescriptors()->count())
if(mControlsIterator.size() != Client::getParameterDescriptors().count())
{
mCalcFunc = Wrapper::getInterfaceTable()->fClearUnitOutputs;
std::cout << "ERROR: " << Wrapper::getName() <<
" wrong number of arguments. Expected " << Wrapper::getParamDescriptors()->count() <<
", got " << mControlsIterator.size() << ". Your .sc file and binary plugin might be different versions." << std::endl;
std::cout << "ERROR: " << Wrapper::getName() << " wrong number of arguments. Expected "
<< Client::getParameterDescriptors().count() << ", got " << mControlsIterator.size()
<< ". Your .sc file and binary plugin might be different versions." << std::endl;
return;
}
mClient.sampleRate(fullSampleRate());
mInputConnections.reserve(mClient.audioChannelsIn());
mOutputConnections.reserve(mClient.audioChannelsOut());
mAudioInputs.reserve(mClient.audioChannelsIn());
mOutputs.reserve(std::max(mClient.audioChannelsOut(),mClient.controlChannelsOut()));
for (int i = 0; i < mClient.audioChannelsIn(); ++i)
mOutputs.reserve(std::max(mClient.audioChannelsOut(), mClient.controlChannelsOut()));
for (int i = 0; i < static_cast<int>(mClient.audioChannelsIn()); ++i)
{
mInputConnections.emplace_back(isAudioRateIn(i));
mAudioInputs.emplace_back(nullptr, 0, 0);
}
for (int i = 0; i < mClient.audioChannelsOut(); ++i)
for (int i = 0; i < static_cast<int>(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);
}
for (int i = 0; i < static_cast<int>(mClient.controlChannelsOut()); ++i) { mOutputs.emplace_back(nullptr, 0, 0); }
set_calc_function<RealTime, &RealTime::next>();
Wrapper::getInterfaceTable()->fClearUnitOutputs(this, 1);
}
void next(int n)
void next(int)
{
mControlsIterator.reset(mInBuf + 1); //mClient.audioChannelsIn());
Wrapper::setParams(mParams,mWorld->mVerbosity > 0, mWorld,mControlsIterator); // forward on inputs N + audio inputs as params
Wrapper::setParams(mParams, mWorld->mVerbosity > 0, mWorld, mControlsIterator); // forward on inputs N + audio inputs as params
mParams.template constrainParameterValues();
const Unit *unit = this;
for (int i = 0; i < mClient.audioChannelsIn(); ++i)
for (size_t i = 0; i < mClient.audioChannelsIn(); ++i)
{
if (mInputConnections[i]) mAudioInputs[i].reset(IN(i), 0, fullBufferSize());
}
for (int i = 0; i < mClient.audioChannelsOut(); ++i)
for (size_t 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);
if (mOutputConnections[i]) mOutputs[i].reset(out(static_cast<int>(i)), 0, fullBufferSize());
}
for (size_t i = 0; i < mClient.controlChannelsOut(); ++i) { mOutputs[i].reset(out(static_cast<int>(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;
ParamSetType mParams;
Client mClient;
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// Non Real Time Processor
template <typename Client, typename Wrapper, typename Params> class NonRealTime
template <typename Client, typename Wrapper>
class NonRealTime
{
using ParamSetType = typename Client::ParamSetType;
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}
NonRealTime(World* w, sc_msg_iter* args)
: mParams{Client::getParameterDescriptors()}
, mClient{Wrapper::setParams(mParams, false, w, args)}
{}
void init(){};
static void launch(World *world, void *inUserData, struct sc_msg_iter *args, void *replyAddr)
static void launch(World *world, void */*inUserData*/, struct sc_msg_iter *args, void *replyAddr)
{
if(args->tags && ((std::string{args->tags}.size() - 1) != Wrapper::getParamDescriptors()->count()))
if (args->tags && ((std::string{args->tags}.size() - 1) != Client::getParameterDescriptors().count()))
{
std::cout << "ERROR: " << Wrapper::getName() <<
" wrong number of arguments. Expected " << Wrapper::getParamDescriptors()->count() <<
", got " << (std::string{args->tags}.size() - 1) << ". Your .sc file and binary plugin might be different versions." << std::endl;
std::cout << "ERROR: " << Wrapper::getName() << " wrong number of arguments. Expected "
<< Client::getParameterDescriptors().count() << ", got " << (std::string{args->tags}.size() - 1)
<< ". Your .sc file and binary plugin might be different versions." << std::endl;
return;
}
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);
Wrapper *w = new Wrapper(
world, args); // this has to be on the heap, because it doesn't get destroyed until the async command is done
Result result = validateParameters(w);
if (!result.ok())
{
std::cout << "ERROR: " << Wrapper::getName() << ": " << result.message().c_str() << std::endl;
delete w;
return;
std::cout << "ERROR: " << Wrapper::getName() << ": " << result.message().c_str() << std::endl;
delete w;
return;
}
args->count = argsPosition;
args->rdpos = argsRdPos;
Wrapper::setParams(w->mParams,false, world, args);
size_t msgSize = args->getbsize();
size_t msgSize = args->getbsize();
std::vector<char> completionMessage(msgSize);
// char * completionMsgData = 0;
if (msgSize)
{
args->getb(completionMessage.data(), 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());
world->ft->fDoAsynchronousCommand(world, replyAddr, Wrapper::getName(), w, process, exchangeBuffers, tidyUp, destroy,
static_cast<int>(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);
}
static void destroy(World *, void *data) { delete static_cast<Wrapper *>(data); }
protected:
ParameterSet<Params> mParams;
Client mClient;
ParamSetType mParams;
Client mClient;
private:
static Result validateParameters(NonRealTime *w, World* world, sc_msg_iter *args)
static Result validateParameters(NonRealTime *w)
{
auto results = w->mParams.template checkParameterValues<ArgumentGetter>(world, args);
auto results = w->mParams.template constrainParameterValues();
for (auto &r : results)
{
if (!r.ok()) return r;
@ -316,122 +226,157 @@ private:
return {};
}
bool process(World *world)
bool process(World *)
{
Result r = mClient.process();///mInputs, mOutputs);
if(!r.ok())
Result r = mClient.process();
if (!r.ok())
{
std::cout << "ERROR: " << Wrapper::getName() << ": " << r.message().c_str();
return false;
return false;
}
return true;
}
bool exchangeBuffers(World *world)
{
mParams.template forEachParamType<BufferT,AssignBuffer>(world);
// for (auto &b : mBuffersOut) b.assignToRT(world);
mParams.template forEachParamType<BufferT, AssignBuffer>(world);
return true;
}
bool tidyUp(World *world)
bool tidyUp(World *)
{
// for (auto &b : mBuffersIn) b.cleanUp();
// for (auto &b : mBuffersOut) b.cleanUp()
mParams.template forEachParamType<BufferT,CleanUpBuffer>();
mParams.template forEachParamType<BufferT, CleanUpBuffer>();
return true;
}
template<size_t N,typename T>
template <size_t N, typename T>
struct AssignBuffer
{
void operator()(typename BufferT::type& p, World* w)
void operator()(const typename BufferT::type &p, World *w)
{
if(auto b = static_cast<SCBufferAdaptor*>(p.get()))
b->assignToRT(w);
if (auto b = static_cast<SCBufferAdaptor *>(p.get())) b->assignToRT(w);
}
};
template<size_t N,typename T>
template <size_t N, typename T>
struct CleanUpBuffer
{
void operator()(typename BufferT::type& p)
void operator()(const typename BufferT::type &p)
{
if(auto b = static_cast<SCBufferAdaptor*>(p.get()))
b->cleanUp();
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;
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>
template <typename Client, typename Wrapper>
class NonRealTimeAndRealTime : public RealTime<Client, Wrapper>, public NonRealTime<Client, Wrapper>
{
static void setup(InterfaceTable *ft, const char *name)
{
RealTime<Client,Wrapper,Params >::setup(ft, name);
NonRealTime<Client,Wrapper, Params>::setup(ft, name);
RealTime<Client,Wrapper>::setup(ft, name);
NonRealTime<Client,Wrapper>::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 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>
template <typename Client, typename Wrapper>
class FluidSCWrapperImpl<Client, Wrapper, std::true_type, std::false_type>
: public NonRealTime<Client, Wrapper>
{
public:
FluidSCWrapperImpl(World* w, sc_msg_iter *args): NonRealTime<Client, Wrapper, Params>(w,args){};
FluidSCWrapperImpl(World* w, sc_msg_iter *args): NonRealTime<Client, Wrapper>(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>
template <typename Client, typename Wrapper>
class FluidSCWrapperImpl<Client, Wrapper, std::false_type, std::true_type> : public RealTime<Client, Wrapper>
{};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Make base class(es), full of CRTP mixin goodness
template <typename Client,typename Params>
using FluidSCWrapperBase = FluidSCWrapperImpl<Client, FluidSCWrapper<Client, Params>,Params, isNonRealTime<Client>, isRealTime<Client>>;
template <typename Client>
using FluidSCWrapperBase = FluidSCWrapperImpl<Client, FluidSCWrapper<Client>, isNonRealTime<Client>, isRealTime<Client>>;
} // namespace impl
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///The main wrapper
template <typename C, typename P> class FluidSCWrapper : public impl::FluidSCWrapperBase<C,P>
template <typename C>
class FluidSCWrapper : public impl::FluidSCWrapperBase<C>
{
using FloatControlsIter = impl::FloatControlsIter;
// Iterate over arguments in sc_msg_iter, via callbacks from params object
template <typename ArgType, size_t N, typename T>
struct Setter
{
static constexpr size_t argSize = C::getParameterDescriptors().template get<N>().fixedSize;
auto fromArgs(World *, FloatControlsIter& args, LongT::type, int) { return args.next(); }
auto fromArgs(World *, FloatControlsIter& args, FloatT::type, int) { return args.next(); }
auto fromArgs(World *, sc_msg_iter* args, LongT::type, int defVal) { return args->geti(defVal); }
auto fromArgs(World *, sc_msg_iter* args, FloatT::type, int) { return args->getf(); }
auto fromArgs(World *w, ArgType args, BufferT::type, int)
{
typename LongT::type bufnum = static_cast<LongT::type>(fromArgs(w, args, LongT::type(), -1));
return BufferT::type(bufnum >= 0 ? new SCBufferAdaptor(bufnum, w) : nullptr);
}
typename T::type operator()(World *w, ArgType args)
{
ParamLiteralConvertor<T, argSize> a;
using LiteralType = typename ParamLiteralConvertor<T, argSize>::LiteralType;
for (size_t i = 0; i < argSize; i++)
a[i] = static_cast<LiteralType>(fromArgs(w, args, a[0], 0));
return a.value();
}
};
template <size_t N, typename T>
using ArgumentSetter = Setter<sc_msg_iter*, N, T>;
template <size_t N, typename T>
using ControlSetter = Setter<FloatControlsIter&, N, T>;
public:
using Client = C;
using Params = P;
FluidSCWrapper() //mParams{*getParamDescriptors()}, //impl::FluidSCWrapperBase<Client,Params>()
{ impl::FluidSCWrapperBase<Client,Params>::init(); }
using ParameterSetType = typename C::ParamSetType;
FluidSCWrapper(World* w, sc_msg_iter *args): impl::FluidSCWrapperBase<Client, Params>(w,args)
{ impl::FluidSCWrapperBase<Client, Params>::init(); }
FluidSCWrapper() // mParams{*getParamDescriptors()}, //impl::FluidSCWrapperBase<Client,Params>()
{
impl::FluidSCWrapperBase<Client>::init();
}
FluidSCWrapper(World* w, sc_msg_iter *args): impl::FluidSCWrapperBase<Client>(w,args)
{
impl::FluidSCWrapperBase<Client>::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)
{
@ -439,42 +384,35 @@ public:
return (ft = setTable ? setTable : ft);
}
static void setup(Params& p, InterfaceTable *ft, const char *name)
static void setup(InterfaceTable *ft, const char *name)
{
getName(name);
getInterfaceTable(ft);
getParamDescriptors(&p);
impl::FluidSCWrapperBase<Client, Params>::setup(ft, name);
impl::FluidSCWrapperBase<Client>::setup(ft, name);
}
template<typename ParameterSet>
static auto& setParams(ParameterSet& p, bool verbose, World* world, impl::FloatControlsIter& inputs)
static auto& setParams(ParameterSetType& p, bool verbose, World* world, FloatControlsIter& inputs, bool constrain = false)
{
//We won't even try and set params if the arguments don't match
if(inputs.size() == getParamDescriptors()->count())
p.template setParameterValues<impl::ControlGetter>(verbose, world, inputs);
if(inputs.size() == C::getParameterDescriptors().count())
{
p.template setParameterValues<ControlSetter>(verbose, world, inputs);
if (constrain)p.template constrainParameterValues();
}
return p;
}
template<typename ParameterSet>
static auto& setParams(ParameterSet& p, bool verbose, World* world, sc_msg_iter *args)
static auto& setParams(ParameterSetType& p, bool verbose, World* world, sc_msg_iter *args)
{
p.template setParameterValues<impl::ArgumentGetter>(verbose,world, args);
p.template setParameterValues<ArgumentSetter>(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)
template <template<typename T> class Client>
void makeSCWrapper(const char *name, InterfaceTable *ft)
{
FluidSCWrapper<Client<ParameterSet<Params>,Rest...>, Params>::setup(params, ft, name);
FluidSCWrapper<Client<float>>::setup(ft, name);
}
} // namespace client

@ -3,7 +3,6 @@
#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>
@ -12,7 +11,6 @@
#include <string>
#include <vector>
// static InterfaceTable *ft;
namespace fluid
{
@ -27,11 +25,11 @@ struct NRTBuf {
: mBuffer(b)
{
}
NRTBuf(World *world, long bufnum, bool rt = false)
NRTBuf(World *world, uint32 bufnum, bool rt = false)
: NRTBuf(rt ? World_GetBuf(world, bufnum)
: World_GetNRTBuf(world, bufnum))
{
if (mBuffer && !mBuffer->samplerate)
if (mBuffer && !static_cast<bool>(mBuffer->samplerate))
mBuffer->samplerate = world->mFullRate.mSampleRate;
}
@ -59,15 +57,15 @@ class SCBufferAdaptor : public NRTBuf, public client::BufferAdaptor
{
public:
// SCBufferAdaptor() = delete;
SCBufferAdaptor(SCBufferAdaptor &) = delete;
SCBufferAdaptor operator=(SCBufferAdaptor &) = delete;
SCBufferAdaptor(const SCBufferAdaptor &) = delete;
SCBufferAdaptor& operator=(const SCBufferAdaptor &) = delete;
SCBufferAdaptor(SCBufferAdaptor&&) = default;
SCBufferAdaptor& operator=(SCBufferAdaptor&&) = default;
SCBufferAdaptor(long bufnum,World *world, bool rt = false)
: NRTBuf(world, bufnum, rt)
SCBufferAdaptor(intptr_t bufnum,World *world, bool rt = false)
: NRTBuf(world, static_cast<uint32>(bufnum), rt)
, mBufnum(bufnum)
, mWorld(world)
{
@ -80,7 +78,7 @@ public:
void assignToRT(World *rtWorld)
{
SndBuf *rtBuf = World_GetBuf(rtWorld, mBufnum);
SndBuf *rtBuf = World_GetBuf(rtWorld, static_cast<uint32>(mBufnum));
*rtBuf = *mBuffer;
rtWorld->mSndBufUpdates[mBufnum].writes++;
}
@ -95,7 +93,7 @@ public:
}
// No locks in (vanilla) SC, so no-ops for these
void acquire() override {}
bool acquire() override { return true; }
void release() override {}
// Validity is based on whether this buffer is within the range the server
@ -110,13 +108,13 @@ public:
return true;
}
FluidTensorView<float, 1> samps(size_t channel, size_t rankIdx = 0) override
FluidTensorView<float, 1> samps(size_t channel) 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 v.col(channel);
}
// Return a 2D chunk
@ -132,112 +130,32 @@ public:
size_t numFrames() const override
{
return valid() ? this->mBuffer->frames : 0;
return valid() ? static_cast<size_t>(this->mBuffer->frames) : 0u;
}
size_t numChans() const override
{
return valid() ? this->mBuffer->channels / mRank : 0;
return valid() ? static_cast<size_t>(this->mBuffer->channels) : 0u;
}
size_t rank() const override { return valid() ? mRank : 0; }
double sampleRate() const override { return valid() ? mBuffer->samplerate : 0; }
void resize(size_t frames, size_t channels, size_t rank) override
void resize(size_t frames, size_t channels, double sampleRate) override
{
SndBuf *thisThing = mBuffer;
mOldData = thisThing->data;
mRank = rank;
mWorld->ft->fBufAlloc(mBuffer, channels * rank, frames,
thisThing->samplerate);
mWorld->ft->fBufAlloc(mBuffer, static_cast<int>(channels), static_cast<int>(frames), sampleRate);
}
int bufnum() { return mBufnum; }
intptr_t 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;
intptr_t 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)

@ -0,0 +1,11 @@
FluidAmpSlice : UGen {
*ar { arg in = 0, absRampUp = 10, absRampDown = 10, absThreshOn = -40, absThreshOff = -40, minSliceLength = 1, minSilenceLength = 1, minLengthAbove = 1, minLengthBelow = 1, lookBack = 0, lookAhead = 0, relRampUp = 1, relRampDown = 1, relThreshOn = -144, relThreshOff = -144, highPassFreq = 250, maxSize = 88200, outputType = 0;
^this.multiNew('audio', in.asAudioRateInput(this), absRampUp, absRampDown, absThreshOn, absThreshOff, minSliceLength, minSilenceLength, minLengthAbove, minLengthBelow, lookBack, lookAhead, relRampUp, relRampDown, relThreshOn, relThreshOff, highPassFreq, maxSize, outputType)
}
checkInputs {
if(inputs.at(16).rate != 'scalar') {
^(": maxSize cannot be modulated.");
};
^this.checkValidInputs;
}
}

@ -0,0 +1,21 @@
FluidBufAmpSlice{
*process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, absRampUp = 10, absRampDown = 10, absThreshOn = -40, absThreshOff = -40, minSliceLength = 1, minSilenceLength = 1, minLengthAbove = 1, minLengthBelow = 1, lookBack = 0, lookAhead = 0, relRampUp = 1, relRampDown = 1, relThreshOn = -144, relThreshOff = -144, highPassFreq = 250, outputType = 0, action;
var maxSize = max(minLengthAbove + lookBack, max(minLengthBelow,lookAhead));
source = source.asUGenInput;
indices = indices.asUGenInput;
source.isNil.if {"FluidBufAmpSlice: Invalid source buffer".throw};
indices.isNil.if {"FluidBufAmpSlice: Invalid features buffer".throw};
server = server ? Server.default;
forkIfNeeded{
server.sendMsg(\cmd, \BufAmpSlice, source, startFrame, numFrames, startChan, numChans, indices, absRampUp, absRampDown, absThreshOn, absThreshOff, minSliceLength, minSilenceLength, minLengthAbove, minLengthBelow, lookBack, lookAhead, relRampUp, relRampDown, relThreshOn, relThreshOff, highPassFreq, maxSize, outputType);
server.sync;
indices = server.cachedBufferAt(indices); indices.updateInfo; server.sync;
action.value(indices);
};
}
}

@ -1,10 +1,19 @@
FluidBufCompose{
*process { arg server, srcBufNum, startAt = 0, nFrames = -1, startChan = 0, nChans = -1, srcGain = 1, dstBufNum, dstStartAt = 0, dstStartChan = 0, dstGain = 0;
*process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, gain = 1, destination, destStartFrame = 0, destStartChan = 0, destGain = 0, action;
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};
source = source.asUGenInput;
destination = destination.asUGenInput;
source.isNil.if {"FluidBufCompose: Invalid source buffer".throw};
destination.isNil.if {"FluidBufCompose: Invalid destination buffer".throw};
server = server ? Server.default;
server.sendMsg(\cmd, \BufCompose, srcBufNum, startAt, nFrames, startChan, nChans, srcGain, dstBufNum, dstStartAt, dstStartChan, dstGain);
forkIfNeeded{
server.sendMsg(\cmd, \BufCompose, source, startFrame, numFrames, startChan, numChans, gain, destination, destStartFrame, destStartChan, destGain);
server.sync;
destination = server.cachedBufferAt(destination); destination.updateInfo; server.sync;
action.value(destination);
};
}
}

@ -1,29 +0,0 @@
FluidBufExperiments {
*allocMatch{|server, srcbuf, rank=1|
var dstbuf,srcbufnum;
srcbufnum = srcbuf.bufnum;
server = server ? Server.default;
dstbuf = Buffer.new(server:server,numFrames:0,numChannels:1);
server.listSendMsg(
[\b_gen, srcbufnum, "BufMatch",dstbuf.bufnum, rank]
);
^dstbuf;
}
*allocMatchAsync{|server, srcbuf, rank=1|
var dstbuf,srcbufnum;
srcbufnum = srcbuf.bufnum;
server = server ? Server.default;
dstbuf = Buffer.new(server:server,numFrames:0,numChannels:1);
server.sendMsg(\cmd, \AsyncBufMatch, srcbufnum, dstbuf.bufnum, rank);
^dstbuf;
}
}

@ -1,16 +1,30 @@
FluidBufHPSS{
*process { arg server, srcBufNum, startAt = 0, nFrames = -1, startChan = 0, nChans = -1, harmBufNum, percBufNum, resBufNum, hFiltSize = 17, pFiltSize = 31, modeFlag, htf1 = 0.1, hta1 = 0, htf2 = 0.5, hta2 = 0, ptf1 = 0.1, pta1 = 0, ptf2 = 0.5, pta2 = 0, winSize = 1024, hopSize = -1, fftSize = -1;
*process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, harmonic, percussive, residual, harmFilterSize = 17, percFilterSize = 31, maskingMode, harmThreshFreq1 = 0.1, harmThreshAmp1 = 0, harmThreshFreq2 = 0.5, harmThreshAmp2 = 0, percThreshFreq1 = 0.1, percThreshAmp1 = 0, percThreshFreq2 = 0.5, percThreshAmp2 = 0, windowSize = 1024, hopSize = -1, fftSize = -1, action;
var maxFFTSize = if (fftSize == -1) {winSize.nextPowerOfTwo} {fftSize};
var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize};
if(srcBufNum.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw};
source = source.asUGenInput;
harmonic = harmonic.asUGenInput;
percussive = percussive.asUGenInput;
residual = residual.asUGenInput;
source.isNil.if {"FluidBufHPSS: Invalid source buffer".throw};
server = server ? Server.default;
harmBufNum = harmBufNum ? -1;
percBufNum = percBufNum ? -1;
harmonic = harmonic ? -1;
percussive = percussive ? -1;
residual = residual ? -1;
//For wrapped RT clients, send maximal param values as aliases of the ones that are passed
//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, \BufHPSS, srcBufNum, startAt, nFrames, startChan, nChans, harmBufNum, percBufNum, resBufNum, hFiltSize, pFiltSize, modeFlag, htf1, hta1, htf2, hta2, ptf1, pta1, ptf2, pta2, winSize, hopSize, fftSize, maxFFTSize, hFiltSize, pFiltSize);
}
forkIfNeeded{
server.sendMsg(\cmd, \BufHPSS, source, startFrame, numFrames, startChan, numChans, harmonic, percussive, residual, harmFilterSize, percFilterSize, maskingMode, harmThreshFreq1, harmThreshAmp1, harmThreshFreq2, harmThreshAmp2, percThreshFreq1, percThreshAmp1, percThreshFreq2, percThreshAmp2, windowSize, hopSize, fftSize, maxFFTSize, harmFilterSize, percFilterSize);
server.sync;
if (harmonic != -1) {harmonic = server.cachedBufferAt(harmonic); harmonic.updateInfo; server.sync;} {harmonic = nil};
if (percussive != -1) {percussive = server.cachedBufferAt(percussive); percussive.updateInfo; server.sync;} {percussive = nil};
if (residual != -1) {residual = server.cachedBufferAt(residual); residual.updateInfo; server.sync;} {residual = nil};
action.value(harmonic, percussive, residual);
};
}
}

@ -0,0 +1,21 @@
FluidBufLoudness{
*process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, kWeighting = 1, truePeak = 1, windowSize = 1024, hopSize = 512, action;
var maxwindowSize = windowSize.nextPowerOfTwo;
source = source.asUGenInput;
features = features.asUGenInput;
source.isNil.if {"FluidBufPitch: Invalid source buffer".throw};
features.isNil.if {"FluidBufPitch: Invalid features buffer".throw};
server = server ? Server.default;
forkIfNeeded{
server.sendMsg(\cmd, \BufLoudness, source, startFrame, numFrames, startChan, numChans, features, kWeighting, truePeak, windowSize, hopSize, maxwindowSize);
server.sync;
features = server.cachedBufferAt(features); features.updateInfo; server.sync;
action.value(features);
};
}
}

@ -0,0 +1,25 @@
FluidBufMFCC{
*process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, numCoeffs = 13, numBands = 40, minFreq = 20, maxFreq = 20000, windowSize = 1024, hopSize = -1, fftSize = -1, action;
var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize};
source = source.asUGenInput;
features = features.asUGenInput;
source.isNil.if {"FluidBufMFCC: Invalid source buffer".throw};
features.isNil.if {"FluidBufMFCC: Invalid features buffer".throw};
server = server ? Server.default;
//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)
// same goes to maxNumCoeffs, which is passed numCoeffs in this case
forkIfNeeded{
server.sendMsg(\cmd, \BufMFCC, source, startFrame, numFrames, startChan, numChans, features, numCoeffs, numBands, minFreq, maxFreq, numCoeffs, windowSize, hopSize, fftSize, maxFFTSize);
server.sync;
features = server.cachedBufferAt(features); features.updateInfo; server.sync;
action.value(features);
};
}
}

@ -0,0 +1,25 @@
FluidBufMelBands{
*process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, numBands = 40, minFreq = 20, maxFreq = 20000, windowSize = 1024, hopSize = -1, fftSize = -1, action;
var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize};
source = source.asUGenInput;
features = features.asUGenInput;
source.isNil.if {"FluidBufMelBands: Invalid source buffer".throw};
features.isNil.if {"FluidBufMelBands: Invalid features buffer".throw};
server = server ? Server.default;
//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)
// same for maxNumBands which is passed numBands
forkIfNeeded{
server.sendMsg(\cmd, \BufMelBands, source, startFrame, numFrames, startChan, numChans, features, numBands, minFreq, maxFreq, numBands, windowSize, hopSize, fftSize, maxFFTSize);
server.sync;
features = server.cachedBufferAt(features); features.updateInfo; server.sync;
action.value(features);
};
}
}

@ -1,13 +1,27 @@
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 = -1, fftSize = -1, winType = 0, randSeed = -1;
*process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, destination, bases, basesMode = 0, activations, actMode = 0, components = 1, iterations = 100, windowSize = 1024, hopSize = -1, fftSize = -1, windowType = 0, randomSeed = -1, action;
if(srcBufNum.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw};
source = source.asUGenInput;
destination = destination.asUGenInput;
bases = bases.asUGenInput;
activations = activations.asUGenInput;
source.isNil.if {"FluidBufNMF: Invalid source buffer".throw};
server = server ? Server.default;
dstBufNum = dstBufNum ? -1;
dictBufNum = dictBufNum ? -1;
actBufNum = actBufNum ? -1;
server.sendMsg(\cmd, \BufNMF, srcBufNum, startAt, nFrames, startChan, nChans, dstBufNum, dictBufNum, dictFlag, actBufNum, actFlag, rank, nIter, winSize, hopSize, fftSize);
destination = destination ? -1;
bases = bases ? -1;
activations = activations ? -1;
forkIfNeeded{
server.sendMsg(\cmd, \BufNMF, source, startFrame, numFrames, startChan, numChans, destination, bases, basesMode, activations, actMode, components, iterations, windowSize, hopSize, fftSize);
server.sync;
if (destination != -1) {destination = server.cachedBufferAt(destination); destination.updateInfo; server.sync;} {destination = nil};
if (bases != -1) {bases = server.cachedBufferAt(bases); bases.updateInfo; server.sync;} {bases = nil};
if (activations != -1) {activations = server.cachedBufferAt(activations); activations.updateInfo; server.sync;} {activations = nil};
action.value(destination, bases, activations);
};
}
}

@ -1,13 +1,21 @@
FluidBufNoveltySlice{
*process { arg server, srcBufNum, startAt = 0, nFrames = -1, startChan = 0, nChans = -1, indBufNum, kernSize = 3, thresh = 0.8, filtSize = 1, winSize = 1024, hopSize = -1, fftSize = -1;
*process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, kernelSize = 3, threshold = 0.8, filterSize = 1, windowSize = 1024, hopSize = -1, fftSize = -1, action;
var maxFFTSize = if (fftSize == -1) {winSize.nextPowerOfTwo} {fftSize};
//var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize}; //ready for when we need it from the RT wrapper
if(srcBufNum.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw};
if(indBufNum.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw};
source = source.asUGenInput;
indices = indices.asUGenInput;
source.isNil.if {"FluidBufNoveltySlice: Invalid source buffer".throw};
indices.isNil.if {"FluidBufNoveltySlice: Invalid features buffer".throw};
server = server ? Server.default;
server.sendMsg(\cmd, \BufNoveltySlice, srcBufNum, startAt, nFrames, startChan, nChans, indBufNum, kernSize, thresh, filtSize, winSize, hopSize, maxFFTSize);
forkIfNeeded{
server.sendMsg(\cmd, \BufNoveltySlice, source, startFrame, numFrames, startChan, numChans, indices, kernelSize, threshold, filterSize, windowSize, hopSize, fftSize);
server.sync;
indices = server.cachedBufferAt(indices); indices.updateInfo; server.sync;
action.value(indices);
};
}
}

@ -0,0 +1,24 @@
FluidBufOnsetSlice{
*process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, function = 0, threshold = 0.1, minSliceLength = 2, filterSize = 5, frameDelta = 0, windowSize = 1024, hopSize = -1, fftSize = -1, action;
var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize};
source = source.asUGenInput;
indices = indices.asUGenInput;
source.isNil.if {"FluidBufOnsetSlice: Invalid source buffer".throw};
indices.isNil.if {"FluidBufOnsetSlice: Invalid features buffer".throw};
server = server ? Server.default;
//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)
forkIfNeeded{
server.sendMsg(\cmd, \BufOnsetSlice, source, startFrame, numFrames, startChan, numChans, indices, function, threshold, minSliceLength, filterSize, frameDelta, windowSize, hopSize, fftSize, maxFFTSize);
server.sync;
indices = server.cachedBufferAt(indices); indices.updateInfo; server.sync;
action.value(indices);
};
}
}

@ -0,0 +1,24 @@
FluidBufPitch{
*process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, algorithm = 2, windowSize = 1024, hopSize = -1, fftSize = -1, action;
var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize};
source = source.asUGenInput;
features = features.asUGenInput;
source.isNil.if {"FluidBufPitch: Invalid source buffer".throw};
features.isNil.if {"FluidBufPitch: Invalid features buffer".throw};
server = server ? Server.default;
//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)
forkIfNeeded{
server.sendMsg(\cmd, \BufPitch, source, startFrame, numFrames, startChan, numChans, features, algorithm, windowSize, hopSize, fftSize, maxFFTSize);
server.sync;
features = server.cachedBufferAt(features); features.updateInfo; server.sync;
action.value(features);
};
}
}

@ -0,0 +1,21 @@
FluidBufRTNoveltySlice{
*process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, feature = 0, kernelSize = 3, threshold = 0.8, filterSize = 1, windowSize = 1024, hopSize = -1, fftSize = -1, action;
var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize};
source = source.asUGenInput;
indices = indices.asUGenInput;
source.isNil.if {"FluidBufNoveltySlice: Invalid source buffer".throw};
indices.isNil.if {"FluidBufNoveltySlice: Invalid features buffer".throw};
server = server ? Server.default;
forkIfNeeded{
server.sendMsg(\cmd, \BufRTNoveltySlice, source, startFrame, numFrames, startChan, numChans, indices, feature, kernelSize, threshold, filterSize, windowSize, hopSize, fftSize, maxFFTSize, kernelSize, filterSize);
server.sync;
indices = server.cachedBufferAt(indices); indices.updateInfo; server.sync;
action.value(indices);
};
}
}

@ -1,18 +1,27 @@
FluidBufSines{
*process { arg server, srcBufNum, startAt = 0, nFrames = -1, startChan = 0, nChans = -1, sineBufNum, resBufNum, bw = 76, thresh = 0.7, minTrackLen = 15, magWeight = 0.1, freqWeight = 1, winSize = 1024, hopSize = -1, fftSize = -1;
*process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, sines, residual, bandwidth = 76, threshold = 0.7, minTrackLen = 15, magWeight = 0.1, freqWeight = 1, windowSize = 1024, hopSize = -1, fftSize = -1, action;
var maxFFTSize = if (fftSize == -1) {winSize.nextPowerOfTwo} {fftSize};
var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize};
if(srcBufNum.isNil) {Error("Invalid Buffer").format(thisMethod.name, this.class.name).throw};
source = source.asUGenInput;
sines = sines.asUGenInput;
residual = residual.asUGenInput;
server = server ? Server.default;
sineBufNum = sineBufNum ? -1;
resBufNum = resBufNum ? -1;
source.isNil.if {"FluidBufSines: Invalid source buffer".throw};
server = server ? Server.default;
sines = sines ? -1;
residual = residual ? -1;
//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, bw, thresh, minTrackLen, magWeight, freqWeight, winSize, hopSize, fftSize, maxFFTSize);
}
forkIfNeeded{
server.sendMsg(\cmd, \BufSines, source, startFrame, numFrames, startChan, numChans, sines, residual, bandwidth, threshold, minTrackLen, magWeight, freqWeight, windowSize, hopSize, fftSize, maxFFTSize);
server.sync;
if (sines != -1) {sines = server.cachedBufferAt(sines); sines.updateInfo; server.sync;} {sines = nil};
if (residual != -1) {residual = server.cachedBufferAt(residual); residual.updateInfo; server.sync;} {residual = nil};
action.value(sines, residual);
};
}
}

@ -0,0 +1,24 @@
FluidBufSpectralShape{
*process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, windowSize = 1024, hopSize = -1, fftSize = -1, action;
var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize};
source = source.asUGenInput;
features = features.asUGenInput;
source.isNil.if {"FluidBufSpectralShape: Invalid source buffer".throw};
features.isNil.if {"FluidBufSpectralShape: Invalid features buffer".throw};
server = server ? Server.default;
//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)
forkIfNeeded{
server.sendMsg(\cmd, \BufSpectralShape, source, startFrame, numFrames, startChan, numChans, features, windowSize, hopSize, fftSize, maxFFTSize);
server.sync;
features = server.cachedBufferAt(features); features.updateInfo; server.sync;
action.value(features);
};
}
}

@ -0,0 +1,19 @@
FluidBufStats{
*process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, stats, numDerivs = 0, low = 0, middle = 50, high = 100, action;
source = source.asUGenInput;
stats = stats.asUGenInput;
source.isNil.if {"FluidBufStats: Invalid source buffer".throw};
stats.isNil.if {"FluidBufStats: Invalid stats buffer".throw};
server = server ? Server.default;
forkIfNeeded{
server.sendMsg(\cmd, \BufStats, source, startFrame, numFrames, startChan, numChans, stats, numDerivs, low, middle, high);
server.sync;
stats = server.cachedBufferAt(stats); stats.updateInfo; server.sync;
action.value(stats);
};
}
}

@ -1,11 +1,19 @@
FluidBufTransientSlice{
*process { arg server, srcBufNum, startAt = 0, nFrames = -1, startChan = 0, nChans = -1, transBufNum, order = 20, blockSize = 256, padSize = 128, skew = 0, threshFwd = 2, threshBack = 1.1, winSize = 14, debounce = 25, minSlice = 1000;
*process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, order = 20, blockSize = 256, padSize = 128, skew = 0, threshFwd = 2, threshBack = 1.1, windowSize = 14, clumpLength = 25, minSliceLength = 1000, action;
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};
source = source.asUGenInput;
indices = indices.asUGenInput;
source.isNil.if {"FluidBufNoveltySlice: Invalid source buffer".throw};
indices.isNil.if {"FluidBufNoveltySlice: Invalid features buffer".throw};
server = server ? Server.default;
server.sendMsg(\cmd, \BufTransientSlice, srcBufNum, startAt, nFrames, startChan, nChans, transBufNum, order, blockSize, padSize, skew, threshFwd, threshBack, winSize, debounce, minSlice);
forkIfNeeded{
server.sendMsg(\cmd, \BufTransientSlice, source, startFrame, numFrames, startChan, numChans, indices, order, blockSize, padSize, skew, threshFwd, threshBack, windowSize, clumpLength, minSliceLength);
server.sync;
indices = server.cachedBufferAt(indices); indices.updateInfo; server.sync;
action.value(indices);
};
}
}

@ -1,15 +1,22 @@
FluidBufTransients {
*process { arg server, srcBufNum, startAt = 0, nFrames = -1, startChan = 0, nChans = -1, transBufNum, resBufNum, order = 20, blockSize = 256, padSize = 128, skew = 0, threshFwd = 2, threshBack = 1.1, winSize = 14, debounce = 25;
*process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, transients, residual, order = 20, blockSize = 256, padSize = 128, skew = 0, threshFwd = 2, threshBack = 1.1, windowSize = 14, clumpLength = 25, action;
if(srcBufNum.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw};
source = source.asUGenInput;
transients = transients.asUGenInput;
residual = residual.asUGenInput;
source.isNil.if {"FluidBufTransients: Invalid source buffer".throw};
server = server ? Server.default;
transBufNum = transBufNum ? -1;
resBufNum = resBufNum ? -1;
transients = transients ? -1;
residual = residual ? -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);
forkIfNeeded{
server.sendMsg(\cmd, \BufTransients, source, startFrame, numFrames, startChan, numChans, transients, residual, order, blockSize, padSize, skew, threshFwd, threshBack, windowSize, clumpLength);
server.sync;
if (transients != -1) {transients = server.cachedBufferAt(transients); transients.updateInfo; server.sync;} {transients = nil};
if (residual != -1) {residual = server.cachedBufferAt(residual); residual.updateInfo; server.sync;} {residual = nil};
action.value(transients, residual);
};
}
}

@ -1,6 +1,6 @@
FluidHPSS : MultiOutUGen {
*ar { arg in = 0, hFiltSize=17, pFiltSize = 31, 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= -1, fftSize= -1, maxFFTSize = 16384, maxHFlitSize = 101, maxPFiltSize = 101;
^this.multiNew('audio', in.asAudioRateInput(this), hFiltSize, pFiltSize, modeFlag, htf1, hta1, htf2, hta2, ptf1, pta1, ptf2, pta2, winSize, hopSize, fftSize, maxFFTSize, maxHFlitSize, maxPFiltSize)
*ar { arg in = 0, harmFilterSize=17, percFilterSize = 31, maskingMode=0, harmThreshFreq1 = 0.1, harmThreshAmp1 = 0, harmThreshFreq2 = 0.5, harmThreshAmp2 = 0, percThreshFreq1 = 0.1, percThreshAmp1 = 0, percThreshFreq2 = 0.5, percThreshAmp2 = 0, windowSize= 1024, hopSize= -1, fftSize= -1, maxFFTSize = 16384, maxHarmFilterSize = 101, maxPercFilterSize = 101;
^this.multiNew('audio', in.asAudioRateInput(this), harmFilterSize, percFilterSize, maskingMode, harmThreshFreq1, harmThreshAmp1, harmThreshFreq2, harmThreshAmp2, percThreshFreq1, percThreshAmp1, percThreshFreq2, percThreshAmp2, windowSize, hopSize, fftSize, maxFFTSize, maxHarmFilterSize, maxPercFilterSize)
}
init { arg ... theInputs;
inputs = theInputs;
@ -11,5 +11,16 @@ FluidHPSS : MultiOutUGen {
];
^channels
}
checkInputs { ^this.checkNInputs(1) }
checkInputs {
if(inputs.at(15).rate != 'scalar') {
^(": maxFFTSize cannot be modulated.");
};
if(inputs.at(16).rate != 'scalar') {
^(": maxHarmFilterSize cannot be modulated.");
};
if(inputs.at(17).rate != 'scalar') {
^(": maxPercFilterSize cannot be modulated.");
};
^this.checkValidInputs;
}
}

@ -0,0 +1,17 @@
FluidLoudness : MultiOutUGen {
*kr { arg in = 0, kWeighting = 1, truePeak = 1, windowSize = 1024, hopSize = 512, maxwindowSize = 16384;
^this.multiNew('control', in.asAudioRateInput(this), kWeighting, truePeak, windowSize, hopSize, maxwindowSize);
}
init {arg ...theInputs;
inputs = theInputs;
^this.initOutputs(2,rate);
}
checkInputs {
if(inputs.at(5).rate != 'scalar') {
^(": maxwindowSize cannot be modulated.");
};
^this.checkValidInputs;
}
}

@ -0,0 +1,20 @@
FluidMFCC : MultiOutUGen {
*kr { arg in = 0, numCoeffs = 13, numBands = 40, minFreq = 20, maxFreq = 20000, maxNumCoeffs = 40, windowSize = 1024, hopSize = -1, fftSize = -1, maxFFTSize = 16384;
^this.multiNew('control', in.asAudioRateInput(this), numCoeffs, numBands, minFreq, maxFreq, maxNumCoeffs, windowSize, hopSize, fftSize, maxFFTSize);
}
init {arg ...theInputs;
inputs = theInputs;
^this.initOutputs(inputs.at(5),rate);
}
checkInputs {
if(inputs.at(5).rate != 'scalar') {
^(": maxNumCoeffs cannot be modulated.");
};
if(inputs.at(9).rate != 'scalar') {
^(": maxFFTSize cannot be modulated.");
};^this.checkValidInputs;
}
}

@ -0,0 +1,20 @@
FluidMelBands : MultiOutUGen {
*kr { arg in = 0, numBands = 40, minFreq = 20, maxFreq = 20000, maxNumBands = 120, windowSize = 1024, hopSize = -1, fftSize = -1, maxFFTSize = 16384;
^this.multiNew('control', in.asAudioRateInput(this), numBands, minFreq, maxFreq, maxNumBands, windowSize, hopSize, fftSize, maxFFTSize);
}
init {arg ...theInputs;
inputs = theInputs;
^this.initOutputs(inputs.at(4),rate);
}
checkInputs {
if(inputs.at(4).rate != 'scalar') {
^(": maxNumBands cannot be modulated.");
};
if(inputs.at(8).rate != 'scalar') {
^(": maxFFTSize cannot be modulated.");
};^this.checkValidInputs;
}
}

@ -0,0 +1,21 @@
FluidNMFFilter : MultiOutUGen {
*ar { arg in = 0, bases, maxComponents = 1, iterations = 10, windowSize = 1024, hopSize = -1, fftSize = -1, maxFFTSize = 16384;
^this.multiNew('audio', in.asAudioRateInput(this), bases, maxComponents, iterations, windowSize, hopSize, fftSize, maxFFTSize);
}
init {arg ...theInputs;
inputs = theInputs;
^this.initOutputs(inputs[2],rate);
}
checkInputs {
if(inputs.at(2).rate != 'scalar') {
^(": maxComponents cannot be modulated.");
};
if(inputs.at(7).rate != 'scalar') {
^(": maxFFTSize cannot be modulated.");
};
^this.checkValidInputs;
}
}

@ -1,7 +1,7 @@
FluidNMFMatch : MultiOutUGen {
*kr { arg in = 0, dictBufNum, maxRank = 1, nIter = 10, winSize = 1024, hopSize = -1, fftSize = -1, maxFFTSize = 16384;
^this.multiNew('control', in, dictBufNum, maxRank, nIter, winSize, hopSize, fftSize, maxFFTSize);
*kr { arg in = 0, bases, maxComponents = 1, iterations = 10, windowSize = 1024, hopSize = -1, fftSize = -1, maxFFTSize = 16384;
^this.multiNew('control', in.asAudioRateInput(this), bases, maxComponents, iterations, windowSize, hopSize, fftSize, maxFFTSize);
}
init {arg ...theInputs;
@ -10,9 +10,12 @@ FluidNMFMatch : MultiOutUGen {
}
checkInputs {
if (inputs.at(0).rate != 'audio', {
^(" input 0 is not audio rate");
});
if(inputs.at(2).rate != 'scalar') {
^(": maxComponents cannot be modulated.");
};
if(inputs.at(7).rate != 'scalar') {
^(": maxFFTSize cannot be modulated.");
};
^this.checkValidInputs;
}
}

@ -0,0 +1,11 @@
FluidOnsetSlice : UGen {
*ar { arg in = 0, function = 0, threshold = 0.5, minSliceLength = 2, filterSize = 5, frameDelta = 0, windowSize = 1024, hopSize = -1, fftSize = -1, maxFFTSize = 16384;
^this.multiNew('audio', in.asAudioRateInput(this), function, threshold, minSliceLength, filterSize, frameDelta, windowSize, hopSize, fftSize, maxFFTSize)
}
checkInputs {
if(inputs.at(9).rate != 'scalar') {
^(": maxFFTSize cannot be modulated.");
};
^this.checkValidInputs;
}
}

@ -0,0 +1,18 @@
FluidPitch : MultiOutUGen {
*kr { arg in = 0, algorithm = 2, windowSize = 1024, hopSize = -1, fftSize = -1, maxFFTSize = 16384;
^this.multiNew('control', in.asAudioRateInput(this), algorithm, windowSize, hopSize, fftSize, maxFFTSize);
}
init {arg ...theInputs;
inputs = theInputs;
^this.initOutputs(2,rate);
}
checkInputs {
if(inputs.at(5).rate != 'scalar') {
^(": maxFFTSize cannot be modulated.");
};
^this.checkValidInputs;
}
}

@ -0,0 +1,17 @@
FluidRTNoveltySlice : UGen {
*ar { arg in = 0, feature = 0, kernelSize = 3, threshold = 0.8, filterSize = 1, windowSize = 1024, hopSize = -1, fftSize = -1, maxFFTSize = 16384, maxKernelSize = 101, maxFilterSize = 100;
^this.multiNew('audio', in.asAudioRateInput(this), feature, kernelSize, threshold, filterSize, windowSize, hopSize, fftSize, maxFFTSize, maxKernelSize, maxFilterSize)
}
checkInputs {
if(inputs.at(8).rate != 'scalar') {
^(": maxFFTSize cannot be modulated.");
};
if(inputs.at(9).rate != 'scalar') {
^(": maxKernelSize cannot be modulated.");
};
if(inputs.at(10).rate != 'scalar') {
^(": maxFilterSize cannot be modulated.");
};
^this.checkValidInputs;
}
}

@ -1,5 +1,11 @@
FluidSTFTPass : UGen {
*ar { arg in = 0, winSize= 1024, hopSize= -1, fftSize= -1, maxFFTSize = 16384;
^this.multiNew('audio', in.asAudioRateInput(this), winSize, hopSize, fftSize, maxFFTSize)
*ar { arg in = 0, windowSize= 1024, hopSize= -1, fftSize= -1, maxFFTSize = 16384;
^this.multiNew('audio', in.asAudioRateInput(this), windowSize, hopSize, fftSize, maxFFTSize)
}
checkInputs {
if(inputs.at(4).rate != 'scalar') {
^": maxFFTSize cannot be modulated.";
};
^this.checkValidInputs
}
}

@ -1,6 +1,6 @@
FluidSines : MultiOutUGen {
*ar { arg in = 0, bw = 76, thresh = 0.7, minTrackLen = 15, magWeight = 0.1, freqWeight = 1.0, winSize= 1024, hopSize= -1, fftSize= -1, maxFFTSize=16384;
^this.multiNew('audio', in.asAudioRateInput(this), bw, thresh, minTrackLen, magWeight, freqWeight ,winSize, hopSize, fftSize, maxFFTSize)
*ar { arg in = 0, bandwidth = 76, threshold = 0.7, minTrackLen = 15, magWeight = 0.1, freqWeight = 1.0, windowSize= 1024, hopSize= -1, fftSize= -1, maxFFTSize=16384;
^this.multiNew('audio', in.asAudioRateInput(this), bandwidth, threshold, minTrackLen, magWeight, freqWeight ,windowSize, hopSize, fftSize, maxFFTSize)
}
init { arg ... theInputs;
inputs = theInputs;
@ -10,5 +10,10 @@ FluidSines : MultiOutUGen {
];
^channels
}
checkInputs { ^this.checkNInputs(1) }
checkInputs {
if(inputs.at(9).rate != 'scalar') {
^(": maxFFTSize cannot be modulated.");
};
^this.checkNInputs(1)
}
}

@ -0,0 +1,18 @@
FluidSpectralShape : MultiOutUGen {
*kr { arg in = 0, windowSize = 1024, hopSize = -1, fftSize = -1, maxFFTSize = 16384;
^this.multiNew('control', in.asAudioRateInput(this), windowSize, hopSize, fftSize, maxFFTSize);
}
init {arg ...theInputs;
inputs = theInputs;
^this.initOutputs(7,rate);
}
checkInputs {
if(inputs.at(4).rate != 'scalar') {
^(": maxFFTSize cannot be modulated.");
};
^this.checkValidInputs;
}
}

@ -1,5 +1,5 @@
FluidTransientSlice : UGen {
*ar { arg in = 0, order = 20, blockSize = 256, padSize = 128, skew = 0.0, threshFwd = 2.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)
*ar { arg in = 0, order = 20, blockSize = 256, padSize = 128, skew = 0.0, threshFwd = 2.0, threshBack = 1.1, windowSize=14, clumpLength=25, minSliceLength = 1000;
^this.multiNew('audio', in.asAudioRateInput(this), order, blockSize, padSize, skew, threshFwd ,threshBack, windowSize, clumpLength, minSliceLength)
}
}

@ -1,6 +1,6 @@
FluidTransients : MultiOutUGen {
*ar { arg in = 0, order = 20, blockSize = 256, padSize = 128, skew = 0.0, threshFwd = 2.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 = 2.0, threshBack = 1.1, windowSize=14, clumpLength=25;
^this.multiNew('audio', in.asAudioRateInput(this), order, blockSize, padSize, skew, threshFwd, threshBack, windowSize, clumpLength)
}
init { arg ... theInputs;
inputs = theInputs;

@ -0,0 +1,193 @@
TITLE:: FluidAmpSlice
SUMMARY:: Amplitude-based Slicer
CATEGORIES:: Libraries>FluidDecomposition
RELATED:: Guides/FluCoMa, Guides/FluidDecomposition
DESCRIPTION::
This class implements an amplitude-based slicer, with various customisable options and conditions to detect absolute and relative amplitude changes as onsets and offsets. It 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).::
FluidAmpSlice is based on two envelop followers on a highpassed version of the signal: one absolute, and one relative. Each have features that will interact, including independent Schmidt triggers and state-aware time contraints. The example code below is unfolding the various possibilites in order of complexity.
The process will return an audio steam with square envelopes around detected slices the different slices, where 1s means in slice and 0s mean in silence.
CLASSMETHODS::
METHOD:: ar
The audio rate version of the object.
ARGUMENT:: in
The audio to be processed.
ARGUMENT:: absRampUp
The number of samples the absolute envelope follower will take to reach the next value when raising.
ARGUMENT:: absRampDown
The number of samples the absolute envelope follower will take to reach the next value when falling.
ARGUMENT:: absThreshOn
The threshold in dB of the absolute envelope follower to trigger an onset, aka to go ON when in OFF state.
ARGUMENT:: absThreshOff
The threshold in dB of the absolute envelope follower to trigger an offset, , aka to go ON when in OFF state.
ARGUMENT:: minSliceLength
The length in samples that the Slice will stay ON. Changes of states during that period will be ignored.
ARGUMENT:: minSilenceLength
The length in samples that the Slice will stay OFF. Changes of states during that period will be ignored.
ARGUMENT:: minLengthAbove
The length in samples that the absolute envelope have to be above the threshold to consider it a valid transition to ON. The Slice will start at the first sample when the condition is met. Therefore, this affects the latency.
ARGUMENT:: minLengthBelow
The length in samples that the absolute envelope have to be below the threshold to consider it a valid transition to OFF. The Slice will end at the first sample when the condition is met. Therefore, this affects the latency.
ARGUMENT:: lookBack
The length of the buffer kept before an onset to allow the algorithm, once a new Slice is detected, to go back in time (up to that many samples) to find the minimum amplitude as the Slice onset point. This affects the latency of the algorithm.
ARGUMENT:: lookAhead
The length of the buffer kept after an offset to allow the algorithm, once the Slice is considered finished, to wait further in time (up to that many samples) to find a minimum amplitude as the Slice offset point. This affects the latency of the algorithm.
ARGUMENT:: relRampUp
The number of samples the relative envelope follower will take to reach the next value when raising. Typically, this will be faster than absRampUp.
ARGUMENT:: relRampDown
The number of samples the relative envelope follower will take to reach the next value when falling. Typically, this will be faster than absRampDown.
ARGUMENT:: relThreshOn
The threshold in dB of the relative envelope follower to trigger an onset, aka to go ON when in OFF state. It is computed on the difference between the two envelope followers.
ARGUMENT:: relThreshOff
The threshold in dB of the relative envelope follower to reset, aka to allow the differential envelop to trigger again.
ARGUMENT:: highPassFreq
The frequency of the fourth-order LinkwitzRiley high-pass filter (https://en.wikipedia.org/wiki/Linkwitz%E2%80%93Riley_filter). This is done first on the signal to minimise low frequency intermodulation with very fast ramp lengths.
ARGUMENT:: maxSize
How large can the buffer be for time-critical conditions, by allocating memory at instantiation time. This cannot be modulated.
ARGUMENT:: outputType
(describe argument here)
RETURNS::
An audio stream with square envelopes around the slices. The latency between the input and the output is STRONG::max(minLengthAbove + lookBack, max(minLengthBelow,lookAhead))::.
EXAMPLES::
code::
//basic tests: highPass sanity
(
{var env, source = SinOsc.ar(320,0,0.5);
env = FluidAmpSlice.ar(source,highPassFreq:250, outputType:1);
[source, env]
}.plot(0.03);
)
//basic tests: absRampUp-Down sanity
(
{var env, source = SinOsc.ar(320,0,0.5);
env = FluidAmpSlice.ar(source,absRampUp:10, absRampDown:1000, outputType:2);
[source.abs, env]
}.plot(0.03);
)
/////////////////////////////
//basic tests: absThresh sanity
(
{var env, source = SinOsc.ar(320,0,LFTri.ar(10).abs);
env = FluidAmpSlice.ar(source,absRampUp:10, absRampDown:100, absThreshOn:-12, absThreshOff: -12);
[source, env]
}.plot(0.1);
)
//basic tests: absThresh histeresis
(
{var env, source = SinOsc.ar(320,0,LFTri.ar(10).abs);
env = FluidAmpSlice.ar(source,absRampUp:10, absRampDown:100, absThreshOn:-12, absThreshOff: -16);
[source, env]
}.plot(0.1);
)
//basic tests: absThresh min slice
(
{var env, source = SinOsc.ar(320,0,LFTri.ar(10).abs);
env = FluidAmpSlice.ar(source,absRampUp:10, absRampDown:100, absThreshOn:-12, absThreshOff: -12, minSliceLength:441);
[source, env]
}.plot(0.1);
)
//basic tests: absThresh min silence
(
{var env, source = SinOsc.ar(320,0,LFTri.ar(10).abs);
env = FluidAmpSlice.ar(source,absRampUp:10, absRampDown:100, absThreshOn:-12, absThreshOff: -12, minSilenceLength:441);
[source, env]
}.plot(0.1);
)
//mid tests: absThresh time histeresis on
(
{var env, source = SinOsc.ar(320,0,LFTri.ar(10).abs);
env = FluidAmpSlice.ar(source,absRampUp:10, absRampDown:100, absThreshOn:-12, absThreshOff: -12, minLengthAbove:441, outputType:0);
[DelayN.ar(source,0.1,441/44100), env]
}.plot(0.1);
)
//mid tests: absThresh time histeresis off
(
{var env, source = SinOsc.ar(320,0,LFTri.ar(10).abs);
env = FluidAmpSlice.ar(source,absRampUp:10, absRampDown:100, absThreshOn:-12, absThreshOff: -12, minLengthBelow:441);
[DelayN.ar(source,0.1,441/44100), env]
}.plot(0.1);
)
//mid tests: absThresh with lookBack
(
{var env, source = SinOsc.ar(320,0,LFTri.ar(10).abs);
env = FluidAmpSlice.ar(source,absRampUp:10, absRampDown:100, absThreshOn:-12, absThreshOff: -12,lookBack:441);
[DelayN.ar(source,0.1,441/44100), env]
}.plot(0.1);
)
//mid tests: absThresh with lookAhead
(
{var env, source = SinOsc.ar(320,0,LFTri.ar(10).abs);
env = FluidAmpSlice.ar(source,absRampUp:10, absRampDown:100, absThreshOn:-12, absThreshOff: -12,lookAhead:441);
[DelayN.ar(source,0.1,441/44100), env]
}.plot(0.1);
)
//mid tests: absThresh with asymetrical lookBack and lookAhead
(
{var env, source = SinOsc.ar(320,0,LFTri.ar(10).abs);
env = FluidAmpSlice.ar(source,absRampUp:10, absRampDown:100, absThreshOn:-12, absThreshOff: -12,lookBack:221, lookAhead:441);
[DelayN.ar(source,0.1,441/44100), env]
}.plot(0.1);
)
//advanced tests: absThresh histeresis, long tail
(
{var env, source = SinOsc.ar(320,0,LFTri.ar(10).abs);
env = FluidAmpSlice.ar(source,absRampUp:10, absRampDown:2000, absThreshOn:-12, absThreshOff: -16);
[source, env]
}.plot(0.1);
)
//solution: have to recut with relThresh
(
{var env, source = SinOsc.ar(320,0,LFTri.ar(10).abs);
env = FluidAmpSlice.ar(source,absRampUp:10, absRampDown:2000, absThreshOn:-12, absThreshOff: -16, relRampUp:5, relRampDown:200, relThreshOn:-1, relThreshOff:-12);
[source, env]
}.plot(0.1);
)
//beware of double trig
(
{var env, source = SinOsc.ar(320,0,LFTri.ar(10).abs);
env = FluidAmpSlice.ar(source,absRampUp:10, absRampDown:2000, absThreshOn:-12, absThreshOff: -16, relRampUp:5, relRampDown:200, relThreshOn:-1, relThreshOff:-1);
[source, env]
}.plot(0.025);
)
//a solution: minSliceLength
(
{var env, source = SinOsc.ar(320,0,LFTri.ar(10).abs);
env = FluidAmpSlice.ar(source,absRampUp:10, absRampDown:2000, absThreshOn:-12, absThreshOff: -16, relRampUp:5, relRampDown:200, relThreshOn:-1, relThreshOff:-1, minSliceLength:441);
[source, env]
}.plot(0.025);
)
//drum slicing, many ways
//load a buffer
b = Buffer.read(s,File.realpath(FluidAmpSlice.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Nicol-LoopE-M.wav");
(
{var env, source = PlayBuf.ar(1,b);
env = FluidAmpSlice.ar(source,absRampUp:2205, absRampDown:2205, absThreshOn:-70, absThreshOff: -80, relRampUp:5, relRampDown:441, relThreshOn:5, relThreshOff:4, minSliceLength:441, outputType:0);
[source, env]
}.plot(1,maxval:[1,10],separately:true);
)
::

@ -5,13 +5,11 @@ RELATED:: Guides/FluCoMa, Guides/FluidDecomposition, Classes/Buffer
DESCRIPTION::
A FluidBufCompose object provides a flexible utility for combining the contents of buffers on the server. It can be used for thing like mixing down multichannel buffers, or converting from left-right stereo to mid-side. We use it extensively in our example code.
At its most simple, the object copies the content of a source buffer into a destination buffer. The flexibility comes from the various flags controlling which portions and channels of the sources to use, and by applying gains (which can be positive or negative) to the source data and the portion of the destination that would be overwritten.
It is part of the Fluid Decomposition Toolkit of the FluCoMa project. footnote::
A FluidBufCompose object provides a flexible utility for combining the contents of buffers on the server. It can be used for thing like mixing down multichannel buffers, or converting from left-right stereo to mid-side. It is used extensively in all the example code of LINK::Guides/FluidDecomposition:: as part 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).::
At its most simple, the object copies the content of a source buffer into a destination buffer. The flexibility comes from the various flags controlling which portions and channels of the source to use, and by applying gains (which can be positive or negative) to the source data and the portion of the destination that would be overwritten.
The algorithm takes a srcBuf, and writes the information at the provided dstBuf. These buffer arguments can all point to the same buffer, which gives great flexibility in transforming and reshaping.
@ -23,38 +21,41 @@ METHOD:: process
ARGUMENT:: server
The server on which the buffers to be processed are allocated.
ARGUMENT:: srcBufNum
ARGUMENT:: source
The bufNum of the source buffer.
ARGUMENT:: startAt
ARGUMENT:: startFrame
The starting point (in samples) from which to copy in the source buffer.
ARGUMENT:: nFrames
The duration (in samples) to copy from the source buffer.
ARGUMENT:: numFrames
The duration (in samples) to copy from the source buffer. The default (-1) copies the full lenght of the buffer.
ARGUMENT:: startChan
The first channel from which to copy in the source buffer.
ARGUMENT:: nChans
The number of channels from which to copy in the source buffer. This parameter will wrap around the number of channels in the source buffer.
ARGUMENT:: numChans
The number of channels from which to copy in the source buffer. This parameter will wrap around the number of channels in the source buffer. The default (-1) copies all of the buffer's channel.
ARGUMENT:: srcGain
ARGUMENT:: gain
The gain applied to the samples to be copied from the source buffer.
ARGUMENT:: dstBufNum
ARGUMENT:: destination
The bufNum of the destination buffer.
ARGUMENT:: dstStartAt
ARGUMENT:: destStartFrame
The time offset (in samples) in the destination buffer to start writing the source at. The destination buffer will be resized if the portion to copy is overflowing.
ARGUMENT:: dstStartChan
ARGUMENT:: destStartChan
The channel offest in the destination buffer to start writing the source at. The destination buffer will be resized if the number of channels to copy is overflowing.
ARGUMENT:: dstGain
The gain applied to the samples in the region of the destination buffer over which the source is to be copied. The default value of 0. would overwrite completely, and a value of 1.0 would sum the source to the material that was present.
ARGUMENT:: destGain
The gain applied to the samples in the region of the destination buffer over which the source is to be copied. The default value (0) will overwrite that section of the destination buffer, and a value of 1.0 would sum the source to the material that was present.
ARGUMENT:: action
A Function to be evaluated once the offline process has finished and destination instance variables have been updated on the client side. The function will be passed destination as an argument.
RETURNS::
Nothing, as the various destination buffers are declared in the function call.
Nothing, as the destination buffer is declared in the function call.
DISCUSSION::
It is important to understand the rules used for determining the final desintinaiton buffer dimensions to get the most out of this object. If needs be, the destination buffer will be resized to the maxima of the requsted source numFrames and numChannels. Frames will be written up to the limit of actually available samples (meaning you can create zero padding); channels will be written modulo the available channels, taking into account the channel offsets, meaning you can have channels repeat or loop into the source buffer's channels. See the examples below.
@ -70,29 +71,29 @@ d = Buffer.new(s);
)
// with basic params (basic summing of each full buffer in all dimensions)
FluidBufCompose.process(s, srcBufNum: b.bufnum, dstBufNum: d.bufnum);
FluidBufCompose.process(s, srcBufNum: c.bufnum, dstBufNum: d.bufnum, dstGain: 1.0);
FluidBufCompose.process(s, source: b, destination: d);
FluidBufCompose.process(s, source: c, destination: d, destGain: 1.0);
d.query;
d.play;
//constructing a mono buffer, with a quiet punch from the synth, with a choked piano resonance from the left channel
d.free; d = Buffer.new(s);
FluidBufCompose.process(s, srcBufNum: b.bufnum, nFrames: 9000, srcGain: 0.5, dstBufNum: d.bufnum);
FluidBufCompose.process(s, srcBufNum: c.bufnum, startAt:30000, nFrames:44100, nChans:1, srcGain:0.9, dstBufNum: d.bufnum, dstGain: 1.0);
FluidBufCompose.process(s, source: b, numFrames: 9000, gain: 0.5, destination: d);
FluidBufCompose.process(s, source: c, startFrame:30000, numFrames:44100, numChans:1, gain:0.9, destination: d, destGain: 1.0);
d.query;
d.play;
//constructing a stereo buffer, with the end of the mono synth in both channels, with a piano resonance in swapped stereo
d.free; d = Buffer.new(s);
FluidBufCompose.process(s, srcBufNum: b.bufnum, startAt: 441000, nChans: 2, srcGain: 0.6, dstBufNum: d.bufnum);
FluidBufCompose.process(s, srcBufNum: c.bufnum, nFrames: 78000, startChan: 1, nChans: 2, srcGain: 0.5, dstStartAt: 22050, dstBufNum: d.bufnum, dstGain: 1.0);
FluidBufCompose.process(s, source: b, startFrame: 441000, numChans: 2, gain: 0.6, destination: d);
FluidBufCompose.process(s, source: c, numFrames: 78000, startChan: 1, numChans: 2, gain: 0.5, destStartFrame: 22050, destination: d, destGain: 1.0);
d.query;
d.play;
//constructing a one second buffer: the first second of each buffer, the mono synth on the right, the piano on the left
d.free; d = Buffer.new(s);
FluidBufCompose.process(s, srcBufNum: b.bufnum, nFrames: 44100, nChans: 1, dstStartChan: 1, dstBufNum: d.bufnum);
FluidBufCompose.process(s, srcBufNum: c.bufnum, nFrames:44100, nChans:1, dstBufNum: d.bufnum, dstGain: 1.0);
FluidBufCompose.process(s, source: b, numFrames: 44100, numChans: 1, destStartChan: 1, destination: d);
FluidBufCompose.process(s, source: c, numFrames:44100, numChans:1, destination: d, destGain: 1.0);
d.query;
d.play;
::
@ -111,16 +112,15 @@ f = Buffer.new(s);
// encode the mid (in c) and the side (in d)
(
FluidBufCompose.process(s,b.bufnum, nChans: 1, srcGain: -3.0.dbamp, dstBufNum: c.bufnum);
FluidBufCompose.process(s,b.bufnum, nChans: 1, srcGain: -3.0.dbamp, dstBufNum: d.bufnum);
FluidBufCompose.process(s,b.bufnum, nChans: 1, srcGain: -3.0.dbamp, startChan: 1, dstBufNum: c.bufnum, dstGain: 1.0);
FluidBufCompose.process(s,b.bufnum, nChans: 1, srcGain: -3.0.dbamp * -1.0, startChan: 1, dstBufNum: d.bufnum, dstGain: 1.0);
FluidBufCompose.process(s,b, numChans: 1, gain: -3.0.dbamp, destination: c);
FluidBufCompose.process(s,b, numChans: 1, gain: -3.0.dbamp, destination: d);
FluidBufCompose.process(s,b, numChans: 1, gain: -3.0.dbamp, startChan: 1, destination: c, destGain: 1.0);
FluidBufCompose.process(s,b, numChans: 1, gain: -3.0.dbamp * -1.0, startChan: 1, destination: d, destGain: 1.0);
)
// (optional) compare auraly the stereo with the MS
c.query;d.query;
b.play;
{PlayBuf.ar(1,[c.bufnum,d.bufnum])}.play;
{PlayBuf.ar(1,[c,d])}.play;
// The geeky bit: copy the side (buffer d) on itself with specific amplitudes and delays, in effect applying a FIR filter through expensive convolution
@ -130,7 +130,7 @@ b.play;
e.free; e = Buffer.new(s);
(
[1.0, -1.0].do({ arg x,y;
FluidBufCompose.process(s, d.bufnum, srcGain: x, dstStartAt: y, dstBufNum: e.bufnum, dstGain: 1.0);
FluidBufCompose.process(s, d, gain: x, destStartFrame: y, destination: e, destGain: 1.0);
});
)
@ -138,7 +138,7 @@ e.free; e = Buffer.new(s);
e.free; e = Buffer.new(s);
(
[0.8, -0.32, -0.24, -0.16, -0.08].do({ arg x,y;
FluidBufCompose.process(s, d.bufnum, srcGain: x, dstStartAt: y, dstBufNum: e.bufnum, dstGain: 1.0);
FluidBufCompose.process(s, d, gain: x, destStartFrame: y, destination: e, destGain: 1.0);
});
)
@ -146,24 +146,22 @@ e.free; e = Buffer.new(s);
e.free; e = Buffer.new(s);
(
[0.982494, -0.066859, -0.064358, -0.061897, -0.059477, -0.057098, -0.054761, -0.052466, -0.050215, -0.048007, -0.045843, -0.043724, -0.041649, -0.03962, -0.037636, -0.035697, -0.033805, -0.031959, -0.030159, -0.028406, -0.026699, -0.025038, -0.023425, -0.021857, -0.020337].do({ arg x,y;
FluidBufCompose.process(s, d.bufnum, srcGain: x, dstStartAt: y, dstBufNum: e.bufnum, dstGain: 1.0);
FluidBufCompose.process(s, d, gain: x, destStartFrame: y, destination: e, destGain: 1.0);
});
)
// play the high-passed side buffer
e.query;
e.play;
// if you want to try the other filters, do not forget to clear the destination buffer since it will add programmatically onto itself and would not create the expected frequency response
// decode the MS back to stereo
(
FluidBufCompose.process(s,c.bufnum, nChans: 2, srcGain: -3.0.dbamp, dstBufNum: f.bufnum);
FluidBufCompose.process(s,e.bufnum, srcGain: -3.0.dbamp, dstBufNum: f.bufnum, dstGain: 1.0);
FluidBufCompose.process(s,e.bufnum, srcGain: -3.0.dbamp * -1.0, dstBufNum: f.bufnum, dstStartChan: 1, dstGain: 1.0);
FluidBufCompose.process(s,c, numChans: 2, gain: -3.0.dbamp, destination: f);
FluidBufCompose.process(s,e, gain: -3.0.dbamp, destination: f, destGain: 1.0);
FluidBufCompose.process(s,e, gain: -3.0.dbamp * -1.0, destination: f, destStartChan: 1, destGain: 1.0);
)
// query and play
f.query;
// play the MS processed version
f.play;
// compare with the original

@ -14,7 +14,7 @@ Driedger, Jonathan, Meinard Uller, and Sascha Disch. 2014. Extending Harmonic
The algorithm takes a buffer in, and divides it into two or three outputs, depending on the mode: LIST::
## an harmonic component;
## a percussive component;
## a residual of the previous two if the flag is set to inter-dependant thresholds. See the modeFlag below.::
## a residual of the previous two if the flag is set to inter-dependant thresholds. See the maskingMode below.::
It 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).
@ -31,70 +31,70 @@ This is the method that calls for the HPSS to be calculated on a given source bu
ARGUMENT:: server
The server on which the buffers to be processed are allocated.
ARGUMENT:: srcBufNum
ARGUMENT:: source
The index of the buffer to use as the source material. The channels of multichannel buffers will be processed sequentially.
ARGUMENT:: startAt
Where in the srcBuf should the NMF process start, in samples.
ARGUMENT:: startFrame
Where in the srcBuf should the HPSS process start, in samples.
ARGUMENT:: nFrames
ARGUMENT:: numFrames
How many frames should be processed.
ARGUMENT:: startChan
For multichannel srcBuf, which channel to start processing at.
ARGUMENT:: nChans
ARGUMENT:: numChans
For multichannel srcBuf, how many channels should be processed.
ARGUMENT:: harmBufNum
ARGUMENT:: harmonic
The index of the buffer where the extracted harmonic component will be reconstructed.
ARGUMENT:: percBufNum
ARGUMENT:: percussive
The index of the buffer where the extracted percussive component will be reconstructed.
ARGUMENT:: resBufNum
ARGUMENT:: residual
The index of the buffer where the residual component will be reconstructed in mode 2.
ARGUMENT:: hFiltSize
ARGUMENT:: harmFilterSize
The size, in spectral frames, of the median filter for the harmonic component. Must be an odd number, >= 3.
ARGUMENT:: pFiltSize
ARGUMENT:: percFilterSize
The size, in spectral bins, of the median filter for the percussive component. Must be an odd number, >=3
ARGUMENT:: modeFlag
ARGUMENT:: maskingMode
The way the masking is applied to the original spectrogram. (0,1,2)
table::
## 0 || The traditional soft mask used in Fitzgerald's original method of 'Wiener-inspired' filtering. Complimentary, soft masks are made for the harmonic and percussive parts by allocating some fraction of a point in time-frequency to each. This provides the fewest artefacts, but the weakest separation. The two resulting buffers will sum to exactly the original material.
## 1 || Relative mode - Better separation, with more artefacts. The harmonic mask is constructed using a binary decision, based on whether a threshold is exceeded at a given time-frequency point (these are set using htf1, hta1, htf2, hta2, see below). The percussive mask is then formed as the inverse of the harmonic one, meaning that as above, the two components will sum to the original sound.
## 1 || Relative mode - Better separation, with more artefacts. The harmonic mask is constructed using a binary decision, based on whether a threshold is exceeded at a given time-frequency point (these are set using harmThreshFreq1, harmThreshAmp1, harmThreshFreq2, harmThreshAmp2, see below). The percussive mask is then formed as the inverse of the harmonic one, meaning that as above, the two components will sum to the original sound.
## 2 || Inter-dependent mode - Thresholds can be varied independently, but are coupled in effect. Binary masks are made for each of the harmonic and percussive components, and the masks are converted to soft at the end so that everything null sums even if the params are independent, that is what makes it harder to control. These aren't guranteed to cover the whole sound; in this case the 'leftovers' will placed into a third buffer.
::
ARGUMENT:: htf1
ARGUMENT:: harmThreshFreq1
In modes 1 and 2, the frequency of the low part of the threshold for the harmonic filter (0-1)
ARGUMENT:: hta1
In modes 1 and 2, the threshold of the low part for the harmonic filter. That threshold applies to all frequencies up to htf1: how much more powerful (in dB) the harmonic median filter needs to be than the percussive median filter for this bin to be counted as harmonic.
ARGUMENT:: harmThreshAmp1
In modes 1 and 2, the threshold of the low part for the harmonic filter. That threshold applies to all frequencies up to harmThreshFreq1: how much more powerful (in dB) the harmonic median filter needs to be than the percussive median filter for this bin to be counted as harmonic.
ARGUMENT:: htf2
ARGUMENT:: harmThreshFreq2
In modes 1 and 2, the frequency of the hight part of the threshold for the harmonic filter. (0-1)
ARGUMENT:: hta2
In modes 1 and 2, the threshold of the high part for the harmonic filter. That threshold applies to all frequencies above htf2. The threshold between htf1 and htf2 is interpolated between hta1 and hta2. How much more powerful (in dB) the harmonic median filter needs to be than the percussive median filter for this bin to be counted as harmonic.
ARGUMENT:: harmThreshAmp2
In modes 1 and 2, the threshold of the high part for the harmonic filter. That threshold applies to all frequencies above harmThreshFreq2. The threshold between harmThreshFreq1 and harmThreshFreq2 is interpolated between harmThreshAmp1 and harmThreshAmp2. How much more powerful (in dB) the harmonic median filter needs to be than the percussive median filter for this bin to be counted as harmonic.
ARGUMENT:: ptf1
ARGUMENT:: percThreshFreq1
In mode 2, the frequency of the low part of the threshold for the percussive filter. (0-1)
ARGUMENT:: pta1
In mode 2, the threshold of the low part for the percussive filter. That threshold applies to all frequencies up to ptf1. How much more powerful (in dB) the percussive median filter needs to be than the harmonic median filter for this bin to be counted as percussive.
ARGUMENT:: percThreshAmp1
In mode 2, the threshold of the low part for the percussive filter. That threshold applies to all frequencies up to percThreshFreq1. How much more powerful (in dB) the percussive median filter needs to be than the harmonic median filter for this bin to be counted as percussive.
ARGUMENT:: ptf2
ARGUMENT:: percThreshFreq2
In mode 2, the frequency of the hight part of the threshold for the percussive filter. (0-1)
ARGUMENT:: pta2
In mode 2, the threshold of the high part for the percussive filter. That threshold applies to all frequencies above ptf2. The threshold between ptf1 and ptf2 is interpolated between pta1 and pta2. How much more powerful (in dB) the percussive median filter needs to be than the harmonic median filter for this bin to be counted as percussive.
ARGUMENT:: percThreshAmp2
In mode 2, the threshold of the high part for the percussive filter. That threshold applies to all frequencies above percThreshFreq2. The threshold between percThreshFreq1 and percThreshFreq2 is interpolated between percThreshAmp1 and percThreshAmp2. How much more powerful (in dB) the percussive median filter needs to be than the harmonic median filter for this bin to be counted as percussive.
ARGUMENT:: winSize
ARGUMENT:: windowSize
The window size in samples. As HPSS 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
ARGUMENT:: hopSize
@ -103,14 +103,16 @@ ARGUMENT:: hopSize
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 than the window size provides interpolation in frequency.
ARGUMENT:: action
A Function to be evaluated once the offline process has finished and all Buffer's instance variables have been updated on the client side. The function will be passed [harmonic, percussive, residual] as an argument.
RETURNS::
Nothing, as the various destination buffers are declared in the function call.
Discussion::
HPSS works by using median filters on the spectral magnitudes of a sound. It hinges on a simple modelling assumption that tonal components will tend to yield concentrations of energy across time, spread out in frequency, and percussive components will manifest as concentrations of energy across frequency, spread out in time. By using median filters across time and frequency respectively, we get initial esitmates of the tonal-ness / transient-ness of a point in time and frequency. These are then combined into 'masks' that are applied to the orginal spectral data in order to produce a separation.
The modeFlag parameter provides different approaches to combinging estimates and producing masks. Some settings (especially in modes 1 & 2) will provide better separation but with more artefacts. These can, in principle, be ameliorated by applying smoothing filters to the masks before transforming back to the time-domain (not yet implemented).
The maskingMode parameter provides different approaches to combinging estimates and producing masks. Some settings (especially in modes 1 & 2) will provide better separation but with more artefacts. These can, in principle, be ameliorated by applying smoothing filters to the masks before transforming back to the time-domain (not yet implemented).
EXAMPLES::
@ -127,42 +129,65 @@ code::
(
Routine{
t = Main.elapsedTime;
FluidBufHPSS.process(s, b.bufnum, harmBufNum: c.bufnum, percBufNum: d.bufnum);
s.sync;
FluidBufHPSS.process(s, b, harmonic: c, percussive: d);
(Main.elapsedTime - t).postln;
}.play
)
//query and play the harmonic
c.query;
//play the harmonic
c.play;
//querry and play the percussive
d.query;
//play the percussive
d.play;
//nullsumming tests
{(PlayBuf.ar(1,c.bufnum))+(PlayBuf.ar(1,d.bufnum))+(-1*PlayBuf.ar(1,b.bufnum,doneAction:2))}.play
{(PlayBuf.ar(1,c))+(PlayBuf.ar(1,d))+(-1*PlayBuf.ar(1,b,doneAction:2))}.play
//more daring parameters, in mode 2
(
Routine{
t = Main.elapsedTime;
FluidBufHPSS.process(s, b.bufnum, harmBufNum: c.bufnum, percBufNum: d.bufnum, resBufNum:e.bufnum, hFiltSize:31, modeFlag:2, htf1: 0.005, hta1: 7.5, htf2: 0.168, hta2: 7.5, ptf1: 0.004, pta1: 26.5, ptf2: 0.152, pta2: 26.5);
s.sync;
FluidBufHPSS.process(s, b, harmonic: c, percussive: d, residual:e, harmFilterSize:31, maskingMode:2, harmThreshFreq1: 0.005, harmThreshAmp1: 7.5, harmThreshFreq2: 0.168, harmThreshAmp2: 7.5, percThreshFreq1: 0.004, percThreshAmp1: 26.5, percThreshFreq2: 0.152, percThreshAmp2: 26.5,windowSize:4096,hopSize:512);
(Main.elapsedTime - t).postln;
}.play
)
//query and play the harmonic
c.query;
//play the harmonic
c.play;
//query and play the percussive
d.query;
//play the percussive
d.play;
//query and play the residual
e.query;
//play the residual
e.play;
//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;
{PlayBuf.ar(1,c) + PlayBuf.ar(1,d) + PlayBuf.ar(1,e) - PlayBuf.ar(1,b,doneAction:2)}.play;
::
STRONG::A stereo buffer example.::
CODE::
// load two very different files
(
b = Buffer.read(s,File.realpath(FluidBufHPSS.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-SA-UprightPianoPedalWide.wav");
c = Buffer.read(s,File.realpath(FluidBufHPSS.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-AcousticStrums-M.wav");
)
// composite one on left one on right as test signals
FluidBufCompose.process(s, c, numFrames:b.numFrames, startFrame:555000,destStartChan:1, destination:b)
b.play
// create 2 new buffers as destinations
d = Buffer.new(s); e = Buffer.new(s);
//run the process on them
(
Routine{
t = Main.elapsedTime;
FluidBufHPSS.process(s, b, harmonic: d, percussive:e);
(Main.elapsedTime - t).postln;
}.play
)
//listen: stereo preserved!
d.play
e.play
::

@ -0,0 +1,115 @@
TITLE:: FluidBufLoudness
SUMMARY:: A Loudness and True-Peak Descriptor on a Buffer
CATEGORIES:: Libraries>FluidDecomposition
RELATED:: Guides/FluCoMa, Guides/FluidDecomposition
DESCRIPTION::
This class implements two loudness descriptors, computing the true peak of the signal as well as applying the filters proposed by broadcasting standards to emulate the perception of amplitude. It 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).::
The process will return a multichannel buffer with two channels per input channel, one for loudness and one for the true peak value of the frame, both in dBfs. More information on broadcasting standardisation of loudness measurement is available at the reference page FOOTNOTE::https://tech.ebu.ch/docs/tech/tech3341.pdf:: and in more musician-friendly explantions here FOOTNOTE::http://designingsound.org/2013/02/06/loudness-and-metering-part-1/::. Each sample represents a value, which is every hopSize. Its sampling rate is STRONG::sourceSR / hopSize::.
CLASSMETHODS::
METHOD:: process
This is the method that calls for the loudness descriptor to be calculated on a given source buffer.
ARGUMENT:: server
The server on which the buffers to be processed are allocated.
ARGUMENT:: source
The index of the buffer to use as the source material to be described. The different channels of multichannel buffers will be processing sequentially.
ARGUMENT:: startFrame
Where in the srcBuf should the process start, in sample.
ARGUMENT:: numFrames
How many frames should be processed.
ARGUMENT:: startChan
For multichannel srcBuf, which channel should be processed first.
ARGUMENT:: numChans
For multichannel srcBuf, how many channel should be processed.
ARGUMENT:: features
The destination buffer for the loudness descriptors.
ARGUMENT:: kWeighting
A flag to switch the perceptual model of loudness. On by default, removing it makes the algorithm more CPU efficient by reverting to a simple RMS of the frame.
ARGUMENT:: truePeak
A flag to switch the computation of TruePeak. On by default, removing it makes the algorithm more CPU efficient by reverting to a simple absolute peak of the frame.
ARGUMENT:: windowSize
The size of the window on which the computation is done. By default 1024 to be similar with all other FluCoMa objects, the EBU specifies other values as per the examples below.
ARGUMENT:: hopSize
How much the buffered window moves forward, in samples. By default 512 to be similar with all other FluCoMa objects, the EBU specifies other values as per the examples below.
ARGUMENT:: action
A Function to be evaluated once the offline process has finished and all Buffer's instance variables have been updated on the client side. The function will be passed [features] as an argument.
RETURNS::
Nothing, as the destination buffer is declared in the function call.
EXAMPLES::
code::
// create a buffer with a short clicking sinusoidal burst (220Hz) starting at frame 8192 for 1024 frames
(
b = Buffer.sendCollection(s, (Array.fill(8192,{0}) ++ (Signal.sineFill(1203,[0,0,0,0,0,1],[0,0,0,0,0,0.5pi]).takeThese({|x,i|i>1023})) ++ Array.fill(8192,{0})));
c = Buffer.new(s);
)
// listen to the source and look at the buffer
b.play; b.plot;
// run the process with basic parameters
(
Routine{
t = Main.elapsedTime;
FluidBufLoudness.process(s, source:b, features: c);
(Main.elapsedTime - t).postln;
}.play
)
// look at the analysis
c.plot(minval:-130, maxval:6)
// The values are interleaved [loudness,truepeak] in the buffer as they are on 2 channels: to get to the right frame, divide the SR of the input by the hopSize, then multiply by 2 because of the channel interleaving
// here we are querying from one frame before (the signal starts at 8192, which is frame 16 (8192/512), therefore starting the query at frame 15, which is index 30.
c.getn(30,10,{|x|x.postln})
// observe that the first frame is silent, as expected. We can appreciate the overshoot of TruePeak of a full range sinewave starting on the second sample (fourth item in the list).
::
STRONG::A stereo buffer example.::
CODE::
// load two very different files
(
b = Buffer.read(s,File.realpath(FluidBufLoudness.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-SA-UprightPianoPedalWide.wav");
c = Buffer.read(s,File.realpath(FluidBufLoudness.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-AcousticStrums-M.wav");
)
// composite one on left one on right as test signals
FluidBufCompose.process(s, c, numFrames:b.numFrames, startFrame:555000,destStartChan:1, destination:b)
b.play
// create a buffer as destinations
c = Buffer.new(s);
//run the process on them with EBU standard Instant Loudness of
(
Routine{
t = Main.elapsedTime;
FluidBufLoudness.process(s, b, features: c, windowSize: 17640, hopSize:4410);
(Main.elapsedTime - t).postln;
}.play
)
// look at the buffer: [loudness,truepeak] for left then [loudness,truepeak] for right
c.plot(minval:-40, maxval:0)
::

@ -0,0 +1,114 @@
TITLE:: FluidBufMFCC
SUMMARY:: Mel-Frequency Cepstral Coefficients as Spectral Descriptors on a Buffer
CATEGORIES:: Libraries>FluidDecomposition
RELATED:: Guides/FluCoMa, Guides/FluidDecomposition, Classes/FluidBufMelBands
DESCRIPTION::
This class implements a classic spectral descriptor, the Mel-Frequency Cepstral Coefficients (https://en.wikipedia.org/wiki/Mel-frequency_cepstrum). The input is first filtered in to STRONG::numBands:: perceptually-spaced bands, as in LINK::Classes/FluidMelBands::. It is then analysed into STRONG::numCoeffs:: number of cepstral coefficients. It has the avantage of being amplitude invarient, except for the first coefficient. It 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).::
The process will return a single multichannel buffer of STRONG::numCoeffs:: per input channel. Each frame represents a value, which is every hopSize.
CLASSMETHODS::
METHOD:: process
This is the method that calls for the spectral shape descriptors to be calculated on a given source buffer.
ARGUMENT:: server
The server on which the buffers to be processed are allocated.
ARGUMENT:: source
The index of the buffer to use as the source material to be described through the various descriptors. The different channels of multichannel buffers will be processing sequentially.
ARGUMENT:: startFrame
Where in the srcBuf should the process start, in sample.
ARGUMENT:: numFrames
How many frames should be processed.
ARGUMENT:: startChan
For multichannel srcBuf, which channel should be processed first.
ARGUMENT:: numChans
For multichannel srcBuf, how many channel should be processed.
ARGUMENT:: features
The destination buffer for the numCoeffs coefficients describing the spectral shape.
ARGUMENT:: numCoeffs
The number of cepstral coefficients to be outputed. It will decide how many channels are produce per channel of the source.
ARGUMENT:: numBands
The number of bands that will be perceptually equally distributed between STRONG::minFreq:: and STRONG::maxFreq::.
ARGUMENT:: minFreq
The lower boundary of the lowest band of the model, in Hz.
ARGUMENT:: maxFreq
The highest boundary of the highest band of the model, in Hz.
ARGUMENT:: windowSize
The window size. As MFCC computation 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
ARGUMENT:: hopSize
The window hop size. As MFCC computation relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts.
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.
ARGUMENT:: action
A Function to be evaluated once the offline process has finished and all Buffer's instance variables have been updated on the client side. The function will be passed [features] as an argument.
RETURNS::
Nothing, as the destination buffer is declared in the function call.
EXAMPLES::
code::
// create some buffers
(
b = Buffer.read(s,File.realpath(FluidBufMFCC.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Nicol-LoopE-M.wav");
c = Buffer.new(s);
)
// run the process with basic parameters
(
Routine{
t = Main.elapsedTime;
FluidBufMFCC.process(s, b, features: c);
(Main.elapsedTime - t).postln;
}.play
)
// listen to the source and look at the buffer
b.play;
c.plot(separately:true)
::
STRONG::A stereo buffer example.::
CODE::
// load two very different files
(
b = Buffer.read(s,File.realpath(FluidBufSpectralShape.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-SA-UprightPianoPedalWide.wav");
c = Buffer.read(s,File.realpath(FluidBufSpectralShape.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-AcousticStrums-M.wav");
)
// composite one on left one on right as test signals
FluidBufCompose.process(s, c, numFrames:b.numFrames, startFrame:555000,destStartChan:1, destination:b)
b.play
// create a buffer as destinations
c = Buffer.new(s);
//run the process on them
(
Routine{
t = Main.elapsedTime;
FluidBufMFCC.process(s, b, numCoeffs:5, features: c);
(Main.elapsedTime - t).postln;
}.play
)
// look at the buffer: 5 coefs for left, then 5 coefs for right (the first of each is linked to the loudness)
c.plot(separately:true)
::

@ -0,0 +1,112 @@
TITLE:: FluidBufMelBands
SUMMARY:: A Perceptually Spread Spectral Contour Descriptor on a Buffer
CATEGORIES:: Libraries>FluidDecomposition
RELATED:: Guides/FluCoMa, Guides/FluidDecomposition, Classes/FluidBufMFCC
DESCRIPTION::
This class implements a spectral shape descriptor where the amplitude is given for a number of equally spread perceptual bands. The spread is based on the Mel scale (https://en.wikipedia.org/wiki/Mel_scale) which is one of the first attempt to mimic pitch perception scientifically. This implementation allows to select the range and number of bands dynamically. It 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).::
The process will return a single multichannel buffer of STRONG::numBands:: per input channel. Each frame represents a value, which is every hopSize.
CLASSMETHODS::
METHOD:: process
This is the method that calls for the spectral shape descriptors to be calculated on a given source buffer.
ARGUMENT:: server
The server on which the buffers to be processed are allocated.
ARGUMENT:: source
The index of the buffer to use as the source material to be described through the various descriptors. The different channels of multichannel buffers will be processing sequentially.
ARGUMENT:: startFrame
Where in the srcBuf should the process start, in sample.
ARGUMENT:: numFrames
How many frames should be processed.
ARGUMENT:: startChan
For multichannel srcBuf, which channel should be processed first.
ARGUMENT:: numChans
For multichannel srcBuf, how many channel should be processed.
ARGUMENT:: features
The destination buffer for the STRONG::numBands:: amplitudes describing the spectral shape.
ARGUMENT:: numBands
The number of bands that will be perceptually equally distributed between STRONG::minFreq:: and STRONG::maxFreq::. It will decide how many channels are produce per channel of the source.
ARGUMENT:: minFreq
The lower boundary of the lowest band of the model, in Hz.
ARGUMENT:: maxFreq
The highest boundary of the highest band of the model, in Hz.
ARGUMENT:: windowSize
The window size. As spectral description 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
ARGUMENT:: hopSize
The window hop size. As spectral description relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts.
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.
ARGUMENT:: action
A Function to be evaluated once the offline process has finished and all Buffer's instance variables have been updated on the client side. The function will be passed [features] as an argument.
RETURNS::
Nothing, as the destination buffer is declared in the function call.
EXAMPLES::
code::
// create some buffers
(
b = Buffer.read(s,File.realpath(FluidBufMelBands.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Nicol-LoopE-M.wav");
c = Buffer.new(s);
)
// run the process with basic parameters
(
Routine{
t = Main.elapsedTime;
FluidBufMelBands.process(s, b, features: c, numBands:10);
(Main.elapsedTime - t).postln;
}.play
)
// listen to the source and look at the buffer
b.play;
c.plot
::
STRONG::A stereo buffer example.::
CODE::
// load two very different files
(
b = Buffer.read(s,File.realpath(FluidBufSpectralShape.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-SA-UprightPianoPedalWide.wav");
c = Buffer.read(s,File.realpath(FluidBufSpectralShape.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-AcousticStrums-M.wav");
)
// composite one on left one on right as test signals
FluidBufCompose.process(s, c, numFrames:b.numFrames, startFrame:555000,destStartChan:1, destination:b)
b.play
// create a buffer as destinations
c = Buffer.new(s);
//run the process on them
(
Routine{
t = Main.elapsedTime;
FluidBufMelBands.process(s, b, features: c, numBands:10);
(Main.elapsedTime - t).postln;
}.play
)
// look at the buffer: 10 bands for left, then 10 bands for right
c.plot(separately:true)
::

@ -1,27 +1,27 @@
TITLE:: FluidBufNMF
SUMMARY:: Buffer-Based Non-Negative Matrix Factorisation on Spectral Frames
CATEGORIES:: Libraries>FluidDecomposition, UGens>Buffer
RELATED:: Guides/FluCoMa, Guides/FluidDecomposition, Classes/FluidNMFMatch
RELATED:: Guides/FluCoMa, Guides/FluidDecomposition, Classes/FluidNMFMatch, Classes/FluidNMFFilter
DESCRIPTION::
The FluidBufNMF object decomposes the spectrum of a sound into a number of components using 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.
::. 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 algorithm takes a buffer in and divides it into a number of components, determined by the rank argument. It works iteratively, by trying to find a combination of spectral templates ('dictionaries') and envelopes ('activations') that yield the original magnitude spectrogram 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 algorithm takes a buffer in and divides it into a number of components, determined by the STRONG::Components:: argument. It works iteratively, by trying to find a combination of spectral templates ('bases') and envelopes ('activations') that yield the original magnitude spectrogram 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 object can return either or all of the following: LIST::
## a spectral contour of each component in the form of a magnitude spectrogram (called a dictionary in NMF lingo);
## a spectral contour of each component in the form of a magnitude spectrogram (called a basis in NMF lingo);
## an amplitude envelope of each component in the form of gains for each consecutive frame of the underlying spectrogram (called an activation in NMF lingo);
## an audio reconstruction of each components in the time domain. ::
The dictionaries and activations can be used to make a kind of vocoder based on what NMF has 'learned' from the original data. Alternatively, taking the matrix product of a dictionary and an activation will yield a synthetic magnitude spectrogram of a component (which could be reconsructed, given some phase informaiton from somewhere).
The bases and activations can be used to make a kind of vocoder based on what NMF has 'learned' from the original data. Alternatively, taking the matrix product of a basis and an activation will yield a synthetic magnitude spectrogram of a component (which could be reconsructed, given some phase informaiton from somewhere).
Some additional options and flexibility can be found through combinations of the dictFlag and actFlag arguments. If these flags are set to 1, the object expects to be supplied with pre-formed spectra (or envelopes) that will be used as seeds for the decomposition, providing more guided results. When set to 2, the supplied buffers won't be updated, so become templates to match against instead. Note that having both dictionaries and activations set to 2 doesn't make sense, so the object will complain.
Some additional options and flexibility can be found through combinations of the basesMode and actMode arguments. If these flags are set to 1, the object expects to be supplied with pre-formed spectra (or envelopes) that will be used as seeds for the decomposition, providing more guided results. When set to 2, the supplied buffers won't be updated, so become templates to match against instead. Note that having both bases and activations set to 2 doesn't make sense, so the object will complain.
If supplying pre-formed data, it's up to the user to make sure that the supplied buffers are the right size: LIST::
## dictionaries must be STRONG::(fft size / 2) + 1:: frames and STRONG::(rank * input channels):: channels
## activations must be STRONG::(input frames / hopSize) + 1:: frames and STRONG::(rank * input channels):: channels
## bases must be STRONG::(fft size / 2) + 1:: frames and STRONG::(components * input channels):: channels
## activations must be STRONG::(input frames / hopSize) + 1:: frames and STRONG::(components * input channels):: channels
::
In this implementation, the components are reconstructed by masking the original spectrum, such that they will sum to yield the original sound.
@ -41,70 +41,70 @@ This is the method that calls for the factorisation to be calculated on a given
ARGUMENT:: server
The server on which the buffers to be processed are allocated.
ARGUMENT:: srcBufNum
ARGUMENT:: source
The index of the buffer to use as the source material to be decomposed through the NMF process. The different channels of multichannel buffers will be processing sequentially.
ARGUMENT:: startAt
ARGUMENT:: startFrame
Where in the srcBuf should the NMF process start, in sample.
ARGUMENT:: nFrames
ARGUMENT:: numFrames
How many frames should be processed.
ARGUMENT:: startChan
For multichannel srcBuf, which channel should be processed first.
ARGUMENT:: nChans
ARGUMENT:: numChans
For multichannel srcBuf, how many channel should be processed.
ARGUMENT:: dstBufNum
The index of the buffer where the different reconstructed ranks will be reconstructed. The buffer will be resized to STRONG::rank * numChannelsProcessed:: channels and STRONG::sourceDuration:: lenght. If STRONG::nil:: is provided, the reconstruction will not happen.
ARGUMENT:: destination
The index of the buffer where the different reconstructed components will be reconstructed. The buffer will be resized to STRONG::components * numChannelsProcessed:: channels and STRONG::sourceDuration:: lenght. If STRONG::nil:: is provided, the reconstruction will not happen.
ARGUMENT:: dictBufNum
The index of the buffer where the different dictionaries will be written to and/or read from: the behaviour is set in the following argument. If STRONG::nil:: is provided, no dictionary will be returned.
ARGUMENT:: bases
The index of the buffer where the different bases will be written to and/or read from: the behaviour is set in the following argument. If STRONG::nil:: is provided, no bases will be returned.
ARGUMENT:: dictFlag
This flag decides of how the dictionnary buffer passed as the previous argument is treated.
ARGUMENT:: basesMode
This flag decides of how the basis buffer passed as the previous argument is treated.
table::
## 0 || The dictionaries are seeded randomly, and the resulting ones will be written after the process in the passed buffer. The buffer is resized to STRONG::rank * numChannelsProcessed:: channels and STRONG::(fftSize / 2 + 1):: lenght.
## 1 || The passed buffer is considered as seed for the dictionaries. Its dimensions should match the values above. The resulting dictionaries will replace the seed ones.
## 2 || The passed buffer is considered as a template for the dictionaries, and will therefore not change. Its dictionaries should match the values above.
## 0 || The bases are seeded randomly, and the resulting ones will be written after the process in the passed buffer. The buffer is resized to STRONG::components * numChannelsProcessed:: channels and STRONG::(fftSize / 2 + 1):: lenght.
## 1 || The passed buffer is considered as seed for the bases. Its dimensions should match the values above. The resulting bases will replace the seed ones.
## 2 || The passed buffer is considered as a template for the bases, and will therefore not change. Its bases should match the values above.
::
ARGUMENT:: actBufNum
ARGUMENT:: activations
The index of the buffer where the different activations will be written to and/or read from: the behaviour is set in the following argument. If STRONG::nil:: is provided, no activation will be returned.
ARGUMENT:: actFlag
ARGUMENT:: actMode
This flag decides of how the activation buffer passed as the previous argument is treated.
table::
## 0 || The activations are seeded randomly, and the resulting ones will be written after the process in the passed buffer. The buffer is resized to STRONG::rank * numChannelsProcessed:: channels and STRONG::(sourceDuration / hopsize + 1):: lenght.
## 0 || The activations are seeded randomly, and the resulting ones will be written after the process in the passed buffer. The buffer is resized to STRONG::components * numChannelsProcessed:: channels and STRONG::(sourceDuration / hopsize + 1):: lenght.
## 1 || The passed buffer is considered as seed for the activations. Its dimensions should match the values above. The resulting activations will replace the seed ones.
## 2 || The passed buffer is considered as a template for the activations, and will therefore not change. Its dimensions should match the values above.
::
ARGUMENT:: rank
ARGUMENT:: components
The number of elements the NMF algorithm will try to divide the spectrogram of the source in.
ARGUMENT:: nIter
ARGUMENT:: iterations
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.
ARGUMENT:: sortFlag
This allows to choose between the different methods of sorting the ranks in order to get similar sonic qualities on a given rank (not implemented yet)
ARGUMENT:: winSize
ARGUMENT:: windowSize
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
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 hop 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.
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.
ARGUMENT:: winType
ARGUMENT:: windowType
The inner FFT/IFFT windowing type (not implemented yet)
ARGUMENT:: randSeed
ARGUMENT:: randomSeed
The NMF process needs to seed its starting point. If specified, the same values will be used. The default of -1 will randomly assign them. (not implemented yet)
ARGUMENT:: action
A Function to be evaluated once the offline process has finished and all Buffer's instance variables have been updated on the client side. The function will be passed [destination, bases, activations] as an argument.
RETURNS::
Nothing, as the various destination buffers are declared in the function call.
@ -128,8 +128,8 @@ Routine {
b.sine2([500],[1], false, false);
c.sine2([5000],[1],false, false);
s.sync;
FluidBufCompose.process(s,b.bufnum, dstBufNum:d.bufnum);
FluidBufCompose.process(s,c.bufnum, dstStartAt:44100, dstBufNum:d.bufnum, dstGain:1);
FluidBufCompose.process(s,b, destination:d);
FluidBufCompose.process(s,c, destStartFrame:44100, destination:d, destGain:1);
s.sync;
d.query;
}.play;
@ -140,20 +140,19 @@ d.plot
d.play //////(beware !!!! loud!!!)
(
// separate them in 2 ranks
// separate them in 2 components
Routine {
FluidBufNMF.process(s, d.bufnum, dstBufNum:e.bufnum, dictBufNum: f.bufnum, actBufNum:g.bufnum, rank:2);
s.sync;
FluidBufNMF.process(s, d, destination:e, bases: f, activations:g, components:2);
e.query;
f.query;
g.query;
}.play
)
// look at the resynthesised separated signal
// look at the resynthesised separated components as audio
e.plot;
// look at the dictionaries signal for 2 spikes
// look at the bases signal for 2 spikes
f.plot;
// look at the activations
@ -174,44 +173,37 @@ y = Buffer.new(s);
~fft_size = 1024;
~frame_size = 512;
~hop_size = 256;
~which_rank = 3;
~which_component = 3;
)
// matrix factorisation, requesting everything
// matrix factorisation, requesting everything - wait for the computation time to appear.
(
Routine{
t = Main.elapsedTime;
FluidBufNMF.process(s,b.bufnum, 0,-1,0,-1,c.bufnum,x.bufnum,0,y.bufnum,0,5,100,0,~frame_size,~hop_size,~fft_size);
s.sync;
FluidBufNMF.process(s,b, 0,-1,0,-1,c,x,0,y,0,5,100,~frame_size,~hop_size,~fft_size);
(Main.elapsedTime - t).postln;
s.sync;
c.query;
s.sync;
x.query;
s.sync;
y.query;
}.play
)
//look at the resynthesised ranks, the dictionaries and the activations
c.plot;x.plot; y.plot;
//look at the resynthesised components, the bases and the activations
c.plot; x.plot; y.plot;
//null test of the sum of sources
{(PlayBuf.ar(5,c.bufnum,doneAction:2).sum)+(-1*PlayBuf.ar(1,b.bufnum,doneAction:2))}.play
{(PlayBuf.ar(5,c,doneAction:2).sum)+(-1*PlayBuf.ar(1,b,doneAction:2))}.play
// play the ranks spread in the stereo field
{Splay.ar(PlayBuf.ar(5,c.bufnum,doneAction:2))}.play
// play the components spread in the stereo field
{Splay.ar(PlayBuf.ar(5,c,doneAction:2))}.play
//play a single source
{PlayBuf.ar(5,c.bufnum,doneAction:2)[~which_rank].dup}.play
{PlayBuf.ar(5,c,doneAction:2)[~which_component].dup}.play
//play noise using one of the dictionaries as filter.
//play noise using one of the bases as filter.
(
{
var chain;
chain = FFT(LocalBuf(~fft_size), WhiteNoise.ar());
chain = chain.pvcollect(~fft_size, {|mag, phase, index|
[mag * BufRd.kr(5,x.bufnum,DC.kr(index),0,1)[~which_rank]];
[mag * BufRd.kr(5,x,DC.kr(index),0,1)[~which_component]];
});
IFFT(chain);
@ -219,16 +211,16 @@ c.plot;x.plot; y.plot;
)
//play noise using one of the activations as envelope.
{WhiteNoise.ar(BufRd.kr(5,y.bufnum,Phasor.ar(1,1/~hop_size,0,(b.numFrames / ~hop_size + 1)),0,1)[~which_rank])*0.5}.play
{WhiteNoise.ar(BufRd.kr(5,y,Phasor.ar(1,1/~hop_size,0,(b.numFrames / ~hop_size + 1)),0,1)[~which_component])*0.5}.play
//play noise through both matching activation and filter
(
{
var chain;
chain = FFT(LocalBuf(~fft_size), WhiteNoise.ar(BufRd.kr(5,y.bufnum,Phasor.ar(1,1/~hop_size,0,(b.numFrames / ~hop_size + 1)),0,1)[~which_rank]*12),0.5,1);
chain = FFT(LocalBuf(~fft_size), WhiteNoise.ar(BufRd.kr(5,y,Phasor.ar(1,1/~hop_size,0,(b.numFrames / ~hop_size + 1)),0,1)[~which_component]*12),0.5,1);
chain = chain.pvcollect(~fft_size, {|mag, phase, index|
[mag * BufRd.kr(5,x.bufnum,DC.kr(index),0,1)[~which_rank]];
[mag * BufRd.kr(5,x,DC.kr(index),0,1)[~which_component]];
});
[0,IFFT(chain)];
@ -236,7 +228,7 @@ c.plot;x.plot; y.plot;
)
::
STRONG::Fixed Dictionnaries:: The process can be trained, and the learnt dictionaries or activations can be used as templates.
STRONG::Fixed Bases: The process can be trained, and the learnt bases or activations can be used as templates.::
CODE::
@ -252,37 +244,31 @@ y = Buffer.new(s);
// train only 2 seconds
(
Routine {
FluidBufNMF.process(s,b.bufnum,0,88200,0,1, c.bufnum, x.bufnum, rank:10);
s.sync;
FluidBufNMF.process(s,b,0,88200,0,1, c, x, components:10);
c.query;
}.play;
)
// find the rank that has the picking sound by changing which channel to listen to
// find the component that has the picking sound by changing which channel to listen to
(
~element = 4;
{PlayBuf.ar(10,c.bufnum)[~element]}.play
{PlayBuf.ar(10,c)[~element]}.play
)
// copy all the other ranks on itself and the picking dictionnary as the sole component of the 1st channel
// copy all the other components on itself and the picking basis as the sole component of the 1st channel
(
Routine{
z = (0..9);
FluidBufCompose.process(s, x.bufnum, startChan: z.removeAt(~element), nChans: 1, dstBufNum: e.bufnum);
s.sync;
e.query;
s.sync;
z.do({|chan| FluidBufCompose.process(s, x.bufnum, startChan:chan, nChans: 1, dstStartChan: 1, dstBufNum: e.bufnum, dstGain:1)});
s.sync;
FluidBufCompose.process(s, x, startChan: z.removeAt(~element), numChans: 1, destination: e);
z.do({|chan| FluidBufCompose.process(s, x, startChan:chan, numChans: 1, destStartChan: 1, destination: e, destGain:1)});
e.query;
}.play;
)
//process the whole file, splitting it with the 2 trained dictionnaries
//process the whole file, splitting it with the 2 trained bases
(
Routine{
FluidBufNMF.process(s, b.bufnum, dstBufNum: c.bufnum, dictBufNum: e.bufnum, dictFlag: 2, actBufNum: y.bufnum, rank:2);
s.sync;
FluidBufNMF.process(s, b, destination: c, bases: e, basesMode: 2, activations: y, components:2);
c.query;
}.play;
)
@ -291,10 +277,10 @@ Routine{
c.play
// it even null-sums
{(PlayBuf.ar(2,c.bufnum,doneAction:2).sum)-(PlayBuf.ar(1,b.bufnum,doneAction:2))}.play
{(PlayBuf.ar(2,c,doneAction:2).sum)-(PlayBuf.ar(1,b,doneAction:2))}.play
::
STRONG::Updating Dictionnaries:: The process can update dictionaries provided as seed.
STRONG::Updating Bases: The process can update bases provided as seed.::
CODE::
(
@ -313,8 +299,8 @@ Routine {
b.sine2([500],[1], false, false);
c.sine2([5000],[1],false, false);
s.sync;
FluidBufCompose.process(s,b.bufnum, dstBufNum:d.bufnum);
FluidBufCompose.process(s,c.bufnum, dstStartAt:44100, dstBufNum:d.bufnum, dstGain:1);
FluidBufCompose.process(s,b, destination:d);
FluidBufCompose.process(s,c, destStartFrame:44100, destination:d, destGain:1);
s.sync;
d.query;
}.play;
@ -325,7 +311,7 @@ d.plot
d.play //////(beware !!!! loud!!!)
(
//make a seeding dictionary of 3 ranks:
//make a seeding basis of 3 components:
var highpass, lowpass, direct;
highpass = Array.fill(513,{|i| (i < 50).asInteger});
lowpass = 1 - highpass;
@ -333,15 +319,14 @@ 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
//check the basis: a steep lowpass, a steep highpass, and a small DC
e.plot
e.query
(
// use the seeding dictionary, without updating
// use the seeding basis, without updating
Routine {
FluidBufNMF.process(s, d.bufnum, dstBufNum:f.bufnum, dictBufNum: e.bufnum, dictFlag: 2, actBufNum:g.bufnum, rank:3);
s.sync;
FluidBufNMF.process(s, d, destination:f, bases: e, basesMode: 2, activations:g, components:3);
e.query;
f.query;
g.query;
@ -351,17 +336,16 @@ Routine {
// look at the resynthesised separated signal
f.plot;
// look at the dictionaries that have not changed
// look at the bases that have not changed
e.plot;
// look at the activations
g.plot;
(
// use the seeding dictionary, with updating this time
// use the seeding bases, 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;
FluidBufNMF.process(s, d, destination:f, bases: e, basesMode: 1, activations:g, components:3);
e.query;
f.query;
g.query;
@ -371,9 +355,9 @@ Routine {
// 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
// look at the bases that have now updated in place (with the 3rd channel being more focused
e.plot;
// look at the activations (sharper 3rd rank at transitions)
// look at the activations (sharper 3rd component at transitions)
g.plot;
::
::

@ -18,42 +18,45 @@ This is the method that calls for the slicing to be calculated on a given source
ARGUMENT:: server
The server on which the buffers to be processed are allocated.
ARGUMENT:: srcBufNum
ARGUMENT:: source
The index of the buffer to use as the source material to be sliced through novelty identification. The different channels of multichannel buffers will be summed.
ARGUMENT:: startAt
ARGUMENT:: startFrame
Where in the srcBuf should the slicing process start, in sample.
ARGUMENT:: nFrames
ARGUMENT:: numFrames
How many frames should be processed.
ARGUMENT:: startChan
For multichannel srcBuf, which channel should be processed.
ARGUMENT:: nChans
ARGUMENT:: numChans
For multichannel srcBuf, how many channel should be summed.
ARGUMENT:: indBufNum
ARGUMENT:: indices
The index of the buffer where the indices (in sample) of the estimated starting points of slices will be written. The first and last points are always the boundary points of the analysis.
ARGUMENT:: kernSize
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
ARGUMENT:: threshold
The normalised threshold, between 0 an 1, on the novelty curve to consider it a segmentation point.
ARGUMENT:: filtSize
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
ARGUMENT:: windowSize
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
ARGUMENT:: hopSize
The window hope size. As novelty estimation 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 hop size. As novelty estimation relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts.
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.
ARGUMENT:: action
A Function to be evaluated once the offline process has finished and indices instance variables have been updated on the client side. The function will be passed indices as an argument.
RETURNS::
Nothing, as the various destination buffers are declared in the function call.
@ -71,8 +74,7 @@ c = Buffer.new(s);
// with basic params
Routine{
t = Main.elapsedTime;
FluidBufNoveltySlice.process(s,b.bufnum, indBufNum: c.bufnum, thresh:0.6);
s.sync;
FluidBufNoveltySlice.process(s,b, indices: c, threshold:0.6);
(Main.elapsedTime - t).postln;
}.play
)
@ -83,14 +85,14 @@ c.query;
//loops over a splice with the MouseX
(
{
BufRd.ar(1, b.bufnum,
BufRd.ar(1, b,
Phasor.ar(0,1,
BufRd.kr(1, c.bufnum,
MouseX.kr(0, BufFrames.kr(c.bufnum) - 1), 0, 1),
BufRd.kr(1, c.bufnum,
MouseX.kr(1, BufFrames.kr(c.bufnum)), 0, 1),
BufRd.kr(1,c.bufnum,
MouseX.kr(0, BufFrames.kr(c.bufnum) - 1), 0, 1)), 0, 1);
BufRd.kr(1, c,
MouseX.kr(0, BufFrames.kr(c) - 1), 0, 1),
BufRd.kr(1, c,
MouseX.kr(1, BufFrames.kr(c)), 0, 1),
BufRd.kr(1,c,
MouseX.kr(0, BufFrames.kr(c) - 1), 0, 1)), 0, 1);
}.play;
)
::
@ -105,7 +107,7 @@ c = Buffer.new(s);
)
// process with a given filterSize
FluidBufNoveltySlice.process(s,b.bufnum, indBufNum: c.bufnum, kernSize:31, thresh:0.35, filtSize:1)
FluidBufNoveltySlice.process(s,b, indices: c, kernelSize:31, threshold:0.3, filterSize:0)
//check the number of slices: it is the number of frames in the transBuf minus the boundary index.
c.query;
@ -113,11 +115,11 @@ c.query;
//play slice number 2
(
{
BufRd.ar(1, b.bufnum,
BufRd.ar(1, b,
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),
BufRd.kr(1, c, DC.kr(2), 0, 1),
BufRd.kr(1, c, DC.kr(3), 0, 1),
(BufRd.kr(1, c, DC.kr(3)) - BufRd.kr(1, c, DC.kr(2), 0, 1) + 1) / s.sampleRate),
0,1);
}.play;
)
@ -129,4 +131,31 @@ c.query;
// 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.
::
::
STRONG::A stereo buffer example.::
CODE::
// make a stereo buffer
b = Buffer.alloc(s,88200,2);
// add some stereo clicks and listen to them
((0..3)*22050+11025).do({|item,index| b.set(item+(index%2), 1.0)});
b.play
// create a new buffer as destinations
c = Buffer.new(s);
//run the process on them
(
// with basic params
Routine{
t = Main.elapsedTime;
FluidBufNoveltySlice.process(s,b, indices: c, threshold:0.6);
(Main.elapsedTime - t).postln;
}.play
)
// list the indicies of detected attacks - the two input channels have been summed
c.getn(0,c.numFrames,{|item|item.postln;})
::

@ -0,0 +1,140 @@
TITLE:: FluidBufOnsetSlice
SUMMARY:: Spectral Difference-Based Audio Buffer Slicer
CATEGORIES:: Libraries>FluidDecomposition
RELATED:: Guides/FluCoMa, Guides/FluidDecomposition
DESCRIPTION::
This class implements many spectral-based onset detection functions, most of them taken from the literature. (http://www.dafx.ca/proceedings/papers/p_133.pdf) Some are already available in SuperCollider's LINK::Classes/Onsets:: object yet not as offline processes. It 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).::
The process will return a buffer which contains indices (in sample) of estimated starting points of different slices.
CLASSMETHODS::
METHOD:: process
This is the method that calls for the slicing to be calculated on a given source buffer.
ARGUMENT:: server
The server on which the buffers to be processed are allocated.
ARGUMENT:: source
The index of the buffer to use as the source material to be sliced through novelty identification. The different channels of multichannel buffers will be summed.
ARGUMENT:: startFrame
Where in the srcBuf should the slicing process start, in sample.
ARGUMENT:: numFrames
How many frames should be processed.
ARGUMENT:: startChan
For multichannel sources, which channel should be processed.
ARGUMENT:: numChans
For multichannel sources, how many channel should be summed.
ARGUMENT:: indices
The index of the buffer where the indices (in sample) of the estimated starting points of slices will be written. The first and last points are always the boundary points of the analysis.
ARGUMENT:: function
The function used to derive a difference curve between spectral frames. It can be any of the following:
TABLE::
##0 || Energy || thresholds on (sum of squares of magnitudes / nBins) (like Onsets \power)
##1 || HFC || thresholds on (sum of (squared magnitudes * binNum) / nBins)
##2 || SpectralFlux || thresholds on (diffence in magnitude between consecutive frames, half rectified)
##3 || MKL || thresholds on (sum of log of magnitude ratio per bin) (or equivalent: sum of difference of the log magnitude per bin) (like Onsets \mkl)
##4 || IS || (WILL PROBABLY BE REMOVED) Itakura - Saito divergence (see literature)
##5 || Cosine || thresholds on (cosine distance between comparison frames)
##6 || PhaseDev || takes the past 2 frames, projects to the current, as anticipated if it was a steady state, then compute the sum of the differences, on which it thresholds (like Onsets \phase)
##7 || WPhaseDev || same as PhaseDev, but weighted by the magnitude in order to remove chaos noise floor (like Onsets \wphase)
##8 || ComplexDev || same as PhaseDev, but in the complex domain - the anticipated amp is considered steady, and the phase is projected, then a complex subtraction is done with the actual present frame. The sum of magnitudes is used to threshold (like Onsets \complex)
##9 || RComplexDev || same as above, but rectified (like Onsets \rcomplex)
::
ARGUMENT:: threshold
The thresholding of a new slice. Value ranges are different for each function, from 0 upwards.
ARGUMENT:: minSliceLength
The minimum duration of a slice in number of hopSize.
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:: frameDelta
For certain functions (HFC, SpectralFlux, MKL, Cosine), the distance does not have to be computed between consecutive frames. By default (0) it is, otherwise this sets the distane between the comparison window in samples.
ARGUMENT:: windowSize
The window size. As spectral differencing 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
ARGUMENT:: hopSize
The window hop size. As spectral differencing relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts. The -1 default value will default to half of windowSize (overlap of 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 -1 default value will default to windowSize.
ARGUMENT:: action
A Function to be evaluated once the offline process has finished and indices instance variables have been updated on the client side. The function will be passed indices as an argument.
RETURNS::
Nothing, as the various destination buffers are declared in the function call.
EXAMPLES::
CODE::
(
//prep some buffers
b = Buffer.read(s,File.realpath(FluidBufOnsetSlice.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Nicol-LoopE-M.wav");
c = Buffer.new(s);
)
(
// with basic params
Routine{
t = Main.elapsedTime;
FluidBufOnsetSlice.process(s,b, indices: c, threshold:0.5);
(Main.elapsedTime - t).postln;
}.play
)
//check the number of slices: it is the number of frames in the transBuf minus the boundary index.
c.query;
//loops over a splice with the MouseX
(
{
BufRd.ar(1, b,
Phasor.ar(0,1,
BufRd.kr(1, c,
MouseX.kr(0, BufFrames.kr(c) - 1), 0, 1),
BufRd.kr(1, c,
MouseX.kr(1, BufFrames.kr(c)), 0, 1),
BufRd.kr(1,c,
MouseX.kr(0, BufFrames.kr(c) - 1), 0, 1)), 0, 1);
}.play;
)
::
STRONG::A stereo buffer example.::
CODE::
// make a stereo buffer
b = Buffer.alloc(s,88200,2);
// add some stereo clicks and listen to them
((0..3)*22050+11025).do({|item,index| b.set(item+(index%2), 1.0)})
b.play
// create a new buffer as destinations
c = Buffer.new(s);
//run the process on them
(
// with basic params
Routine{
t = Main.elapsedTime;
FluidBufOnsetSlice.process(s,b, indices: c, threshold:0.1);
(Main.elapsedTime - t).postln;
}.play
)
// list the indicies of detected attacks - the two input channels have been summed
c.getn(0,c.numFrames,{|item|item.postln;})
::

@ -0,0 +1,175 @@
TITLE:: FluidBufPitch
SUMMARY:: A Selection of Pitch Descriptors on a Buffer
CATEGORIES:: Libraries>FluidDecomposition
RELATED:: Guides/FluCoMa, Guides/FluidDecomposition, Classes/SpecCentroid, Classes/SpecFlatness, Classes/SpecCentroid, Classes/SpecPcile
DESCRIPTION::
This class implements three popular pitch descriptors, computed as frequency and the confidence in its value. It 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).::
The process will return a multichannel buffer with two channels per input channel, one for pitch and one for the pitch tracking confidence. Each sample represents a value, which is every hopSize. Its sampling rate is sourceSR / hopSize.
CLASSMETHODS::
METHOD:: process
This is the method that calls for the pitch descriptor to be calculated on a given source buffer.
ARGUMENT:: server
The server on which the buffers to be processed are allocated.
ARGUMENT:: source
The index of the buffer to use as the source material to be pitch-tracked. The different channels of multichannel buffers will be processing sequentially.
ARGUMENT:: startFrame
Where in the srcBuf should the process start, in sample.
ARGUMENT:: numFrames
How many frames should be processed.
ARGUMENT:: startChan
For multichannel srcBuf, which channel should be processed first.
ARGUMENT:: numChans
For multichannel srcBuf, how many channel should be processed.
ARGUMENT:: features
The destination buffer for the pitch descriptors.
ARGUMENT:: algorithm
The algorithm to estimate the pitch. The options are:
TABLE::
## 0 || Cepstrum: Returns a pitch estimate as the location of the second highest peak in the Cepstrum of the signal (after DC).
## 1 || Harmonic Product Spectrum: Implements the Harmonic Product Spectrum algorithm for pitch detection . See e.g. FOOTNOTE:: A. Lerch, "An Introduction to Audio Content Analysis: Applications in Signal Processing and Music Informatics." John Wiley & Sons, 2012.https://onlinelibrary.wiley.com/doi/book/10.1002/9781118393550 ::
## 2 || YinFFT: Implements the frequency domain version of the YIN algorithm, as described in FOOTNOTE::P. M. Brossier, "Automatic Annotation of Musical Audio for Interactive Applications.” QMUL, London, UK, 2007. :: See also https://essentia.upf.edu/documentation/reference/streaming_PitchYinFFT.html
::
ARGUMENT:: windowSize
The window size. As sinusoidal 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
ARGUMENT:: hopSize
The window hop size. As sinusoidal estimation relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts.
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.
ARGUMENT:: action
A Function to be evaluated once the offline process has finished and all Buffer's instance variables have been updated on the client side. The function will be passed [features] as an argument.
RETURNS::
Nothing, as the destination buffer is declared in the function call.
EXAMPLES::
code::
// create a buffer with a short clicking sinusoidal burst (220Hz) starting at frame 8192 for 1024 frames
(
b = Buffer.sendCollection(s, (Array.fill(8192,{0}) ++ (Signal.sineFill(1203,[0,0,0,0,0,1],[0,0,0,0,0,0.5pi]).takeThese({|x,i|i>1023})) ++ Array.fill(8192,{0})));
c = Buffer.new(s);
)
// listen to the source and look at the buffer
b.play; b.plot;
// run the process with basic parameters
(
Routine{
t = Main.elapsedTime;
FluidBufPitch.process(s, b, features: c);
(Main.elapsedTime - t).postln;
}.play
)
// look at the analysis
c.plot(separately:true)
// The values are interleaved [pitch,confidence] in the buffer as they are on 2 channels: to get to the right frame, divide the SR of the input by the hopSize, then multiply by 2 because of the channel interleaving
// here we are querying from one frame before (the signal starts at 8192, which is frame 16 (8192/512), therefore starting the query at frame 15, which is index 30.
c.getn(30,10,{|x|x.postln})
// observe that the first frame is silent, as expected. The next frame's confidence is low-ish, because the window is half full (window of 1024, overlap of 512). Then a full window is analysed, with strong confidence. Then another half full window, then silence, as expected.
::
STRONG::A stereo buffer example.::
CODE::
// load two very different files
(
b = Buffer.read(s,File.realpath(FluidBufPitch.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-SA-UprightPianoPedalWide.wav");
c = Buffer.read(s,File.realpath(FluidBufPitch.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-AcousticStrums-M.wav");
)
// composite one on left one on right as test signals
FluidBufCompose.process(s, c, numFrames:b.numFrames, startFrame:555000,destStartChan:1, destination:b)
b.play
// create a buffer as destinations
c = Buffer.new(s);
//run the process on them
(
Routine{
t = Main.elapsedTime;
FluidBufPitch.process(s, b, features: c);
(Main.elapsedTime - t).postln;
}.play
)
// look at the buffer: [pitch,confidence] for left then [pitch,confidence] for right
c.plot(separately:true)
::
STRONG::A musical example.::
code::
// create some buffers
(
b = Buffer.read(s,File.realpath(FluidBufPitch.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-ASWINE-ScratchySynth-M.wav");
c = Buffer.new(s);
)
// run the process with basic parameters and retrieve the array in the langage side
(
Routine{
t = Main.elapsedTime;
FluidBufPitch.process(s, b, features: c,action:{c.loadToFloatArray(action: {|x| d = x.reshape((x.size()/2).asInt, 2)})});
(Main.elapsedTime - t).postln;
}.play
)
//look at the retrieved formatted array of [pitch,confidence] values
d.postln
//iterate and make an array of the indices which are fitting the conditions
(
e = Array.new;
d.do({
arg val, i;
if ((val[0] > 500) && (val[1] > 0.666)) {e = e.add(i)}; // if pitch is greater than 500Hz and confidence higher than 0.666, keep the index
});
)
e.postln;
//granulate only the frames that are in our buffer
// We need to convert our indices to frame start. Their position was (index * hopSize) - (windowSize) in FluidBufPitch
f = e.collect({arg i; (i * 512) - 1024});
// define a basic grain synth
(
SynthDef(\grain,
{ arg out=0, buf =0 , ind = 0, pan = 0;
var env;
env = EnvGen.kr(Env.new([0,1,0],[512/s.sampleRate].dup,\sine), doneAction: Done.freeSelf);
Out.ar(out, Pan2.ar(PlayBuf.ar(1,buf,startPos:ind),pan));
}).add;
)
// start the sequence
(
a = Pxrand(f, inf).asStream;
Routine({
loop({
Synth(\grain, [\buf, b, \ind, a.next, \pan, (2.0.rand - 1)]);
(256/s.sampleRate).wait;
})
}).play;
)
::

@ -0,0 +1,169 @@
TITLE:: FluidBufRTNoveltySlice
SUMMARY:: Buffer-Based Novelty-Based Slicer
CATEGORIES:: Libraries>FluidDecomposition, UGens>Buffer
RELATED:: Guides/FluCoMa, Guides/FluidDecomposition
DESCRIPTION::
This class implements a non-real-time slicer using an algorithm assessing novelty in the signal to estimate the slicing points. A novelty curve is being derived from running a kernel across the diagonal of the similarity matrix, and looking for peak of changes. It implements the seminal results published in 'Automatic Audio Segmentation Using a Measure of Audio Novelty' by J Foote. It 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).::
The process will return a buffer which contains indices (in sample) of estimated starting points of different slices.
CLASSMETHODS::
METHOD:: process
This is the method that calls for the slicing to be calculated on a given source buffer.
ARGUMENT:: server
The server on which the buffers to be processed are allocated.
ARGUMENT:: source
The index of the buffer to use as the source material to be sliced through novelty identification. The different channels of multichannel buffers will be summed.
ARGUMENT:: startFrame
Where in the srcBuf should the slicing process start, in sample.
ARGUMENT:: numFrames
How many frames should be processed.
ARGUMENT:: startChan
For multichannel srcBuf, which channel should be processed.
ARGUMENT:: numChans
For multichannel srcBuf, how many channel should be summed.
ARGUMENT:: indices
The index of the buffer where the indices (in sample) of the estimated starting points of slices will be written. The first and last points are always the boundary points of the analysis.
ARGUMENT:: feature
The feature on which novelty is computed.
table::
##0 || Spectrum || todo
##1 || MFCC || todo
##2 || Pitch || todo
##3 || Loudness || todo
::
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:: threshold
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:: windowSize
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
ARGUMENT:: hopSize
The window hop size. As novelty estimation relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts.
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.
ARGUMENT:: action
A Function to be evaluated once the offline process has finished and indices instance variables have been updated on the client side. The function will be passed indices as an argument.
RETURNS::
Nothing, as the various destination buffers are declared in the function call.
EXAMPLES::
code::
// load some buffers
(
b = Buffer.read(s,File.realpath(FluidBufRTNoveltySlice.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-AcousticStrums-M.wav");
c = Buffer.new(s);
)
(
// with basic params
Routine{
t = Main.elapsedTime;
FluidBufRTNoveltySlice.process(s,b, indices: c, threshold:0.6);
(Main.elapsedTime - t).postln;
}.play
)
//check the number of slices: it is the number of frames in the transBuf minus the boundary index.
c.query;
//loops over a splice with the MouseX
(
{
BufRd.ar(1, b,
Phasor.ar(0,1,
BufRd.kr(1, c,
MouseX.kr(0, BufFrames.kr(c) - 1), 0, 1),
BufRd.kr(1, c,
MouseX.kr(1, BufFrames.kr(c)), 0, 1),
BufRd.kr(1,c,
MouseX.kr(0, BufFrames.kr(c) - 1), 0, 1)), 0, 1);
}.play;
)
::
STRONG::Examples of the impact of the filterSize::
CODE::
// load some buffers
(
b = Buffer.read(s,File.realpath(FluidBufRTNoveltySlice.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-AcousticStrums-M.wav");
c = Buffer.new(s);
)
// process with a given filterSize
FluidBufRTNoveltySlice.process(s,b, indices: c, kernSize:31, threshold:0.3, 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,
Line.ar(
BufRd.kr(1, c, DC.kr(2), 0, 1),
BufRd.kr(1, c, DC.kr(3), 0, 1),
(BufRd.kr(1, c, DC.kr(3)) - BufRd.kr(1, c, 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 = 1), 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.
::
STRONG::A stereo buffer example.::
CODE::
// make a stereo buffer
b = Buffer.alloc(s,88200,2);
// add some stereo clicks and listen to them
((0..3)*22050+11025).do({|item,index| b.set(item+(index%2), 1.0)});
b.play
// create a new buffer as destinations
c = Buffer.new(s);
//run the process on them
(
// with basic params
Routine{
t = Main.elapsedTime;
FluidBufRTNoveltySlice.process(s,b, indices: c, threshold:0.6);
(Main.elapsedTime - t).postln;
}.play
)
// list the indicies of detected attacks - the two input channels have been summed
c.getn(0,c.numFrames,{|item|item.postln;})
::

@ -21,31 +21,31 @@ METHOD:: process
ARGUMENT:: server
The server on which the buffers to be processed are allocated.
ARGUMENT:: srcBufNum
The index of the buffer to use as the source material to be decomposed through the NMF process. The different channels of multichannel buffers will be processing sequentially.
ARGUMENT:: source
The index of the buffer to use as the source material to be decomposed through the sinusoidal modelling process. The different channels of multichannel buffers will be processing sequentially.
ARGUMENT:: startAt
ARGUMENT:: startFrame
Where in the srcBuf should the process start, in sample.
ARGUMENT:: nFrames
ARGUMENT:: numFrames
How many frames should be processed.
ARGUMENT:: startChan
For multichannel srcBuf, which channel should be processed first.
ARGUMENT:: nChans
ARGUMENT:: numChans
For multichannel srcBuf, how many channel should be processed.
ARGUMENT:: sineBufNum
ARGUMENT:: sines
The index of the buffer where the extracted sinusoidal component will be reconstructed.
ARGUMENT:: resBufNum
ARGUMENT:: residual
The index of the buffer where the residual of the sinusoidal component will be reconstructed.
ARGUMENT:: bandwidth
The width in bins of the fragment of the fft window that is considered a normal deviation for a potential continuous sinusoidal track. It has an effect on CPU cost: the widest is more accurate but more computationally expensive.
ARGUMENT:: thresh
ARGUMENT:: threshold
The normalised threshold, between 0 an 1, to consider a peak as a sinusoidal component from the normalized cross-correlation.
ARGUMENT:: minTrackLen
@ -57,15 +57,18 @@ ARGUMENT:: magWeight
ARGUMENT:: freqWeight
The weight of the frequency proximity of a peak when trying to associate it to an existing track (relative to magWeight - suggested between 0 to 1)
ARGUMENT:: winSize
ARGUMENT:: windowSize
The window size. As sinusoidal 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
ARGUMENT:: hopSize
The window hope size. As sinusoidal estimation 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 hop size. As sinusoidal estimation relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts.
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.
ARGUMENT:: action
A Function to be evaluated once the offline process has finished and all Buffer's instance variables have been updated on the client side. The function will be passed [sines, residual] as an argument.
RETURNS::
Nothing, as the various destination buffers are declared in the function call.
@ -83,19 +86,45 @@ d = Buffer.new(s);
(
Routine{
t = Main.elapsedTime;
FluidBufSines.process(s, b.bufnum, sineBufNum: c.bufnum, resBufNum:d.bufnum);
s.sync;
FluidBufSines.process(s, b, sines: c, residual:d);
(Main.elapsedTime - t).postln;
}.play
)
// listen to each component
c.query;
c.play;
d.query;
d.play;
//nullsumming tests
{(PlayBuf.ar(1, c.bufnum)) + (PlayBuf.ar(1,d.bufnum)) - (PlayBuf.ar(1,b.bufnum,doneAction:2))}.play
{(PlayBuf.ar(1, c)) + (PlayBuf.ar(1,d)) - (PlayBuf.ar(1,b,doneAction:2))}.play
::
STRONG::A stereo buffer example.::
CODE::
// load two very different files
(
b = Buffer.read(s,File.realpath(FluidBufSines.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-SA-UprightPianoPedalWide.wav");
c = Buffer.read(s,File.realpath(FluidBufSines.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-AcousticStrums-M.wav");
)
// composite one on left one on right as test signals
FluidBufCompose.process(s, c, numFrames:b.numFrames, startFrame:555000,destStartChan:1, destination:b)
b.play
// create 2 new buffers as destinations
d = Buffer.new(s); e = Buffer.new(s);
//run the process on them
(
Routine{
t = Main.elapsedTime;
FluidBufSines.process(s, b, sines: d, residual:e, threshold:0.3);
(Main.elapsedTime - t).postln;
}.play
)
//listen: stereo preserved!
d.play
e.play
::

@ -0,0 +1,119 @@
TITLE:: FluidBufSpectralShape
SUMMARY:: Seven Spectral Shape Descriptors on a Buffer
CATEGORIES:: Libraries>FluidDecomposition
RELATED:: Guides/FluCoMa, Guides/FluidDecomposition, Classes/SpecCentroid, Classes/SpecFlatness, Classes/SpecCentroid, Classes/SpecPcile
DESCRIPTION::
This class implements seven of the most popular spectral shape descriptors, computed on a linear scale for both amplitude and frequency. It 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).::
The descriptors are:
LIST::
##the four first statistical moments (https://en.wikipedia.org/wiki/Moment_(mathematics) ), more commonly known as:
LIST::
## the spectral centroid (1) in Hz. This is the point that splits the spectrum in 2 halves of equal energy. It is the weighted average of the magnitude spectrum.
## the spectral spread (2) in Hz. This is the standard deviation of the spectrum envelop, or the average of the distance to the centroid.
## the normalised skewness (3) as ratio. This indicates how tilted is the spectral curve in relation to the middle of the spectral frame, i.e. half of the Nyquist frequency. If it is below that frequency, i.e. the central bin of the magnitude spectrum, it is positive.
## the normalised kurtosis (4) as ratio. This indicates how focused is the spectral curve. If it is peaky, it is high.
::
## the rolloff (5) in Hz. This indicates the frequency under which 95% of the energy is included.
## the flatness (6) in dB. This is the ratio of geometric mean of the magnitude, over the arithmetic mean of the magnitudes. It yields a very approximate measure on how noisy a signal is.
## the crest (7) in dB. This is the ratio of the loudest magnitude over the RMS of the whole frame. A high number is an indication of a loud peak poking out from the overal spectral curve.
The drawings in Peeters 2003 (http://recherche.ircam.fr/anasyn/peeters/ARTICLES/Peeters_2003_cuidadoaudiofeatures.pdf) are useful, as are the commented examples below. For the mathematically-inclined reader, the tutorials and code offered here (https://www.audiocontentanalysis.org/) are interesting to further the understanding.
::
The process will return a multichannel buffer with the seven channels per input channel, each containing the 7 shapes. Each sample represents a value, which is every hopSize.
CLASSMETHODS::
METHOD:: process
This is the method that calls for the spectral shape descriptors to be calculated on a given source buffer.
ARGUMENT:: server
The server on which the buffers to be processed are allocated.
ARGUMENT:: source
The index of the buffer to use as the source material to be described through the various descriptors. The different channels of multichannel buffers will be processing sequentially.
ARGUMENT:: startFrame
Where in the srcBuf should the process start, in sample.
ARGUMENT:: numFrames
How many frames should be processed.
ARGUMENT:: startChan
For multichannel srcBuf, which channel should be processed first.
ARGUMENT:: numChans
For multichannel srcBuf, how many channel should be processed.
ARGUMENT:: features
The destination buffer for the 7 spectral features describing the spectral shape.
ARGUMENT:: windowSize
The window size. As spectral shape 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
ARGUMENT:: hopSize
The window hop size. As spectral shape estimation relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts.
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.
ARGUMENT:: action
A Function to be evaluated once the offline process has finished and all Buffer's instance variables have been updated on the client side. The function will be passed [features] as an argument.
RETURNS::
Nothing, as the destination buffer is declared in the function call.
EXAMPLES::
code::
// create some buffers
(
b = Buffer.read(s,File.realpath(FluidBufSpectralShape.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Nicol-LoopE-M.wav");
c = Buffer.new(s);
)
// run the process with basic parameters
(
Routine{
t = Main.elapsedTime;
FluidBufSpectralShape.process(s, b, features: c);
(Main.elapsedTime - t).postln;
}.play
)
// listen to the source and look at the buffer
b.play;
c.plot(separately:true)
::
STRONG::A stereo buffer example.::
CODE::
// load two very different files
(
b = Buffer.read(s,File.realpath(FluidBufSpectralShape.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-SA-UprightPianoPedalWide.wav");
c = Buffer.read(s,File.realpath(FluidBufSpectralShape.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-AcousticStrums-M.wav");
)
// composite one on left one on right as test signals
FluidBufCompose.process(s, c, numFrames:b.numFrames, startFrame:555000,destStartChan:1, destination:b)
b.play
// create a buffer as destinations
c = Buffer.new(s);
//run the process on them
(
Routine{
t = Main.elapsedTime;
FluidBufSpectralShape.process(s, b, features: c);
(Main.elapsedTime - t).postln;
}.play
)
// look at the buffer: 7shapes for left, then 7 shapes for right
c.plot(separately:true)
::

@ -0,0 +1,139 @@
TITLE:: FluidBufStats
SUMMARY:: Computing Statistics on Buffers as Series.
CATEGORIES:: Libraries>FluidDecomposition, UGens>Buffer
RELATED:: Guides/FluCoMa, Guides/FluidDecomposition
DESCRIPTION::
This class implements non-real-time statistical analysis on buffer channels. Typically, a buffer would hold various time series (i.e. descriptors over time), and link::Classes/FluidBufStats:: allows this series to be described statistically. It 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).::
The process returns a buffer where each channel of the STRONG::source:: buffer has been reduced to 7 statistics: mean, standard deviation, skewness, kurtosis, followed by 3 percentiles, by default the minimum value, the median, and the maximum value. Moreover, it is possible to request the same 7 stats to be applied to derivative of the input. These are useful to describe statistically the rate of change of the time series. The STRONG::stats:: buffer will grow accordingly, yielding the seven same statistical description of the n requested derivatives. Therefore, the STRONG::stats:: buffer will have as many channel as the input buffer, and as many frames as 7 times the requested STRONG::numDerivs::.
CLASSMETHODS::
METHOD:: process
This is the method that calls for the slicing to be calculated on a given source buffer.
ARGUMENT:: server
The server on which the buffers to be processed are allocated.
ARGUMENT:: source
The index of the buffer to use as the source material to be processed. The different channels of multichannel buffers will be considered independently as time series.
ARGUMENT:: startFrame
The starting point (in samples) from which to copy in the source buffer.
ARGUMENT:: numFrames
The duration (in samples) to copy from the source buffer. The default (-1) copies the full lenght of the buffer.
ARGUMENT:: startChan
The first channel from which to copy in the source buffer.
ARGUMENT:: numChans
The number of channels from which to copy in the source buffer. This parameter will wrap around the number of channels in the source buffer. The default (-1) copies all of the buffer's channel.
ARGUMENT:: stats
The index of the buffer to write the statistics to. Each channel is the fruit of the statistical computations on the same channel number of the source buffer.
ARGUMENT:: numDerivs
The number of derivatives of the original time series for the statistic to be computed on. By default, none are computed. This will influence the number of frames the stats buffer will have.
ARGUMENT:: low
The rank requested for the first percentile value. By default, it is percentile 0.0, which is the minimum of the given channel of the source buffer.
ARGUMENT:: middle
The rank requested for the second percentile value. By default, it is percentile 50.0, which is the median of the given channel of the source buffer.
ARGUMENT:: high
The rank requested for the third percentile value. By default, it is percentile 100.0, which is the maximum of the given channel of the source buffer.
ARGUMENT:: action
A Function to be evaluated once the offline process has finished and indices instance variables have been updated on the client side. The function will be passed stats as an argument.
RETURNS::
Nothing, as the destination buffer is declared in the function call.
EXAMPLES::
STRONG::A didactic example::
CODE::
// make a buffer of known lenght
b = Buffer.alloc(s,101);
// add known values - here, a ramp up
b.setn(0, Array.fill(101,{|i|i / 100}));
// create a new buffer as destinations
c = Buffer.new(s);
//run the process on them
(
Routine{
t = Main.elapsedTime;
FluidBufStats.process(s, b, stats:c, numDerivs:1);
(Main.elapsedTime - t).postln;
}.play
)
// list the statistics. The first seven are for the source buffer values themselves, the last seven for the first derivative of the source buffer.
c.getn(0,c.numFrames,{|item|item.postln;})
// replace the source values by a ramp down
b.setn(0, Array.fill(101,{|i| 1 - (i / 100)}));
// run the process and read the values
FluidBufStats.process(s, b, stats:c, numDerivs:1, action:{c.getn(0,c.numFrames,{|item|item.postln;})});
// replace the source values by halfsine
b.setn(0, Array.fill(101,{|i| (i * pi/ 100).sin}));
b.plot
// run the process and read the values
FluidBufStats.process(s, b, stats:c, numDerivs:1, action:{c.getn(0,c.numFrames,{|item|item.postln;})});
// replace the source values by partial halfsine
b.setn(0, Array.fill(101,{|i| (i * pi/ 50).sin.max(0)}));
b.plot
// run the process and read the values
FluidBufStats.process(s, b, stats:c, numDerivs:1, action:{c.getn(0,c.numFrames,{|item|item.postln;})});
// replace the source values by positive white noise
b.setn(0, Array.fill(101,{1.0.rand}));
b.plot
// run the process and read the values
FluidBufStats.process(s, b, stats:c, numDerivs:1, action:{c.getn(0,c.numFrames,{|item|item.postln;})});
::
STRONG::A musical example::
CODE::
// todo: port the Max one
::
STRONG::Stereo Input Behaviour::
CODE::
// make a buffer of known lenght
b = Buffer.alloc(s,101,2);
// add known values - here, a ramp up on the left and negative random values on the right
b.setn(0, Array.fill(101,{|i|[i / 100,-1.0.rand]}).flat);
// plot to confirm
b.plot
// create a new buffer as destinations
c = Buffer.new(s);
// run the stats and send back the values
FluidBufStats.process(s, b, stats:c, numDerivs:1, action:{c.getn(0,c.numFrames * c.numChannels,{|item|d = item; d.postln})});
//looking at the result is not easy to grasp, since it is interleaved: first number is mean of L, second is mean of R, third is stddev of L, fourth is stddev or R
//this will make it tidier - the first value of each line is Left, the second is Right
d.reshape(14,2).do({|x,i|["mean\t\t","stddev\t\t","skew\t\t\t", "kurtosis\t", "min\t\t\t", "median\t\t", "max\t\t\t","d-mean\t","d-stddev\t","d-skew\t\t", "d-kurtosis", "d-min\t\t", "d-median\t", "d-max\t\t"].at(i).post;x.round(0.01).postln})
::

@ -18,22 +18,22 @@ METHOD:: process
ARGUMENT:: server
The server on which the buffers to be processed are allocated.
ARGUMENT:: srcBufNum
ARGUMENT:: source
The index of the buffer to use as the source material to be sliced through transient identification. The different channels of multichannel buffers will be summed.
ARGUMENT:: startAt
ARGUMENT:: startFrame
Where in the srcBuf should the slicing process start, in sample.
ARGUMENT:: nFrames
ARGUMENT:: numFrames
How many frames should be processed.
ARGUMENT:: startChan
For multichannel srcBuf, which channel should be processed.
ARGUMENT:: nChans
ARGUMENT:: numChans
For multichannel srcBuf, how many channel should be summed.
ARGUMENT:: transBufNum
ARGUMENT:: indices
The index of the buffer where the indices (in sample) of the estimated starting points of slices will be written. The first and last points are always the boundary points of the analysis.
ARGUMENT:: order
@ -54,15 +54,18 @@ ARGUMENT:: threshFwd
ARGUMENT:: threshBack
The threshold of the offset of the smoothed error function. As it proceeds backwards in time, it allows tight ending of the identification of the anomaly.
ARGUMENT:: winSize
ARGUMENT:: windowSize
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
ARGUMENT:: clumpLength
The window size in sample within which positive detections will be clumped together to avoid overdetecting in time.
ARGUMENT:: minSlice
ARGUMENT:: minSliceLength
The minimum duration of a slice in samples.
ARGUMENT:: action
A Function to be evaluated once the offline process has finished and indices instance variables have been updated on the client side. The function will be passed indices as an argument.
RETURNS::
Nothing, as the destination buffer is declared in the function call.
@ -76,12 +79,11 @@ b = Buffer.read(s,File.realpath(FluidBufTransientSlice.class.filenameSymbol).dir
c = Buffer.new(s);
)
// with basic parameters
// with basic parameters (wait for the computation time to appear)
(
Routine{
t = Main.elapsedTime;
FluidBufTransientSlice.process(s,b.bufnum, transBufNum:c.bufnum);
s.sync;
FluidBufTransientSlice.process(s,b, indices:c);
(Main.elapsedTime - t).postln;
}.play
)
@ -92,25 +94,53 @@ c.query;
//loops over a splice
(
{
BufRd.ar(1, b.bufnum,
BufRd.ar(1, b,
Phasor.ar(0,1,
BufRd.kr(1, c.bufnum,
MouseX.kr(0, BufFrames.kr(c.bufnum) - 1), 0, 1),
BufRd.kr(1, c.bufnum,
MouseX.kr(1, BufFrames.kr(c.bufnum)), 0, 1),
BufRd.kr(1,c.bufnum,
MouseX.kr(0, BufFrames.kr(c.bufnum) - 1), 0, 1)), 0, 1);
BufRd.kr(1, c,
MouseX.kr(0, BufFrames.kr(c) - 1), 0, 1),
BufRd.kr(1, c,
MouseX.kr(1, BufFrames.kr(c)), 0, 1),
BufRd.kr(1,c,
MouseX.kr(0, BufFrames.kr(c) - 1), 0, 1)), 0, 1);
}.play;
)
// with everything changed to make it much better, at the cost of computation time (only 10 seconds are processed here)
// with everything changed to make it much better, at the cost of computation time (only 10 seconds are processed here, again wait for the (longer) computation time to appear)
(
Routine{
t = Main.elapsedTime;
FluidBufTransientSlice.process(s,b.bufnum, 0, 220500, 0, 1, c.bufnum, 200, 2048, 1024, 1, 3, 1, 15, 30, 4410);
s.sync;
FluidBufTransientSlice.process(s,b, 0, 220500, 0, 1, c, 200, 2048, 1024, 1, 3, 1, 15, 30, 4410);
(Main.elapsedTime - t).postln;
}.play
)
// play with the same player above to hear the segmentation difference
::
STRONG::A stereo buffer example.::
CODE::
// make a stereo buffer
b = Buffer.alloc(s,88200,2);
// add some stereo clicks and listen to them
((0..3)*22050+11025).do({|item,index| b.set(item+(index%2), 1.0)})
b.play
// create a new buffer as destinations
c = Buffer.new(s);
//run the process on them
(
// with basic params
Routine{
t = Main.elapsedTime;
FluidBufTransientSlice.process(s,b, indices: c, threshFwd: 1.2);
(Main.elapsedTime - t).postln;
}.play
)
// list the indicies of detected attacks - the two input channels have been summed
c.getn(0,c.numFrames,{|item|item.postln;})
::

@ -23,25 +23,25 @@ This is the method that calls for the transient extraction to be performed on a
ARGUMENT:: server
The server on which the buffers to be processed are allocated.
ARGUMENT:: srcBufNum
ARGUMENT:: source
The index of the buffer to use as the source material to be decomposed through the NMF process. The different channels of multichannel buffers will be processing sequentially.
ARGUMENT:: startAt
ARGUMENT:: startFrame
Where in the srcBuf should the NMF process start, in sample.
ARGUMENT:: nFrames
ARGUMENT:: numFrames
How many frames should be processed.
ARGUMENT:: startChan
For multichannel srcBuf, which channel should be processed first.
ARGUMENT:: nChans
ARGUMENT:: numChans
For multichannel srcBuf, how many channel should be processed.
ARGUMENT:: transBufNum
ARGUMENT:: transients
The index of the buffer where the extracted transient component will be reconstructed.
ARGUMENT:: resBufNum
ARGUMENT:: residual
The index of the buffer where the estimated continuous component will be reconstructed.
ARGUMENT:: order
@ -62,12 +62,15 @@ ARGUMENT:: threshFwd
ARGUMENT:: threshBack
The threshold of the offset of the smoothed error function. As it proceeds backwards in time, it allows tight ending of the identification of the anomaly.
ARGUMENT:: winSize
ARGUMENT:: windowSize
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 positive.
ARGUMENT:: debounce
ARGUMENT:: clumpLength
The window size in sample within which positive detections will be clumped together to avoid overdetecting in time.
ARGUMENT:: action
A Function to be evaluated once the offline process has finished and all Buffer's instance variables have been updated on the client side. The function will be passed [transients, residual] as an argument.
RETURNS::
Nothing, as the various destination buffers are declared in the function call.
@ -85,29 +88,59 @@ d = Buffer.new(s);
(
Routine{
t = Main.elapsedTime;
FluidBufTransients.process(s,b.bufnum, transBufNum:c.bufnum, resBufNum:d.bufnum);
s.sync;
FluidBufTransients.process(s,b, transients:c, residual:d);
(Main.elapsedTime - t).postln;
}.play
);
// wait for the duration to appear in the post window as a cue that the computation is finished
c.query;
c.play;
d.query;
d.play;
//nullsumming tests
{(PlayBuf.ar(1,c.bufnum))+(PlayBuf.ar(1,d.bufnum))+(-1*PlayBuf.ar(1,b.bufnum,doneAction:2))}.play
{(PlayBuf.ar(1,c))+(PlayBuf.ar(1,d))+(-1*PlayBuf.ar(1,b,doneAction:2))}.play
// with everything changed to make it much better, at the cost of computation time (only 5 seconds are processed here)
(
Routine{
t = Main.elapsedTime;
FluidBufTransients.process(s,b.bufnum, 0, 220500, 0, 1, c.bufnum, d.bufnum, 200, 2048, 1024, 1, 3, 1, 15, 30);
s.sync;
FluidBufTransients.process(s,b, 0, 220500, 0, 1, c, d, 200, 2048, 1024, 1, 3, 1, 15, 30);
(Main.elapsedTime - t).postln;
}.play
)
// wait for the duration to appear in the post window as a cue that the computation is finished
c.play;
d.play;
::
STRONG::A stereo buffer example.::
CODE::
// load two very different files
(
b = Buffer.read(s,File.realpath(FluidBufTransients.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-SynthTwoVoices-M.wav");
c = Buffer.read(s,File.realpath(FluidBufTransients.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-AcousticStrums-M.wav");
)
// composite one on left one on right as test signals
FluidBufCompose.process(s, c, numFrames:b.numFrames, startFrame:555000, destStartChan:1, destination:b)
b.play
// create 2 new buffers as destinations
d = Buffer.new(s); e = Buffer.new(s);
//run the process on them
(
Routine{
t = Main.elapsedTime;
FluidBufTransients.process(s, b, transients: d, residual:e, threshFwd:1.2, clumpLength:40);
(Main.elapsedTime - t).postln;
}.play
)
//listen: stereo preserved!
d.play
e.play
::

@ -24,7 +24,7 @@ RETURNS::
EXAMPLES::
Summing with the inverse (gain of -1) with a delay of the latency gives us CPU-expensive silence.
Summing with the inverse (gain of -1) gives us CPU-expensive silence.
CODE::
{ var source = PinkNoise.ar(0.1); source + FluidGain.ar(source,-1); }.play
::

@ -12,7 +12,7 @@ Driedger, Jonathan, Meinard Uller, and Sascha Disch. 2014. Extending Harmonic
The algorithm takes an audio in, and divides it into two or three outputs, depending on the mode: LIST::
## an harmonic component;
## a percussive component;
## a residual of the previous two if the flag is set to inter-dependant thresholds. See the modeFlag below.::
## a residual of the previous two if the flag is set to inter-dependant thresholds. See the maskingMode below.::
It 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).
@ -27,73 +27,70 @@ METHOD:: ar
ARGUMENT:: in
The input to be processed.
ARGUMENT:: hFiltSize
ARGUMENT:: harmFilterSize
The size, in spectral frames, of the median filter for the harmonic component. Must be an odd number, >= 3.
ARGUMENT:: pFiltSize
ARGUMENT:: percFilterSize
The size, in spectral bins, of the median filter for the percussive component. Must be an odd number, >=3
ARGUMENT:: modeFlag
ARGUMENT:: maskingMode
The way the masking is applied to the original spectrogram. (0,1,2)
table::
## 0 || The traditional soft mask used in Fitzgerald's original method of 'Wiener-inspired' filtering. Complimentary, soft masks are made for the harmonic and percussive parts by allocating some fraction of a point in time-frequency to each. This provides the fewest artefacts, but the weakest separation. The two resulting buffers will sum to exactly the original material.
## 1 || Relative mode - Better separation, with more artefacts. The harmonic mask is constructed using a binary decision, based on whether a threshold is exceeded at a given time-frequency point (these are set using htf1, hta1, htf2, hta2, see below). The percussive mask is then formed as the inverse of the harmonic one, meaning that as above, the two components will sum to the original sound.
## 1 || Relative mode - Better separation, with more artefacts. The harmonic mask is constructed using a binary decision, based on whether a threshold is exceeded at a given time-frequency point (these are set using harmThreshFreq1, harmThreshAmp1, harmThreshFreq2, harmThreshAmp2, see below). The percussive mask is then formed as the inverse of the harmonic one, meaning that as above, the two components will sum to the original sound.
## 2 || Inter-dependent mode - Thresholds can be varied independently, but are coupled in effect. Binary masks are made for each of the harmonic and percussive components, and the masks are converted to soft at the end so that everything null sums even if the params are independent, that is what makes it harder to control. These aren't guranteed to cover the whole sound; in this case the 'leftovers' will placed into a third buffer.
::
ARGUMENT:: htf1
ARGUMENT:: harmThreshFreq1
In modes 1 and 2, the frequency of the low part of the threshold for the harmonic filter (0-1)
ARGUMENT:: hta1
In modes 1 and 2, the threshold of the low part for the harmonic filter. That threshold applies to all frequencies up to htf1: how much more powerful (in dB) the harmonic median filter needs to be than the percussive median filter for this bin to be counted as harmonic.
ARGUMENT:: harmThreshAmp1
In modes 1 and 2, the threshold of the low part for the harmonic filter. That threshold applies to all frequencies up to harmThreshFreq1: how much more powerful (in dB) the harmonic median filter needs to be than the percussive median filter for this bin to be counted as harmonic.
ARGUMENT:: htf2
ARGUMENT:: harmThreshFreq2
In modes 1 and 2, the frequency of the hight part of the threshold for the harmonic filter. (0-1)
ARGUMENT:: hta2
In modes 1 and 2, the threshold of the high part for the harmonic filter. That threshold applies to all frequencies above htf2. The threshold between htf1 and htf2 is interpolated between hta1 and hta2. How much more powerful (in dB) the harmonic median filter needs to be than the percussive median filter for this bin to be counted as harmonic.
ARGUMENT:: harmThreshAmp2
In modes 1 and 2, the threshold of the high part for the harmonic filter. That threshold applies to all frequencies above harmThreshFreq2. The threshold between harmThreshFreq1 and harmThreshFreq2 is interpolated between harmThreshAmp1 and harmThreshAmp2. How much more powerful (in dB) the harmonic median filter needs to be than the percussive median filter for this bin to be counted as harmonic.
ARGUMENT:: ptf1
ARGUMENT:: percThreshFreq1
In mode 2, the frequency of the low part of the threshold for the percussive filter. (0-1)
ARGUMENT:: pta1
In mode 2, the threshold of the low part for the percussive filter. That threshold applies to all frequencies up to ptf1. How much more powerful (in dB) the percussive median filter needs to be than the harmonic median filter for this bin to be counted as percussive.
ARGUMENT:: percThreshAmp1
In mode 2, the threshold of the low part for the percussive filter. That threshold applies to all frequencies up to percThreshFreq1. How much more powerful (in dB) the percussive median filter needs to be than the harmonic median filter for this bin to be counted as percussive.
ARGUMENT:: ptf2
ARGUMENT:: percThreshFreq2
In mode 2, the frequency of the hight part of the threshold for the percussive filter. (0-1)
ARGUMENT:: pta2
In mode 2, the threshold of the high part for the percussive filter. That threshold applies to all frequencies above ptf2. The threshold between ptf1 and ptf2 is interpolated between pta1 and pta2. How much more powerful (in dB) the percussive median filter needs to be than the harmonic median filter for this bin to be counted as percussive.
ARGUMENT:: percThreshAmp2
In mode 2, the threshold of the high part for the percussive filter. That threshold applies to all frequencies above percThreshFreq2. The threshold between percThreshFreq1 and percThreshFreq2 is interpolated between percThreshAmp1 and percThreshAmp2. How much more powerful (in dB) the percussive median filter needs to be than the harmonic median filter for this bin to be counted as percussive.
ARGUMENT:: winSize
ARGUMENT:: windowSize
The window size. As sinusoidal 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
ARGUMENT:: hopSize
The window hope size. As sinusoidal estimation relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts. The -1 default value will default to half of winSize (overlap of 2).
The window hop size. As sinusoidal estimation relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts. The -1 default value will default to half of windowSize (overlap of 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 -1 default value will default to windowSize.
ARGUMENT:: maxFFTSize
How large can the FFT be, by allocating memory at instantiation time. This is not modulatable.
How large can the FFT be, by allocating memory at instantiation time. This cannot be modulated.
ARGUMENT::maxHFlitSize
How large can the harmonic filter be modulated to (hFiltSize), by allocating memory at instantiation time. This is not modulatable.
ARGUMENT:: maxPFiltSize
How large can the percussive filter be modulated to (pFiltSize), by allocating memory at instantiation time. This is not modulatable.
ARGUMENT::maxHarmFilterSize
How large can the harmonic filter be modulated to (harmFilterSize), by allocating memory at instantiation time. This cannot be modulated.
ARGUMENT:: maxPercFilterSize
How large can the percussive filter be modulated to (percFilterSize), by allocating memory at instantiation time. This cannot be modulated.
RETURNS::
An array of three audio streams: [0] is the harmonic part extracted, [1] is the percussive part extracted, [2] is the rest. The latency between the input and the output is ((hFiltSize - 1) * hopSize) + winSize) samples.
An array of three audio streams: [0] is the harmonic part extracted, [1] is the percussive part extracted, [2] is the rest. The latency between the input and the output is ((harmFilterSize - 1) * hopSize) + windowSize) samples.
Discussion::
HPSS works by using median filters on the spectral magnitudes of a sound. It hinges on a simple modelling assumption that tonal components will tend to yield concentrations of energy across time, spread out in frequency, and percussive components will manifest as concentrations of energy across frequency, spread out in time. By using median filters across time and frequency respectively, we get initial esitmates of the tonal-ness / transient-ness of a point in time and frequency. These are then combined into 'masks' that are applied to the orginal spectral data in order to produce a separation.
The modeFlag parameter provides different approaches to combinging estimates and producing masks. Some settings (especially in modes 1 & 2) will provide better separation but with more artefacts. These can, in principle, be ameliorated by applying smoothing filters to the masks before transforming back to the time-domain (not yet implemented).
The maskingMode parameter provides different approaches to combinging estimates and producing masks. Some settings (especially in modes 1 & 2) will provide better separation but with more artefacts. These can, in principle, be ameliorated by applying smoothing filters to the masks before transforming back to the time-domain (not yet implemented).
EXAMPLES::
@ -102,19 +99,19 @@ CODE::
b = Buffer.read(s,File.realpath(FluidHPSS.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-SynthTwoVoices-M.wav");
// run with basic parameters (left is harmonic, right is percussive)
{FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1))}.play
{FluidHPSS.ar(PlayBuf.ar(1,b,loop:1))}.play
// run in mode 1
{FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1),17,51,1,0.05,40,0.1,-40)}.play
{FluidHPSS.ar(PlayBuf.ar(1,b,loop:1),17,51,1,0.05,40,0.1,-40)}.play
// run in mode 2, listening to:
//the harmonic stream
{FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1),15,31,2,0.05,40,0.1,-40, 0.1, -10, 0.2, 10)[0].dup}.play
{FluidHPSS.ar(PlayBuf.ar(1,b,loop:1),15,31,2,0.05,40,0.1,-40, 0.1, -10, 0.2, 10)[0].dup}.play
// the percussive stream
{FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1),15,31,2,0.05,40,0.1,-40, 0.1, -10, 0.2, 10)[1].dup}.play
{FluidHPSS.ar(PlayBuf.ar(1,b,loop:1),15,31,2,0.05,40,0.1,-40, 0.1, -10, 0.2, 10)[1].dup}.play
// the residual stream
{FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1),15,31,2,0.05,40,0.1,-40, 0.1, -10, 0.2, 10)[2].dup}.play
{FluidHPSS.ar(PlayBuf.ar(1,b,loop:1),15,31,2,0.05,40,0.1,-40, 0.1, -10, 0.2, 10)[2].dup}.play
// null test (the process add a latency of ((hFiltSize - 1) * hopSize) + winSize) samples
{var sig = PlayBuf.ar(1,b.bufnum,loop:1); [FluidHPSS.ar(sig, 17, 31).sum - DelayN.ar(sig, 1, ((((17 - 1) * 512) + 1024) / s.sampleRate))]}.play
// null test (the process add a latency of ((harmFilterSize - 1) * hopSize) + windowSize) samples
{var sig = PlayBuf.ar(1,b,loop:1); [FluidHPSS.ar(sig, 17, 31).sum - DelayN.ar(sig, 1, ((((17 - 1) * 512) + 1024) / s.sampleRate))]}.play
::

@ -0,0 +1,115 @@
TITLE:: FluidLoudness
SUMMARY:: A Loudness and True-Peak Descriptor in Real-Time
CATEGORIES:: Libraries>FluidDecomposition
RELATED:: Guides/FluCoMa, Guides/FluidDecomposition
DESCRIPTION::
This class implements two loudness descriptors, computing the true peak of the signal as well as applying the filters proposed by broadcasting standards to emulate the perception of amplitude. It 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).::
The process will return a multichannel control steam with [loudness, truepeak] values, both in dBfs, which will be repeated if no change happens within the algorithm, i.e. when the hopSize is larger than the server's kr period. More information on broadcasting standardisation of loudness measurement is available at the reference page FOOTNOTE::https://tech.ebu.ch/docs/tech/tech3341.pdf:: and in more musician-friendly explantions here FOOTNOTE::http://designingsound.org/2013/02/06/loudness-and-metering-part-1/::.
CLASSMETHODS::
METHOD:: kr
The audio rate in, control rate out version of the object.
ARGUMENT:: in
The audio to be processed.
ARGUMENT:: kWeighting
A flag to switch the perceptual model of loudness. On by default, removing it makes the algorithm more CPU efficient by reverting to a simple RMS of the frame.
ARGUMENT:: truePeak
A flag to switch the computation of TruePeak. On by default, removing it makes the algorithm more CPU efficient by reverting to a simple absolute peak of the frame.
ARGUMENT:: windowSize
The size of the window on which the computation is done. By default 1024 to be similar with all other FluCoMa objects, the EBU specifies other values as per the examples below.
ARGUMENT:: hopSize
How much the buffered window moves forward, in samples. By default 512 to be similar with all other FluCoMa objects, the EBU specifies other values as per the examples below.
ARGUMENT:: maxwindowSize
How large can the windowSize be, by allocating memory at instantiation time. This cannot be modulated.
RETURNS::
A 2-channel KR signal with the [pitch, confidence] descriptors. The latency is windowSize.
EXAMPLES::
code::
//create a monitoring bus for the descriptors
b = Bus.new(\control,0,2);
//create a monitoring window for the values
(
w = Window("Loudness Monitor", Rect(10, 10, 220, 65)).front;
c = Array.fill(2, {arg i; StaticText(w, Rect(10, i * 25 + 10, 135, 20)).background_(Color.grey(0.7)).align_(\right)});
c[0].string = ("Loudness: ");
c[1].string = ("Peak: ");
a = Array.fill(2, {arg i;
StaticText(w, Rect(150, i * 25 + 10, 60, 20)).background_(Color.grey(0.7)).align_(\center);
});
)
//routine to update the parameters
(
r = Routine {
{
b.get({ arg val;
{
if(w.isClosed.not) {
val.do({arg item,index;
a[index].string = item.round(0.01)})
}
}.defer
});
0.1.wait;
}.loop
}.play
)
//basic test, with default values
(
x = {var source = PinkNoise.ar(0.25);
Out.kr(b, FluidLoudness.kr(source));
source.dup;
}.play;
)
//free this
x.free
//the EBU standard specifies that the window should be 400ms long, and update every 100ms, for instantaneous loudness. At SR=44100, this means the following settings. Various test signals are loaded.
(
x = {
arg freq=220, type = 1, noise = 0;
var source = PinkNoise.ar(noise) + Select.ar(type,[DC.ar(),SinOsc.ar(freq,mul:0.1), VarSaw.ar(freq,mul:0.1), Saw.ar(freq,0.1), Pulse.ar(freq,mul:0.1)]);
Out.kr(b, FluidLoudness.kr(source,windowSize:17640,hopSize:4410,maxwindowSize:17640));
source.dup;
}.play;
)
// change the various frequencies to see the impact of the filter for the loudness. The TruePeak is steady.
x.set(\freq, 440)
x.set(\freq, 110)
x.set(\freq, 55)
x.set(\freq, 3000)
x.set(\freq, 9000)
// adding harmonics, by changing to triangle (2), saw (3) or square (4) shows that spectral algo are more resilient when signal are richer
x.set(\type, 2)
x.set(\type, 3)
x.set(\type, 4)
// adding noise shows its impact on loudness
x.set(\noise, 0.25)
// and removing the oscilator
x.set(\type, 0)
// and measuring silence
x.set(\noise, 0)
::

@ -0,0 +1,130 @@
TITLE:: FluidMFCC
SUMMARY:: Mel-Frequency Cepstral Coefficients as Spectral Descriptors in Real-Time
CATEGORIES:: Libraries>FluidDecomposition
RELATED:: Guides/FluCoMa, Guides/FluidDecomposition, Classes/FluidMelBands
DESCRIPTION::
This class implements a classic spectral descriptor, the Mel-Frequency Cepstral Coefficients (https://en.wikipedia.org/wiki/Mel-frequency_cepstrum). The input is first filtered in to STRONG::numBands:: perceptually-spaced bands, as in LINK::Classes/FluidMelBands::. It is then analysed into STRONG::numCoeffs:: number of cepstral coefficients. It has the avantage of being amplitude invarient, except for the first coefficient. It 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).::
The process will return a multichannel control steam of STRONG::maxNumCoeffs::, which will be repeated if no change happens within the algorythm, i.e. when the hopSize is larger than the server's kr period.
CLASSMETHODS::
METHOD:: kr
The audio rate in, control rate out version of the object.
ARGUMENT:: in
The audio to be processed.
ARGUMENT:: numCoeffs
The number of cepstral coefficients to be outputed. It is limited by the maxNumCoeffs parameter. When the number is smaller than the maximum, the output is zero-padded.
ARGUMENT:: numBands
The number of bands that will be perceptually equally distributed between minFreq and maxFreq to describe the spectral shape before it is converted to cepstral coefficients.
ARGUMENT:: minFreq
The lower boundary of the lowest band of the model, in Hz.
ARGUMENT:: maxFreq
The highest boundary of the highest band of the model, in Hz.
ARGUMENT:: maxNumCoeffs
The maximum number of cepstral coefficients that can be computed. This sets the number of channels of the output, and therefore cannot be modulated.
ARGUMENT:: windowSize
The window size. As MFCC computation 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
ARGUMENT:: hopSize
The window hop size. As MFCC computation relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts. The -1 default value will default to half of windowSize (overlap of 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 -1 default value will default to windowSize.
ARGUMENT:: maxFFTSize
How large can the FFT be, by allocating memory at instantiation time. This cannot be modulated.
RETURNS::
A KR signal of STRONG::maxNumCoeffs:: channels. The latency is windowSize.
EXAMPLES::
code::
//create a monitoring bus for the descriptors
b = Bus.new(\control,0,13);
//create a monitoring window for the values
(
w = Window("MFCCs Monitor", Rect(10, 10, 420, 320)).front;
a = MultiSliderView(w,Rect(10, 10, 400, 300)).elasticMode_(1).isFilled_(1);
)
//run the window updating routine.
(
r = Routine {
{
b.get({ arg val;
{
if(w.isClosed.not) {
a.value = val;
}
}.defer
});
0.01.wait;
}.loop
}.play
)
//play a simple sound to observe the values
(
x = {arg type = 0;
var source = Select.ar(type,[SinOsc.ar(220),Saw.ar(220),Pulse.ar(220)]) * LFTri.kr(0.1).exprange(0.01,0.1);
Out.kr(b,FluidMFCC.kr(source,maxNumCoeffs:13));
source.dup;
}.play;
)
// change the wave types, observe the amplitude invariance of the descriptors, apart from the leftmost coefficient
x.set(\type, 1)
x.set(\type, 2)
x.set(\type, 0)
// free this source
x.free
// load a more exciting one
c = Buffer.read(s,File.realpath(FluidMelBands.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-SynthTwoVoices-M.wav");
// analyse with parameters to be changed
(
x = {arg bands = 40, low = 20, high = 20000;
var source = PlayBuf.ar(1,c,loop:1);
Out.kr(b,FluidMelBands.kr(source, bands, low, high, 40) / 10);
source.dup;
}.play;
)
// observe the number of bands. The unused ones at the top are not updated
x.set(\bands,20)
// back to the full range
x.set(\bands,40)
// focus all the bands on a mid range
x.set(\low,320, \high, 8000)
// focusing on the low end shows the fft resolution issue. One could restart the analysis with a larger fft to show more precision
x.set(\low,20, \high, 160)
// back to full range
x.set(\low,20, \high, 20000)
// free everything
x.free;b.free;c.free;r.stop;
::
STRONG::A musical example::
CODE::
// todo: port the Max one
::

@ -0,0 +1,123 @@
TITLE:: FluidMelBands
SUMMARY:: A Perceptually Spread Spectral Contour Descriptor in Real-Time
CATEGORIES:: Libraries>FluidDecomposition
RELATED:: Guides/FluCoMa, Guides/FluidDecomposition, Classes/FluidMFCC
DESCRIPTION::
This class implements a spectral shape descriptor where the amplitude is given for a number of equally spread perceptual bands. The spread is based on the Mel scale (https://en.wikipedia.org/wiki/Mel_scale) which is one of the first attempt to mimic pitch perception scientifically. This implementation allows to select the range and number of bands dynamically. It 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).::
The process will return a multichannel control steam of size STRONG::maxNumBands::, which will be repeated if no change happens within the algorythm, i.e. when the hopSize is larger than the server's kr period.
CLASSMETHODS::
METHOD:: kr
The audio rate in, control rate out version of the object.
ARGUMENT:: in
The audio to be processed.
ARGUMENT:: numBands
The number of bands that will be perceptually equally distributed between STRONG::minFreq:: and STRONG::maxFreq::. It is limited by the STRONG::maxNumBands:: parameter. When the number is smaller than the maximum, the output is zero-padded.
ARGUMENT:: minFreq
The lower boundary of the lowest band of the model, in Hz.
ARGUMENT:: maxFreq
The highest boundary of the highest band of the model, in Hz.
ARGUMENT:: maxNumBands
The maximum number of Mel bands that can be modelled. This sets the number of channels of the output, and therefore cannot be modulated.
ARGUMENT:: windowSize
The window size. As sinusoidal 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
ARGUMENT:: hopSize
The window hop size. As sinusoidal estimation relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts. The -1 default value will default to half of windowSize (overlap of 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 -1 default value will default to windowSize.
ARGUMENT:: maxFFTSize
How large can the FFT be, by allocating memory at instantiation time. This cannot be modulated.
RETURNS::
A KR signal of STRONG::maxNumBands:: channels, giving the measure amplitudes for each band. The latency is windowSize.
EXAMPLES::
code::
//create a monitoring bus for the descriptors
b = Bus.new(\control,0,40);
//create a monitoring window for the values
(
w = Window("Mel Bands Monitor", Rect(10, 10, 620, 320)).front;
a = MultiSliderView(w,Rect(10, 10, 600, 300)).elasticMode_(1).isFilled_(1);
)
//run the window updating routine.
(
r = Routine {
{
b.get({ arg val;
{
if(w.isClosed.not) {
a.value = val;
}
}.defer
});
0.01.wait;
}.loop
}.play
)
//play a simple sound to observe the values
(
x = {
var source = SinOsc.ar(LFTri.kr(0.1).exprange(80,800),0,0.1);
Out.kr(b,FluidMelBands.kr(source,maxNumBands:40) / 50);
source.dup;
}.play;
)
// free this source
x.free
// load a more exciting one
c = Buffer.read(s,File.realpath(FluidMelBands.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-SynthTwoVoices-M.wav");
// analyse with parameters to be changed
(
x = {arg bands = 40, low = 20, high = 20000;
var source = PlayBuf.ar(1,c,loop:1);
Out.kr(b,FluidMelBands.kr(source, bands, low, high, 40) / 10);
source.dup;
}.play;
)
// observe the number of bands. The unused ones at the top are not updated
x.set(\bands,20)
// back to the full range
x.set(\bands,40)
// focus all the bands on a mid range
x.set(\low,320, \high, 8000)
// focusing on the low end shows the fft resolution issue. One could restart the analysis with a larger fft to show more precision
x.set(\low,20, \high, 160)
// back to full range
x.set(\low,20, \high, 20000)
// free everything
x.free;b.free;c.free;r.stop;
::
STRONG::A musical example::
CODE::
// todo: port the Max one
::

@ -0,0 +1,228 @@
TITLE:: FluidNMFFilter
SUMMARY:: Real-Time Non-Negative Matrix Factorisation with Fixed Bases
CATEGORIES:: Libraries>FluidDecomposition
RELATED:: Guides/FluCoMa, Guides/FluidDecomposition, Classes/FluidBufNMF, Classes/FluidNMFMatch
DESCRIPTION::
The FluidNMFFilter object decomposes and resynthesises 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. ::
It outputs at AR the resynthesis of the best factorisation. 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 (number of components) is determined by the number of channels in the supplied buffer of templates, up to a maximum set by the STRONG::maxComponents:: 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). ::
CLASSMETHODS::
METHOD:: ar
The real-time processing method. It takes an audio input, and will yield a audio stream in the form of a multichannel array of size STRONG::maxComponents:: . If the bases buffer has fewer than maxComponents channels, the remaining outputs will be zeroed.
ARGUMENT:: in
The signal input to the factorisation process.
ARGUMENT:: bases
The server index of the buffer containing the different bases that the input signal will be matched against. Bases must be STRONG::(fft size / 2) + 1:: frames. If the buffer has more than STRONG::maxComponents:: channels, the excess will be ignored.
ARGUMENT::maxComponents
The maximum number of elements the NMF algorithm will try to divide the spectrogram of the source in. This dictates the number of output channels for the ugen. This cannot be modulated.
ARGUMENT:: iterations
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:: windowSize
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 hop 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 -1 default value will default to half of windowSize (overlap of 2).
ARGUMENT:: fftSize
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. The -1 default value will default to windowSize.
ARGUMENT:: maxFFTSize
How large can the FFT be, by allocating memory at instantiation time. This cannot be modulated.
RETURNS::
A multichannel kr output, giving for each basis component the activation amount.
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);
)
(
// 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,b, destination:d);
FluidBufCompose.process(s,c, destStartFrame:44100, destination:d, destGain:1);
s.sync;
d.query;
}.play;
)
// check
d.plot
d.play //////(beware !!!! loud!!!)
(
// separate them in 2 components
Routine {
FluidBufNMF.process(s, d, bases: e, components:2);
s.sync;
e.query;
}.play
)
// check for 2 spikes in the spectra
e.plot
//listen how the filter isolates each component and places them in each channel separately.
{FluidNMFFilter.ar(SinOsc.ar(500),e,2)}.play
{FluidNMFFilter.ar(SinOsc.ar(5000),e,2)}.play
{FluidNMFFilter.ar(SinOsc.ar([500,5000]).sum,e,2)}.play
::
STRONG::A guitar processor::
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.new(s);
)
// train only 2 seconds
(
Routine {
FluidBufNMF.process(s,b,0,88200,0,1, c, x, components:10,fftSize:2048);
c.query;
}.play;
)
// wait for the query to print
// then find the component that has more sustain pitch than pick (TODO: use descriptors with stats)
(
~element = 4;
{PlayBuf.ar(10,c)[~element]}.play;
)
// copy all the other components on itself and the picking basis as the sole component of the 1st channel
(
Routine{
z = (0..9);
FluidBufCompose.process(s, x, startChan: z.removeAt(~element), numChans: 1, destination: e);
z.do({|chan| FluidBufCompose.process(s, x, startChan:chan, numChans: 1, destStartChan: 1, destination: e, destGain:1)});
e.query;
}.play;
)
e.plot;
//we can then use the resynthesised signal to sent in a delay
(
{
var source, todelay, delay1, delay2, delay3, feedback, mod1, mod2, mod3, mod4;
//read the source
source = PlayBuf.ar(1, b);
// 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 = FluidNMFFilter.ar(source,e,2,fftSize:2048)[0]; //reading the channel of the activations on the pick basis
// 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
source.dup + ([delay1+delay3,delay2+delay3]*(3.dbamp))
//listen to the delays in solo by uncommenting the following line
// [delay1+delay3,delay2+delay3]
// [source, todelay]
}.play;
)
::
STRONG::Strange Processor::
CODE::
//set some buffers
(
b = Buffer.read(s,File.realpath(FluidNMFMatch.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Nicol-LoopE-M.wav");
c = Buffer.alloc(s,1025,3);
d = Buffer.alloc(s,44100);
)
// play a source and circular record the last second, continuously
(
e = { var source = PlayBuf.ar(1,b,loop:1);
BufWr.ar(source, d, Phasor.ar(1, end:44100));
source.dup;
}.play;
)
// after at least 1 second, trigger a first factorisation
(
Routine {
FluidBufNMF.process(s, d, bases:c, windowSize:2048, components:3);
c.query;
}.play;
)
c.plot
// wait for the query to print
// then start the splitting effect
(
f = {var source = In.ar(0,2);
ReplaceOut.ar(0, Splay.ar(FluidNMFFilter.ar(source.sum, c, 3, windowSize:2048)));
}.play(addAction:\addToTail);
)
// kill this boring splitter
f.free;
// more fun: processing the 3 component independently
(
f = {arg bases = c.bufnum;
var source, x,y,z, rev, dist;
source = In.ar(0,2);
#x,y,z = FluidNMFFilter.ar(source.sum, bases, 3, windowSize:2048);
rev = FreeVerb.ar(x);
dist = (z * 10).atan * 0.1;
ReplaceOut.ar(0, Splay.ar([rev,y,dist]));
}.play(addAction:\addToTail);
)
// set the bases
f.set(\bases, c.bufnum)
// here you can retrigger the factorisation
g = Buffer.alloc(s,1025,3);
FluidBufNMF.process(s, d, bases:g, windowSize:2048, components:3);
f.set(\bases, g.bufnum)
//free
f.free; e.free;
::

@ -1,12 +1,12 @@
TITLE:: FluidNMFMatch
SUMMARY:: Real-Time Non-Negative Matrix Factorisation with Fixed Dictionaries
SUMMARY:: Real-Time Non-Negative Matrix Factorisation with Fixed Bases
CATEGORIES:: Libraries>FluidDecomposition
RELATED:: Guides/FluCoMa, Guides/FluidDecomposition, Classes/FluidBufNMF
RELATED:: Guides/FluCoMa, Guides/FluidDecomposition, Classes/FluidBufNMF, Classes/FluidNMFFilter
DESCRIPTION::
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. ::
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.
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 (number of components) is determined by the number of channels in the supplied buffer of templates, up to a maximum set by the STRONG::maxComponents:: 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.
@ -20,39 +20,39 @@ FluidBufNMF is part of the Fluid Decomposition Toolkit of the FluCoMa project. f
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::maxRank:: . If the dictionary buffer has fewer than maxRank channels, the remaining outputs will be zeroed.
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::maxComponents:: . If the bases buffer has fewer than maxComponents channels, the remaining outputs will be zeroed.
ARGUMENT:: in
The signal input to the factorisation process.
ARGUMENT:: dictBufNum
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:: bases
The server index of the buffer containing the different bases that the input signal will be matched against. Bases must be STRONG::(fft size / 2) + 1:: frames. If the buffer has more than STRONG::maxComponents:: channels, the excess will be ignored.
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. This is not modulatable.
ARGUMENT::maxComponents
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. This cannot be modulated.
ARGUMENT:: nIter
ARGUMENT:: iterations
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
ARGUMENT:: windowSize
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 -1 default value will default to half of winSize (overlap of 2).
The window hop 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 -1 default value will default to half of windowSize (overlap of 2).
ARGUMENT:: fftSize
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. The -1 default value will default to windowSize.
ARGUMENT:: maxFFTSize
How large can the FFT be, by allocating memory at instantiation time. This is not modulatable.
How large can the FFT be, by allocating memory at instantiation time. This cannot be modulated.
RETURNS::
A multichannel kr output, giving for each dictionary component the activation amount.
A multichannel kr output, giving for each basis component the activation amount.
EXAMPLES::
STRONG::A didactic example::
CODE::
CODE::
(
// create buffers
b= Buffer.alloc(s,44100);
@ -67,8 +67,8 @@ Routine {
b.sine2([500],[1], false, false);
c.sine2([5000],[1],false, false);
s.sync;
FluidBufCompose.process(s,b.bufnum, dstBufNum:d.bufnum);
FluidBufCompose.process(s,c.bufnum, dstStartAt:44100, dstBufNum:d.bufnum, dstGain:1);
FluidBufCompose.process(s,b, destination:d);
FluidBufCompose.process(s,c, destStartFrame:44100, destination:d, destGain:1);
s.sync;
d.query;
}.play;
@ -79,70 +79,64 @@ d.plot
d.play //////(beware !!!! loud!!!)
(
// separate them in 2 ranks
// separate them in 2 components
Routine {
FluidBufNMF.process(s, d.bufnum, dictBufNum: e.bufnum, rank:2);
FluidBufNMF.process(s, d, bases: e, components: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)}.plot(1)
{FluidNMFMatch.kr(SinOsc.ar(500),e,2)}.plot(1)
{FluidNMFMatch.kr(SinOsc.ar(5000),e.bufnum,2)}.plot(1)
{FluidNMFMatch.kr(SinOsc.ar(5000),e,2)}.plot(1)
{FluidNMFMatch.kr(SinOsc.ar([500,5000]).sum,e.bufnum,2)}.plot(1)
{FluidNMFMatch.kr(SinOsc.ar([500,5000]).sum,e,2)}.plot(1)
::
STRONG::A pick compressor::
CODE::
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);
e = Buffer.new(s);
)
// train only 2 seconds
(
Routine {
FluidBufNMF.process(s,b.bufnum,0,88200,0,1, c.bufnum, x.bufnum, rank:10,fftSize:2048);
s.sync;
FluidBufNMF.process(s,b,0,88200,0,1, c, x, components:10,fftSize:2048);
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
// then find the component that has the picking sound by changing which channel to listen to
(
~element = 3;
{PlayBuf.ar(10,c.bufnum)[~element]}.play
~element = 6;
{PlayBuf.ar(10,c)[~element]}.play;
)
// copy all the other ranks on itself and the picking dictionnary as the sole component of the 1st channel
// copy all the other components on itself and the picking basis as the sole component of the 1st channel
(
Routine{
z = (0..9);
FluidBufCompose.process(s, x.bufnum, startChan: z.removeAt(~element), nChans: 1, dstBufNum: e.bufnum);
s.sync;
e.query;
s.sync;
z.do({|chan| FluidBufCompose.process(s, x.bufnum, startChan:chan, nChans: 1, dstStartChan: 1, dstBufNum: e.bufnum, dstGain:1)});
s.sync;
FluidBufCompose.process(s, x, startChan: z.removeAt(~element), numChans: 1, destination: e);
z.do({|chan| FluidBufCompose.process(s, x, startChan:chan, numChans: 1, destStartChan: 1, destination: e, destGain:1)});
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);
//using this trained basis we can see the envelop (activations) of each component
{FluidNMFMatch.kr(PlayBuf.ar(1,b),e,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
@ -150,7 +144,7 @@ e.plot;
{
var source, todelay, delay1, delay2, delay3, feedback, mod1, mod2, mod3, mod4;
//read the source
source = PlayBuf.ar(1, b.bufnum);
source = PlayBuf.ar(1, b);
// generate modulators that are coprime in frequency
mod1 = SinOsc.ar(1, 0, 0.001);
@ -160,7 +154,7 @@ e.plot;
// 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
LagUD.ar(K2A.ar(FluidNMFMatch.kr(source,e,2,fftSize:2048)[0]), //reading the channel of the activations on the pick basis
80/44100, // lag uptime (compressor's attack)
1000/44100, // lag downtime (compressor's decay)
(1/(2.dbamp) // compressor's threshold inverted
@ -193,46 +187,43 @@ e = Buffer.new(s);
// train where all objects are present
(
Routine {
FluidBufNMF.process(s,b.bufnum,130000,150000,0,1, c.bufnum, x.bufnum, rank:10);
s.sync;
FluidBufNMF.process(s,b,130000,150000,0,1, c, x, components:10);
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
// then find a component for each item you want to find. You could also sum them. Try to find a component with a good object-to-rest ratio
(
~dog =2;
{PlayBuf.ar(10,c.bufnum)[~dog]}.play
{PlayBuf.ar(10,c)[~dog]}.play
)
(
~bird = 5;
{PlayBuf.ar(10,c.bufnum)[~bird]}.play
~bird = 3;
{PlayBuf.ar(10,c)[~bird]}.play
)
// copy at least one other rank to a third rank, a sort of left-over channel
// copy at least one other component to a third filter, a sort of left-over channel
(
Routine{
FluidBufCompose.process(s, x.bufnum, startChan:~dog, nChans: 1, dstBufNum: e.bufnum);
FluidBufCompose.process(s, x.bufnum, startChan:~bird, nChans: 1, dstStartChan: 1, dstBufNum: e.bufnum, dstGain:1);
s.sync;
(0..9).removeAll([~dog,~bird]).do({|chan|FluidBufCompose.process(s,x.bufnum, startChan:chan, nChans: 1, dstStartChan: 2, dstBufNum: e.bufnum, dstGain:1)});
s.sync;
FluidBufCompose.process(s, x, startChan:~dog, numChans: 1, destination: e);
FluidBufCompose.process(s, x, startChan:~bird, numChans: 1, destStartChan: 1, destination: e, destGain:1);
(0..9).removeAll([~dog,~bird]).do({|chan|FluidBufCompose.process(s,x, startChan:chan, numChans: 1, destStartChan: 2, destination: e, destGain:1)});
e.query;
}.play;
)
e.plot;
//using this trained dictionary we can then see the activation...
//using this trained basis we can then see the activation... (wait for 5 seconds before it prints!)
(
{
var source, blips;
//read the source
source = PlayBuf.ar(2, b.bufnum);
blips = FluidNMFMatch.kr(source.sum,e.bufnum,3);
}.plot(10);
source = PlayBuf.ar(2, b);
blips = FluidNMFMatch.kr(source.sum,e,3);
}.plot(5);
)
// ...and use some threshold to 'find' objects...
@ -240,9 +231,9 @@ e.plot;
{
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);
source = PlayBuf.ar(2, b);
blips = Schmidt.kr(FluidNMFMatch.kr(source.sum,e,3),0.5,[10,1,1000]);
}.plot(5);
)
// ...and use these to sonify them
@ -250,8 +241,8 @@ e.plot;
{
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]);
source = PlayBuf.ar(2, b);
blips = Schmidt.kr(FluidNMFMatch.kr(source.sum,e,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;
@ -260,7 +251,7 @@ e.plot;
::
STRONG::Pretrained piano::
CODE::
//load in the sound in and a pretrained dictionary
//load in the sound in and a pretrained basis
(
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");
@ -268,12 +259,12 @@ e.plot;
b.play
c.query
//use the pretrained dictionary to compute activations of each notes to drive the amplitude of a resynth
//use the pretrained bases 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 = PlayBuf.ar(2, b,loop:1).sum;
resynth = SinOsc.ar((21..108).midicps, 0, FluidNMFMatch.kr(source,c,88,10,4096).madd(0.002)).sum;
[source, resynth]
}.play
)
@ -283,14 +274,14 @@ c.query
(
{
var source, resynth, chain, trig, acts;
source = PlayBuf.ar(2,b.bufnum,loop:1).sum;
source = PlayBuf.ar(2,b,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);
acts = Latch.kr(FluidNMFMatch.kr(source,c,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;
@ -315,4 +306,3 @@ c.query
CODE::
//to be completed
::

@ -0,0 +1,85 @@
TITLE:: FluidOnsetSlice
SUMMARY:: Spectral Difference-Based Real-Time Audio Slicer
CATEGORIES:: Libraries>FluidDecomposition
RELATED:: Guides/FluCoMa, Guides/FluidDecomposition
DESCRIPTION::
This class implements many spectral based onset detection functions, most of them taken from the literature. (http://www.dafx.ca/proceedings/papers/p_133.pdf) Some are already available in SuperCollider's LINK::Classes/Onsets:: object. It 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).::
The process will return an audio steam with sample-long impulses at estimated starting points of the different slices.
CLASSMETHODS::
METHOD:: ar
The audio rate version of the object.
ARGUMENT:: in
The audio to be processed.
ARGUMENT:: function
The function used to derive a difference curve between spectral frames. It can be any of the following:
TABLE::
##0 || Energy || thresholds on (sum of squares of magnitudes / nBins) (like Onsets \power)
##1 || HFC || thresholds on (sum of (squared magnitudes * binNum) / nBins)
##2 || SpectralFlux || thresholds on (diffence in magnitude between consecutive frames, half rectified)
##3 || MKL || thresholds on (sum of log of magnitude ratio per bin) (or equivalent: sum of difference of the log magnitude per bin) (like Onsets \mkl)
##4 || IS || (WILL PROBABLY BE REMOVED) Itakura - Saito divergence (see literature)
##5 || Cosine || thresholds on (cosine distance between comparison frames)
##6 || PhaseDev || takes the past 2 frames, projects to the current, as anticipated if it was a steady state, then compute the sum of the differences, on which it thresholds (like Onsets \phase)
##7 || WPhaseDev || same as PhaseDev, but weighted by the magnitude in order to remove chaos noise floor (like Onsets \wphase)
##8 || ComplexDev || same as PhaseDev, but in the complex domain - the anticipated amp is considered steady, and the phase is projected, then a complex subtraction is done with the actual present frame. The sum of magnitudes is used to threshold (like Onsets \complex)
##9 || RComplexDev || same as above, but rectified (like Onsets \rcomplex)
::
ARGUMENT:: threshold
The thresholding of a new slice. Value ranges are different for each function, from 0 upwards.
ARGUMENT:: minSliceLength
The minimum duration of a slice in number of hopSize.
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:: frameDelta
For certain functions (HFC, SpectralFlux, MKL, Cosine), the distance does not have to be computed between consecutive frames. By default (0) it is, otherwise this sets the distane between the comparison window in samples.
ARGUMENT:: windowSize
The window size. As sinusoidal 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
ARGUMENT:: hopSize
The window hop size. As sinusoidal estimation relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts. The -1 default value will default to half of windowSize (overlap of 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 -1 default value will default to windowSize.
ARGUMENT:: maxFFTSize
How large can the FFT be, by allocating memory at instantiation time. This cannot be modulated.
RETURNS::
An audio stream with impulses at detected transients. The latency between the input and the output is windowSize at maximum.
EXAMPLES::
code::
//load some sounds
b = Buffer.read(s,File.realpath(FluidOnsetSlice.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Nicol-LoopE-M.wav");
// basic param (the process add a latency of windowSize samples
{var sig = PlayBuf.ar(1,b,loop:1); [FluidOnsetSlice.ar(sig) * 0.5, DelayN.ar(sig, 1, 1024/ s.sampleRate)]}.play
// other parameters
{var sig = PlayBuf.ar(1,b,loop:1); [FluidOnsetSlice.ar(sig, 2, 0.06, 55, 7, 0, 128, 64) * 0.5, DelayN.ar(sig, 1, (128)/ s.sampleRate)]}.play
// more musical trans-trigged autopan
(
{
var sig, trig, syncd, pan;
sig = PlayBuf.ar(1,b,loop:1);
trig = FluidOnsetSlice.ar(sig, 1, 1.8, 100, 8, 0, 128);
syncd = DelayN.ar(sig, 1, ( 128 / s.sampleRate));
pan = TRand.ar(-1,1,trig);
Pan2.ar(syncd,pan);
}.play
)
::

@ -0,0 +1,153 @@
TITLE:: FluidPitch
SUMMARY:: A Selection of Pitch Descriptors in Real-Time
CATEGORIES:: Libraries>FluidDecomposition
RELATED:: Guides/FluCoMa, Guides/FluidDecomposition, Classes/Pitch
DESCRIPTION::
This class implements three popular pitch descriptors, computed as frequency and the confidence in its value. It 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).::
The process will return a multichannel control steam with [pitch, confidence] values, which will be repeated if no change happens within the algorithm, i.e. when the hopSize is larger than the server's kr period.
CLASSMETHODS::
METHOD:: kr
The audio rate in, control rate out version of the object.
ARGUMENT:: in
The audio to be processed.
ARGUMENT:: algorithm
The algorithm to estimate the pitch. The options are:
TABLE::
## 0 || Cepstrum: Returns a pitch estimate as the location of the second highest peak in the Cepstrum of the signal (after DC).
## 1 || Harmonic Product Spectrum: Implements the Harmonic Product Spectrum algorithm for pitch detection . See e.g. FOOTNOTE:: A. Lerch, "An Introduction to Audio Content Analysis: Applications in Signal Processing and Music Informatics." John Wiley & Sons, 2012.https://onlinelibrary.wiley.com/doi/book/10.1002/9781118393550 ::
## 2 || YinFFT: Implements the frequency domain version of the YIN algorithm, as described in FOOTNOTE::P. M. Brossier, "Automatic Annotation of Musical Audio for Interactive Applications.” QMUL, London, UK, 2007. :: See also https://essentia.upf.edu/documentation/reference/streaming_PitchYinFFT.html
::
ARGUMENT:: windowSize
The window size. As sinusoidal 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
ARGUMENT:: hopSize
The window hop size. As sinusoidal estimation relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts. The -1 default value will default to half of windowSize (overlap of 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 -1 default value will default to windowSize.
ARGUMENT:: maxFFTSize
How large can the FFT be, by allocating memory at instantiation time. This cannot be modulated.
RETURNS::
A 2-channel KR signal with the [pitch, confidence] descriptors. The latency is windowSize.
EXAMPLES::
code::
//create a monitoring bus for the descriptors
b = Bus.new(\control,0,4);
//create a monitoring window for the values
(
w = Window("Frequency Monitor", Rect(10, 10, 220, 115)).front;
c = Array.fill(4, {arg i; StaticText(w, Rect(10, i * 25 + 10, 135, 20)).background_(Color.grey(0.7)).align_(\right)});
c[0].string = ("FluidPitch: ");
c[1].string = ("confidence: ");
c[2].string = ("SC Pitch: ");
c[3].string = ("Confidence: ");
a = Array.fill(4, {arg i;
StaticText(w, Rect(150, i * 25 + 10, 60, 20)).background_(Color.grey(0.7)).align_(\center);
});
)
//routine to update the parameters
(
r = Routine {
{
b.get({ arg val;
{
if(w.isClosed.not) {
val.do({arg item,index;
a[index].string = item.round(0.01)})
}
}.defer
});
0.1.wait;
}.loop
}.play
)
//test signals, all in one synth
(
x = {
arg freq=220, type = 0, noise = 0;
var source = PinkNoise.ar(noise) + Select.ar(type,[SinOsc.ar(freq,mul:0.1), VarSaw.ar(freq,mul:0.1), Saw.ar(freq,0.1), Pulse.ar(freq,mul:0.1), Mix.new(Array.fill(8, {arg i; SinOsc.ar(LFNoise1.kr(0.1.rand,10,220*(i+1)),mul:(i+1).reciprocal * 0.1)}))]);
Out.kr(b, FluidPitch.kr(source) ++ Pitch.kr(source));
source.dup;
}.play;
)
// the built-in is slightly better on pure sinewaves
x.set(\freq, 440)
// adding harmonics, by changing to triangle (1), saw (2) or square (3) shows that spectral algo are more resilient when signal are richer
x.set(\type, 1)
x.set(\type, 2)
x.set(\type, 3)
// adding noise shows the comparative sturdiness of the spectral pitch tracker
x.set(\noise, 0.05)
//if latency is no issue, getting a higher windowSize will stabilise the algorithm even more
::
STRONG::a more musical example::
CODE::
// play a noisy synth file
b = Buffer.read(s,File.realpath(FluidPitch.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-ASWINE-ScratchySynth-M.wav");
b.play(true);
//insert a selective reverb - sending only the material with very high pitch confidence
(
f = {var source, rev;
source = In.ar(0,2);
rev = FreeVerb.ar(DelayN.ar(source,delaytime:1024/s.sampleRate) * Lag.kr((FluidPitch.kr(source.sum)[1] > 0.98),0.01), 1);
ReplaceOut.ar(0, rev+ source);
}.play(addAction:\addToTail);
)
// free the effect
f.free
// insert a stereo delay instead (as well) using the same
(
f = {
var source, todelay, delay1, delay2, delay3, feedback, mod1, mod2, mod3, mod4;
//read the source
source = In.ar(0,2);
// 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);
// gate the signal to send to the delays
todelay = DelayN.ar(source,delaytime:1024/s.sampleRate) * Lag.kr((FluidPitch.kr(source.sum)[1] > 0.98),0.01);
// 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.35),0.123,0.122+(mod1*mod2));
delay2 = DelayC.ar(BPF.ar(todelay+feedback[0]+(feedback[2] * 0.3), 1987, 6.7,0.35),0.345,0.344+(mod3*mod4));
delay3 = DelayC.ar(BPF.ar(todelay+feedback[1], 1456, 6.7,0.35),0.567,0.566+(mod1*mod3),0.6);
LocalOut.ar([delay1,delay2, delay3]); // write the feedback for the delays
ReplaceOut.ar(0, source + [delay1+delay3,delay2+delay3]);
}.play(addAction:\addToTail);
)
::

@ -0,0 +1,81 @@
TITLE:: FluidRTNoveltySlice
SUMMARY:: Spectral Difference-Based Real-Time Audio Slicer
CATEGORIES:: Libraries>FluidDecomposition
RELATED:: Guides/FluCoMa, Guides/FluidDecomposition
DESCRIPTION::
This class implements many spectral based onset detection functions, most of them taken from the literature. (http://www.dafx.ca/proceedings/papers/p_133.pdf) Some are already available in SuperCollider's LINK::Classes/Onsets:: object. It 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).::
The process will return an audio steam with sample-long impulses at estimated starting points of the different slices.
CLASSMETHODS::
METHOD:: ar
The audio rate version of the object.
ARGUMENT:: in
The audio to be processed.
ARGUMENT:: feature
The feature on which novelty is computed.
table::
##0 || Spectrum || todo
##1 || MFCC || todo
##2 || Pitch || todo
##3 || Loudness || todo
::
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:: threshold
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:: windowSize
The window size. As sinusoidal 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
ARGUMENT:: hopSize
The window hop size. As sinusoidal estimation relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts. The -1 default value will default to half of windowSize (overlap of 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 -1 default value will default to windowSize.
ARGUMENT:: maxFFTSize
How large can the FFT be, by allocating memory at instantiation time. This cannot be modulated.
ARGUMENT:: maxKernelSize
This cannot be modulated.
ARGUMENT:: maxFilterSize
This cannot be modulated.
RETURNS::
An audio stream with impulses at detected transients. The latency between the input and the output is windowSize at maximum.
EXAMPLES::
code::
//load some sounds
b = Buffer.read(s,File.realpath(FluidRTNoveltySlice.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Nicol-LoopE-M.wav");
// basic param (the process add a latency of windowSize samples
{var sig = PlayBuf.ar(1,b,loop:1); [FluidRTNoveltySlice.ar(sig,0,3,0.2) * 0.5, DelayN.ar(sig, 1, 1024/ s.sampleRate)]}.play
// other parameters
{var sig = PlayBuf.ar(1,b,loop:1); [FluidRTNoveltySlice.ar(sig, 0, 31, 0.05, 4, 128, 64) * 0.5, DelayN.ar(sig, 1, (128)/ s.sampleRate)]}.play
// more musical trans-trigged autopan
(
{
var sig, trig, syncd, pan;
sig = PlayBuf.ar(1,b,loop:1);
trig = FluidRTNoveltySlice.ar(sig, 0, 0.2, 100, 8, 0, 128);
syncd = DelayN.ar(sig, 1, ( 128 / s.sampleRate));
pan = TRand.ar(-1,1,trig);
Pan2.ar(syncd,pan);
}.play
)
::

@ -16,21 +16,21 @@ METHOD:: ar
ARGUMENT:: in
The input to be passed-through
ARGUMENT:: winSize
ARGUMENT:: windowSize
The size of the buffered window to be analysed, in samples. It will add that much latency to the signal.
ARGUMENT:: hopSize
How much the buffered window moves forward, in samples. The -1 default value will default to half of winSize (overlap of 2).
How much the buffered window moves forward, in samples. The -1 default value will default to half of windowSize (overlap of 2).
ARGUMENT:: fftSize
How large will the FFT be, zero-padding the buffer to the right size, which should be bigger than the windowSize argument, bigger than 4 samples, and should be a power of 2. This is a way to oversample the FFT for extra precision. The -1 default value will default to windowSize.
ARGUMENT:: maxFFTSize
How large can the FFT be, by allocating memory at instantiation time. This is not modulatable.
How large can the FFT be, by allocating memory at instantiation time. This cannot be modulated.
RETURNS::
Same as input, delayed by the winSize.
Same as input, delayed by the windowSize.
EXAMPLES::

@ -21,10 +21,10 @@ METHOD:: ar
ARGUMENT:: in
The input to be processed
ARGUMENT:: bw
ARGUMENT:: bandwidth
The width in bins of the fragment of the fft window that is considered a normal deviation for a potential continuous sinusoidal track. It has an effect on CPU cost: the widest is more accurate but more computationally expensive.
ARGUMENT:: thresh
ARGUMENT:: threshold
The normalised threshold, between 0 an 1, to consider a peak as a sinusoidal component from the normalized cross-correlation.
ARGUMENT:: minTrackLen
@ -36,20 +36,20 @@ ARGUMENT:: magWeight
ARGUMENT:: freqWeight
The weight of the frequency proximity of a peak when trying to associate it to an existing track (relative to magWeight - suggested between 0 to 1)
ARGUMENT:: winSize
ARGUMENT:: windowSize
The window size. As sinusoidal 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
ARGUMENT:: hopSize
The window hope size. As sinusoidal estimation relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts. The -1 default value will default to half of winSize (overlap of 2).
The window hop size. As sinusoidal estimation relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts. The -1 default value will default to half of windowSize (overlap of 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 -1 default value will default to windowSize.
ARGUMENT:: maxFFTSize
How large can the FFT be, by allocating memory at instantiation time. This is not modulatable.
How large can the FFT be, by allocating memory at instantiation time. This cannot be modulated.
RETURNS::
An array of two audio streams: [0] is the harmonic part extracted, [1] is the rest. The latency between the input and the output is (( hopSize * minTrackLen) + winSize) samples.
An array of two audio streams: [0] is the harmonic part extracted, [1] is the rest. The latency between the input and the output is (( hopSize * minTrackLen) + windowSize) samples.
EXAMPLES::
@ -59,12 +59,11 @@ CODE::
b = Buffer.read(s,File.realpath(FluidSines.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-SynthTwoVoices-M.wav");
// run with large parameters - left is sinusoidal model, right is residual
{FluidSines.ar(PlayBuf.ar(1,b.bufnum,loop:1),thresh: 0.2, minTrackLen: 2, winSize: 2048, fftSize: 8192)}.play
{FluidSines.ar(PlayBuf.ar(1,b,loop:1),threshold: 0.2, minTrackLen: 2, windowSize: 2048, fftSize: 8192)}.play
// interactive parameters with a narrower bandwidth
{FluidSines.ar(PlayBuf.ar(1,b.bufnum,loop:1),30,MouseX.kr(), 5, winSize: 1000, hopSize: 200, fftSize: 4096)}.play
{FluidSines.ar(PlayBuf.ar(1,b,loop:1),30,MouseX.kr(), 5, windowSize: 1000, hopSize: 200, fftSize: 4096)}.play
// null test (the process add a latency of (( hopSize * minTrackLen) + winSize) samples
{var sig = PlayBuf.ar(1,b.bufnum,loop:1); [FluidSines.ar(sig).sum - DelayN.ar(sig, 1, ((( 512 * 15) + 1024)/ s.sampleRate))]}.play
::
// null test (the process add a latency of (( hopSize * minTrackLen) + windowSize) samples
{var sig = PlayBuf.ar(1,b,loop:1); [FluidSines.ar(sig).sum - DelayN.ar(sig, 1, ((( 512 * 15) + 1024)/ s.sampleRate))]}.play
::

@ -0,0 +1,237 @@
TITLE:: FluidSpectralShape
SUMMARY:: Seven Spectral Shape Descriptors in Real-Time
CATEGORIES:: Libraries>FluidDecomposition
RELATED:: Guides/FluCoMa, Guides/FluidDecomposition, Classes/SpecCentroid, Classes/SpecFlatness, Classes/SpecCentroid, Classes/SpecPcile
DESCRIPTION::
This class implements seven of the most popular spectral shape descriptors, computed on a linear scale for both amplitude and frequency. It 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).::
The descriptors are:
LIST::
##the four first statistical moments (https://en.wikipedia.org/wiki/Moment_(mathematics) ), more commonly known as:
LIST::
## the spectral centroid (1) in Hz. This is the point that splits the spectrum in 2 halves of equal energy. It is the weighted average of the magnitude spectrum.
## the spectral spread (2) in Hz. This is the standard deviation of the spectrum envelop, or the average of the distance to the centroid.
## the normalised skewness (3) as ratio. This indicates how tilted is the spectral curve in relation to the middle of the spectral frame, i.e. half of the Nyquist frequency. If it is below that frequency, i.e. the central bin of the magnitude spectrum, it is positive.
## the normalised kurtosis (4) as ratio. This indicates how focused is the spectral curve. If it is peaky, it is high.
::
## the rolloff (5) in Hz. This indicates the frequency under which 95% of the energy is included.
## the flatness (6) in dB. This is the ratio of geometric mean of the magnitude, over the arithmetic mean of the magnitudes. It yields a very approximate measure on how noisy a signal is.
## the crest (7) in dB. This is the ratio of the loudest magnitude over the RMS of the whole frame. A high number is an indication of a loud peak poking out from the overal spectral curve.
The drawings in Peeters 2003 (http://recherche.ircam.fr/anasyn/peeters/ARTICLES/Peeters_2003_cuidadoaudiofeatures.pdf) are useful, as are the commented examples below. For the mathematically-inclined reader, the tutorials and code offered here (https://www.audiocontentanalysis.org/) are interesting to further the understanding.
::
The process will return a multichannel control steam with the seven values, which will be repeated if no change happens within the algorythm, i.e. when the hopSize is larger than the server's kr period.
CLASSMETHODS::
METHOD:: kr
The audio rate in, control rate out version of the object.
ARGUMENT:: in
The audio to be processed.
ARGUMENT:: windowSize
The window size. As spectral shape 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
ARGUMENT:: hopSize
The window hop size. As spectral shape estimation relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts. The -1 default value will default to half of windowSize (overlap of 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 -1 default value will default to windowSize.
ARGUMENT:: maxFFTSize
How large can the FFT be, by allocating memory at instantiation time. This cannot be modulated.
RETURNS::
A 7-channel KR signal with the seven spectral shape descriptors. The latency is windowSize.
EXAMPLES::
code::
//create a monitoring bus for the descriptors
b = Bus.new(\control,0,7);
//create a monitoring window for the values
(
w = Window("spectral Shape Monitor", Rect(10, 10, 220, 190)).front;
c = Array.fill(7, {arg i; StaticText(w, Rect(10, i * 25 + 10, 135, 20)).background_(Color.grey(0.7)).align_(\right)});
c[0].string = ("Centroid: ");
c[1].string = ("Spread: ");
c[2].string = ("Skewness: ");
c[3].string = ("Kurtosis: ");
c[4].string = ("Rolloff: ");
c[5].string = ("Flatness: ");
c[6].string = ("Crest: ");
a = Array.fill(7, {arg i;
StaticText(w, Rect(150, i * 25 + 10, 60, 20)).background_(Color.grey(0.7)).align_(\center);
});
)
//run the window updating routine.
(
r = Routine {
{
b.get({ arg val;
{
if(w.isClosed.not) {
val.do({arg item,index;
a[index].string = item.round(0.01)})
}
}.defer
});
0.01.wait;
}.loop
}.play
)
//play a simple sound to observe the values
(
{
var source;
source = BPF.ar(WhiteNoise.ar(), 330, 55/330);
Out.kr(b,FluidSpectralShape.kr(source));
source.dup;
}.play;
)
::
STRONG::A commented tutorial on how each descriptor behaves with test signals: ::
CODE::
// as above, create a monitoring bus for the descriptors
b = Bus.new(\control,0,7);
//again, create a monitoring window for the values
(
w = Window("Spectral Shape Monitor", Rect(10, 10, 220, 190)).front;
c = Array.fill(7, {arg i; StaticText(w, Rect(10, i * 25 + 10, 135, 20)).background_(Color.grey(0.7)).align_(\right)});
c[0].string = ("Centroid: ");
c[1].string = ("Spread: ");
c[2].string = ("Skewness: ");
c[3].string = ("Kurtosis: ");
c[4].string = ("Rolloff: ");
c[5].string = ("Flatness: ");
c[6].string = ("Crest: ");
a = Array.fill(7, {arg i;
StaticText(w, Rect(150, i * 25 + 10, 60, 20)).background_(Color.grey(0.7)).align_(\center);
});
)
// this time, update a little more slowly.
(
r = Routine {
{
b.get({ arg val;
{
if(w.isClosed.not) {
val.do({arg item,index;
a[index].string = item.round(0.01)})
}
}.defer
});
0.2.wait;
}.loop
}.play
)
// first, a sine wave
(
x = {
arg freq=220;
var source;
source = SinOsc.ar(freq,mul:0.1);
Out.kr(b, VarLag.kr(FluidSpectralShape.kr(source),1024/s.sampleRate));
source.dup;
}.play;
)
// at 220, the centroid is on the frequency, the spread is narrow, but as wide as the FFT Hann window ripples, the skewness is high as we are low and therefore far left of the middle bin (aka half-Nyquist), the Kurtosis is incredibly high as we have a very peaky spectrum. The rolloff is slightly higher than the frequency, taking into account the FFT windowing ripples, the flatness is incredibly low, as we have one peak and not much else, and the crest is quite high, because most of the energy is in a few peaky bins.
x.set(\freq, 440)
// at 440, the skewness has changed (we are nearer the middle of the spectrogram) and the Kurtosis too, although it is still so high it is quite in the same order of magnitude. The rest is stable, as expected.
x.set(\freq, 11000)
// at 11kHz, kurtosis is still in the thousand, but skewness is almost null, as expected.
x.free
// second, broadband noise
(
x = {
arg type = 0;
var source;
source = Select.ar(type,[WhiteNoise.ar(0.1),PinkNoise.ar(0.1)]);
Out.kr(b, VarLag.kr(FluidSpectralShape.kr(source),1024/s.sampleRate));
source.dup;
}.play;
)
// white noise has a linear repartition of energy, so we would expect a centroid in the middle bin (aka half-Nyquist) with a spread covering the full range (+/- a quarter-Nyquist), with a skewness almost null since we are centered, and a very low Kurtosis since we are flat. The rolloff should be almost at Nyquist, the flatness as high as it gets, and the crest quite low.
x.set(\type, 1)
// pink noise has a drop of 3dB per octave across the spectrum, so we would, by comparison, expect a lower centroid, a slighly higher skewness and kurtosis, a lower rolloff, a slighly lower flatness and a higher crest for the larger low-end energy.
x.free
// third, bands of noise
(
x = {
arg type = 0;
var source, chain;
chain = FFT(LocalBuf(1024), WhiteNoise.ar(0.5));
chain = chain.pvcollect(1024, {arg mag,phase;[mag,phase]},5,11,1);
source = Select.ar(type,[
BPF.ar(BPF.ar(WhiteNoise.ar(0.5),330,0.666),330,0.666),
IFFT(chain)]);
Out.kr(b, VarLag.kr(FluidSpectralShape.kr(source),1024/s.sampleRate));
source.dup;
}.play;
)
// a second-order bandpass filter on whitenoise, centred on 330Hz with one octave bandwidth, gives us a centroid quite high. This is due to the exponential behaviour of the filter, with a gentle slope. Observe the spectral analyser:
s.freqscope
// at first it seems quite centred, but then flip the argument FrqScl to lin(ear) and observe how high the spectrum goes. If we set it to a brickwall spectral filter tuned on the same frequencies:
x.set(\type, 1)
// we have a much narrower register, and our centroid and spread, as well as the kurtosis and flatness, agrees with this reading.
x.free
//fourth, equally spaced sines
(
x = {
arg freq = 220;
var source;
source = Mix.fill(7, {arg ind; SinOsc.ar(freq + (ind * (220 / 6)), 0, 0.02)});
Out.kr(b,FluidSpectralShape.kr(source));
source.dup;
}.play;
)
// this example shows a similar result to the brickwall spectral bandpass above. If we move the central frequency nearer the half-Nyquist:
x.set(\freq, 8800)
// we can observe that the linear spread is kept the same, since there is the same linear distance in Hz between our frequencies. Skewness is a good indication here of where we are in the spectrum with the shape.
::

@ -34,13 +34,13 @@ ARGUMENT:: threshFwd
ARGUMENT:: threshBack
The threshold of the offset of the smoothed error function. As it proceeds backwards in time, it allows tight ending of the identification of the anomaly.
ARGUMENT:: winSize
ARGUMENT:: windowSize
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
ARGUMENT:: clumpLength
The window size in sample within with positive detections will be clumped together to avoid overdetecting in time.
ARGUMENT:: minSlice
ARGUMENT:: minSliceLength
The minimum duration of a slice in samples.
RETURNS::
@ -54,18 +54,18 @@ CODE::
b = Buffer.read(s,File.realpath(FluidTransientSlice.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-SynthTwoVoices-M.wav");
// basic param (the process add a latency of (blockSize + padSize - 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
{var sig = PlayBuf.ar(1,b,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,minSlice:2205) * 0.5, DelayN.ar(sig, 1, ((256 + 128 - 80)/ s.sampleRate))]}.play
{var sig = PlayBuf.ar(1,b,loop:1); [FluidTransientSlice.ar(sig,order:80,minSliceLength: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,minSlice:4410);
syncd = DelayN.ar(sig, 1, ((256 + 128 - 10)/ s.sampleRate));
sig = PlayBuf.ar(1,b,loop:1);
trig = FluidTransientSlice.ar(sig,order:10,minSliceLength:4410);
syncd = DelayN.ar(sig, 1, ((256 + 128 - 20)/ s.sampleRate));
pan = TRand.ar(-1,1,trig);
Pan2.ar(syncd,pan);
}.play

@ -39,10 +39,10 @@ ARGUMENT:: threshFwd
ARGUMENT:: threshBack
The threshold of the offset of the smoothed error function. As it proceeds backwards in time, it allows tight ending of the identification of the anomaly.
ARGUMENT:: winSize
ARGUMENT:: windowSize
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 positive.
ARGUMENT:: debounce
ARGUMENT:: clumpLength
The window size in sample within which positive detections will be clumped together to avoid overdetecting in time.
RETURNS::
@ -56,11 +56,11 @@ CODE::
b = Buffer.read(s,File.realpath(FluidTransients.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-SynthTwoVoices-M.wav");
// basic parameters
{FluidTransients.ar(PlayBuf.ar(1, b.bufnum, loop:1))}.play
{FluidTransients.ar(PlayBuf.ar(1, b, loop:1))}.play
// tweaked parameterss
{FluidTransients.ar(PlayBuf.ar(1, b.bufnum, loop:1), 80, threshFwd:MouseX.kr(0,5), threshBack:MouseY.kr(0,2))}.play
{FluidTransients.ar(PlayBuf.ar(1, b, loop:1), 80, threshFwd:MouseX.kr(0,5), threshBack:MouseY.kr(0,2))}.play
// null test (the process add a latency of (blockSize + padding - order) samples
{var sig = PlayBuf.ar(1, b.bufnum, loop:1); [FluidTransients.ar(sig).sum - DelayN.ar(sig, 1, ((256 + 128 - 20)/ s.sampleRate))]}.play
::
{var sig = PlayBuf.ar(1, b, loop:1); [FluidTransients.ar(sig).sum - DelayN.ar(sig, 1, ((256 + 128 - 20)/ s.sampleRate))]}.play
::

@ -0,0 +1,45 @@
//this patch requests a folder and will iterate through all accepted audiofiles and concatenate them in the destination buffer. It will also yield an array with the numFrame where files start in the new buffer.
(
var fileNames;
c = [0];
FileDialog.new({|selection|
var total, totaldur = 0, maxchans = 0;
t = Main.elapsedTime;
fileNames = PathName.new(selection[0])
.entries
.select({|f|
[\wav, \WAV, \mp3,\aif].includes(f.extension.asSymbol);});
total = fileNames.size();
fileNames.do({arg fp;
SoundFile.use(fp.asAbsolutePath , {
arg file;
var dur = file.numFrames;
c = c.add(dur);
totaldur = totaldur + dur;
maxchans = maxchans.max(file.numChannels);
});
});
c.postln;
totaldur.postln;
maxchans.postln;
Routine{
b = Buffer.alloc(s,totaldur,maxchans);
s.sync;
fileNames.do{|f, i|
f.postln;
("Loading"+(i+1)+"of"+total).postln;
Buffer.read(s, f.asAbsolutePath,action:{arg tempbuf; FluidBufCompose.process(s,tempbuf,destination:b,destStartFrame:c[i],action:{tempbuf.free});});
s.sync;
};
("loading buffers done in" + (Main.elapsedTime - t).round(0.1) + "seconds.").postln;
}.play;
}, fileMode:2);
)
b.plot
c.postln
b.play
Buffer.freeAll

@ -0,0 +1,38 @@
//destination buffer
(
b = Buffer.new();
c = Array.new();
)
//this patch requests a folder and will iterate through all accepted audiofiles and concatenate them in the destination buffer. It will also yield an array with the numFrame where files start in the new buffer.
(
var tempbuf,dest=0, fileNames;
FileDialog.new({|selection|
var total;
t = Main.elapsedTime;
fileNames = PathName.new(selection[0])
.entries
.select({|f|
[\wav, \WAV, \mp3,\aif].includes(f.extension.asSymbol);});
total = fileNames.size();
Routine{
fileNames.do{|f, i|
f.postln;
("Loading"+(i+1)+"of"+total).postln;
tempbuf = Buffer.read(s,f.asAbsolutePath);
s.sync;
c = c.add(dest);
FluidBufCompose.process(s,tempbuf,destStartFrame:dest,destination:b);
s.sync;
dest = b.numFrames;
};
("loading buffers done in" + (Main.elapsedTime - t).round(0.1) + "seconds.").postln;
}.play;
}, fileMode:2);
)
b.plot
c.postln
b.play

@ -0,0 +1,57 @@
s.reboot
//this patch does just-in-time nmf processes on buffer, faking a slightly delayed real-time version of it
//what is happening:
//a circular buffer is doing a fake real time - every half second, it sends a frame to be proceesed by NMF~, requesting 3 ranks. Because this latter process is randomly seeded and not sorted, the 3 ranks are not getting similar results each time, hence the random pan
(
b = Buffer.alloc(s,s.sampleRate * 2);
c = Buffer.new(s,0,3);
d = Buffer.new(s,0,3);
e = Buffer.read(s,File.realpath(FluidBufNMF.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Nicol-LoopE-M.wav");
g = Bus.audio(s,1);
h = Buffer.loadCollection(s, Signal.rectWindow(22491).fade(0,440).fade(22049,22489,1,0).put(22490,0));
SynthDef(\becauseIcan,{arg bufnum = 0, nmfa = 0, nmfb = 0, input = 0, env = 0;
var head, head2, duration, audioin, halfdur;
duration = BufFrames.kr(bufnum) / 2;
halfdur = duration / 2;
head = Phasor.ar(0,1,0,duration);
head2 = (head + halfdur) % duration;
// circular buffer writer
audioin = In.ar(input,1);
BufWr.ar(audioin,bufnum,head,0);
BufWr.ar(audioin,bufnum,head+duration,0);
// cue the calculations via the language
SendReply.ar(head > 500, '/processplease',2);
SendReply.ar(head > (halfdur + 500), '/processplease',1);
// read the 2 buffers with an envelop
Out.ar(0, Splay.ar(BufRd.ar(3,nmfa,head,0,1) * BufRd.ar(1,env,head,0,1)) + Splay.ar(BufRd.ar(3,nmfb,head2,0,1) * BufRd.ar(1,env,head2,0,1)));
}).add;
SynthDef(\playa, { arg output = 0, bufnum = 0;
Out.ar(output,PlayBuf.ar(1,bufnum,loop:1));
}).add;
)
// instantiate the player
x = Synth(\playa,[\output, g.index, \bufnum, e.bufnum]);
// instantiate the processor
y = Synth(\becauseIcan,[\bufnum, b.bufnum, \nmfa, c.bufnum, \nmfb, d.bufnum, \input, g.index, \env, h.bufnum], x, 'addAfter');
// instantiate the listener to cue the processing from the language side
(
w = OSCFunc({ arg msg;
if(msg[3]== 1, {
FluidBufNMF.process(s, b, numFrames: 22500, destination: c.bufnum, iterations: 3, fftSize: 1024, windowSize: 512, hopSize: 256);
}, {
FluidBufNMF.process(s, b, 22050, 22500, destination: d.bufnum, iterations: 3, fftSize: 1024, windowSize: 512, hopSize: 256);
});}, '/processplease', s.addr);
)
// stop it all
b.free;c.free;d.free;e.free;f.free;g.free;w.clear;x.free; y.free;

@ -1,15 +1,34 @@
if(MSVC)
target_compile_options(${PLUGIN} PRIVATE /W4 /WX)
else()
target_compile_options(${PLUGIN} PRIVATE -Wall -Wextra -Wpedantic -Wreturn-type -Wconversion)
endif()
set_target_properties(${PLUGIN} PROPERTIES
CXX_STANDARD 14
CXX_STANDARD_REQUIRED YES
CXX_EXTENSIONS NO
)
target_link_libraries(
${PLUGIN}
PRIVATE
PUBLIC
FLUID_DECOMPOSITION
FLUID_SC_WRAPPER
PRIVATE
FFTLIB
)
target_include_directories(
${PLUGIN}
PRIVATE
${LOCAL_INCLUDES}
)
target_include_directories(
${PLUGIN}
SYSTEM PRIVATE
"${SC_PATH}/include/plugin_interface"
"${SC_PATH}/include/common"
@ -32,49 +51,21 @@ if (SUPERNOVA)
endif()
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANG)
target_add_definitions(${PLUGIN} -fvisibility=hidden)
target_compile_definitions(${PLUGIN} PRIVATE -fvisibility=hidden)
include (CheckCXXCompilerFlag)
CHECK_CXX_COMPILER_FLAG(-msse HAS_CXX_SSE)
CHECK_CXX_COMPILER_FLAG(-msse2 HAS_CXX_SSE2)
CHECK_CXX_COMPILER_FLAG(-mfpmath=sse HAS_CXX_FPMATH_SSE)
CHECK_CXX_COMPILER_FLAG(-mavx HAS_AVX)
CHECK_CXX_COMPILER_FLAG(-mavx2 HAS_AVX2)
# target_compile_features(
# ${PLUGIN}
# PUBLIC
# "$<$<NOT:$<CONFIG:DEBUG>>: -mavx -msse -msse2 -msse3 -msse4>"
#
# )
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse")
# endif()
#
# CHECK_C_COMPILER_FLAG(-msse2 HAS_SSE2)
#
#
# if (HAS_SSE2)
# set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msse2")
# endif()
# if (HAS_CXX_SSE2)
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse2")
# endif()
#
#
#
# if (HAS_FPMATH_SSE)
# set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mfpmath=sse")
# endif()
# if (HAS_CXX_FPMATH_SSE)
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfpmath=sse")
# endif()
#
# if(NATIVE)
# add_definitions(-march=native)
# endif()
#
# CHECK_CXX_COMPILER_FLAG(-msse HAS_CXX_SSE)
# CHECK_CXX_COMPILER_FLAG(-msse2 HAS_CXX_SSE2)
# CHECK_CXX_COMPILER_FLAG(-mfpmath=sse HAS_CXX_FPMATH_SSE)
# CHECK_CXX_COMPILER_FLAG(-mavx HAS_AVX)
# CHECK_CXX_COMPILER_FLAG(-mavx2 HAS_AVX2)
target_compile_options(
${PLUGIN}
PUBLIC
"$<$<NOT:$<CONFIG:DEBUG>>: -mavx -msse -msse2 -msse3 -msse4>"
)
endif()
if(MINGW)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mstackrealign")

@ -0,0 +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 <clients/rt/AmpSlice.hpp>
#include <FluidSCWrapper.hpp>
static InterfaceTable *ft;
PluginLoad(FluidSTFTUGen)
{
ft = inTable;
using namespace fluid::client;
makeSCWrapper<AmpSlice>("FluidAmpSlice", 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,15 @@
// 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 <clients/rt/AmpSlice.hpp>
#include <FluidSCWrapper.hpp>
static InterfaceTable *ft;
PluginLoad(OfflineFluidDecompositionUGens)
{
ft = inTable;
using namespace fluid::client;
makeSCWrapper<NRTAmpSlice>("BufAmpSlice", ft);
}

@ -1,3 +1,4 @@
// 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>
@ -5,10 +6,9 @@
static InterfaceTable *ft;
PluginLoad(OfflineFluidDecompositionUGens) {
PluginLoad(OfflineFluidDecompositionUGens)
{
ft = inTable;
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");
makeSCWrapper<BufferComposeClient>("BufCompose", ft);
}

@ -1,157 +0,0 @@
//Can I reallocate buffers on the server? Yes I can.
#include "SC_PlugIn.h"
static InterfaceTable *ft;
//Can we do buffer resizing in a BufGen. I think so.
//Goal here is to mimic the NMF case, allowing
//the dst buffer to be resized accordingly (and not need to
// precalculate the number of frames in advance lang-side
/**
/buf_gen approach: Seems to work, but having to do the 'swap' bewteen NRT mirror buffer and RT buffer
in the same thread seems smelly, given how the allocation sequeneces in SC_SequenceCommand seem to work.
**/
void bufferMatch(World *world, struct SndBuf *srcBuf, struct sc_msg_iter *msg) {
size_t srcFrameCount = srcBuf->frames;
size_t srcChanCount = srcBuf->channels;
long dstBufNum = msg->geti();
size_t rank = msg->geti();
if (dstBufNum == -1) {
Print("BufferMatch is not happy because there is no output buffer "
"specified.\n");
return;
}
// This sequence follows what I saw in SC_SequenceCommand.cpp. Pretty much.
// Get the NRT thread mirror buffer
SndBuf *dstBufNRT = World_GetNRTBuf(world, dstBufNum);
// Call the allocation function on that
SCErr err = ft->fBufAlloc(dstBufNRT, srcChanCount * rank, srcFrameCount,
srcBuf->samplerate);
// If we were posh, we'd check for errors
// Norhing will happen, unless we (a) assign the allocated data back to the
// main buffer pool (b?) Tell the server the buffer has changed Get the RT
// buffer
SndBuf *dstBuf = World_GetBuf(world, dstBufNum);
// Assign value to our NRT buffer pointer's value
*dstBuf = *dstBufNRT;
// Ping the server
world->mSndBufUpdates[dstBufNum].writes++;
}
/**
ASync command version. Is this an abuse of Async command? Doesn't *seem* to be, but there is almost no
documentation for its proper use :-|
**/
//Struct that holds all our state between stages
struct BufferFunTimeCmdData
{
long dstbuf;
long srcbuf;
size_t rank;
SndBuf* newdst;
};
//'Stage2()' happens in the NRT thread. Here we do our heavy stuff
bool aSyncBufferFunNrtStage(World *world, void *inUserData) {
BufferFunTimeCmdData *data = (BufferFunTimeCmdData *)inUserData;
SndBuf *src = World_GetNRTBuf(world, data->srcbuf);
SndBuf *dst = World_GetNRTBuf(world, data->dstbuf);
SCErr err = ft->fBufAlloc(dst, src->channels * data->rank, src->frames,
src->samplerate);
data->newdst = dst;
return true;
}
//'Statge3()' happens back in the RT thread, here we swap our new buffers
//SC will send the completion message after this
bool aSyncBufferFunRtStage(World *world, void *inUserData) {
BufferFunTimeCmdData *data = (BufferFunTimeCmdData *)inUserData;
// Norhing will happen, unless we (a) assign the allocated data back to the
// main buffer pool (b?) Tell the server the buffer has changed Get the RT
// buffer
SndBuf *dstBuf = World_GetBuf(world, data->dstbuf);
// Assign value to our NRT buffer pointer's value
*dstBuf = *data->newdst;
// Ping the server
world->mSndBufUpdates[data->dstbuf].writes++;
return true;
}
//'Stage 4()' is back on the NRT, we don't have anything to do here. SC will send 'done' after this
bool aSyncBufferFunFinalBit(World *world, void *inUserData) { return true; }
//Here we free any resources, including the struct we made at the start
void aSyncBufferFunCleanUp(World *world, void *inUserData) {
BufferFunTimeCmdData *data = (BufferFunTimeCmdData *)inUserData;
RTFree(world, data);
// scsynth will take care of the completion message
}
//This is our entry point. We need to make a struct and populate it with good things
void aSyncBufferFunMain(World *inWorld, void *inUserData,
struct sc_msg_iter *msg, void *replyAddr) {
BufferFunTimeCmdData *data =
(BufferFunTimeCmdData *)RTAlloc(inWorld, sizeof(BufferFunTimeCmdData));
data->srcbuf = msg->geti();
data->dstbuf = msg->geti();
data->rank = msg->geti();
bool ok = true;
if (data->srcbuf < 0) {
Print("No source buffer");
ok = false;
}
if (data->dstbuf < 0) {
Print("No dst buffer");
ok = false;
}
if (!ok) {
RTFree(inWorld, data);
return;
}
// how to pass a string argument: [WILL BE USEFUL FOR WINDOW FUNCTIONS?]
// const char *name = msg->gets(); // get the string argument
// if (name) {
// data->name = (char*)RTAlloc(inWorld, strlen(name)+1); // allocate
// space, free it in cmdCleanup. strcpy(data->name, name); // copy the
// string
// }
//Deal with completion message
size_t completionMsgSize = msg->getbsize();
char* completionMsgString = 0;
if(completionMsgSize)
{
//allocate string
completionMsgString = (char*)RTAlloc(inWorld,completionMsgSize);
msg->getb(completionMsgString,completionMsgSize);
}
//Now, set the wheels in motion
DoAsynchronousCommand(inWorld, replyAddr, "AsyncBufMatch", data,
(AsyncStageFn)aSyncBufferFunNrtStage,
(AsyncStageFn)aSyncBufferFunRtStage,
(AsyncStageFn)aSyncBufferFunFinalBit,
aSyncBufferFunCleanUp, completionMsgSize,
completionMsgString);
}
PluginLoad(BufferFunTime) {
ft = inTable;
//BufGen version: all in the NRT thread
DefineBufGen("BufMatch", bufferMatch);
//ASync version: swaps between NRT and RT threads
DefinePlugInCmd("AsyncBufMatch", aSyncBufferFunMain, nullptr);
}

@ -1,24 +0,0 @@
s.reboot
//Quickie test of buffer allocation working
//Read a sound file
a = Buffer.read(s,"../../release-packaging/AudioFiles/Tremblay-AaS-SynthTwoVoices-M.wav".resolveRelative);
//size it appropriately (in our server code) and return the new object
f = FluidBufExperiments.allocMatch(s,a,rank:5);
//Make sure everything is kosher:
a.query
f.query
//Try full async version
s.reboot
//Quickie test of buffer allocation working
//Read a sound file
a = Buffer.read(s,"../../release-packaging/AudioFiles/Tremblay-AaS-SynthTwoVoices-M.wav".resolveRelative);
//Pass buffer to this, along with a rank. It will allocate a new buffer,
//size it appropriately (in our server code) and return the new object
f = FluidBufExperiments.allocMatchAsync(s,a,rank:5);
//Make sure everything is kosher:
a.query
f.query

@ -1,4 +1,5 @@
// FD_BufHPSS, an NRT buffer HPSS Processor
// 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 <clients/rt/HPSSClient.hpp>
@ -6,8 +7,9 @@
static InterfaceTable *ft;
PluginLoad(OfflineFluidDecompositionUGens) {
PluginLoad(OfflineFluidDecompositionUGens)
{
ft = inTable;
using namespace fluid::client;
makeSCWrapper<NRTHPSS,double,float>("BufHPSS",NRTHPSSParams,ft);
makeSCWrapper<NRTHPSS>("BufHPSS", 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/LoudnessClient.hpp>
#include <FluidSCWrapper.hpp>
static InterfaceTable *ft;
PluginLoad(OfflineFluidDecompositionUGens) {
ft = inTable;
using namespace fluid::client;
makeSCWrapper<NRTLoudnessClient>("BufLoudness", 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/MFCCClient.hpp>
#include <FluidSCWrapper.hpp>
static InterfaceTable *ft;
PluginLoad(FluidSTFTUGen) {
ft = inTable;
using namespace fluid::client;
makeSCWrapper<NRTMFCCClient>("BufMFCC", 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/MelBandsClient.hpp>
#include <FluidSCWrapper.hpp>
static InterfaceTable *ft;
PluginLoad(FluidSTFTUGen) {
ft = inTable;
using namespace fluid::client;
makeSCWrapper<NRTMelBandsClient>("BufMelBands", ft);
}

@ -1,10 +1,12 @@
#include <clients/nrt/NMFClient.hpp>
#include <FluidSCWrapper.hpp>
static InterfaceTable *ft;
PluginLoad(OfflineFluidDecompositionUGens) {
PluginLoad(OfflineFluidDecompositionUGens)
{
ft = inTable;
using namespace fluid::client;
makeSCWrapper<NMFClient,double,float>("BufNMF",NMFParams, ft);
makeSCWrapper<NMFClient>("BufNMF", ft);
}

@ -7,8 +7,9 @@
static InterfaceTable* ft;
PluginLoad(OfflineFluidDecompositionUGens) {
PluginLoad(OfflineFluidDecompositionUGens)
{
ft = inTable;
using namespace fluid::client;
makeSCWrapper<NoveltyClient,double,float>("BufNoveltySlice",NoveltyParams,ft);
makeSCWrapper<NoveltyClient>("BufNoveltySlice", 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,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 <clients/rt/OnsetSlice.hpp>
#include <clients/nrt/FluidNRTClientWrapper.hpp>
#include <FluidSCWrapper.hpp>
static InterfaceTable *ft;
PluginLoad(OfflineFluidDecompositionUGens) {
ft = inTable;
using namespace fluid::client;
makeSCWrapper<NRTOnsetSlice>("BufOnsetSlice", 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/PitchClient.hpp>
#include <FluidSCWrapper.hpp>
static InterfaceTable *ft;
PluginLoad(FluidSTFTUGen) {
ft = inTable;
using namespace fluid::client;
makeSCWrapper<NRTPitchClient>("BufPitch", 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,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 <clients/rt/RTNoveltySlice.hpp>
#include <clients/nrt/FluidNRTClientWrapper.hpp>
#include <FluidSCWrapper.hpp>
static InterfaceTable *ft;
PluginLoad(OfflineFluidDecompositionUGens) {
ft = inTable;
using namespace fluid::client;
makeSCWrapper<NRTRTNoveltySlice>("BufRTNoveltySlice", ft);
}

@ -7,9 +7,10 @@
static InterfaceTable *ft;
PluginLoad(OfflineFluidDecompositionUGens) {
PluginLoad(OfflineFluidDecompositionUGens)
{
ft = inTable;
using namespace fluid::client;
makeSCWrapper<NRTSines,double,float>("BufSines",NRTSineParams,ft);
makeSCWrapper<NRTSines>("BufSines", 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,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 <clients/rt/SpectralShapeClient.hpp>
#include <clients/nrt/FluidNRTClientWrapper.hpp>
#include <FluidSCWrapper.hpp>
static InterfaceTable *ft;
PluginLoad(OfflineFluidDecompositionUGens) {
ft = inTable;
using namespace fluid::client;
makeSCWrapper<NRTSpectralShapeClient>("BufSpectralShape", 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,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 <clients/nrt/BufferStats.hpp>
#include <FluidSCWrapper.hpp>
static InterfaceTable *ft;
PluginLoad(OfflineFluidDecompositionUGens)
{
ft = inTable;
using namespace fluid::client;
makeSCWrapper<BufferStats>("BufStats", ft);
}

@ -10,5 +10,5 @@ static InterfaceTable* ft;
PluginLoad(OfflineFluidDecompositionUGens) {
ft = inTable;
using namespace fluid::client;
makeSCWrapper<NRTTransientSlice,double,float>("BufTransientSlice",NRTTransientSliceParams, ft);
makeSCWrapper<NRTTransientSlice>("BufTransientSlice", ft);
}

@ -1,11 +1,13 @@
#include <clients/nrt/FluidNRTClientWrapper.hpp>
#include <clients/rt/TransientClient.hpp>
#include <FluidSCWrapper.hpp>
static InterfaceTable *ft;
PluginLoad(OfflineFluidDecompositionUGens) {
PluginLoad(OfflineFluidDecompositionUGens)
{
ft = inTable;
using namespace fluid::client;
makeSCWrapper<NRTTransients,double,float>("BufTransients",NRTTransientParams,ft);
makeSCWrapper<NRTTransients>("BufTransients", ft);
}

@ -1,13 +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 <FluidSCWrapper.hpp>
#include <clients/rt/GainClient.hpp>
#include <SC_PlugIn.hpp>
static InterfaceTable *ft;
PluginLoad(FluidGainUgen) {
PluginLoad(FluidGainUgen)
{
ft = inTable;
using namespace fluid::client;
makeSCWrapper<GainClient,double,float>("FluidGain", GainParams,ft);
makeSCWrapper<GainClient>("FluidGain", ft);
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save