diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f2cab5..f7c2712 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mavx -msse -msse2 -msse3 -msse4") - +set(CMAKE_CXX_EXTENSIONS OFF) if(APPLE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -stdlib=libc++") @@ -16,7 +16,7 @@ endif() project (fluid_decomposition_supercollider LANGUAGES CXX) -# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic") option(SUPERNOVA "Build plugins for supernova" OFF) @@ -43,7 +43,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/)) @@ -64,6 +64,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 $<$>:-mavx -msse -msse2 -msse3 -msse4> + ) + add_library(FLUID_SC_WRAPPER INTERFACE) target_sources(FLUID_SC_WRAPPER INTERFACE diff --git a/include/FluidSCWrapper.hpp b/include/FluidSCWrapper.hpp index 3b9c92f..f93aa09 100644 --- a/include/FluidSCWrapper.hpp +++ b/include/FluidSCWrapper.hpp @@ -64,7 +64,7 @@ public: 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(static_cast(unit)->mClient.latency())}; auto ft = Wrapper::getInterfaceTable(); @@ -76,7 +76,7 @@ public: } RealTime() - : mControlsIterator{mInBuf + mSpecialIndex + 1,mNumInputs - mSpecialIndex - 1} + : mControlsIterator{mInBuf + mSpecialIndex + 1,static_cast(static_cast(mNumInputs) - mSpecialIndex - 1)} , mParams{Wrapper::Client::getParameterDescriptors()} , mClient{Wrapper::setParams(mParams,mWorld->mVerbosity > 0, mWorld, mControlsIterator,true)} {} @@ -97,47 +97,49 @@ public: 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) + for (int i = 0; i < static_cast(mClient.audioChannelsIn()); ++i) { mInputConnections.emplace_back(isAudioRateIn(i)); mAudioInputs.emplace_back(nullptr, 0, 0); } - for (int i = 0; i < mClient.audioChannelsOut(); ++i) + for (int i = 0; i < static_cast(mClient.audioChannelsOut()); ++i) { mOutputConnections.emplace_back(true); mOutputs.emplace_back(nullptr, 0, 0); } - for (int i = 0; i < mClient.controlChannelsOut(); ++i) { mOutputs.emplace_back(nullptr, 0, 0); } - + for (int i = 0; i < static_cast(mClient.controlChannelsOut()); ++i) { mOutputs.emplace_back(nullptr, 0, 0); } + + set_calc_function(); Wrapper::getInterfaceTable()->fClearUnitOutputs(this, 1); - mClient.sampleRate(fullSampleRate()); + } - 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 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()); + if (mOutputConnections[i]) mOutputs[i].reset(out(static_cast(i)), 0, fullBufferSize()); } - for (int i = 0; i < mClient.controlChannelsOut(); ++i) { mOutputs[i].reset(out(i), 0, 1); } + for (size_t i = 0; i < mClient.controlChannelsOut(); ++i) { mOutputs[i].reset(out(static_cast(i)), 0, 1); } mClient.process(mAudioInputs, mOutputs); } @@ -165,19 +167,19 @@ class NonRealTime public: static void setup(InterfaceTable *ft, const char *name) { DefinePlugInCmd(name, launch, nullptr); } - NonRealTime(World *world, sc_msg_iter *args) + NonRealTime(World* w, sc_msg_iter* args) : mParams{Client::getParameterDescriptors()} - , mClient{mParams} + , 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) != Client::getParameterDescriptors().count())) { - std::cout << "ERROR: " << Wrapper::getName() << " wrong number of arguments. Expected " + 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; @@ -186,8 +188,6 @@ public: 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 - Wrapper::setParams(w->mParams, false, world, args); - Result result = validateParameters(w); if (!result.ok()) { @@ -202,13 +202,13 @@ public: if (msgSize) { args->getb(completionMessage.data(), msgSize); } world->ft->fDoAsynchronousCommand(world, replyAddr, Wrapper::getName(), w, process, exchangeBuffers, tidyUp, destroy, - msgSize, completionMessage.data()); + static_cast(msgSize), completionMessage.data()); } static bool process(World *world, void *data) { return static_cast(data)->process(world); } static bool exchangeBuffers(World *world, void *data) { return static_cast(data)->exchangeBuffers(world); } static bool tidyUp(World *world, void *data) { return static_cast(data)->tidyUp(world); } - static void destroy(World *world, void *data) { delete static_cast(data); } + static void destroy(World *, void *data) { delete static_cast(data); } protected: ParamSetType mParams; @@ -226,7 +226,7 @@ private: return {}; } - bool process(World *world) + bool process(World *) { Result r = mClient.process(); @@ -245,7 +245,7 @@ private: return true; } - bool tidyUp(World *world) + bool tidyUp(World *) { mParams.template forEachParamType(); return true; @@ -329,23 +329,24 @@ class FluidSCWrapper : public impl::FluidSCWrapperBase { static constexpr size_t argSize = C::getParameterDescriptors().template get().fixedSize; - auto fromArgs(World *w, FloatControlsIter& args, LongT::type, int) { return args.next(); } - auto fromArgs(World *w, FloatControlsIter& args, FloatT::type, int) { return args.next(); } - auto fromArgs(World *w, sc_msg_iter* args, LongT::type, int defVal) { return args->geti(defVal); } - auto fromArgs(World *w, sc_msg_iter* args, FloatT::type, int) { return args->getf(); } + 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 = fromArgs(w, args, LongT::type(), -1); + typename LongT::type bufnum = static_cast(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 a; + using LiteralType = typename ParamLiteralConvertor::LiteralType; - for (auto i = 0; i < argSize; i++) - a[i] = fromArgs(w, args, a[0], 0); + for (size_t i = 0; i < argSize; i++) + a[i] = static_cast(fromArgs(w, args, a[0], 0)); return a.value(); } diff --git a/include/SCBufferAdaptor.hpp b/include/SCBufferAdaptor.hpp index cff93bc..23c4bf1 100644 --- a/include/SCBufferAdaptor.hpp +++ b/include/SCBufferAdaptor.hpp @@ -25,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(mBuffer->samplerate)) mBuffer->samplerate = world->mFullRate.mSampleRate; } @@ -64,8 +64,8 @@ public: 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(bufnum), rt) , mBufnum(bufnum) , mWorld(world) { @@ -78,7 +78,7 @@ public: void assignToRT(World *rtWorld) { - SndBuf *rtBuf = World_GetBuf(rtWorld, mBufnum); + SndBuf *rtBuf = World_GetBuf(rtWorld, static_cast(mBufnum)); *rtBuf = *mBuffer; rtWorld->mSndBufUpdates[mBufnum].writes++; } @@ -108,13 +108,13 @@ public: return true; } - FluidTensorView samps(size_t channel, size_t rankIdx = 0) override + FluidTensorView samps(size_t channel) override { FluidTensorView v{mBuffer->data, 0, static_cast(mBuffer->frames), static_cast(mBuffer->channels)}; - return v.col(rankIdx + channel * mRank); + return v.col(channel); } // Return a 2D chunk @@ -130,36 +130,32 @@ public: size_t numFrames() const override { - return valid() ? this->mBuffer->frames : 0; + return valid() ? static_cast(this->mBuffer->frames) : 0u; } size_t numChans() const override { - return valid() ? this->mBuffer->channels / mRank : 0; + return valid() ? static_cast(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, double sampleRate) 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, sampleRate); + mWorld->ft->fBufAlloc(mBuffer, static_cast(channels), static_cast(frames), sampleRate); } - int bufnum() { return mBufnum; } + intptr_t bufnum() { return mBufnum; } void realTime(bool rt) { mRealTime = rt; } protected: bool mRealTime{false}; float *mOldData{0}; - long mBufnum; + intptr_t mBufnum; World *mWorld; - size_t mRank{1}; }; std::ostream& operator <<(std::ostream& os, SCBufferAdaptor& b) diff --git a/release-packaging/Classes/FluidAmpSlice.sc b/release-packaging/Classes/FluidAmpSlice.sc new file mode 100644 index 0000000..73d9e38 --- /dev/null +++ b/release-packaging/Classes/FluidAmpSlice.sc @@ -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; + } +} diff --git a/release-packaging/Classes/FluidBufAmpSlice.sc b/release-packaging/Classes/FluidBufAmpSlice.sc new file mode 100644 index 0000000..568c308 --- /dev/null +++ b/release-packaging/Classes/FluidBufAmpSlice.sc @@ -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); + }; + } +} diff --git a/release-packaging/Classes/FluidBufCompose.sc b/release-packaging/Classes/FluidBufCompose.sc index 9e7c3ce..1e4a569 100644 --- a/release-packaging/Classes/FluidBufCompose.sc +++ b/release-packaging/Classes/FluidBufCompose.sc @@ -4,8 +4,8 @@ FluidBufCompose{ source = source.asUGenInput; destination = destination.asUGenInput; - if(source.isNil) {Error("Invalid Buffer").format(thisMethod.name, this.class.name).throw}; - if(destination.isNil) {Error("Invalid Buffer").format(thisMethod.name, this.class.name).throw}; + source.isNil.if {"FluidBufCompose: Invalid source buffer".throw}; + destination.isNil.if {"FluidBufCompose: Invalid destination buffer".throw}; server = server ? Server.default; diff --git a/release-packaging/Classes/FluidBufHPSS.sc b/release-packaging/Classes/FluidBufHPSS.sc index b0c53ee..77b5b8b 100644 --- a/release-packaging/Classes/FluidBufHPSS.sc +++ b/release-packaging/Classes/FluidBufHPSS.sc @@ -1,14 +1,14 @@ FluidBufHPSS{ - *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, winSize = 1024, hopSize = -1, fftSize = -1, action; + *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}; source = source.asUGenInput; harmonic = harmonic.asUGenInput; percussive = percussive.asUGenInput; residual = residual.asUGenInput; - if(source.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw}; + source.isNil.if {"FluidBufHPSS: Invalid source buffer".throw}; server = server ? Server.default; harmonic = harmonic ? -1; @@ -19,7 +19,7 @@ FluidBufHPSS{ //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, \BufHPSS, source, startFrame, numFrames, startChan, numChans, harmonic, percussive, residual, harmFilterSize, percFilterSize, maskingMode, harmThreshFreq1, harmThreshAmp1, harmThreshFreq2, harmThreshAmp2, percThreshFreq1, percThreshAmp1, percThreshFreq2, percThreshAmp2, winSize, hopSize, fftSize, maxFFTSize, harmFilterSize, percFilterSize); + 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}; diff --git a/release-packaging/Classes/FluidBufLoudness.sc b/release-packaging/Classes/FluidBufLoudness.sc new file mode 100644 index 0000000..58f3f8e --- /dev/null +++ b/release-packaging/Classes/FluidBufLoudness.sc @@ -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); + }; + } +} diff --git a/release-packaging/Classes/FluidBufMFCC.sc b/release-packaging/Classes/FluidBufMFCC.sc new file mode 100644 index 0000000..131ae3c --- /dev/null +++ b/release-packaging/Classes/FluidBufMFCC.sc @@ -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); + }; + } +} diff --git a/release-packaging/Classes/FluidBufMelBands.sc b/release-packaging/Classes/FluidBufMelBands.sc new file mode 100644 index 0000000..5583ab1 --- /dev/null +++ b/release-packaging/Classes/FluidBufMelBands.sc @@ -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); + }; + } +} diff --git a/release-packaging/Classes/FluidBufNMF.sc b/release-packaging/Classes/FluidBufNMF.sc index 92b1f07..a46879b 100644 --- a/release-packaging/Classes/FluidBufNMF.sc +++ b/release-packaging/Classes/FluidBufNMF.sc @@ -1,5 +1,5 @@ FluidBufNMF { - *process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, destination, bases, basesMode = 0, activations, actMode = 0, rank = 1, numIter = 100, winSize = 1024, hopSize = -1, fftSize = -1, winType = 0, randSeed = -1, action; + *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; source = source.asUGenInput; @@ -7,7 +7,7 @@ FluidBufNMF { bases = bases.asUGenInput; activations = activations.asUGenInput; - if(source.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw}; + source.isNil.if {"FluidBufNMF: Invalid source buffer".throw}; server = server ? Server.default; @@ -16,7 +16,7 @@ FluidBufNMF { activations = activations ? -1; forkIfNeeded{ - server.sendMsg(\cmd, \BufNMF, source, startFrame, numFrames, startChan, numChans, destination, bases, basesMode, activations, actMode, rank, numIter, winSize, hopSize, fftSize); + 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}; diff --git a/release-packaging/Classes/FluidBufNoveltySlice.sc b/release-packaging/Classes/FluidBufNoveltySlice.sc index 64bb56d..e55af95 100644 --- a/release-packaging/Classes/FluidBufNoveltySlice.sc +++ b/release-packaging/Classes/FluidBufNoveltySlice.sc @@ -1,18 +1,18 @@ FluidBufNoveltySlice{ - *process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, kernSize = 3, threshold = 0.8, filterSize = 1, winSize = 1024, hopSize = -1, fftSize = -1, action; + *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}; //ready for when we need it from the RT wrapper + //var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize}; //ready for when we need it from the RT wrapper source = source.asUGenInput; indices = indices.asUGenInput; - if(source.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw}; - if(indices.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw}; + source.isNil.if {"FluidBufNoveltySlice: Invalid source buffer".throw}; + indices.isNil.if {"FluidBufNoveltySlice: Invalid features buffer".throw}; server = server ? Server.default; forkIfNeeded{ - server.sendMsg(\cmd, \BufNoveltySlice, source, startFrame, numFrames, startChan, numChans, indices, kernSize, threshold, filterSize, winSize, hopSize, fftSize); + 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); diff --git a/release-packaging/Classes/FluidBufOnsetSlice.sc b/release-packaging/Classes/FluidBufOnsetSlice.sc index 795390f..d7bbcac 100644 --- a/release-packaging/Classes/FluidBufOnsetSlice.sc +++ b/release-packaging/Classes/FluidBufOnsetSlice.sc @@ -1,13 +1,13 @@ FluidBufOnsetSlice{ - *process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, function = 0, threshold = 0.1, debounce = 2, filterSize = 5, frameDelta = 0, winSize = 1024, hopSize = -1, fftSize = -1, action; + *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) {winSize.nextPowerOfTwo} {fftSize}; + var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize}; source = source.asUGenInput; indices = indices.asUGenInput; - if(source.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw}; - if(indices.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw}; + source.isNil.if {"FluidBufOnsetSlice: Invalid source buffer".throw}; + indices.isNil.if {"FluidBufOnsetSlice: Invalid features buffer".throw}; server = server ? Server.default; @@ -15,7 +15,7 @@ FluidBufOnsetSlice{ //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, debounce, filterSize, frameDelta, winSize, hopSize, fftSize, maxFFTSize); + 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); diff --git a/release-packaging/Classes/FluidBufPitch.sc b/release-packaging/Classes/FluidBufPitch.sc new file mode 100644 index 0000000..d0ad4ce --- /dev/null +++ b/release-packaging/Classes/FluidBufPitch.sc @@ -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); + }; + } +} diff --git a/release-packaging/Classes/FluidBufRTNoveltySlice.sc b/release-packaging/Classes/FluidBufRTNoveltySlice.sc new file mode 100644 index 0000000..5741f15 --- /dev/null +++ b/release-packaging/Classes/FluidBufRTNoveltySlice.sc @@ -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); + }; + } +} diff --git a/release-packaging/Classes/FluidBufSines.sc b/release-packaging/Classes/FluidBufSines.sc index 2f1a8a3..0da582b 100644 --- a/release-packaging/Classes/FluidBufSines.sc +++ b/release-packaging/Classes/FluidBufSines.sc @@ -1,13 +1,13 @@ FluidBufSines{ - *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, winSize = 1024, hopSize = -1, fftSize = -1, action; + *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}; source = source.asUGenInput; sines = sines.asUGenInput; residual = residual.asUGenInput; - if(source.isNil) {Error("Invalid Buffer").format(thisMethod.name, this.class.name).throw}; + source.isNil.if {"FluidBufSines: Invalid source buffer".throw}; server = server ? Server.default; sines = sines ? -1; @@ -17,7 +17,7 @@ FluidBufSines{ //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, \BufSines, source, startFrame, numFrames, startChan, numChans, sines, residual, bandwidth, threshold, minTrackLen, magWeight, freqWeight, winSize, hopSize, fftSize, maxFFTSize); + 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}; diff --git a/release-packaging/Classes/FluidBufSpectralShape.sc b/release-packaging/Classes/FluidBufSpectralShape.sc index 2105a8e..72b8b61 100644 --- a/release-packaging/Classes/FluidBufSpectralShape.sc +++ b/release-packaging/Classes/FluidBufSpectralShape.sc @@ -1,7 +1,7 @@ FluidBufSpectralShape{ - *process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, winSize = 1024, hopSize = -1, fftSize = -1, action; + *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) {winSize.nextPowerOfTwo} {fftSize}; + var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize}; source = source.asUGenInput; features = features.asUGenInput; @@ -15,7 +15,7 @@ FluidBufSpectralShape{ //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, winSize, hopSize, fftSize, maxFFTSize); + 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); diff --git a/release-packaging/Classes/FluidBufStats.sc b/release-packaging/Classes/FluidBufStats.sc new file mode 100644 index 0000000..8a838c3 --- /dev/null +++ b/release-packaging/Classes/FluidBufStats.sc @@ -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); + }; + } +} diff --git a/release-packaging/Classes/FluidBufTransientSlice.sc b/release-packaging/Classes/FluidBufTransientSlice.sc index 69e3556..25fb83a 100644 --- a/release-packaging/Classes/FluidBufTransientSlice.sc +++ b/release-packaging/Classes/FluidBufTransientSlice.sc @@ -1,16 +1,16 @@ FluidBufTransientSlice{ - *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, winSize = 14, debounce = 25, minSlice = 1000, action; + *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; source = source.asUGenInput; indices = indices.asUGenInput; - if(source.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw}; - if(indices.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw}; + source.isNil.if {"FluidBufNoveltySlice: Invalid source buffer".throw}; + indices.isNil.if {"FluidBufNoveltySlice: Invalid features buffer".throw}; server = server ? Server.default; forkIfNeeded{ - server.sendMsg(\cmd, \BufTransientSlice, source, startFrame, numFrames, startChan, numChans, indices, order, blockSize, padSize, skew, threshFwd, threshBack, winSize, debounce, minSlice); + 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); diff --git a/release-packaging/Classes/FluidBufTransients.sc b/release-packaging/Classes/FluidBufTransients.sc index ad817cf..521ef50 100644 --- a/release-packaging/Classes/FluidBufTransients.sc +++ b/release-packaging/Classes/FluidBufTransients.sc @@ -1,18 +1,18 @@ FluidBufTransients { - *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, winSize = 14, debounce = 25, action; + *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; source = source.asUGenInput; transients = transients.asUGenInput; residual = residual.asUGenInput; - if(source.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw}; + source.isNil.if {"FluidBufTransients: Invalid source buffer".throw}; server = server ? Server.default; transients = transients ? -1; residual = residual ? -1; forkIfNeeded{ - server.sendMsg(\cmd, \BufTransients, source, startFrame, numFrames, startChan, numChans, transients, residual, order, blockSize, padSize, skew, threshFwd, threshBack, winSize, debounce); + 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}; diff --git a/release-packaging/Classes/FluidHPSS.sc b/release-packaging/Classes/FluidHPSS.sc index 85c7c8d..44e92bf 100644 --- a/release-packaging/Classes/FluidHPSS.sc +++ b/release-packaging/Classes/FluidHPSS.sc @@ -1,6 +1,6 @@ FluidHPSS : MultiOutUGen { - *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, winSize= 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, winSize, hopSize, fftSize, maxFFTSize, maxHarmFilterSize, maxPercFilterSize) + *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; diff --git a/release-packaging/Classes/FluidLoudness.sc b/release-packaging/Classes/FluidLoudness.sc new file mode 100644 index 0000000..36bece6 --- /dev/null +++ b/release-packaging/Classes/FluidLoudness.sc @@ -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; + } +} diff --git a/release-packaging/Classes/FluidMFCC.sc b/release-packaging/Classes/FluidMFCC.sc new file mode 100644 index 0000000..76d37b6 --- /dev/null +++ b/release-packaging/Classes/FluidMFCC.sc @@ -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; + } +} diff --git a/release-packaging/Classes/FluidMelBands.sc b/release-packaging/Classes/FluidMelBands.sc new file mode 100644 index 0000000..3021006 --- /dev/null +++ b/release-packaging/Classes/FluidMelBands.sc @@ -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; + } +} diff --git a/release-packaging/Classes/FluidNMFFilter.sc b/release-packaging/Classes/FluidNMFFilter.sc new file mode 100644 index 0000000..a4ab279 --- /dev/null +++ b/release-packaging/Classes/FluidNMFFilter.sc @@ -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; + } +} diff --git a/release-packaging/Classes/FluidNMFMatch.sc b/release-packaging/Classes/FluidNMFMatch.sc index 68ef57f..0c438d5 100644 --- a/release-packaging/Classes/FluidNMFMatch.sc +++ b/release-packaging/Classes/FluidNMFMatch.sc @@ -1,7 +1,7 @@ FluidNMFMatch : MultiOutUGen { - *kr { arg in = 0, bases, maxRank = 1, numIter = 10, winSize = 1024, hopSize = -1, fftSize = -1, maxFFTSize = 16384; - ^this.multiNew('control', in.asAudioRateInput(this), bases, maxRank, numIter, 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; @@ -11,7 +11,7 @@ FluidNMFMatch : MultiOutUGen { checkInputs { if(inputs.at(2).rate != 'scalar') { - ^(": maxRank cannot be modulated."); + ^(": maxComponents cannot be modulated."); }; if(inputs.at(7).rate != 'scalar') { ^(": maxFFTSize cannot be modulated."); diff --git a/release-packaging/Classes/FluidOnsetSlice.sc b/release-packaging/Classes/FluidOnsetSlice.sc index b50980a..2bedb64 100644 --- a/release-packaging/Classes/FluidOnsetSlice.sc +++ b/release-packaging/Classes/FluidOnsetSlice.sc @@ -1,6 +1,6 @@ FluidOnsetSlice : UGen { - *ar { arg in = 0, function = 0, threshold = 0.5, debounce = 2, filterSize = 5, frameDelta = 0, winSize = 1024, hopSize = -1, fftSize = -1, maxFFTSize = 16384; - ^this.multiNew('audio', in.asAudioRateInput(this), function, threshold, debounce, filterSize, frameDelta, winSize, hopSize, fftSize, maxFFTSize) + *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') { diff --git a/release-packaging/Classes/FluidPitch.sc b/release-packaging/Classes/FluidPitch.sc new file mode 100644 index 0000000..66bdfb9 --- /dev/null +++ b/release-packaging/Classes/FluidPitch.sc @@ -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; + } +} diff --git a/release-packaging/Classes/FluidRTNoveltySlice.sc b/release-packaging/Classes/FluidRTNoveltySlice.sc new file mode 100644 index 0000000..813d15a --- /dev/null +++ b/release-packaging/Classes/FluidRTNoveltySlice.sc @@ -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; + } +} diff --git a/release-packaging/Classes/FluidSTFTPass.sc b/release-packaging/Classes/FluidSTFTPass.sc index caa0005..d3456fd 100644 --- a/release-packaging/Classes/FluidSTFTPass.sc +++ b/release-packaging/Classes/FluidSTFTPass.sc @@ -1,6 +1,6 @@ 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') { diff --git a/release-packaging/Classes/FluidSines.sc b/release-packaging/Classes/FluidSines.sc index 3d2f16a..5edf880 100644 --- a/release-packaging/Classes/FluidSines.sc +++ b/release-packaging/Classes/FluidSines.sc @@ -1,6 +1,6 @@ FluidSines : MultiOutUGen { - *ar { arg in = 0, bandwidth = 76, threshold = 0.7, minTrackLen = 15, magWeight = 0.1, freqWeight = 1.0, winSize= 1024, hopSize= -1, fftSize= -1, maxFFTSize=16384; - ^this.multiNew('audio', in.asAudioRateInput(this), bandwidth, threshold, 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; diff --git a/release-packaging/Classes/FluidSpectralShape.sc b/release-packaging/Classes/FluidSpectralShape.sc index 216ea7c..ada4f20 100644 --- a/release-packaging/Classes/FluidSpectralShape.sc +++ b/release-packaging/Classes/FluidSpectralShape.sc @@ -1,7 +1,7 @@ FluidSpectralShape : MultiOutUGen { - *kr { arg in = 0, winSize = 1024, hopSize = -1, fftSize = -1, maxFFTSize = 16384; - ^this.multiNew('control', in.asAudioRateInput(this), winSize, hopSize, fftSize, maxFFTSize); + *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; diff --git a/release-packaging/Classes/FluidTransientSlice.sc b/release-packaging/Classes/FluidTransientSlice.sc index 1b9517a..b4f4ecc 100644 --- a/release-packaging/Classes/FluidTransientSlice.sc +++ b/release-packaging/Classes/FluidTransientSlice.sc @@ -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) } } diff --git a/release-packaging/Classes/FluidTransients.sc b/release-packaging/Classes/FluidTransients.sc index fa8d735..ef0c968 100644 --- a/release-packaging/Classes/FluidTransients.sc +++ b/release-packaging/Classes/FluidTransients.sc @@ -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; diff --git a/release-packaging/HelpSource/Classes/FluidAmpSlice.schelp b/release-packaging/HelpSource/Classes/FluidAmpSlice.schelp new file mode 100644 index 0000000..8a45899 --- /dev/null +++ b/release-packaging/HelpSource/Classes/FluidAmpSlice.schelp @@ -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 Union’s 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 Linkwitz–Riley 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); +) +:: diff --git a/release-packaging/HelpSource/Classes/FluidBufHPSS.schelp b/release-packaging/HelpSource/Classes/FluidBufHPSS.schelp index c17540c..5532826 100644 --- a/release-packaging/HelpSource/Classes/FluidBufHPSS.schelp +++ b/release-packaging/HelpSource/Classes/FluidBufHPSS.schelp @@ -94,7 +94,7 @@ ARGUMENT:: percThreshFreq2 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 @@ -146,7 +146,7 @@ d.play; ( Routine{ t = Main.elapsedTime; - 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,winSize:4096,hopSize:512); + 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 ) diff --git a/release-packaging/HelpSource/Classes/FluidBufLoudness.schelp b/release-packaging/HelpSource/Classes/FluidBufLoudness.schelp new file mode 100644 index 0000000..7cd256e --- /dev/null +++ b/release-packaging/HelpSource/Classes/FluidBufLoudness.schelp @@ -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 Union’s 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) +:: diff --git a/release-packaging/HelpSource/Classes/FluidBufMFCC.schelp b/release-packaging/HelpSource/Classes/FluidBufMFCC.schelp new file mode 100644 index 0000000..3228b5b --- /dev/null +++ b/release-packaging/HelpSource/Classes/FluidBufMFCC.schelp @@ -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 Union’s 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) +:: \ No newline at end of file diff --git a/release-packaging/HelpSource/Classes/FluidBufMelBands.schelp b/release-packaging/HelpSource/Classes/FluidBufMelBands.schelp new file mode 100644 index 0000000..5643f92 --- /dev/null +++ b/release-packaging/HelpSource/Classes/FluidBufMelBands.schelp @@ -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 Union’s 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) +:: \ No newline at end of file diff --git a/release-packaging/HelpSource/Classes/FluidBufNMF.schelp b/release-packaging/HelpSource/Classes/FluidBufNMF.schelp index dd67306..092d5c6 100644 --- a/release-packaging/HelpSource/Classes/FluidBufNMF.schelp +++ b/release-packaging/HelpSource/Classes/FluidBufNMF.schelp @@ -1,14 +1,14 @@ 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): 788–91. 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 ('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 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 basis in NMF lingo); @@ -20,8 +20,8 @@ The bases and activations can be used to make a kind of vocoder based on what NM 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:: -## bases 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. @@ -57,7 +57,7 @@ ARGUMENT:: numChans For multichannel srcBuf, how many channel should be processed. ARGUMENT:: destination - 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. + 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:: 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. @@ -65,7 +65,7 @@ ARGUMENT:: bases ARGUMENT:: basesMode This flag decides of how the basis buffer passed as the previous argument is treated. table:: - ## 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::rank * numChannelsProcessed:: channels and STRONG::(fftSize / 2 + 1):: lenght. + ## 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. :: @@ -76,30 +76,30 @@ ARGUMENT:: activations 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:: numIter +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:: 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 @@ -140,16 +140,16 @@ d.plot d.play //////(beware !!!! loud!!!) ( -// separate them in 2 ranks +// separate them in 2 components Routine { - FluidBufNMF.process(s, d, destination:e, bases: f, activations:g, rank:2); + 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 bases signal for 2 spikes @@ -173,7 +173,7 @@ y = Buffer.new(s); ~fft_size = 1024; ~frame_size = 512; ~hop_size = 256; -~which_rank = 3; +~which_component = 3; ) // matrix factorisation, requesting everything - wait for the computation time to appear. @@ -185,16 +185,16 @@ Routine{ }.play ) -//look at the resynthesised ranks, the bases and the activations +//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,doneAction:2).sum)+(-1*PlayBuf.ar(1,b,doneAction:2))}.play -// play the ranks spread in the stereo field +// 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,doneAction:2)[~which_rank].dup}.play +{PlayBuf.ar(5,c,doneAction:2)[~which_component].dup}.play //play noise using one of the bases as filter. ( @@ -203,7 +203,7 @@ c.plot; x.plot; y.plot; chain = FFT(LocalBuf(~fft_size), WhiteNoise.ar()); chain = chain.pvcollect(~fft_size, {|mag, phase, index| - [mag * BufRd.kr(5,x,DC.kr(index),0,1)[~which_rank]]; + [mag * BufRd.kr(5,x,DC.kr(index),0,1)[~which_component]]; }); IFFT(chain); @@ -211,16 +211,16 @@ c.plot; x.plot; y.plot; ) //play noise using one of the activations as envelope. -{WhiteNoise.ar(BufRd.kr(5,y,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,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,DC.kr(index),0,1)[~which_rank]]; + [mag * BufRd.kr(5,x,DC.kr(index),0,1)[~which_component]]; }); [0,IFFT(chain)]; @@ -244,18 +244,18 @@ y = Buffer.new(s); // train only 2 seconds ( Routine { - FluidBufNMF.process(s,b,0,88200,0,1, c, x, rank:10); + 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)[~element]}.play ) -// copy all the other ranks on itself and the picking basis 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); @@ -268,7 +268,7 @@ Routine{ //process the whole file, splitting it with the 2 trained bases ( Routine{ - FluidBufNMF.process(s, b, destination: c, bases: e, basesMode: 2, activations: y, rank:2); + FluidBufNMF.process(s, b, destination: c, bases: e, basesMode: 2, activations: y, components:2); c.query; }.play; ) @@ -311,7 +311,7 @@ d.plot d.play //////(beware !!!! loud!!!) ( -//make a seeding basis 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; @@ -326,7 +326,7 @@ e.query ( // use the seeding basis, without updating Routine { - FluidBufNMF.process(s, d, destination:f, bases: e, basesMode: 2, activations:g, rank:3); + FluidBufNMF.process(s, d, destination:f, bases: e, basesMode: 2, activations:g, components:3); e.query; f.query; g.query; @@ -345,7 +345,7 @@ g.plot; ( // use the seeding bases, with updating this time Routine { - FluidBufNMF.process(s, d, destination:f, bases: e, basesMode: 1, activations:g, rank:3); + FluidBufNMF.process(s, d, destination:f, bases: e, basesMode: 1, activations:g, components:3); e.query; f.query; g.query; @@ -358,6 +358,6 @@ f.plot; // 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; :: diff --git a/release-packaging/HelpSource/Classes/FluidBufNoveltySlice.schelp b/release-packaging/HelpSource/Classes/FluidBufNoveltySlice.schelp index 301c1b0..92d3842 100644 --- a/release-packaging/HelpSource/Classes/FluidBufNoveltySlice.schelp +++ b/release-packaging/HelpSource/Classes/FluidBufNoveltySlice.schelp @@ -36,7 +36,7 @@ ARGUMENT:: numChans 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:: threshold @@ -45,11 +45,11 @@ ARGUMENT:: threshold 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. @@ -107,7 +107,7 @@ c = Buffer.new(s); ) // process with a given filterSize -FluidBufNoveltySlice.process(s,b, indices: c, kernSize:31, threshold:0.3, filterSize:0) +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; diff --git a/release-packaging/HelpSource/Classes/FluidBufOnsetSlice.schelp b/release-packaging/HelpSource/Classes/FluidBufOnsetSlice.schelp index 9b3b183..ecd692f 100644 --- a/release-packaging/HelpSource/Classes/FluidBufOnsetSlice.schelp +++ b/release-packaging/HelpSource/Classes/FluidBufOnsetSlice.schelp @@ -52,7 +52,7 @@ ARGUMENT:: function ARGUMENT:: threshold The thresholding of a new slice. Value ranges are different for each function, from 0 upwards. -ARGUMENT:: debounce +ARGUMENT:: minSliceLength The minimum duration of a slice in number of hopSize. ARGUMENT:: filterSize @@ -61,11 +61,11 @@ ARGUMENT:: filterSize 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:: winSize +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 hope 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 winSize (overlap of 2). + 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. diff --git a/release-packaging/HelpSource/Classes/FluidBufPitch.schelp b/release-packaging/HelpSource/Classes/FluidBufPitch.schelp new file mode 100644 index 0000000..accd403 --- /dev/null +++ b/release-packaging/HelpSource/Classes/FluidBufPitch.schelp @@ -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 Union’s 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; +) +:: diff --git a/release-packaging/HelpSource/Classes/FluidBufRTNoveltySlice.schelp b/release-packaging/HelpSource/Classes/FluidBufRTNoveltySlice.schelp new file mode 100644 index 0000000..e90651f --- /dev/null +++ b/release-packaging/HelpSource/Classes/FluidBufRTNoveltySlice.schelp @@ -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 Union’s 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;}) +:: diff --git a/release-packaging/HelpSource/Classes/FluidBufSines.schelp b/release-packaging/HelpSource/Classes/FluidBufSines.schelp index 53c8237..c283211 100644 --- a/release-packaging/HelpSource/Classes/FluidBufSines.schelp +++ b/release-packaging/HelpSource/Classes/FluidBufSines.schelp @@ -57,11 +57,11 @@ 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. diff --git a/release-packaging/HelpSource/Classes/FluidBufSpectralShape.schelp b/release-packaging/HelpSource/Classes/FluidBufSpectralShape.schelp new file mode 100644 index 0000000..756561b --- /dev/null +++ b/release-packaging/HelpSource/Classes/FluidBufSpectralShape.schelp @@ -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 Union’s 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) +:: \ No newline at end of file diff --git a/release-packaging/HelpSource/Classes/FluidBufStats.schelp b/release-packaging/HelpSource/Classes/FluidBufStats.schelp new file mode 100644 index 0000000..207be64 --- /dev/null +++ b/release-packaging/HelpSource/Classes/FluidBufStats.schelp @@ -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 Union’s 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}) +:: diff --git a/release-packaging/HelpSource/Classes/FluidBufTransientSlice.schelp b/release-packaging/HelpSource/Classes/FluidBufTransientSlice.schelp index 936ec28..e2f3bd6 100644 --- a/release-packaging/HelpSource/Classes/FluidBufTransientSlice.schelp +++ b/release-packaging/HelpSource/Classes/FluidBufTransientSlice.schelp @@ -54,13 +54,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 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 diff --git a/release-packaging/HelpSource/Classes/FluidBufTransients.schelp b/release-packaging/HelpSource/Classes/FluidBufTransients.schelp index 1fa220e..7773e4c 100644 --- a/release-packaging/HelpSource/Classes/FluidBufTransients.schelp +++ b/release-packaging/HelpSource/Classes/FluidBufTransients.schelp @@ -62,10 +62,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. ARGUMENT:: action @@ -135,7 +135,7 @@ d = Buffer.new(s); e = Buffer.new(s); ( Routine{ t = Main.elapsedTime; - FluidBufTransients.process(s, b, transients: d, residual:e, threshFwd:1.2, debounce:40); + FluidBufTransients.process(s, b, transients: d, residual:e, threshFwd:1.2, clumpLength:40); (Main.elapsedTime - t).postln; }.play ) diff --git a/release-packaging/HelpSource/Classes/FluidHPSS.schelp b/release-packaging/HelpSource/Classes/FluidHPSS.schelp index c9b0e2d..084519f 100644 --- a/release-packaging/HelpSource/Classes/FluidHPSS.schelp +++ b/release-packaging/HelpSource/Classes/FluidHPSS.schelp @@ -66,11 +66,11 @@ ARGUMENT:: percThreshFreq2 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. @@ -84,17 +84,14 @@ ARGUMENT::maxHarmFilterSize 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 ((harmFilterSize - 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 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:: CODE:: @@ -115,6 +112,6 @@ b = Buffer.read(s,File.realpath(FluidHPSS.class.filenameSymbol).dirname.withTrai // the residual stream {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 ((harmFilterSize - 1) * hopSize) + winSize) samples +// 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 :: diff --git a/release-packaging/HelpSource/Classes/FluidLoudness.schelp b/release-packaging/HelpSource/Classes/FluidLoudness.schelp new file mode 100644 index 0000000..449beb2 --- /dev/null +++ b/release-packaging/HelpSource/Classes/FluidLoudness.schelp @@ -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 Union’s 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) +:: diff --git a/release-packaging/HelpSource/Classes/FluidMFCC.schelp b/release-packaging/HelpSource/Classes/FluidMFCC.schelp new file mode 100644 index 0000000..8bedc57 --- /dev/null +++ b/release-packaging/HelpSource/Classes/FluidMFCC.schelp @@ -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 Union’s 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 +:: diff --git a/release-packaging/HelpSource/Classes/FluidMelBands.schelp b/release-packaging/HelpSource/Classes/FluidMelBands.schelp new file mode 100644 index 0000000..e5784ba --- /dev/null +++ b/release-packaging/HelpSource/Classes/FluidMelBands.schelp @@ -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 Union’s 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 +:: diff --git a/release-packaging/HelpSource/Classes/FluidNMFFilter.schelp b/release-packaging/HelpSource/Classes/FluidNMFFilter.schelp new file mode 100644 index 0000000..0493d38 --- /dev/null +++ b/release-packaging/HelpSource/Classes/FluidNMFFilter.schelp @@ -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): 788–91. 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 Union’s 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; +:: diff --git a/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp b/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp index 27bc90d..d2c05c3 100644 --- a/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp +++ b/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp @@ -1,12 +1,12 @@ TITLE:: FluidNMFMatch 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): 788–91. 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,25 +20,25 @@ 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 bases 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:: 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::maxRank:: channels, the excess will be ignored. + 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 +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:: numIter +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. @@ -79,9 +79,9 @@ d.plot d.play //////(beware !!!! loud!!!) ( -// separate them in 2 ranks +// separate them in 2 components Routine { - FluidBufNMF.process(s, d, bases: e, rank:2); + FluidBufNMF.process(s, d, bases: e, components:2); s.sync; e.query; }.play @@ -111,19 +111,19 @@ e = Buffer.new(s); // train only 2 seconds ( Routine { - FluidBufNMF.process(s,b,0,88200,0,1, c, x, rank:10,fftSize:2048); + 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 = 6; {PlayBuf.ar(10,c)[~element]}.play; ) -// copy all the other ranks on itself and the picking basis 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); @@ -135,7 +135,7 @@ Routine{ e.plot; -//using this trained basis we can see the envelop (activations) of each rank +//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. @@ -187,13 +187,13 @@ e = Buffer.new(s); // train where all objects are present ( Routine { - FluidBufNMF.process(s,b,130000,150000,0,1, c, x, rank:10); + 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)[~dog]}.play @@ -205,7 +205,7 @@ Routine { ) -// 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, startChan:~dog, numChans: 1, destination: e); @@ -306,4 +306,3 @@ c.query CODE:: //to be completed :: - \ No newline at end of file diff --git a/release-packaging/HelpSource/Classes/FluidOnsetSlice.schelp b/release-packaging/HelpSource/Classes/FluidOnsetSlice.schelp index a99ea2b..449c05f 100644 --- a/release-packaging/HelpSource/Classes/FluidOnsetSlice.schelp +++ b/release-packaging/HelpSource/Classes/FluidOnsetSlice.schelp @@ -35,7 +35,7 @@ ARGUMENT:: function ARGUMENT:: threshold The thresholding of a new slice. Value ranges are different for each function, from 0 upwards. -ARGUMENT:: debounce +ARGUMENT:: minSliceLength The minimum duration of a slice in number of hopSize. ARGUMENT:: filterSize @@ -44,11 +44,11 @@ ARGUMENT:: filterSize 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:: 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. @@ -57,7 +57,7 @@ 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 winSize at maximum. + An audio stream with impulses at detected transients. The latency between the input and the output is windowSize at maximum. EXAMPLES:: @@ -65,7 +65,7 @@ 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 winSize samples +// 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 @@ -82,4 +82,4 @@ b = Buffer.read(s,File.realpath(FluidOnsetSlice.class.filenameSymbol).dirname.wi Pan2.ar(syncd,pan); }.play ) -:: \ No newline at end of file +:: diff --git a/release-packaging/HelpSource/Classes/FluidPitch.schelp b/release-packaging/HelpSource/Classes/FluidPitch.schelp new file mode 100644 index 0000000..af6a0f1 --- /dev/null +++ b/release-packaging/HelpSource/Classes/FluidPitch.schelp @@ -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 Union’s 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); +) +:: diff --git a/release-packaging/HelpSource/Classes/FluidRTNoveltySlice.schelp b/release-packaging/HelpSource/Classes/FluidRTNoveltySlice.schelp new file mode 100644 index 0000000..166cf76 --- /dev/null +++ b/release-packaging/HelpSource/Classes/FluidRTNoveltySlice.schelp @@ -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 Union’s 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 +) +:: \ No newline at end of file diff --git a/release-packaging/HelpSource/Classes/FluidSTFTPass.schelp b/release-packaging/HelpSource/Classes/FluidSTFTPass.schelp index 2a29a35..2f73b18 100644 --- a/release-packaging/HelpSource/Classes/FluidSTFTPass.schelp +++ b/release-packaging/HelpSource/Classes/FluidSTFTPass.schelp @@ -16,11 +16,11 @@ 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. @@ -30,7 +30,7 @@ ARGUMENT:: maxFFTSize RETURNS:: - Same as input, delayed by the winSize. + Same as input, delayed by the windowSize. EXAMPLES:: diff --git a/release-packaging/HelpSource/Classes/FluidSines.schelp b/release-packaging/HelpSource/Classes/FluidSines.schelp index 69851dc..fd6152f 100644 --- a/release-packaging/HelpSource/Classes/FluidSines.schelp +++ b/release-packaging/HelpSource/Classes/FluidSines.schelp @@ -36,11 +36,11 @@ 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. @@ -49,7 +49,7 @@ ARGUMENT:: maxFFTSize 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,11 +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,loop:1),threshold: 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,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 +// 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 :: diff --git a/release-packaging/HelpSource/Classes/FluidSpectralShape.schelp b/release-packaging/HelpSource/Classes/FluidSpectralShape.schelp index 779453c..6376579 100644 --- a/release-packaging/HelpSource/Classes/FluidSpectralShape.schelp +++ b/release-packaging/HelpSource/Classes/FluidSpectralShape.schelp @@ -10,12 +10,12 @@ 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 spectral bins as units. 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 spectral bins. 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 the bin representing that frequency, i.e. the central bin of the magnitude spectrum, it is positive. + ## 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 bin number. This indicates the bin under which 95% of the energy is included. + ## 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. @@ -32,11 +32,11 @@ METHOD:: kr ARGUMENT:: in The audio to be processed. -ARGUMENT:: winSize - 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:: 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 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 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. @@ -45,7 +45,7 @@ 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 winSize. + A 7-channel KR signal with the seven spectral shape descriptors. The latency is windowSize. EXAMPLES:: @@ -58,7 +58,7 @@ b = Bus.new(\control,0,7); //create a monitoring window for the values ( -w = Window("Frequency Monitor", Rect(10, 10, 220, 190)).front; +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: "); @@ -74,7 +74,7 @@ a = Array.fill(7, {arg i; }); ) -//run the wondow updating routine. +//run the window updating routine. ( r = Routine { { @@ -113,7 +113,7 @@ b = Bus.new(\control,0,7); //again, create a monitoring window for the values ( -w = Window("Frequency Monitor", Rect(10, 10, 220, 190)).front; +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: "); @@ -129,7 +129,7 @@ a = Array.fill(7, {arg i; }); ) -// this time, update a little more slowly, and convert in Hz the 3 descriptors published in bins by the algorythm. +// this time, update a little more slowly. ( r = Routine { { @@ -138,13 +138,7 @@ r = Routine { { if(w.isClosed.not) { val.do({arg item,index; - if ((index < 2) || (index == 4)) - { - a[index].string = (item * s.sampleRate / 1024).round(0.01); - } { - a[index].string = item.round(0.01); - }; - }) + a[index].string = item.round(0.01)}) } }.defer }); diff --git a/release-packaging/HelpSource/Classes/FluidTransientSlice.schelp b/release-packaging/HelpSource/Classes/FluidTransientSlice.schelp index 8f58206..1df863d 100644 --- a/release-packaging/HelpSource/Classes/FluidTransientSlice.schelp +++ b/release-packaging/HelpSource/Classes/FluidTransientSlice.schelp @@ -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:: @@ -57,14 +57,14 @@ b = Buffer.read(s,File.realpath(FluidTransientSlice.class.filenameSymbol).dirnam {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,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,loop:1); - trig = FluidTransientSlice.ar(sig,order:10,minSlice:4410); + 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); diff --git a/release-packaging/HelpSource/Classes/FluidTransients.schelp b/release-packaging/HelpSource/Classes/FluidTransients.schelp index 7173d3e..8724858 100644 --- a/release-packaging/HelpSource/Classes/FluidTransients.schelp +++ b/release-packaging/HelpSource/Classes/FluidTransients.schelp @@ -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:: diff --git a/release-packaging/ignore/Examples/buffer_compositing/fileiterator-2passes.sc b/release-packaging/ignore/Examples/buffer_compositing/fileiterator-2passes.sc index dfb6b1f..79c2ecb 100644 --- a/release-packaging/ignore/Examples/buffer_compositing/fileiterator-2passes.sc +++ b/release-packaging/ignore/Examples/buffer_compositing/fileiterator-2passes.sc @@ -1,7 +1,7 @@ //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; +var fileNames; c = [0]; FileDialog.new({|selection| @@ -21,13 +21,16 @@ FileDialog.new({|selection| 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; - tempbuf = Buffer.read(s, f.asAbsolutePath,action:{FluidBufCompose.process(s,tempbuf,destStartFrame:c[i],destination:b);}); + 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; diff --git a/release-packaging/ignore/Examples/nmf/JiT-NMF.scd b/release-packaging/ignore/Examples/nmf/JiT-NMF.scd index bc81aa4..7ec4394 100644 --- a/release-packaging/ignore/Examples/nmf/JiT-NMF.scd +++ b/release-packaging/ignore/Examples/nmf/JiT-NMF.scd @@ -47,9 +47,9 @@ y = Synth(\becauseIcan,[\bufnum, b.bufnum, \nmfa, c.bufnum, \nmfb, d.bufnum, \in ( w = OSCFunc({ arg msg; if(msg[3]== 1, { - FluidBufNMF.process(s, b, numFrames: 22500, destination: c.bufnum, rank: 3, fftSize: 1024, winSize: 512, hopSize: 256); + 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, rank: 3, fftSize: 1024, winSize: 512, hopSize: 256); + FluidBufNMF.process(s, b, 22050, 22500, destination: d.bufnum, iterations: 3, fftSize: 1024, windowSize: 512, hopSize: 256); });}, '/processplease', s.addr); ) diff --git a/scripts/target_post.cmake b/scripts/target_post.cmake index b62e751..b06821e 100644 --- a/scripts/target_post.cmake +++ b/scripts/target_post.cmake @@ -5,17 +5,30 @@ # 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 @@ -42,45 +55,17 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANG) 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 - # "$<$>: -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 + "$<$>: -mavx -msse -msse2 -msse3 -msse4>" + ) endif() if(MINGW) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mstackrealign") diff --git a/src/FluidAmpSlice/CMakeLists.txt b/src/FluidAmpSlice/CMakeLists.txt new file mode 100644 index 0000000..3693881 --- /dev/null +++ b/src/FluidAmpSlice/CMakeLists.txt @@ -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) diff --git a/src/FluidAmpSlice/FluidAmpSlice.cpp b/src/FluidAmpSlice/FluidAmpSlice.cpp new file mode 100644 index 0000000..4a3abe0 --- /dev/null +++ b/src/FluidAmpSlice/FluidAmpSlice.cpp @@ -0,0 +1,14 @@ + +// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Union’s Horizon 2020 research and innovation programme (grant agreement No 725899) + +#include +#include + +static InterfaceTable *ft; + +PluginLoad(FluidSTFTUGen) +{ + ft = inTable; + using namespace fluid::client; + makeSCWrapper("FluidAmpSlice", ft); +} diff --git a/src/FluidBufAmpSlice/CMakeLists.txt b/src/FluidBufAmpSlice/CMakeLists.txt new file mode 100644 index 0000000..3693881 --- /dev/null +++ b/src/FluidBufAmpSlice/CMakeLists.txt @@ -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) diff --git a/src/FluidBufAmpSlice/FluidBufAmpSlice.cpp b/src/FluidBufAmpSlice/FluidBufAmpSlice.cpp new file mode 100644 index 0000000..a861cfd --- /dev/null +++ b/src/FluidBufAmpSlice/FluidBufAmpSlice.cpp @@ -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 Union’s Horizon 2020 research and innovation programme (grant agreement No 725899) + +#include +#include + +static InterfaceTable *ft; + +PluginLoad(OfflineFluidDecompositionUGens) +{ + ft = inTable; + using namespace fluid::client; + makeSCWrapper("BufAmpSlice", ft); +} diff --git a/src/FluidBufLoudness/CMakeLists.txt b/src/FluidBufLoudness/CMakeLists.txt new file mode 100644 index 0000000..3693881 --- /dev/null +++ b/src/FluidBufLoudness/CMakeLists.txt @@ -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) diff --git a/src/FluidBufLoudness/FluidBufLoudness.cpp b/src/FluidBufLoudness/FluidBufLoudness.cpp new file mode 100644 index 0000000..e0b90b1 --- /dev/null +++ b/src/FluidBufLoudness/FluidBufLoudness.cpp @@ -0,0 +1,13 @@ + +// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Union’s Horizon 2020 research and innovation programme (grant agreement No 725899) + +#include +#include + +static InterfaceTable *ft; + +PluginLoad(OfflineFluidDecompositionUGens) { + ft = inTable; + using namespace fluid::client; + makeSCWrapper("BufLoudness", ft); +} diff --git a/src/FluidBufMFCC/CMakeLists.txt b/src/FluidBufMFCC/CMakeLists.txt new file mode 100644 index 0000000..3693881 --- /dev/null +++ b/src/FluidBufMFCC/CMakeLists.txt @@ -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) diff --git a/src/FluidBufMFCC/FluidBufMFCC.cpp b/src/FluidBufMFCC/FluidBufMFCC.cpp new file mode 100644 index 0000000..d4a9912 --- /dev/null +++ b/src/FluidBufMFCC/FluidBufMFCC.cpp @@ -0,0 +1,13 @@ + +// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Union’s Horizon 2020 research and innovation programme (grant agreement No 725899) + +#include +#include + +static InterfaceTable *ft; + +PluginLoad(FluidSTFTUGen) { + ft = inTable; + using namespace fluid::client; + makeSCWrapper("BufMFCC", ft); +} diff --git a/src/FluidBufMelBands/CMakeLists.txt b/src/FluidBufMelBands/CMakeLists.txt new file mode 100644 index 0000000..3693881 --- /dev/null +++ b/src/FluidBufMelBands/CMakeLists.txt @@ -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) diff --git a/src/FluidBufMelBands/FluidBufMelBands.cpp b/src/FluidBufMelBands/FluidBufMelBands.cpp new file mode 100644 index 0000000..9e4a6cb --- /dev/null +++ b/src/FluidBufMelBands/FluidBufMelBands.cpp @@ -0,0 +1,13 @@ + +// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Union’s Horizon 2020 research and innovation programme (grant agreement No 725899) + +#include +#include + +static InterfaceTable *ft; + +PluginLoad(FluidSTFTUGen) { + ft = inTable; + using namespace fluid::client; + makeSCWrapper("BufMelBands", ft); +} diff --git a/src/FluidBufPitch/CMakeLists.txt b/src/FluidBufPitch/CMakeLists.txt new file mode 100644 index 0000000..3693881 --- /dev/null +++ b/src/FluidBufPitch/CMakeLists.txt @@ -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) diff --git a/src/FluidBufPitch/FluidBufPitch.cpp b/src/FluidBufPitch/FluidBufPitch.cpp new file mode 100644 index 0000000..b4e5f8b --- /dev/null +++ b/src/FluidBufPitch/FluidBufPitch.cpp @@ -0,0 +1,13 @@ + +// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Union’s Horizon 2020 research and innovation programme (grant agreement No 725899) + +#include +#include + +static InterfaceTable *ft; + +PluginLoad(FluidSTFTUGen) { + ft = inTable; + using namespace fluid::client; + makeSCWrapper("BufPitch", ft); +} diff --git a/src/FluidBufRTNoveltySlice/CMakeLists.txt b/src/FluidBufRTNoveltySlice/CMakeLists.txt new file mode 100644 index 0000000..3693881 --- /dev/null +++ b/src/FluidBufRTNoveltySlice/CMakeLists.txt @@ -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) diff --git a/src/FluidBufRTNoveltySlice/FluidBufRTNoveltySlice.cpp b/src/FluidBufRTNoveltySlice/FluidBufRTNoveltySlice.cpp new file mode 100644 index 0000000..c637663 --- /dev/null +++ b/src/FluidBufRTNoveltySlice/FluidBufRTNoveltySlice.cpp @@ -0,0 +1,14 @@ + +// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Union’s Horizon 2020 research and innovation programme (grant agreement No 725899) + +#include +#include +#include + +static InterfaceTable *ft; + +PluginLoad(OfflineFluidDecompositionUGens) { + ft = inTable; + using namespace fluid::client; + makeSCWrapper("BufRTNoveltySlice", ft); +} diff --git a/src/FluidBufStats/CMakeLists.txt b/src/FluidBufStats/CMakeLists.txt new file mode 100644 index 0000000..3693881 --- /dev/null +++ b/src/FluidBufStats/CMakeLists.txt @@ -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) diff --git a/src/FluidBufStats/FluidBufStats.cpp b/src/FluidBufStats/FluidBufStats.cpp new file mode 100644 index 0000000..fddfaf3 --- /dev/null +++ b/src/FluidBufStats/FluidBufStats.cpp @@ -0,0 +1,14 @@ + +// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Union’s Horizon 2020 research and innovation programme (grant agreement No 725899) + +#include +#include + +static InterfaceTable *ft; + +PluginLoad(OfflineFluidDecompositionUGens) +{ + ft = inTable; + using namespace fluid::client; + makeSCWrapper("BufStats", ft); +} diff --git a/src/FluidLoudness/CMakeLists.txt b/src/FluidLoudness/CMakeLists.txt new file mode 100644 index 0000000..3693881 --- /dev/null +++ b/src/FluidLoudness/CMakeLists.txt @@ -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) diff --git a/src/FluidLoudness/FluidLoudness.cpp b/src/FluidLoudness/FluidLoudness.cpp new file mode 100644 index 0000000..378fdff --- /dev/null +++ b/src/FluidLoudness/FluidLoudness.cpp @@ -0,0 +1,13 @@ + +// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Union’s Horizon 2020 research and innovation programme (grant agreement No 725899) + +#include +#include + +static InterfaceTable *ft; + +PluginLoad(FluidSTFTUGen) { + ft = inTable; + using namespace fluid::client; + makeSCWrapper("FluidLoudness", ft); +} diff --git a/src/FluidMFCC/CMakeLists.txt b/src/FluidMFCC/CMakeLists.txt new file mode 100644 index 0000000..3693881 --- /dev/null +++ b/src/FluidMFCC/CMakeLists.txt @@ -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) diff --git a/src/FluidMFCC/FluidMFCC.cpp b/src/FluidMFCC/FluidMFCC.cpp new file mode 100644 index 0000000..8e786ff --- /dev/null +++ b/src/FluidMFCC/FluidMFCC.cpp @@ -0,0 +1,13 @@ + +// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Union’s Horizon 2020 research and innovation programme (grant agreement No 725899) + +#include +#include + +static InterfaceTable *ft; + +PluginLoad(FluidSTFTUGen) { + ft = inTable; + using namespace fluid::client; + makeSCWrapper("FluidMFCC", ft); +} diff --git a/src/FluidMelBands/CMakeLists.txt b/src/FluidMelBands/CMakeLists.txt new file mode 100644 index 0000000..3693881 --- /dev/null +++ b/src/FluidMelBands/CMakeLists.txt @@ -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) diff --git a/src/FluidMelBands/FluidMelBands.cpp b/src/FluidMelBands/FluidMelBands.cpp new file mode 100644 index 0000000..8881261 --- /dev/null +++ b/src/FluidMelBands/FluidMelBands.cpp @@ -0,0 +1,13 @@ + +// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Union’s Horizon 2020 research and innovation programme (grant agreement No 725899) + +#include +#include + +static InterfaceTable *ft; + +PluginLoad(FluidSTFTUGen) { + ft = inTable; + using namespace fluid::client; + makeSCWrapper("FluidMelBands", ft); +} diff --git a/src/FluidNMFFilter/CMakeLists.txt b/src/FluidNMFFilter/CMakeLists.txt new file mode 100644 index 0000000..3693881 --- /dev/null +++ b/src/FluidNMFFilter/CMakeLists.txt @@ -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) diff --git a/src/FluidNMFFilter/FluidNMFFilter.cpp b/src/FluidNMFFilter/FluidNMFFilter.cpp new file mode 100644 index 0000000..6e20346 --- /dev/null +++ b/src/FluidNMFFilter/FluidNMFFilter.cpp @@ -0,0 +1,14 @@ + +// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Union’s Horizon 2020 research and innovation programme (grant agreement No 725899) + +#include +#include + +static InterfaceTable *ft; + +PluginLoad(FluidSTFTUGen) +{ + ft = inTable; + using namespace fluid::client; + makeSCWrapper("FluidNMFFilter", ft); +} diff --git a/src/FluidPitch/CMakeLists.txt b/src/FluidPitch/CMakeLists.txt new file mode 100644 index 0000000..3693881 --- /dev/null +++ b/src/FluidPitch/CMakeLists.txt @@ -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) diff --git a/src/FluidPitch/FluidPitch.cpp b/src/FluidPitch/FluidPitch.cpp new file mode 100644 index 0000000..4821cc4 --- /dev/null +++ b/src/FluidPitch/FluidPitch.cpp @@ -0,0 +1,13 @@ + +// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Union’s Horizon 2020 research and innovation programme (grant agreement No 725899) + +#include +#include + +static InterfaceTable *ft; + +PluginLoad(FluidSTFTUGen) { + ft = inTable; + using namespace fluid::client; + makeSCWrapper("FluidPitch", ft); +} diff --git a/src/FluidRTNoveltySlice/CMakeLists.txt b/src/FluidRTNoveltySlice/CMakeLists.txt new file mode 100644 index 0000000..3693881 --- /dev/null +++ b/src/FluidRTNoveltySlice/CMakeLists.txt @@ -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) diff --git a/src/FluidRTNoveltySlice/FluidRTNoveltySlice.cpp b/src/FluidRTNoveltySlice/FluidRTNoveltySlice.cpp new file mode 100644 index 0000000..8e23423 --- /dev/null +++ b/src/FluidRTNoveltySlice/FluidRTNoveltySlice.cpp @@ -0,0 +1,13 @@ + +// A tool from the FluCoMa project, funded by the European Research Council (ERC) under the European Union’s Horizon 2020 research and innovation programme (grant agreement No 725899) + +#include +#include + +static InterfaceTable *ft; + +PluginLoad(FluidSTFTUGen) { + ft = inTable; + using namespace fluid::client; + makeSCWrapper("FluidRTNoveltySlice", ft); +}