Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ schelp
interfaces/*
*.log
**/.DS_Store
.vscode
35 changes: 17 additions & 18 deletions doc/AmpGate.rst
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
:digest: Amplitude-based Gating Slicer
:digest: Gate Detection on a Signal
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gate Detection can be on anything. Amplitude Gating on a Signal?

:species: slicer
:sc-categories: Libraries>FluidDecomposition
:sc-related: Guides/FluidCorpusManipulationToolkit
:see-also: BufAmpGate, AmpSlice, OnsetSlice, NoveltySlice, TransientSlice
:description: This class implements an amplitude-based slicer, with various customisable options and conditions to detect absolute amplitude changes as onsets and offsets.
:discussion:
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.
:description: Absolute amplitude threshold gate detector on a real-time signal
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "realtime" as opposed to "real-time" is the more consistent way to put it with the rest of the documentation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also makes it more consistent when the inevitable "non-realtime" appears

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed


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.
:discussion:
AmpGate outputs a audio-rate, single-channel signal that is either 0, indicating the gate is closed, or 1, indicating the gate is open. The gate detects an onset (opens) when the internal envelope follower (controlled by ``rampUp`` and ``rampDown``) goes above a specified ``onThreshold`` (in dB) for at least ``minLengthAbove`` samples. The gate will stay open until the envelope follower goes below ``offThreshold`` (in dB) for at least ``minLengthBelow`` samples, which triggers an offset.

:output: An audio stream with square envelopes around the slices. The latency between the input and the output is **max(minLengthAbove + lookBack, max(minLengthBelow,lookAhead))**.
The latency between the input and the output is **max(minLengthAbove + lookBack, max(minLengthBelow,lookAhead))**.

:output: An audio stream of gate information, always either 1, indicating the gate is open, or 0, indicating the gate is closed.

:control rampUp:

Expand All @@ -22,41 +22,40 @@

:control onThreshold:

The threshold in dB of the envelope follower to trigger an onset, aka to go ON when in OFF state.
The threshold in dB of the envelope follower to trigger an onset: go from 0 (closed) to 1 (open).

:control offThreshold:

The threshold in dB of the envelope follower to trigger an offset, , aka to go ON when in OFF state.
The threshold in dB of the envelope follower to trigger an offset: go from 1 (open) to 0 (closed).

:control minSliceLength:

The length in samples that the Slice will stay ON. Changes of states during that period will be ignored.
The minimum length in samples for which the gate will stay open. Changes of states during this period after an onset will be ignored.

:control minSilenceLength:

The length in samples that the Slice will stay OFF. Changes of states during that period will be ignored.
The minimum length in samples for which the gate will stay closed. Changes of states during that period after an offset will be ignored.

:control minLengthAbove:

The length in samples that the 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.
The length in samples that the envelope must be above the threshold to consider it a valid transition to 1. The gate will change to 1 at the first sample when the condition is met. Therefore, this affects the latency (see latency equation in the description).

:control minLengthBelow:

The length in samples that the 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.
The length in samples that the envelope must be below the threshold to consider it a valid transition to 0. The gate will change to 0 at the first sample when the condition is met. Therefore, this affects the latency (see latency equation in the description).

:control 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.
When an onset is detected, the algorithm will look in the recent past (via an internal recorded buffer of this length in samples) for a minimum in the envelope follower to identify as the onset point. This affects the latency of the algorithm (see latency equation in the description).

:control 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.

