|
|
TITLE:: FluidBufAmpSlice
|
|
|
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 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).::
|
|
|
|
|
|
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 buffer which contains indices (in sample) of estimated starting points of different slices.
|
|
|
|
|
|
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:: 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:: 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:: slowRampUp
|
|
|
The number of samples the absolute envelope follower will take to reach the next value when raising.
|
|
|
|
|
|
ARGUMENT:: slowRampDown
|
|
|
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 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:: 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.
|
|
|
|
|
|
RETURNS::
|
|
|
Nothing, as the destination buffer is declared in the function call.
|
|
|
|
|
|
EXAMPLES::
|
|
|
|
|
|
code::
|
|
|
// detrending explained
|
|
|
// define a test signal and a destination buffer
|
|
|
(
|
|
|
b = Buffer.sendCollection(s, Array.fill(44100,{|i| sin(i*pi/ (44100/640)) * ((((35000-i)/30000)%0.8) + 0.2)}));
|
|
|
c = Buffer.new(s);
|
|
|
)
|
|
|
// the source is a sinewave that does not go to silence and has sharp-ish amplitude bumps as onsets we try to track
|
|
|
b.play
|
|
|
b.plot
|
|
|
|
|
|
//
|
|
|
FluidBufAmpSlice.process(s, b,indices: c,fastRampUp: 5,fastRampDown: 50,slowRampUp: 220,slowRampDown: 220, onThreshold: 10, offThreshold: 10,floor: -60);
|
|
|
c.query
|
|
|
c.getn(0,c.numFrames,{|item|item.postln;})
|
|
|
|
|
|
//beware of multiple triggers at the begining of the 2nd cycle above). A solution: Schmidth triggers
|
|
|
FluidBufAmpSlice.process(s, b,indices: c,fastRampUp: 5,fastRampDown: 50,slowRampUp: 220,slowRampDown: 220, onThreshold: 10, offThreshold: 7,floor: -60);
|
|
|
c.query
|
|
|
c.getn(0,c.numFrames,{|item|item.postln;})
|
|
|
|
|
|
// we got most of them sorted, but there is another solution: minslicelength
|
|
|
FluidBufAmpSlice.process(s, b,indices: c,fastRampUp: 5,fastRampDown: 50,slowRampUp: 220,slowRampDown: 220, onThreshold: 10, offThreshold: 7,floor: -60, minSliceLength: 500);
|
|
|
c.query
|
|
|
c.getn(0,c.numFrames,{|item|item.postln;})
|
|
|
::
|
|
|
|
|
|
STRONG::A musical example.::
|
|
|
CODE::
|
|
|
//load a buffer
|
|
|
(
|
|
|
b = Buffer.read(s,File.realpath(FluidBufAmpSlice.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Nicol-LoopE-M.wav");
|
|
|
c = Buffer.new(s);
|
|
|
)
|
|
|
|
|
|
// slice the samples
|
|
|
FluidBufAmpSlice.process(s,b,indices:c,fastRampUp: 10,fastRampDown: 2205,slowRampUp: 4410,slowRampDown: 4410,onThreshold: 10,offThreshold: 5,floor: -40,minSliceLength: 4410,highPassFreq: 20);
|
|
|
c.query
|
|
|
c.getn(0,c.numFrames,{|item|item.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(1, c,
|
|
|
MouseX.kr(0, BufFrames.kr(c) - 1), 0, 1),
|
|
|
BufRd.kr(1, c,
|
|
|
MouseX.kr(1, BufFrames.kr(c)), 0, 1),
|
|
|
BufRd.kr(1,c,
|
|
|
MouseX.kr(0, BufFrames.kr(c) - 1), 0, 1)), 0, 1);
|
|
|
}.play;
|
|
|
)
|
|
|
::
|
|
|
|
|
|
STRONG::A stereo buffer example.::
|
|
|
CODE::
|
|
|
// make a stereo buffer
|
|
|
b = Buffer.alloc(s,88200,2);
|
|
|
|
|
|
// add some stereo clicks and listen to them
|
|
|
((0..3)*22050+11025).do({|item,index| b.set(item+(index%2), 1.0)})
|
|
|
b.play
|
|
|
|
|
|
// create a new buffer as destinations
|
|
|
c = Buffer.new(s);
|
|
|
|
|
|
//run the process on them
|
|
|
(
|
|
|
// with basic params
|
|
|
Routine{
|
|
|
t = Main.elapsedTime;
|
|
|
FluidBufAmpSlice.process(s,b, indices: c, fastRampUp: 10,fastRampDown: 2205,slowRampUp: 4410,slowRampDown: 4410, onThreshold: 10,offThreshold: 5);
|
|
|
(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;})
|
|
|
::
|