Live Granulation different approaches to live granulation with gui examples
Part of: miSCellaneous
See also: Buffer Granulation, Introduction to miSCellaneous, VarGui, VarGui shortcut builds, PLx suite, PbindFx, kitchen studies, Sieves and Psieve patterns, PSPdiv
This file is a complement to the Buffer Granulation tutorial. It contains variants of granulation that are applied to an audio signal directly, without the explicit use of a buffer. As with buffer granulation there exist language-driven and server-driven as well as hybrid variants. Especially pattern-based live granulation raises certain accuracy issues, which are summarized in this tutorial.
WARNING:
1.) Be careful with amplitudes !
To reduce feedback I've built in tanh, LPF and delay into examples with SoundIn,
though better use headphones to avoid feedback at all.
For tips on reducing feedback or creative use of feedback see Nathaniel Virgo's Feedback quark and his post here:
http://new-supercollider-mailing-lists-forums-use-these.2681727.n2.nabble.com/Feedback-td3527243.html
2.) Avoid too early freeing of audio buses:
a) When there are still running synths, unintendedly sound might be routed to a processing resp. feedback chain.
b) For the same reason don't free buses if they are hard-wired to SynthDefs, you still want to use.
You can free buses on oaccasion if you are sure that nothing is running and you won't need them again.
Keep in mind that you can also (re-)start the server with a higher number of audio buses available (Ex. 2c)
3.) I haven't used below setups for live performances. Although all of them work stable for me as they are, in general hangs can occasionally happen with pattern-driven setups. Often this can be tracked down to sequences of extremely short event durations (and/or long grain durations). Where this can happen as a side-effect, thresholds can be built in.
Another possible source of hangs is careless deep nesting of Patterns where mistakes can easily occur. Starting with clear Pattern structures is recommended - and if more complications are involved: testing without sound first, after saving your patch, might be a good idea.
NOTE:
With the exception of Exs. 2d all examples use SoundIn, supposing to use input from your computer resp. soundcard. Depending on your setup you might have to change the standard input (bus = 0) passed to VarGui resp. the SoundIn ugen. Use headphones to avoid feedback!
NOTE:
All live granulation variants of this file can of course be applied to any signal, thus also to any playback of a buffer. Vice versa all variants from Buffer Granulation can be applied to a buffer, which is occasionally (or continuously) filled with live input.
(
s = Server.local;
Server.default = s;
s.boot;
)
1.) Live granulation scheduled by server
This can be done with the GrainIn ugen or self-defined enveloping. An advantage of server-driven live granulation is, that accuracy issues and their workarounds, as described in Exs. 2a-2d, are avoided. As a restriction, sequenced processing of grains, e.g. as with PbindFx, is more difficult.
Example 1a: Basic live granulation with GrainIn
(
~audioBus_ex_1a = Bus.audio(s, 1);
// pass envelopes with buffer, hanning is GrainIn's default anyway
h = Signal.hanningWindow(1000);
e = Buffer.loadCollection(s, h);
SynthDef(\soundIn, { |out, in, ampIn = 1|
Out.ar(out, SoundIn.ar(in) * ampIn)
}).add;
SynthDef(\live_gran_1a, { |out, in, envBuf, trigRate = 50, overlap = 0.5, panMax = 0.5,
panType = 0, amp = 1, minGrainDur = 0.001|
var inSig, sig, trig, grainDur, pan;
inSig = In.ar(in, 1);
// reduce feedback
inSig = DelayC.ar(LPF.ar(inSig.tanh, 2000), 0.1, 0.01);
trig = Impulse.ar(trigRate);
grainDur = max(trigRate.reciprocal * overlap, minGrainDur);
// select L/R or random sequencing
pan = Demand.ar(
trig,
0,
Dswitch1([
Dseq([1, -1], inf),
Dwhite(-1, 1)
], panType)
) * min(panMax, 0.999);
sig = GrainIn.ar(
2,
trig,
grainDur,
inSig,
pan,
envBuf
);
Out.ar(out, sig * EnvGate.new * amp);
}).add;
)
// to avoid feedback use with headphones
(
// start granulation (first row) before to ensure right order
VarGui(
synthCtr: [
[
\envBuf, e.bufnum,
\in, ~audioBus_ex_1a.index,
\trigRate, [5, 500, \lin, 0, 50],
\overlap, [0.05, 3, \lin, 0, 0.5],
\panType, [0, 1, \lin, 1, 0],
\panMax, [0, 1, \lin, 0, 0.5],
\amp, [0, 1, \lin, 0, 0.1]
],[
\out, ~audioBus_ex_1a.index,
// you might have to pass a different 'in' bus for SoundIn
// depending on your setup
\in, 0,
\ampIn, [0, 1, \lin, 0, 1]
]
],
synth: [\live_gran_1a, \soundIn]
).gui
)
Even with overlapping grains, sequenced effect processing per grain is possible with GrainIn, but requires more effort, see Ex. 1e in the Buffer Granulation tutorial.
Example 1b: Live granulation with server-driven enveloping
In this example only non-overlapping grains are regarded, for overlapping a multichannel approach as in Ex. 1e of the Buffer Granulation tutorial can be applied. In comparison to live granulation example 1a there is hardly an advantage in this basic variant. However intermediate processings can be built into this SynthDef, which aren't possible in the above example, where granulation is encapsulated in a UGen, manipulation of the envelope signal could be one.
(
~audioBus_ex_1b = Bus.audio(s, 1);
// pass envelopes with buffer
h = Signal.hanningWindow(1000);
e = Buffer.loadCollection(s, h);
SynthDef(\soundIn, { |out, in, ampIn = 1|
Out.ar(out, SoundIn.ar(in) * ampIn)
}).add;
// suppose overlap < 1
SynthDef(\live_gran_1b, { |out, in, envBuf, trigRate = 50, overlap = 0.5, panMax = 0.5,
panType = 0, amp = 1, minGrainDur = 0.001, interpolation = 4|
var inSig, env, numFrames = BufFrames.kr(envBuf), startTrig, grainDur,
latchedTrigRate, latchedStartTrig, latchedOverlap, pan;
inSig = In.ar(in, 1);
// reduce feedback
inSig = DelayC.ar(LPF.ar(inSig.tanh, 2000), 0.1, 0.01);
startTrig = Impulse.ar(trigRate);
// why this ? - shape of envelope shouldn't be changed while application
latchedTrigRate = Latch.ar(K2A.ar(trigRate), startTrig);
latchedStartTrig = Impulse.ar(latchedTrigRate);
latchedOverlap = Latch.ar(K2A.ar(overlap), startTrig);
latchedOverlap = max(latchedOverlap, latchedTrigRate * minGrainDur);
grainDur = (latchedOverlap / latchedTrigRate);
env = BufRd.ar(
1,
envBuf,
Sweep.ar(
latchedStartTrig,
latchedOverlap.reciprocal * latchedTrigRate * numFrames,
).clip(0, numFrames - 1),
interpolation: interpolation
);
pan = Demand.ar(
latchedStartTrig,
0,
Dswitch1([
Dseq([1, -1], inf),
Dwhite(-1, 1)
], panType)
) * panMax * 0.999;
Out.ar(out, Pan2.ar(inSig * env * amp, pan) * EnvGate.new);
}).add
)
// to avoid feedback use with headphones
(
// start granulation (first row) before to ensure right order
VarGui(
synthCtr: [
[
\envBuf, e.bufnum,
\in, ~audioBus_ex_1b.index,
\trigRate, [5, 500, \lin, 0, 50],
\overlap, [0.05, 0.99, \lin, 0, 0.5],
\panType, [0, 1, \lin, 1, 0],
\panMax, [0, 1, \lin, 0, 0.5],
\amp, [0, 1, \lin, 0, 0.1]
],[
\out, ~audioBus_ex_1b.index,
// you might have to pass a different 'in' bus for SoundIn
// depending on your setup
\in, 0,
\ampIn, [0, 1, \lin, 0, 1]
]
],
synth: [\live_gran_1b, \soundIn]
).gui
)
2.) Live granulation driven by language
The crucial point of this strategy is a kind of incompatibility of In.ar and OffsetOut. Normally we use OffsetOut for exact buffer granulation with patterns, as the start of the synth (the grain) is corrected by a shift (standard Out.ar is only able to start at control block boundaries). However, when In.ar and OffsetOut.ar are used in the same synth the read input signal is also shifted, which results in a correct grain distance but an fluctuating input delay. This can be overcome by a little trick: we can use OffsetOut to send a trigger to a bus, which indicates its correct delay. Then the trigger can be read in the same synth and trigger again a gated envelope for the input signal, the resulting grain can be output with normal Out.ar. So grain distances are correct and input is not fluctuating (see Ex. 2d for an accuracy comparison). Nevertheless language-driven sequencing is not sample-exact in realtime, no matter if In.ar is used or not. This is related to hardware control and cannot be overcome:
http://new-supercollider-mailing-lists-forums-use-these.2681727.n2.nabble.com/sample-accuracy-with-lang-timing-was-Various-timing-issues-td7615278.html
This might be an issue with a strictly periodic input signal and very short grain distances. You can e.g. check out with granulation of a fixed-pitch triangular wave or similar, every few seconds or so there will happen an audible jump due to an irregular time interval, which is necessary for clock calibration; at that point there will, in general, also be a phase shift of the input signal (see last example of 2d). This might not be noticed with an input of spoken voice e.g.
Control block length is also a boundary for gated envelopes defined with EnvGen – here envelopes are established with an env buffer used by BufRd and Sweep ugens, a flexible method as Envelopes shape can be defined arbitrarily in language.
Example 2a: Basic Pbind live granulation
At that point I haven't seen a solution to pass the trigger bus as argument, in the example it is hard-wired. As the SynthDef uses a SetResetFF ugen, which relies on two triggers within control block length, grainDelta shouldn't be shorter than that. If shorter grainDeltas are required this would be possible with alternating SynthDefs and buses. For the same reason this SynthDef should only be used once at the same time by a Pbind/EventStreamPlayer. Alternatively – as in Ex. 2b – a SynthDef factory can produce a number of structurally equal SynthDefs bound to a number of buses.
(
// input bus and trigger bus
// trigger bus must be hard-wired in SynthDef though
~audioBus_ex_2a = Bus.audio(s, 1);
~trigBus_ex_2a = Bus.audio(s, 1);
// pass envelopes with buffer
h = Signal.hanningWindow(1000);
e = Buffer.loadCollection(s, h);
SynthDef(\soundIn, { |out, in, ampIn = 1|
Out.ar(out, SoundIn.ar(in) * ampIn)
}).add;
SynthDef(\live_gran_2a, { |out, inBus, envBuf, grainDelta, pan = 0,
overlap = 1, amp = 1, minGrainDur = 0.001, interpolation = 4|
var inSig, offset, env, numFrames = BufFrames.ir(envBuf), gate,
defaultOffset = ControlRate.ir.reciprocal;
inSig = In.ar(inBus, 1);
// reduce feedback
inSig = LPF.ar(inSig.tanh, 2000);
// low overlap bound given by minimal grain duration
overlap = max(overlap, minGrainDur / grainDelta);
// impulse for offset check
OffsetOut.ar(~trigBus_ex_2a, Impulse.ar(0));
// additional gate to start with 0 in delayed case
// this is necessary as OffsetOut outputs impulse twice
gate = SetResetFF.ar(In.ar(~trigBus_ex_2a));
env = BufRd.ar(
1,
envBuf,
Sweep.ar(
In.ar(~trigBus_ex_2a),
(overlap * grainDelta).reciprocal * numFrames,
).clip(0, numFrames - 1),
interpolation: interpolation
);
// to finish synth
Line.ar(dur: overlap * grainDelta + defaultOffset, doneAction: 2);
// shifted accurate output
Out.ar(out, Pan2.ar(env * gate * inSig * amp, pan));
}).add
)
// to avoid feedback use with headphones
// start synth and event stream player
(
p = Pbind(
\instrument, \live_gran_2a,
\inBus, ~audioBus_ex_2a,
\envBuf, e,
\dur, Pfunc { ~trigRate.reciprocal },
\grainDelta, Pkey(\dur),
\overlap, Pfunc { ~overlap },
\amp, Pfunc { ~amp },
\pan, Pfunc { ~panMax } * PLseq([1, -1]),
\addAction, \addToTail
);
VarGui([
\trigRate, [20, 500, \lin, 0, 100],
\overlap, [0.1, 2, \lin, 0, 0.5],
\panMax, [0, 1, \lin, 0, 0.5],
\amp, [0, 1, \lin, 0, 0.1]
],[
\out, ~audioBus_ex_2a.index,
// you might have to pass a different 'in' bus for SoundIn
// depending on your setup
\in, 0,
\ampIn, [0, 1, \lin, 0, 1]
], p, \soundIn
).gui
)
Example 2b: Parallel Pbind live granulation
As trigger buses have to be hard-wired, a SynthDef factory produces a number of structurally equal SynthDefs bound to a number of buses. In this variant the granulation is combined with a bandpass filter.
(
// input bus and trigger bus
// trigger bus must be hard-wired in SynthDef though
~audioBus_ex_2b = Bus.audio(s, 1);
// pass envelopes with buffer
h = Signal.hanningWindow(1000);
e = Buffer.loadCollection(s, h);
SynthDef(\soundIn, { |out, in, ampIn = 1|
Out.ar(out, SoundIn.ar(in) * ampIn)
}).add;
// "SynthDef factory", as each SynthDef neads hard-wired bus, make 3 of each
~trigBus_ex_2b = { Bus.audio(s, 1) } ! 3;
{ |i|
var name = \live_gran_2b ++ "_" ++ i.asString;
SynthDef(name, { |out, inBus, envBuf, grainDelta, pan = 0, rq = 0.1, center = 500,
overlap = 1, amp = 1, minGrainDur = 0.001, interpolation = 4|
var sig, inSig, offset, env, numFrames = BufFrames.ir(envBuf), gate,
defaultOffset = ControlRate.ir.reciprocal;
inSig = In.ar(inBus, 1);
// reduce feedback
inSig = LPF.ar(inSig.tanh, 2000);
// amplitude compensation for lower rq of bandpass filter
inSig = BPF.ar(inSig, center, rq, (rq ** -1) * (400 / center ** 0.5));
// low overlap bound given by minimal grain duration
overlap = max(overlap, minGrainDur / grainDelta);
// impulse for offset check
OffsetOut.ar(~trigBus_ex_2b[i], Impulse.ar(0));
// additional gate to start with 0 in delayed case
// this is necessary as OffsetOut outputs impulse twice
gate = SetResetFF.ar(In.ar(~trigBus_ex_2b[i]));
env = BufRd.ar(
1,
envBuf,
Sweep.ar(
In.ar(~trigBus_ex_2b[i]),
(overlap * grainDelta).reciprocal * numFrames,
).clip(0, numFrames - 1),
interpolation: interpolation
);
// to finish synth
Line.ar(dur: overlap * grainDelta + defaultOffset, doneAction: 2);
sig = env * gate * inSig * amp;
// shifted accurate output
Out.ar(out, Pan2.ar(sig, pan));
}).add
} ! 3
)
// to avoid feedback use with headphones
// start synth and event stream players
(
// pattern maker, we need the three different instruments
p = { |i| Pbind(
\instrument, \live_gran_2b ++ "_" ++ i.asString,
\inBus, ~audioBus_ex_2b,
\envBuf, e,
\dur, Pfunc { ~trigRate.reciprocal },
\grainDelta, Pkey(\dur),
\overlap, Pfunc { ~overlap },
\rq, Pfunc { ~rq },
\center, Pfunc { ~center },
\amp, Pfunc { ~amp },
\pan, Pfunc { ~panMax } * PLseq([1, -1]),
\addAction, \addToTail
) };
VarGui([
\trigRate, [20, 500, \lin, 0, 100],
\overlap, [0.1, 2, \lin, 0, 0.5],
\panMax, [0, 1, \lin, 0, 0.5],
\center, [100, 3000, \exp, 0, 800],
\rq, [0.1, 1, \lin, 0, 0.1],
\amp, [0, 1, \lin, 0, 0.1]
] ! 3,[
\out, ~audioBus_ex_2b.index,
// you might have to pass a different 'in' bus for SoundIn
// depending on your setup
\in, 0,
\ampIn, [0, 1, \lin, 0, 1]
], p ! 3, \soundIn
).gui
)
Example 2c: Live granulation with PbindFx
// extended ressources needed
(
s.options.numPrivateAudioBusChannels = 1024;
s.options.memSize = 8192 * 16;
s.reboot;
)
(
// input bus and trigger bus
// trigger bus must be hard-wired in SynthDef though
~audioBus_ex_2c = Bus.audio(s, 1);
~trigBus_ex_2c = Bus.audio(s, 1);
// pass envelopes with buffer
h = Signal.hanningWindow(1000);
e = Buffer.loadCollection(s, h);
SynthDef(\soundIn, { |out, in, ampIn = 1|
Out.ar(out, SoundIn.ar(in) * ampIn)
}).add;
// two fx SynthDefs
SynthDef(\resample, { |out = 0, in, mix = 0.5, amp = 1, resampleRate = 44100|
var sig, inSig = In.ar(in, 2);
sig = Latch.ar(inSig, Impulse.ar(resampleRate));
Out.ar(out, ((1 - mix) * inSig + (sig * mix)) * amp);
}).add;
SynthDef(\bpf, { |out = 0, in, freq = 440, rq = 0.1, amp = 1, mix = 1|
var sig, inSig = In.ar(in, 2);
sig = BPF.ar(inSig, freq, rq, (rq ** -1) * (400 / freq ** 0.5));
Out.ar(out, (mix * sig + ((1 - mix) * inSig)) * amp);
}).add;
SynthDef(\live_gran_2c, { |out, inBus, envBuf, grainDelta, pan = 0,
overlap = 1, amp = 1, minGrainDur = 0.001, interpolation = 4|
var inSig, offset, env, numFrames = BufFrames.ir(envBuf), gate,
defaultOffset = ControlRate.ir.reciprocal;
inSig = In.ar(inBus, 1);
// reduce feedback
inSig = LPF.ar(inSig.tanh, 2000);
// low overlap bound given by minimal grain duration
overlap = max(overlap, minGrainDur / grainDelta);
// impulse for offset check
OffsetOut.ar(~trigBus_ex_2c, Impulse.ar(0));
// additional gate to start with 0 in delayed case
// this is necessary as OffsetOut outputs impulse twice
gate = SetResetFF.ar(In.ar(~trigBus_ex_2c));
env = BufRd.ar(
1,
envBuf,
Sweep.ar(
In.ar(~trigBus_ex_2c),
(overlap * grainDelta).reciprocal * numFrames,
).clip(0, numFrames - 1),
interpolation: interpolation
);
// to finish synth
Line.ar(dur: overlap * grainDelta + defaultOffset, doneAction: 2);
// shifted accurate output
Out.ar(out, Pan2.ar(env * gate * inSig * amp, pan));
}).add
)
(
// start without fx
~fxOrder = 0;
// playing the PbindFx in a new group ensures that soundIn synth is placed before when started later
g = Group.new;
p = PbindFx([
\instrument, \live_gran_2c,
\inBus, ~audioBus_ex_2c,
// allow hard-wired bus (bus connections check)
\otherBusArgs, [\inBus, ~trigBus_ex_2c.index.asFloat],
\envBuf, e,
\dur, 1 / PL(\trigRate),
\grainDelta, Pkey(\dur),
\overlap, PL(\overlap),
\amp, PL(\amp),
\pan, PL(\panMax) * PLseq([1, -1]),
\fxOrder, PL(\fxOrder, envir: 't'),
\cleanupDelay, 0.1,
\group, g
],[
\fx, \resample,
\mix, 1,
\amp, PL(\amp_resample),
\resampleRate, PL(\resampleRate_resample),
\cleanupDelay, 0.01
],[
\fx, \bpf,
\freq, PL(\freq_bpf),
\rq, PL(\rq_bpf),
\mix, 1,
\amp, PL(\amp_bpf),
\cleanupDelay, 0.01
]
);
VarGui([
\trigRate, [20, 500, \lin, 0, 100],
\overlap, [0.1, 2, \lin, 0, 0.5],
\panMax, [0, 1, \lin, 0, 0.5],
\amp, [0, 1, \lin, 0, 0.1],
\resampleRate_resample, [200, 3000, \exp, 0, 500],
\amp_resample, [0, 1, \lin, 0, 1],
\freq_bpf, [50, 3000, \exp, 0, 200],
\rq_bpf, [0.1, 1, \lin, 0, 0.3],
\amp_bpf, [0, 1, \lin, 0, 1]
],[
\out, ~audioBus_ex_2c.index,
// you might have to pass a different 'in' bus for SoundIn
// depending on your setup
\in, 0,
\ampIn, [0, 1, \lin, 0, 1]
], p, \soundIn
).gui(
varColorGroups: (0..8).clumps([4, 2, 3]),
tryColumnNum: 1,
labelWidth: 140,
sliderWidth: 350
)
)
// check fxs and their params, mix is fixed to 1
// resample
~fxOrder = 1
// band pass
~fxOrder = 2
// resample -> band pass
~fxOrder = [1, 2]
// band pass -> resample
~fxOrder = [2, 1]
// fx alternations
// Pstutter with repeats equal 2 ensures L/R balance
~fxOrder = Pstutter(2, PLseq([1, 2]))
~fxOrder = Pstutter(2, PLseq([[1, 2], 1]))
~fxOrder = Pstutter(2, PLseq([[1, 2], 2]))
~fxOrder = Pstutter(2, PLseq([[2, 1], 1]))
~fxOrder = Pstutter(2, PLseq([[2, 1], 2]))
~fxOrder = Pstutter(2, PLseq([[1, 2], [2, 1]]))
Example 2d: Accuracy comparison
This example compares the inaccuracies occuring with "normal" usage of Out.ar and the usage of In.ar + OffsetOut.ar with the strategy recommended in Ex. 2a. Run the three examples, the audio output is played and recorded into three files in the recordings directory (you get the path with thisProcess.platform.recordingsDir).
// test with source of added sines, inaccuracies with straight use of Out.ar
(
~audioBus_ex_2d1 = Bus.audio(s, 1);
// pass envelopes with buffer
// envelope with sharp attack to make effect more clear
h = Env([0, 1, 1, 0], [1, 10, 1].normalizeSum, curve: \sine).discretize(1000);
e = Buffer.loadCollection(s, h);
// sine as test signal
SynthDef(\sineIn, { |directOut, granOut, freq = 200, amp = 0.03, in|
freq = freq * (1..8);
Out.ar(directOut, SinOsc.ar(freq).sum * amp * EnvGate.new);
Out.ar(granOut, SinOsc.ar(freq).sum * amp);
}).add;
// envelopes in left channel, granulation in right
SynthDef(\live_gran_shaky_1, { |out, inBus, envBuf, grainDelta,
overlap = 1, amp = 1, interpolation = 4|
var inSig, offset, env, numFrames = BufFrames.ir(envBuf),
defaultOffset = ControlRate.ir.reciprocal;
inSig = In.ar(inBus, 1);
// reduce feedback with sound in source
inSig = LPF.ar(inSig.tanh, 2000);
env = BufRd.ar(
1,
envBuf,
Sweep.ar(
1,
(overlap * grainDelta).reciprocal * numFrames,
).clip(0, numFrames - 1),
interpolation: interpolation
);
// to finish synth
Line.ar(dur: overlap * grainDelta + defaultOffset, doneAction: 2);
Out.ar(out + 1, inSig * env);
}).add;
p = Pfindur(1, Pbind(
\instrument, \live_gran_shaky_1,
\inBus, ~audioBus_ex_2d1,
\envBuf, e,
\dur, 0.005,
\grainDelta, Pkey(\dur),
\overlap, 0.5,
\amp, 0.1,
\addAction, \addToTail
));
)
(
// record live input and granulation (which comes with delay due to normal pbind latency)
// see the file in an editor then:
// distances are irregular due to Out ugen's accuracy limit control block boundary,
// though source signal is not delayed
{
~date = Date.getDate.stamp;
~fileName = thisProcess.platform.recordingsDir +/+
("live_gran_shaky_1" ++ "_" ++ ~date ++ ".aiff");
s.record(~fileName);
s.sync;
x = Synth(\sineIn, args: [directOut: 0, granOut: ~audioBus_ex_2d1]);
p.play;
1.5.wait;
x.release;
s.stopRecording;
}.fork
)
/////////////////////////
// test with source of added sines, inaccuracies with use of In.ar + OffsetOut.ar
(
~audioBus_ex_2d2 = Bus.audio(s, 1);
// pass envelopes with buffer
// envelope with sharp attack to make effect more clear
h = Env([0, 1, 1, 0], [1, 10, 1].normalizeSum, curve: \sine).discretize(1000);
e = Buffer.loadCollection(s, h);
// sine as test signal
SynthDef(\sineIn, { |directOut, granOut, freq = 200, amp = 0.03, in|
freq = freq * (1..8);
Out.ar(directOut, SinOsc.ar(freq).sum * amp * EnvGate.new);
Out.ar(granOut, SinOsc.ar(freq).sum * amp);
}).add;
// envelopes in left channel, granulation in right
SynthDef(\live_gran_shaky_2, { |out, inBus, envBuf, grainDelta,
overlap = 1, amp = 1, interpolation = 4|
var inSig, offset, env, numFrames = BufFrames.ir(envBuf),
defaultOffset = ControlRate.ir.reciprocal;
inSig = In.ar(inBus, 1);
// reduce feedback with sound in source
inSig = LPF.ar(inSig.tanh, 2000);
env = BufRd.ar(
1,
envBuf,
Sweep.ar(
1,
(overlap * grainDelta).reciprocal * numFrames,
).clip(0, numFrames - 1),
interpolation: interpolation
);
// to finish synth
Line.ar(dur: overlap * grainDelta + defaultOffset, doneAction: 2);
OffsetOut.ar(out + 1, inSig * env);
}).add;
p = Pfindur(1, Pbind(
\instrument, \live_gran_shaky_2,
\inBus, ~audioBus_ex_2d2,
\envBuf, e,
\dur, 0.005,
\grainDelta, Pkey(\dur),
\overlap, 0.5,
\amp, 0.1,
\addAction, \addToTail
));
)
// record live input and granulation (which comes with delay due to normal pbind latency)
// see the file in an editor then:
// distances are regular due to OffsetOut, but source signal is delayed
// between 0 and control block length
(
{
~date = Date.getDate.stamp;
~fileName = thisProcess.platform.recordingsDir +/+
("live_gran_shaky_2" ++ "_" ++ ~date ++ ".aiff");
s.record(~fileName);
s.sync;
x = Synth(\sineIn, args: [directOut: 0, granOut: ~audioBus_ex_2d2]);
p.play;
1.5.wait;
x.release;
s.stopRecording;
}.fork
)
/////////////////////////
// test with source of added sines, granulation done as in Ex. 2a
(
// input bus and trigger bus
// trigger bus must be hard-wired in SynthDef though
~audioBus_ex_2d3 = Bus.audio(s, 1);
~trigBus_ex_2d3 = Bus.audio(s, 1);
// pass envelopes with buffer
// envelope with sharp attack to make effect more clear
h = Env([0, 1, 1, 0], [1, 10, 1].normalizeSum, curve: \sine).discretize(1000);
e = Buffer.loadCollection(s, h);
// sine as test signal
SynthDef(\sineIn, { |directOut, granOut, freq = 200, amp = 0.03, in|
freq = freq * (1..8);
Out.ar(directOut, SinOsc.ar(freq).sum * amp * EnvGate.new);
Out.ar(granOut, SinOsc.ar(freq).sum * amp);
}).add;
// envelopes in left channel, granulation in right
SynthDef(\live_gran_ok, { |out, inBus, envBuf, grainDelta, gate,
overlap = 1, amp = 1, interpolation = 4|
var inSig, offset, env, numFrames = BufFrames.ir(envBuf),
defaultOffset = ControlRate.ir.reciprocal;
inSig = In.ar(inBus, 1);
// reduce feedback with sound in source
inSig = LPF.ar(inSig.tanh, 2000);
// impulse for offset check
OffsetOut.ar(~trigBus_ex_2d3, Impulse.ar(0));
// necessary additional gate to start with 0 in delayed case
gate = SetResetFF.ar(In.ar(~trigBus_ex_2d3));
env = BufRd.ar(
1,
envBuf,
Sweep.ar(
In.ar(~trigBus_ex_2d3),
(overlap * grainDelta).reciprocal * numFrames,
).clip(0, numFrames - 1),
interpolation: interpolation
);
// to finish synth
Line.ar(dur: overlap * grainDelta + defaultOffset, doneAction: 2);
// shifted accurate output
Out.ar(out + 1, inSig * env * gate);
}).add;
p = Pfindur(1, Pbind(
\instrument, \live_gran_ok,
\inBus, ~audioBus_ex_2d3,
\envBuf, e,
\dur, 0.005,
\grainDelta, Pkey(\dur),
\overlap, 0.5,
\amp, 0.1,
\addAction, \addToTail
));
)
(
// record live input and granulation (which comes with delay due to normal pbind latency)
// see the file in an editor then:
// distances are regular due to OffsetOut, and source signal is not delayed
// However also in this case, because of hardware output calibration, it happens that phase shifts
// occur in granulation.
// This is currently unavoidable in SC's realtime mode, but probably irrelevant in most cases of
// live granulation, where there is no strictly periodic input signal
{
~date = Date.getDate.stamp;
~fileName = thisProcess.platform.recordingsDir +/+
("\live_gran_ok" ++ "_" ++ ~date ++ ".aiff");
s.record(~fileName);
s.sync;
x = Synth(\sineIn, args: [directOut: 0, granOut: ~audioBus_ex_2d3]);
p.play;
1.5.wait;
x.release;
s.stopRecording;
}.fork
)