When an offset is detected, the algorithm will wait this duration (in samples) to find a minimum in the envelope follower to identify as the offset point. This affects the latency of the algorithm (see latency equation in the description).
:control 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. A frequency of 0 bypasses the filter.
The frequency of the fourth-order Linkwitz-Riley high-pass filter (https://en.wikipedia.org/wiki/Linkwitz%E2%80%93Riley_filter) applied to the input signal to minimise low frequency intermodulation with very short ramp lengths. A frequency of 0 bypasses the filter.

:control maxSize:

How large can the buffer be for time-critical conditions, by allocating memory at instantiation time. This cannot be modulated.

The size of the buffer to allocate at instantiation time for keeping track of the time-critical conditions (``minSliceLength``, ``minSilenceLength``, ``minLengthAbove``, ``minLengthBelow``, ``lookBack``, and ``lookAhead``). This cannot be modulated.
52 changes: 24 additions & 28 deletions doc/BufAmpGate.rst
Original file line number Diff line number Diff line change
@@ -1,86 +1,82 @@
:digest: Amplitude-based Gating Slicer for Buffers
:digest: Gate Detection on a Bfufer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo - and again the ambiguity of the name as discussed above. Amplitude Gating on a Buffer?

:species: buffer-proc
:sc-categories: Libraries>FluidDecomposition
:sc-related: Guides/FluidCorpusManipulationToolkit
:see-also: AmpGate, BufAmpSlice, BufOnsetSlice, BufNoveltySlice, BufTransientSlice
:description: This class implements an amplitude-based slicer, with various customisable options and conditions to detect absolute amplitude changes as onsets and offsets.
:description: Absolute amplitude threshold gate detector on audio in a buffer

:discussion:
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.
BufAmpGate outputs a two-channel buffer containing open and close positions of the gates. Each frame of the buffer contains an onset (opening) position on channel 0 and the corresponding offset (closing) position on channel 1 (both in samples). The buffer will have as many frames as gate events detected.

The gate detects an onset (opens) when the internal envelope follower (controlled by ``rampUp`` and ``rampDown``) goes above a specified ``onThreshold`` (in dB) for at least ``minLengthAbove`` samples. The gate will stay open until the envelope follower goes below ``offThreshold`` (in dB) for at least ``minLengthBelow`` samples, which triggers an offset.

:output: Nothing, as the destination buffer is declared in the function call.


:control 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.
The buffer to analyse for gate information. Multichannel buffers will be summed to mono for analysis.

:control startFrame:

Where in the srcBuf should the slicing process start, in sample.
Where in ``source`` to begin the analysis (in samples). The default is 0.

:control numFrames:

How many frames should be processed.
How many frames (audio samples) to analyse. The default of -1 indicates to analyse through end of the buffer

:control startChan:

For multichannel sources, which channel should be processed.
For multichannel sources, at which channel to begin the analysis. The default is 0.

:control numChans:

For multichannel sources, how many channel should be summed.
For multichannel sources, how many channels should be included in the analysis (starting from ``startChan``). The default of -1 indicates to include all the channels from ``startChan`` through the rest of the buffer. If more than one channel is specified, the channels will be summed to mono for analysis.

:control 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.
The buffer to write the gate information into. Buffer will be resized appropriately so each frame contains an onset (opening) position on channel 0 and the corresponding offset (closing) position on channel 1 (both in samples). The buffer will have as many frames as gate events detected.

:control rampUp:

The number of samples the envelope follower will take to reach the next value when raising.
The number of samples the envelope follower will take to reach the next value when rising.

:control rampDown:

The number of samples the envelope follower will take to reach the next value when falling.
The number of samples the envelope follower will take to reach the next value when falling.

:control onThreshold:

The threshold in dB of the envelope follower to trigger an onset, aka to go ON when in OFF state.
The threshold in dB of the envelope follower to trigger an onset.

:control offThreshold:

The threshold in dB of the envelope follower to trigger an offset, , aka to go ON when in OFF state.
The threshold in dB of the envelope follower to trigger an offset.

:control minSliceLength:

The length in samples that the Slice will stay ON. Changes of states during that period will be ignored.
The minimum length in samples for which the gate will stay open. Changes of states during this period after an onset will be ignored.

:control minSilenceLength:

The length in samples that the Slice will stay OFF. Changes of states during that period will be ignored.
The minimum length in samples for which the gate will stay closed. Changes of states during that period after an offset will be ignored.

:control minLengthAbove:

The length in samples that the 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.
The length in samples that the envelope must be above the threshold to consider it a valid onset. The onset will be triggered at the first sample when the condition is met.

:control minLengthBelow:

The length in samples that the 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.
The length in samples that the envelope must be below the threshold to consider it a valid offset. The offset will be triggered at the first sample when the condition is met.

:control 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.
When an onset is detected, the algorithm will look in the recent past (this length in samples) for a minimum in the envelope follower to identify as the onset point.

:control 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.

When an offset is detected, the algorithm will wait this duration (in samples) to find a minimum in the envelope follower to identify as the offset point.
:control 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. A frequency of 0 bypasses the filter.

:control maxSize:

How large can the buffer be for time-critical conditions, by allocating memory at instantiation time. This cannot be modulated.

The frequency of the fourth-order Linkwitz-Riley high-pass filter (https://en.wikipedia.org/wiki/Linkwitz%E2%80%93Riley_filter) applied to the signal signal to minimise low frequency intermodulation with very short ramp lengths. A frequency of 0 bypasses the filter.
106 changes: 37 additions & 69 deletions example-code/sc/AmpGate.scd
Original file line number Diff line number Diff line change
@@ -1,76 +1,44 @@

strong::Watch the gate::
code::
//basic tests: threshold 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: threshold 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: threshold 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: threshold 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: threshold 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: threshold 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: threshold 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: threshold with lookAhead

// make sure the third bus is only output.
(
{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);
s.options.numOutputBusChannels_(4);
s.options.numInputBusChannels_(4);
s.reboot;
)
//mid tests: threshold with asymetrical lookBack and lookAhead

~src = Buffer.read(s,FluidFilesPath("Nicol-LoopE-M.wav"));

(
{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);
{
var source = PlayBuf.ar(1,~src,BufRateScale.ir(~src),loop:1);
var env = FluidAmpGate.ar(source, rampUp:441, rampDown:2205, onThreshold:-27, offThreshold: -31, minSilenceLength:4410, lookBack:441, highPassFreq:20).poll;
var sig = DelayN.ar(source,delaytime:441/44100) * env.lag(0.02); // compenstate for latency.
[sig,sig,env];
}.scope;
)
//drum slicing, many ways
//load a buffer
b = Buffer.read(s,FluidFilesPath("Nicol-LoopE-M.wav"));
//have fun with a gate (explore lookahead and lookback, but correct for latency, which will be the greatest of the lookahead and lookback)

::
strong::Use for manipulating FX::
code::

~src = Buffer.read(s,FluidFilesPath("Harker-DS-TenOboeMultiphonics-M.wav"));

(
{var env, source = PlayBuf.ar(1,b);
env = FluidAmpGate.ar(source, rampUp:441, rampDown:2205, onThreshold:-27, offThreshold: -31, minSilenceLength:4410, lookBack:441, highPassFreq:20);
[DelayN.ar(source,delaytime:441/44100), env]
}.plot(2, separately:true);
{
arg thresh = -35;
var src = PlayBuf.ar(1,~src,BufRateScale.ir(~src),loop:1);
var localbuf = LocalBuf(s.sampleRate).clear;
var gate = FluidAmpGate.ar(src,10,10,thresh,thresh-5,441,441,441,441).poll;
var phs = Phasor.ar(0,1 * gate,0,localbuf.numFrames/2); // only write into the buffer when the gate is open
var trig = Dust.kr(50) * (1-gate); // only trigger grains from that buffer when the gate is closed
var dur = 0.1;
var pos = ((phs + localbuf.numFrames/2) - TRand.kr(dur*SampleRate.ir,localbuf.numFrames/2)) / localbuf.numFrames;
var sig = GrainBuf.ar(2,trig,dur,localbuf,1,pos,4,TChoose.kr(trig,[-0.9,0.9]),mul:6.dbamp);
BufWr.ar(src,localbuf,[phs,phs+(localbuf.numFrames/2)]);
sig + src;
}.scope;
)
::

::
Loading