diff --git a/release-packaging/Classes/FluidAmpGate.sc b/release-packaging/Classes/FluidAmpGate.sc new file mode 100644 index 0000000..a87f406 --- /dev/null +++ b/release-packaging/Classes/FluidAmpGate.sc @@ -0,0 +1,11 @@ +FluidAmpGate : UGen { + *ar { arg in = 0, rampUp = 10, rampDown = 10, onThreshold = -90, offThreshold = -90, minSliceLength = 1, minSilenceLength = 1, minLengthAbove = 1, minLengthBelow = 1, lookBack = 0, lookAhead = 0, highPassFreq = 85, maxSize = 88200; + ^this.multiNew('audio', in.asAudioRateInput(this), rampUp, rampDown, onThreshold, offThreshold, minSliceLength, minSilenceLength, minLengthAbove, minLengthBelow, lookBack, lookAhead, highPassFreq, maxSize, 0) + } + checkInputs { + if(inputs.at(13).rate != 'scalar') { + ^(": maxSize cannot be modulated."); + }; + ^this.checkValidInputs; + } +} diff --git a/release-packaging/Classes/FluidAmpSlice.sc b/release-packaging/Classes/FluidAmpSlice.sc index 27640a9..998545a 100644 --- a/release-packaging/Classes/FluidAmpSlice.sc +++ b/release-packaging/Classes/FluidAmpSlice.sc @@ -1,11 +1,9 @@ FluidAmpSlice : UGen { - *ar { arg in = 0, absRampUp = 10, absRampDown = 10, absThreshOn = -90, absThreshOff = -90, minSliceLength = 1, minSilenceLength = 1, minLengthAbove = 1, minLengthBelow = 1, lookBack = 0, lookAhead = 0, relRampUp = 1, relRampDown = 1, relThreshOn = 144, relThreshOff = -144, highPassFreq = 85, maxSize = 88200; - ^this.multiNew('audio', in.asAudioRateInput(this), absRampUp, absRampDown, absThreshOn, absThreshOff, minSliceLength, minSilenceLength, minLengthAbove, minLengthBelow, lookBack, lookAhead, relRampUp, relRampDown, relThreshOn, relThreshOff, highPassFreq, maxSize, 0) + *ar { arg in = 0, fastRampUp = 1, fastRampDown = 1, slowRampUp = 100, slowRampDown = 100, onThreshold = -144, offThreshold = -144, floor = -144, highPassFreq = 85, minSliceLength = 2; + + ^this.multiNew('audio', in.asAudioRateInput(this), fastRampUp, fastRampDown, slowRampUp, slowRampDown, onThreshold, offThreshold, floor, highPassFreq, minSliceLength) } checkInputs { - if(inputs.at(16).rate != 'scalar') { - ^(": maxSize cannot be modulated."); - }; ^this.checkValidInputs; } } diff --git a/release-packaging/Classes/FluidBufAmpGate.sc b/release-packaging/Classes/FluidBufAmpGate.sc new file mode 100644 index 0000000..c867ba8 --- /dev/null +++ b/release-packaging/Classes/FluidBufAmpGate.sc @@ -0,0 +1,37 @@ +FluidBufAmpGate : UGen { + + *new1 { |rate, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, rampUp = 10, rampDown = 10, onThreshold = -90, offThreshold = -90, minSliceLength = 1, minSilenceLength = 1, minLengthAbove = 1, minLengthBelow = 1, lookBack = 0, lookAhead = 0, highPassFreq = 85, doneAction = 0, blocking| + 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}; + + ^super.new1(rate, source, startFrame, numFrames, startChan, numChans, indices, rampUp, rampDown, onThreshold, offThreshold, minSliceLength, minSilenceLength, minLengthAbove, minLengthBelow, lookBack, lookAhead, highPassFreq, maxSize, 0, doneAction, blocking); + } + + *kr { |source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, rampUp = 10, rampDown = 10, onThreshold = -90, offThreshold = -90, minSliceLength = 1, minSilenceLength = 1, minLengthAbove = 1, minLengthBelow = 1, lookBack = 0, lookAhead = 0, highPassFreq = 85, doneAction = 0| + + ^this.multiNew(\control, source, startFrame, numFrames, startChan, numChans, indices, rampUp, rampDown, onThreshold, offThreshold, minSliceLength, minSilenceLength, minLengthAbove, minLengthBelow, lookBack, lookAhead, highPassFreq, doneAction,blocking:0); + } + + *process { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, rampUp = 10, rampDown = 10, onThreshold = -90, offThreshold = -90, minSliceLength = 1, minSilenceLength = 1, minLengthAbove = 1, minLengthBelow = 1, lookBack = 0, lookAhead = 0, highPassFreq = 85, action | + + ^FluidNRTProcess.new( + server, this, action, [indices] + ).process( + source, startFrame, numFrames, startChan, numChans, indices, rampUp, rampDown, onThreshold, offThreshold, minSliceLength, minSilenceLength, minLengthAbove, minLengthBelow, lookBack, lookAhead, highPassFreq + ); + } + + *processBlocking { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, rampUp = 10, rampDown = 10, onThreshold = -90, offThreshold = -90, minSliceLength = 1, minSilenceLength = 1, minLengthAbove = 1, minLengthBelow = 1, lookBack = 0, lookAhead = 0, highPassFreq = 85, action| + + ^FluidNRTProcess.new( + server, this, action, [indices], blocking: 1 + ).process( + source, startFrame, numFrames, startChan, numChans, indices, rampUp, rampDown, onThreshold, offThreshold, minSliceLength, minSilenceLength, minLengthAbove, minLengthBelow, lookBack, lookAhead, highPassFreq + ); + } +} diff --git a/release-packaging/Classes/FluidBufAmpSlice.sc b/release-packaging/Classes/FluidBufAmpSlice.sc index 8205564..aa6f287 100644 --- a/release-packaging/Classes/FluidBufAmpSlice.sc +++ b/release-packaging/Classes/FluidBufAmpSlice.sc @@ -1,7 +1,6 @@ FluidBufAmpSlice : UGen { - *new1 { |rate, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, absRampUp = 10, absRampDown = 10, absThreshOn = -90, absThreshOff = -90, minSliceLength = 1, minSilenceLength = 1, minLengthAbove = 1, minLengthBelow = 1, lookBack = 0, lookAhead = 0, relRampUp = 1, relRampDown = 1, relThreshOn = 144, relThreshOff = -144, highPassFreq = 85, doneAction = 0, blocking| - var maxSize = max(minLengthAbove + lookBack, max(minLengthBelow,lookAhead)); + *new1 { |rate, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, fastRampUp = 1, fastRampDown = 1, slowRampUp = 100, slowRampDown = 100, onThreshold = -144, offThreshold = -144, floor = -144, highPassFreq = 85, minSliceLength = 2, doneAction = 0, blocking| source = source.asUGenInput; indices = indices.asUGenInput; @@ -9,29 +8,29 @@ FluidBufAmpSlice : UGen { source.isNil.if {"FluidBufAmpSlice: Invalid source buffer".throw}; indices.isNil.if {"FluidBufAmpSlice: Invalid features buffer".throw}; - ^super.new1(rate, source, startFrame, numFrames, startChan, numChans, indices, absRampUp, absRampDown, absThreshOn, absThreshOff, minSliceLength, minSilenceLength, minLengthAbove, minLengthBelow, lookBack, lookAhead, relRampUp, relRampDown, relThreshOn, relThreshOff, highPassFreq, maxSize, 0, doneAction, blocking); + ^super.new1(rate, source, startFrame, numFrames, startChan, numChans, indices, fastRampUp, fastRampDown, slowRampUp, slowRampDown, onThreshold, offThreshold, floor, highPassFreq, minSliceLength, doneAction, blocking); } - *kr { |source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, absRampUp = 10, absRampDown = 10, absThreshOn = -90, absThreshOff = -90, minSliceLength = 1, minSilenceLength = 1, minLengthAbove = 1, minLengthBelow = 1, lookBack = 0, lookAhead = 0, relRampUp = 1, relRampDown = 1, relThreshOn = 144, relThreshOff = -144, highPassFreq = 85, doneAction = 0| + *kr { |source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, fastRampUp = 1, fastRampDown = 1, slowRampUp = 100, slowRampDown = 100, onThreshold = -144, offThreshold = -144, floor = -144, highPassFreq = 85, minSliceLength = 2, doneAction = 0| - ^this.multiNew(\control, source, startFrame, numFrames, startChan, numChans, indices, absRampUp, absRampDown, absThreshOn, absThreshOff, minSliceLength, minSilenceLength, minLengthAbove, minLengthBelow, lookBack, lookAhead, relRampUp, relRampDown, relThreshOn, relThreshOff, highPassFreq, 0, doneAction,blocking:0); + ^this.multiNew(\control, source, startFrame, numFrames, startChan, numChans, indices, fastRampUp, fastRampDown, slowRampUp, slowRampDown, onThreshold, offThreshold, floor, highPassFreq, minSliceLength, doneAction,blocking:0); } - *process { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, absRampUp = 10, absRampDown = 10, absThreshOn = -90, absThreshOff = -90, minSliceLength = 1, minSilenceLength = 1, minLengthAbove = 1, minLengthBelow = 1, lookBack = 0, lookAhead = 0, relRampUp = 1, relRampDown = 1, relThreshOn = 144, relThreshOff = -144, highPassFreq = 85, action | + *process { |server,source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, fastRampUp = 1, fastRampDown = 1, slowRampUp = 100, slowRampDown = 100, onThreshold = -144, offThreshold = -144, floor = -144, highPassFreq = 85, minSliceLength = 2, action | ^FluidNRTProcess.new( server, this, action, [indices] ).process( - source, startFrame, numFrames, startChan, numChans, indices, absRampUp, absRampDown, absThreshOn, absThreshOff, minSliceLength, minSilenceLength, minLengthAbove, minLengthBelow, lookBack, lookAhead, relRampUp, relRampDown, relThreshOn, relThreshOff, highPassFreq + source, startFrame, numFrames, startChan, numChans, indices, fastRampUp, fastRampDown, slowRampUp, slowRampDown, onThreshold, offThreshold, floor, highPassFreq, minSliceLength ); } - *processBlocking { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, absRampUp = 10, absRampDown = 10, absThreshOn = -90, absThreshOff = -90, minSliceLength = 1, minSilenceLength = 1, minLengthAbove = 1, minLengthBelow = 1, lookBack = 0, lookAhead = 0, relRampUp = 1, relRampDown = 1, relThreshOn = 144, relThreshOff = -144, highPassFreq = 85, action| + *processBlocking { |server,source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, fastRampUp = 1, fastRampDown = 1, slowRampUp = 100, slowRampDown = 100, onThreshold = -144, offThreshold = -144, floor = -144, highPassFreq = 85, minSliceLength = 2, action| ^FluidNRTProcess.new( server, this, action, [indices], blocking: 1 ).process( - source, startFrame, numFrames, startChan, numChans, indices, absRampUp, absRampDown, absThreshOn, absThreshOff, minSliceLength, minSilenceLength, minLengthAbove, minLengthBelow, lookBack, lookAhead, relRampUp, relRampDown, relThreshOn, relThreshOff, highPassFreq + source, startFrame, numFrames, startChan, numChans, indices, fastRampUp, fastRampDown, slowRampUp, slowRampDown, onThreshold, offThreshold, floor, highPassFreq, minSliceLength ); } } diff --git a/release-packaging/HelpSource/Classes/FluidAmpGate.schelp b/release-packaging/HelpSource/Classes/FluidAmpGate.schelp new file mode 100644 index 0000000..d83c8f8 --- /dev/null +++ b/release-packaging/HelpSource/Classes/FluidAmpGate.schelp @@ -0,0 +1,136 @@ +TITLE:: FluidAmpGate +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 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 an envelop follower on a highpassed version of the signal, which is then going through a Schmidt trigger 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:: rampUp + The number of samples the absolute envelope follower will take to reach the next value when raising. + +ARGUMENT:: rampDown + The number of samples the absolute envelope follower will take to reach the next value when falling. + +ARGUMENT:: onThreshold + The threshold in dB of the absolute envelope follower to trigger an onset, aka to go ON when in OFF state. + +ARGUMENT:: offThreshold + 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:: 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. + +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: absThresh sanity +( +{var env, source = SinOsc.ar(320,0,LFTri.ar(10).abs); + env = FluidAmpGate.ar(source, rampUp:5, rampDown:25, onThreshold:-12, offThreshold: -12); + [source, env] +}.plot(0.1); +) +//basic tests: absThresh hysteresis +( +{var env, source = SinOsc.ar(320,0,LFTri.ar(10).abs); + env = FluidAmpGate.ar(source, rampUp:5, rampDown:25, onThreshold:-12, offThreshold: -16); + [source, env] +}.plot(0.1); +) +//basic tests: absThresh min slice +( +{var env, source = SinOsc.ar(320,0,LFTri.ar(10).abs); + env = FluidAmpGate.ar(source, rampUp:5, rampDown:25, onThreshold:-12, offThreshold: -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 = FluidAmpGate.ar(source, rampUp:5, rampDown:25, onThreshold:-12, offThreshold: -12, minSilenceLength:441); + [source, env] +}.plot(0.1); +) +//mid tests: absThresh time hysteresis on +( +{var env, source = SinOsc.ar(320,0,LFTri.ar(10).abs); + env = FluidAmpGate.ar(source, rampUp:5, rampDown:25, onThreshold:-12, offThreshold: -12, minLengthAbove:441); + [DelayN.ar(source,0.1,441/44100), env] +}.plot(0.1); +) +//mid tests: absThresh time hysteresis off +( +{var env, source = SinOsc.ar(320,0,LFTri.ar(10).abs); + env = FluidAmpGate.ar(source, rampUp:5, rampDown:25, onThreshold:-12, offThreshold: -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 = FluidAmpGate.ar(source, rampUp:5, rampDown:25, onThreshold:-12, offThreshold: -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 = FluidAmpGate.ar(source, rampUp:5, rampDown:25, onThreshold:-12, offThreshold: -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 = FluidAmpGate.ar(source, rampUp:5, rampDown:25, onThreshold:-12, offThreshold: -12, lookBack:221, lookAhead:441); + [DelayN.ar(source,0.1,441/44100), env] +}.plot(0.1); +) +//drum slicing, many ways +//load a buffer +b = Buffer.read(s,File.realpath(FluidAmpGate.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Nicol-LoopE-M.wav"); +//have fun with a gate (explore lookahead and lookback, but correct for latency) +( +{var env, source = PlayBuf.ar(1,b); + env = FluidAmpGate.ar(source, rampUp:1103, rampDown:2205, onThreshold:-27, offThreshold: -31, minSilenceLength:1100, lookBack:441, highPassFreq:40); + [DelayN.ar(source,delaytime:441/44100), env] +}.plot(2, separately:true); +) +:: diff --git a/release-packaging/HelpSource/Classes/FluidAmpSlice.schelp b/release-packaging/HelpSource/Classes/FluidAmpSlice.schelp index 69d85b1..82af2ee 100644 --- a/release-packaging/HelpSource/Classes/FluidAmpSlice.schelp +++ b/release-packaging/HelpSource/Classes/FluidAmpSlice.schelp @@ -4,11 +4,11 @@ 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).:: +This class implements an amplitude-based slicer, with various customisable options and conditions to detect relative amplitude changes as onsets. 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. +FluidAmpSlice is based on two envelop followers on a highpassed version of the signal: one slow that gives the trend, and one fast. Each have features that will interact. 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. +The process will return an audio steam with sample-long impulses at estimated starting points of the different slices. CLASSMETHODS:: @@ -18,56 +18,35 @@ METHOD:: ar 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:: fastRampUp + The number of samples the fast envelope follower will take to reach the next value when raising. Typically, this will be faster than slowRampUp. -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:: fastRampDown + The number of samples the fast envelope follower will take to reach the next value when falling. Typically, this will be faster than slowRampDown. -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:: slowRampUp + The number of samples the absolute envelope follower will take to reach the next value when raising. -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:: slowRampDown + The number of samples the absolute envelope follower will take to reach the next value when falling. -ARGUMENT:: relThreshOn +ARGUMENT:: onThreshold 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 +ARGUMENT:: offThreshold The threshold in dB of the relative envelope follower to reset, aka to allow the differential envelop to trigger again. +ARGUMENT:: floor + The level in dB the slowRamp needs to be above to consider a detected difference valid, allowing to ignore the slices in the noise floor. + 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:: minSliceLength + The length in samples that the Slice will stay ON. Changes of states during that period will be ignored. 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))::. + An audio stream with square envelopes around the slices. The latency between the input and the output is dependant on the relation between the two envelope followers. EXAMPLES:: diff --git a/release-packaging/HelpSource/Classes/FluidBufAmpGate.schelp b/release-packaging/HelpSource/Classes/FluidBufAmpGate.schelp new file mode 100644 index 0000000..e91b817 --- /dev/null +++ b/release-packaging/HelpSource/Classes/FluidBufAmpGate.schelp @@ -0,0 +1,196 @@ +TITLE:: FluidBufAmpGate +SUMMARY:: Amplitude-based Slicer for Buffers +CATEGORIES:: Libraries>FluidDecomposition +RELATED:: Guides/FluCoMa, Guides/FluidDecomposition, Guides/FluidBufMultiThreading + +DESCRIPTION:: +This class implements an amplitude-based slicer, with various customisable options and conditions to detect absolute 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).:: + +FluidBufAmpGate is based on an envelop follower on a highpassed version of the signal, which is then going through a Schmidt trigger and state-aware time contraints. The example code below is unfolding the various possibilites in order of complexity. + +The process will return a two-channel buffer with the addresses of the onset on the first channel, and the address of the offset on the second channel. + +STRONG::Threading:: + +By default, this UGen spawns a new thread to avoid blocking the server command queue, so it is free to go about with its business. For a more detailed discussion of the available threading and monitoring options, including the two undocumented Class Methods below (.processBlocking and .kr) please read the guide LINK::Guides/FluidBufMultiThreading::. + +CLASSMETHODS:: + +METHOD:: process + This is the method that calls for the slicing to be calculated on a given source buffer. + +ARGUMENT:: server + The server on which the buffers to be processed are allocated. + +ARGUMENT:: source + The index of the buffer to use as the source material to be sliced through novelty identification. The different channels of multichannel buffers will be summed. + +ARGUMENT:: startFrame + Where in the srcBuf should the slicing process start, in sample. + +ARGUMENT:: numFrames + How many frames should be processed. + +ARGUMENT:: startChan + For multichannel sources, which channel should be processed. + +ARGUMENT:: numChans + For multichannel sources, how many channel should be summed. + +ARGUMENT:: indices + The index of the buffer where the indices (in sample) of the estimated starting points of slices will be written. The first and last points are always the boundary points of the analysis. + +ARGUMENT:: rampUp + The number of samples the absolute envelope follower will take to reach the next value when raising. + +ARGUMENT:: rampDown + The number of samples the absolute envelope follower will take to reach the next value when falling. + +ARGUMENT:: onThreshold + The threshold in dB of the absolute envelope follower to trigger an onset, aka to go ON when in OFF state. + +ARGUMENT:: offThreshold + 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:: 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:: action + A Function to be evaluated once the offline process has finished and indices instance variables have been updated on the client side. The metric will be passed indices as an argument. + +RETURNS:: + Nothing, as the destination buffer is declared in the function call. + +EXAMPLES:: + +code:: +// define a test signal and a destination buffer +( +b = Buffer.sendCollection(s, Array.fill(44100,{|i| sin(i*pi/ (44100/640)) * (sin(i*pi/ 22050)).abs})); +c = Buffer.new(s); +) +b.play +b.plot + +//basic tests: absThresh sanity +FluidBufAmpGate.process(s, b, indices:c, rampUp:5, rampDown:25, onThreshold:-12, offThreshold: -12) +c.query +c.getn(0,c.numFrames*2,{|item|item.postln;}) + +//basic tests: absThresh hysteresis +FluidBufAmpGate.process(s, b, indices:c, rampUp:5, rampDown:25, onThreshold:-12, offThreshold: -16) +c.query +c.getn(0,c.numFrames*2,{|item|item.postln;}) + +//basic tests: absThresh min slice +FluidBufAmpGate.process(s, b, indices:c, rampUp:5, rampDown:25, onThreshold:-12, offThreshold: -12, minSliceLength:441) +c.query +c.getn(0,c.numFrames*2,{|item|item.postln;}) + +//basic tests: absThresh min silence +FluidBufAmpGate.process(s, b, indices:c, rampUp:5, rampDown:25, onThreshold:-12, offThreshold: -12, minSilenceLength:441) +c.query +c.getn(0,c.numFrames*2,{|item|item.postln;}) + +//mid tests: absThresh time hysteresis on +FluidBufAmpGate.process(s, b, indices:c, rampUp:5, rampDown:25, onThreshold:-12, offThreshold: -12, minLengthAbove:441) +c.query +c.getn(0,c.numFrames*2,{|item|item.postln;}) + +//mid tests: absThresh time hysteresis off +FluidBufAmpGate.process(s, b, indices:c, rampUp:5, rampDown:25, onThreshold:-12, offThreshold: -12, minLengthBelow:441) +c.query +c.getn(0,c.numFrames*2,{|item|item.postln;}) + +//mid tests: absThresh with lookBack +FluidBufAmpGate.process(s, b, indices:c, rampUp:5, rampDown:25, onThreshold:-12, offThreshold: -12, lookBack:441) +c.query +c.getn(0,c.numFrames*2,{|item|item.postln;}) + +//mid tests: absThresh with lookAhead +FluidBufAmpGate.process(s, b, indices:c, rampUp:5, rampDown:25, onThreshold:-12, offThreshold: -12, lookAhead:441) +c.query +c.getn(0,c.numFrames*2,{|item|item.postln;}) + +//mid tests: absThresh with asymetrical lookBack and lookAhead +FluidBufAmpGate.process(s, b, indices:c, rampUp:5, rampDown:25, onThreshold:-12, offThreshold: -12, lookBack:221, lookAhead:441) +c.query +c.getn(0,c.numFrames*2,{|item|item.postln;}) +:: + +STRONG::A musical example.:: +CODE:: +//load a buffer +( +b = Buffer.read(s,File.realpath(FluidBufAmpGate.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Nicol-LoopE-M.wav"); +c = Buffer.new(s); +) + +// slice the samples +FluidBufAmpGate.process(s, b, indices:c, rampUp:1103, rampDown:2205, onThreshold:-27, offThreshold: -31, minSilenceLength:1100, lookBack:441, highPassFreq:40) +c.query +c.getn(0,c.numFrames*2,{|item|item.postln;}) +//reformatting to read the onsets and offsets as pairs +c.getn(0,c.numFrames*2,{|items|items.reshape(c.numFrames,2).do({|x| x.postln});}) + +//loops over a splice with the MouseX, taking the respective onset and offset of a given slice +( +{ + BufRd.ar(1, b, + Phasor.ar(0,1, + BufRd.kr(2, c, + MouseX.kr(0, BufFrames.kr(c)), 0, 1)[0], + BufRd.kr(2, c, + MouseX.kr(1, BufFrames.kr(c)), 0, 1)[1], + BufRd.kr(2,c, + MouseX.kr(0, BufFrames.kr(c)), 0, 1)[0] + ), 0, 1); +}.play; +) +:: + +STRONG::A stereo buffer example.:: +CODE:: +// make a stereo buffer +b = Buffer.alloc(s,88200,2); + +// add some stereo clicks and listen to them +((0..3)*22050+11025).do({|item,index| b.set(item+(index%2), 1.0)}) +b.play + +// create a new buffer as destinations +c = Buffer.new(s); + +//run the process on them +( +// with basic params +Routine{ + t = Main.elapsedTime; + FluidBufAmpGate.process(s, b, indices: c, rampUp:1, rampDown:20); + (Main.elapsedTime - t).postln; +}.play +) + +// list the indicies of detected attacks - the two input channels have been summed. The two channels of the output, respectively onset and offset indices, are interleaved as this is the SuperCollider buffer data formatting +c.getn(0,c.numFrames*2,{|item|item.postln;}) +// a more readable version: deinterleave onsetand offset +c.getn(0,c.numFrames*2,{|items|items.reshape(c.numFrames,2).do({|x| x.postln});}) +:: diff --git a/release-packaging/HelpSource/Classes/FluidBufAmpSlice.schelp b/release-packaging/HelpSource/Classes/FluidBufAmpSlice.schelp index 9542d55..9777088 100644 --- a/release-packaging/HelpSource/Classes/FluidBufAmpSlice.schelp +++ b/release-packaging/HelpSource/Classes/FluidBufAmpSlice.schelp @@ -4,11 +4,11 @@ CATEGORIES:: Libraries>FluidDecomposition RELATED:: Guides/FluCoMa, Guides/FluidDecomposition, Guides/FluidBufMultiThreading 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).:: +This class implements an amplitude-based slicer,with various customisable options and conditions to detect relative amplitude changes as onsets. 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. +FluidBufAmpSlice is based on two envelop followers on a highpassed version of the signal: one slow that gives the trend, and one fast. Each have features that will interact. The example code below is unfolding the various possibilites in order of complexity. -The process will return a two-channel buffer with the addresses of the onset on the first channel, and the address of the offset on the second channel. +The process will return a buffer which contains indices (in sample) of estimated starting points of different slices. STRONG::Threading:: @@ -40,51 +40,33 @@ 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:: 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:: fastRampUp + The number of samples the fast envelope follower will take to reach the next value when raising. Typically, this will be faster than slowRampUp. -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:: fastRampDown + The number of samples the fast envelope follower will take to reach the next value when falling. Typically, this will be faster than slowRampDown. -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:: slowRampUp + The number of samples the absolute envelope follower will take to reach the next value when raising. -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:: slowRampDown + The number of samples the absolute envelope follower will take to reach the next value when falling. -ARGUMENT:: relThreshOn +ARGUMENT:: onThreshold 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 +ARGUMENT:: offThreshold The threshold in dB of the relative envelope follower to reset, aka to allow the differential envelop to trigger again. +ARGUMENT:: floor + The level in dB the slowRamp needs to be above to consider a detected difference valid, allowing to ignore the slices in the noise floor. + 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:: minSliceLength + The length in samples that the Slice will stay ON. Changes of states during that period will be ignored. + 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 metric will be passed indices as an argument.