Signal Processing in SuperCollider
1. A Tour of available Unit Generators.
SuperCollider has over 250 unit generators.
If you count the unary and binary operators, there are over 300.
This tour covers many, but not all of them.
categories of unit generators:
sources: periodic, aperiodic
filters
distortion
panning
reverbs
delays and buffer ugens
granular synthesis
control: envelopes, triggers, counters, gates, lags, decays
spectral
2. Techniques
broadening a sound:
decorrelation, beat frequencies, delays.
series and parallel structures.
Note: The "Help browser" (Help.gui) contains a category for UGens, which provides another useful way to get an overview of the available UGens, including those which were created since this tour was written.
(
s = Server.internal;
Server.default = s;
s.boot;
)
Periodic Sources: Oscillators.
LF - "Low Frequency" Unit Generators.
LFPar, LFCub, LFTri, Impulse, LFSaw, LFPulse, VarSaw, SyncSaw
geometric waveforms, not band limited.
will cause aliasing at higher frequencies.
LFPar, LFCub, LFTri, LFSaw, Impulse
arguments: frequency, phase, mul, add
// parabolic approximation of sine
{ LFPar.ar(LFPar.kr(LFPar.kr(0.2,0,8,10),0, 400,800),0,0.1) }.scope(1, zoom: 4);
{ LFPar.ar(LFPar.kr(0.2, 0, 400,800),0,0.1) }.scope(1, zoom: 4);
{ LFPar.ar(800,0,0.1) }.scope(1, zoom: 4);
// since it is not band limited, there are aliasing artifacts
{ LFPar.ar(XLine.kr(100,15000,6),0,0.1) }.scope(1, zoom: 4);
// cubic approximation of sine
{ LFCub.ar(LFCub.kr(LFCub.kr(0.2,0,8,10),0, 400,800),0,0.1) }.scope(1, zoom: 4);
{ LFCub.ar(LFCub.kr(0.2, 0, 400,800),0,0.1) }.scope(1, zoom: 4);
{ LFCub.ar(800,0,0.1) }.scope(1, zoom: 4);
{ LFCub.ar(XLine.kr(100,15000,6),0,0.1) }.scope(1, zoom: 4);
{ LFTri.ar(LFTri.kr(LFTri.kr(0.2,0,8,10),0, 400,800),0,0.1) }.scope(1, zoom: 4);
{ LFTri.ar(LFTri.kr(0.2, 0, 400,800),0,0.1) }.scope(1, zoom: 4);
{ LFTri.ar(800,0,0.1) }.scope(1, zoom: 4);
{ LFTri.ar(XLine.kr(100,15000,6),0,0.1) }.scope(1, zoom: 4);
{ LFSaw.ar(LFSaw.kr(LFSaw.kr(0.2,0,8,10),0, 400,800),0,0.1) }.scope(1, zoom: 4);
{ LFSaw.ar(LFSaw.kr(0.2, 0, 400,800),0,0.1) }.scope(1, zoom: 4);
{ LFSaw.ar(100,0,0.1) }.scope(1, zoom: 4);
{ LFSaw.ar(XLine.kr(100,15000,6),0,0.1) }.scope(1, zoom: 4);
{ Impulse.ar(LFTri.kr(LFTri.kr(0.2,0,8,10),0, 400,800),0,0.1) }.scope(1, zoom: 4);
{ Impulse.ar(LFTri.kr(0.2, 0, 400,800),0,0.1) }.scope(1, zoom: 4);
{ Impulse.ar(100,0,0.1) }.scope(1, zoom: 4);
{ Impulse.ar(XLine.kr(100,15000,6),0,0.1) }.scope(1, zoom: 4);
arguments: frequency, phase, width, mul, add
{ LFPulse.ar(LFPulse.kr(LFPulse.kr(0.2,0,0.5,8,10),0,0.5, 400,800),0,0.5,0.1) }.scope(1, zoom: 4);
{ LFPulse.ar(LFPulse.kr(3, 0, 0.3, 200, 200), 0, 0.2, 0.1) }.scope(1, zoom: 4);
{ LFPulse.ar(XLine.kr(100,15000,6),0,0.5,0.1) }.scope(1, zoom: 4);
// pulse width modulation
{ LFPulse.ar(100,0,MouseY.kr(0,1),0.1) }.scope(1, zoom: 4);
{ LFPulse.ar(100,0,LFTri.kr(0.2,0,0.5,0.5),0.1) }.scope(1, zoom: 4);
{ VarSaw.ar(VarSaw.kr(VarSaw.kr(0.2,0,0.2,8,10),0,0.2, 400,800),0,0.2,0.1) }.scope(1, zoom: 4);
{ VarSaw.ar(VarSaw.kr(0.2, 0, 0.2, 400,800),0,0.2,0.1) }.scope(1, zoom: 4);
{ VarSaw.ar(XLine.kr(100,15000,6),0,0.2,0.1) }.scope(1, zoom: 4);
// pulse width modulation
{ VarSaw.ar(100,0,MouseY.kr(0,1),0.1) }.scope(1, zoom: 4);
{ VarSaw.ar(100,0,LFTri.kr(0.2,0,0.5,0.5),0.1) }.scope(1, zoom: 4);
arguments: syncFreq, sawFreq, mul, add
{ SyncSaw.ar(100, MouseX.kr(100, 1000), 0.1) }.scope(1, zoom: 4);
{ SyncSaw.ar(100, Line.kr(100, 800, 12), 0.1) }.scope(1, zoom: 4);
Band Limited Oscillators
SinOsc, FSinOsc, Blip, Saw, Pulse
will not alias.
arguments: frequency, phase, mul, add
{ SinOsc.ar(SinOsc.kr(SinOsc.kr(0.2,0,8,10),0, 400,800),0,0.1) }.scope(1, zoom: 4);
{ SinOsc.ar(SinOsc.kr(0.2, 0, 400,800),0,0.1) }.scope(1, zoom: 4);
{ SinOsc.ar(800,0,0.1) }.scope(1, zoom: 4);
{ SinOsc.ar(XLine.kr(100,15000,6),0,0.1) }.scope(1, zoom: 4);
{ FSinOsc.ar(800,0,0.1) }.scope(1, zoom: 4);
// FSinOsc should not be frequency modulated.
// Since it is based on a filter at the edge of stability, it will blow up:
{ FSinOsc.ar(FSinOsc.kr(FSinOsc.kr(0.2,0,8,10),0, 400,800),0,0.1) }.scope(1, zoom: 4);
arguments: frequency, numHarmonics, mul, add
{ Blip.ar(XLine.kr(20000,200,6),100,0.2) }.scope(1);
{ Blip.ar(XLine.kr(100,15000,6),100,0.2) }.scope(1); // no aliasing
// modulate number of harmonics
{ Blip.ar(200,Line.kr(1,100,20),0.2) }.scope(1);
arguments: frequency, mul, add
{ Saw.ar(XLine.kr(20000,200,6),0.2) }.scope(1);
{ Saw.ar(XLine.kr(100,15000,6),0.2) }.scope(1); // no aliasing
arguments: frequency, width, mul, add
{ Pulse.ar(XLine.kr(20000,200,6),0.3,0.2) }.scope(1);
{ Pulse.ar(XLine.kr(100,15000,6),0.3,0.2) }.scope(1); // no aliasing
// modulate pulse width
{ Pulse.ar(200, Line.kr(0.01,0.99,8), 0.2) }.scope(1);
// two band limited square waves thru a resonant low pass filter
{ RLPF.ar(Pulse.ar([100,250],0.5,0.1), XLine.kr(8000,400,5), 0.05) }.scope(1);
Klang - sine oscillator bank
arguments: `[ frequencies, amplitudes, phases ], mul, add
{ Klang.ar(`[ [800, 1000, 1200],[0.3, 0.3, 0.3],[pi,pi,pi]], 1, 0) * 0.4}.scope(1);
{ Klang.ar(`[ {exprand(400, 2000)}.dup(16), nil, nil ], 1, 0) * 0.04 }.scope(1);
Table Oscillators
Osc, COsc, VOsc, VOsc3
Use a buffer allocated on the server.
arguments: buffer number, frequency, phase, mul, add.
(
b = Buffer.alloc(s, 2048, 1, bufnum: 80);
b.sine1(1.0/(1..6), true, true, true);
)
{ Osc.ar(80, 100, 0, 0.1) }.scope(1, zoom:4);
b.sine1(1.0/(1..12));
b.sine1(1.0/(1..24));
b.sine1(1.0/(1..32));
b.sine1([1.0/(1,3..12), 0].flop.flat.postln);
b.sine1([1.0/(1,3..32).squared, 0].flop.flat.postln);
b.sine1((1.dup(4) ++ 0.dup(8)).scramble.postln);
b.sine1((1.dup(4) ++ 0.dup(8)).scramble.postln);
b.sine1((1.dup(4) ++ 0.dup(8)).scramble.postln);
b.sine1((1.dup(4) ++ 0.dup(8)).scramble.postln);
b.sine1({1.0.rand2.cubed}.dup(8).round(1e-3).postln);
b.sine1({1.0.rand2.cubed}.dup(12).round(1e-3).postln);
b.sine1({1.0.rand2.cubed}.dup(16).round(1e-3).postln);
b.sine1({1.0.rand2.cubed}.dup(24).round(1e-3).postln);
COsc - two oscillators, detuned
arguments: buffer number, frequency, beat frequency, mul, add.
b.sine1(1.0/(1..6), true, true, true);
{ COsc.ar(80, 100, 1, 0.1) }.scope(1, zoom:4);
// change buffer as above.
VOsc - multiple wave table crossfade oscillators
arguments: buffer number, frequency, phase, mul, add.
(
// allocate tables 80 to 87
8.do {|i| s.sendMsg(\b_alloc, 80+i, 1024); };
)
(
// fill tables 80 to 87
8.do({|i|
var n, a;
// generate array of harmonic amplitudes
n = (i+1)**2; // num harmonics for each table: [1,4,9,16,25,36,49,64]
a = {|j| ((n-j)/n).squared }.dup(n);
// fill table
s.listSendMsg([\b_gen, 80+i, \sine1, 7] ++ a);
});
)
{ VOsc.ar(MouseX.kr(80,87), 120, 0, 0.3) }.scope(1, zoom:4);
(
// allocate and fill tables 80 to 87
8.do({|i|
// generate array of harmonic amplitudes
a = {1.0.rand2.cubed }.dup((i+1)*4);
// fill table
s.listSendMsg([\b_gen, 80+i, \sine1, 7] ++ a);
});
)
VOsc3 - three VOscs summed.
arguments: buffer number, freq1, freq2, freq3, beat frequency, mul, add.
// chorusing
{ VOsc3.ar(MouseX.kr(80,87), 120, 121.04, 119.37, 0.2) }.scope(1, zoom:4);
// chords
{ VOsc3.ar(MouseX.kr(80,87), 120, 151.13, 179.42, 0.2) }.scope(1, zoom:4);
Aperiodic Sources: Noise.
LF "Low Frequency" Noise Generators.
LFNoise0, LFNoise1, LFNoise2, LFClipNoise
arguments: frequency, mul, add
{ LFClipNoise.ar(MouseX.kr(200, 10000, 1), 0.125) }.scope(1);
{ LFNoise0.ar(MouseX.kr(200, 10000, 1), 0.25) }.scope(1);
{ LFNoise1.ar(MouseX.kr(200, 10000, 1), 0.25) }.scope(1);
{ LFNoise2.ar(MouseX.kr(200, 10000, 1), 0.25) }.scope(1);
// used as controls
{ LFPar.ar(LFClipNoise.kr(MouseX.kr(0.5, 64, 1), 200, 400), 0, 0.2) }.scope(1, zoom:8);
{ LFPar.ar(LFNoise0.kr(MouseX.kr(0.5, 64, 1), 200, 400), 0, 0.2) }.scope(1, zoom:8);
{ LFPar.ar(LFNoise1.kr(MouseX.kr(0.5, 64, 1), 200, 400), 0, 0.2) }.scope(1, zoom:8);
{ LFPar.ar(LFNoise2.kr(MouseX.kr(0.5, 64, 1), 200, 400), 0, 0.2) }.scope(1, zoom:8);
Broad Spectrum Noise Generators
ClipNoise, WhiteNoise, PinkNoise, BrownNoise, GrayNoise
arguments: mul, add
{ ClipNoise.ar(0.2) }.scope(1);
{ WhiteNoise.ar(0.2) }.scope(1);
{ PinkNoise.ar(0.4) }.scope(1);
{ BrownNoise.ar(0.2) }.scope(1);
{ GrayNoise.ar(0.2) }.scope(1);
Impulse Noise Generators
arguments: density, mul, add
{ Dust.ar(MouseX.kr(1,10000,1), 0.4) }.scope(1, zoom:4);
{ Dust2.ar(MouseX.kr(1,10000,1), 0.4) }.scope(1, zoom:4);
Chaotic Noise Generators
arguments: chaosParam, mul, add
{ Crackle.ar(MouseX.kr(1,2), 0.5) }.scope(1);
Filters
Low Pass, High Pass
arguments: in, freq, mul, add
{ LPF.ar(WhiteNoise.ar, MouseX.kr(1e2,2e4,1), 0.2) }.scope(1);
{ HPF.ar(WhiteNoise.ar, MouseX.kr(1e2,2e4,1), 0.2) }.scope(1);
{ LPF.ar(Saw.ar(100), MouseX.kr(1e2,2e4,1), 0.2) }.scope(1);
{ HPF.ar(Saw.ar(100), MouseX.kr(1e2,2e4,1), 0.2) }.scope(1);
Band Pass, Band Cut
arguments: in, freq, rq, mul, add
rq is the reciprocal of the Q of the filter,
or in other words: the bandwidth in Hertz = rq * freq.
{ BPF.ar(WhiteNoise.ar, MouseX.kr(1e2,2e4,1), 0.4, 0.4) }.scope(1);
{ BRF.ar(WhiteNoise.ar, MouseX.kr(1e2,2e4,1), 0.4, 0.2) }.scope(1);
{ BPF.ar(Saw.ar(100), MouseX.kr(1e2,2e4,1), 0.4, 0.4) }.scope(1);
{ BRF.ar(Saw.ar(100), MouseX.kr(1e2,2e4,1), 0.4, 0.2) }.scope(1);
// modulating the bandwidth
{ BPF.ar(WhiteNoise.ar, 3000, MouseX.kr(0.01,0.7,1), 0.4) }.scope(1);
Resonant Low Pass, High Pass, Band Pass
arguments: in, freq, rq, mul, add
{ RLPF.ar(WhiteNoise.ar, MouseX.kr(1e2,2e4,1), 0.2, 0.2) }.scope(1);
{ RHPF.ar(WhiteNoise.ar, MouseX.kr(1e2,2e4,1), 0.2, 0.2) }.scope(1);
{ RLPF.ar(Saw.ar(100), MouseX.kr(1e2,2e4,1), 0.2, 0.2) }.scope(1);
{ RHPF.ar(Saw.ar(100), MouseX.kr(1e2,2e4,1), 0.2, 0.2) }.scope(1);
Resonz - resonant band pass filter with uniform amplitude
arguments: in, freq, rq, mul, add
// modulate frequency
{ Resonz.ar(WhiteNoise.ar(0.5), XLine.kr(1000,8000,10), 0.05) }.scope(1);
// modulate bandwidth
{ Resonz.ar(WhiteNoise.ar(0.5), 2000, XLine.kr(1, 0.001, 8)) }.scope(1);
// modulate bandwidth opposite direction
{ Resonz.ar(WhiteNoise.ar(0.5), 2000, XLine.kr(0.001, 1, 8)) }.scope(1);
Ringz - ringing filter.
Internally it is the same as Resonz but the bandwidth is expressed as a ring time.
arguments: in, frequency, ring time, mul, add
{ Ringz.ar(Dust.ar(3, 0.3), 2000, 2) }.scope(1, zoom:4);
{ Ringz.ar(WhiteNoise.ar(0.005), 2000, 0.5) }.scope(1);
// modulate frequency
{ Ringz.ar(WhiteNoise.ar(0.005), XLine.kr(100,3000,10), 0.5) }.scope(1, zoom:4);
{ Ringz.ar(Impulse.ar(6, 0, 0.3), XLine.kr(100,3000,10), 0.5) }.scope(1, zoom:4);
// modulate ring time
{ Ringz.ar(Impulse.ar(6, 0, 0.3), 2000, XLine.kr(0.04, 4, 8)) }.scope(1, zoom:4);
Simpler Filters
6 dB / octave
{ OnePole.ar(WhiteNoise.ar(0.5), MouseX.kr(-0.99, 0.99)) }.scope(1);
{ OneZero.ar(WhiteNoise.ar(0.5), MouseX.kr(-0.49, 0.49)) }.scope(1);
NonLinear Filters
// a signal with impulse noise.
{ Saw.ar(500, 0.1) + Dust2.ar(100, 0.9) }.scope(1);
// after applying median filter
{ Median.ar(3, Saw.ar(500, 0.1) + Dust2.ar(100, 0.9)) }.scope(1);
// a signal with impulse noise.
{ Saw.ar(500, 0.1) + Dust2.ar(100, 0.9) }.scope(1);
// after applying slew rate limiter
{ Slew.ar(Saw.ar(500, 0.1) + Dust2.ar(100, 0.9),1000,1000) }.scope(1);
Formant Filter
Formlet - A filter whose impulse response is similar to a FOF grain.
{ Formlet.ar(Impulse.ar(MouseX.kr(2,300,1), 0, 0.4), 800, 0.01, 0.1) }.scope(1, zoom:4);
Klank - resonant filter bank
arguments: `[ frequencies, amplitudes, ring times ], mul, add
{ Klank.ar(`[[200, 671, 1153, 1723], nil, [1, 1, 1, 1]], Impulse.ar(2, 0, 0.1)) }.play;
{ Klank.ar(`[[200, 671, 1153, 1723], nil, [1, 1, 1, 1]], Dust.ar(8, 0.1)) }.play;
{ Klank.ar(`[[200, 671, 1153, 1723], nil, [1, 1, 1, 1]], PinkNoise.ar(0.007)) }.play;
{ Klank.ar(`[ {exprand(200, 4000)}.dup(12), nil, nil ], PinkNoise.ar(0.007)) }.scope(1);
{ Klank.ar(`[ (1..13)*200, 1/(1..13), nil ], PinkNoise.ar(0.01)) }.scope(1);
{ Klank.ar(`[ (1,3..13)*200, 1/(1,3..13), nil ], PinkNoise.ar(0.01)) }.scope(1);
Distortion
abs, max, squared, cubed
{ SinOsc.ar(300, 0, 0.2) }.scope(1);
{ SinOsc.ar(300, 0, 0.2).abs }.scope(1);
{ SinOsc.ar(300, 0, 0.2).max(0) }.scope(1);
{ SinOsc.ar(300, 0).squared * 0.2 }.scope(1);
{ SinOsc.ar(300, 0).cubed * 0.2 }.scope(1);
distort, softclip, clip2, fold2, wrap2,
{ SinOsc.ar(300, 0, MouseX.kr(0.1,80,1)).distort * 0.2 }.scope(1);
{ SinOsc.ar(300, 0, MouseX.kr(0.1,80,1)).softclip * 0.2 }.scope(1);
{ SinOsc.ar(300, 0, MouseX.kr(0.1,80,1)).clip2(1) * 0.2 }.scope(1);
{ SinOsc.ar(300, 0, MouseX.kr(0.1,80,1)).fold2(1) * 0.2 }.scope(1);
{ SinOsc.ar(300, 0, MouseX.kr(0.1,80,1)).wrap2(1) * 0.2 }.scope(1);
{ SinOsc.ar(300, 0, MouseX.kr(0.1,80,1)).wrap2(1) * 0.2 }.scope(1);
scaleneg
{ SinOsc.ar(200, 0, 0.2).scaleneg(MouseX.kr(-1,1)) }.scope(1);
waveshaping by phase modulating a 0 Hz sine oscillator
(currently there is a limit of 8pi)
(
{
var in;
in = SinOsc.ar(300, 0, MouseX.kr(0.1,8pi,1));
SinOsc.ar(0, in, 0.2); // 0 Hz sine oscillator
}.scope(1);
)
input is used to look up a value in a table.
Chebyshev polynomials are typically used to fill the table.
s.sendMsg(\b_alloc, 80, 1024); // allocate table
// fill with chebyshevs
s.listSendMsg([\b_gen, 80, \cheby, 7] ++ {1.0.rand2.squared}.dup(6));
{ Shaper.ar(80, SinOsc.ar(600, 0, MouseX.kr(0,1))) * 0.3; }.scope(1);
s.listSendMsg([\b_gen, 80, \cheby, 7] ++ {1.0.rand2.squared}.dup(6));
s.listSendMsg([\b_gen, 80, \cheby, 7] ++ {1.0.rand2.squared}.dup(6));
Panning
(
s = Server.internal;
Server.default = s;
s.quit;
s.options.numOutputBusChannels = 8;
s.options.numInputBusChannels = 8;
s.boot;
)
Pan2 - equal power stereo pan a mono source
arguments: in, pan position, level
pan controls typically range from -1 to +1
{ Pan2.ar(BrownNoise.ar, MouseX.kr(-1,1), 0.3) }.scope(2);
{ Pan2.ar(BrownNoise.ar, SinOsc.kr(0.2), 0.3) }.scope(2);
LinPan2 - linear pan a mono source (not equal power)
arguments: in, pan position, level
{ LinPan2.ar(BrownNoise.ar, MouseX.kr(-1,1), 0.3) }.scope(2);
{ LinPan2.ar(BrownNoise.ar, SinOsc.kr(0.2), 0.3) }.scope(2);
Balance2 - balance a stereo source
arguments: left in, right in, pan position, level
{ Balance2.ar(BrownNoise.ar, BrownNoise.ar, MouseX.kr(-1,1), 0.3) }.scope(2);
Pan4 - equal power quad panner
{ Pan4.ar(BrownNoise.ar, MouseX.kr(-1,1), MouseY.kr(1,-1), 0.3) }.scope(4);
PanAz - azimuth panner to any number of channels
arguments: num channels, in, pan position, level, width
{ PanAz.ar(5, BrownNoise.ar, MouseX.kr(-1,1), 0.3, 2) }.scope(5);
// change width to 3
{ PanAz.ar(5, BrownNoise.ar, MouseX.kr(-1,1), 0.3, 3) }.scope(5);
XFade2 - equal power cross fade between two inputs
arguments: in1, in2, crossfade, level
{ XFade2.ar(BrownNoise.ar, SinOsc.ar(500), MouseX.kr(-1,1), 0.3) }.scope(1);
PanB2 and DecodeB2 - 2D ambisonics panner and decoder
(
{
var w, x, y, p, lf, rf, rr, lr;
p = BrownNoise.ar; // source
// B-format encode
#w, x, y = PanB2.ar(p, MouseX.kr(-1,1), 0.3);
// B-format decode to quad. outputs in clockwise order
#lf, rf, rr, lr = DecodeB2.ar(4, w, x, y);
[lf, rf, lr, rr] // reorder to my speaker arrangement: Lf Rf Lr Rr
}.scope(4);
)
Rotate2 - rotate a sound field of ambisonic or even stereo sound.
(
{
// rotation of stereo sound via mouse
var x, y;
x = Mix.fill(4, { LFSaw.ar(200 + 2.0.rand2, 0, 0.1) }); // left in
y = WhiteNoise.ar * LFPulse.kr(3,0,0.7,0.2); // right in
#x, y = Rotate2.ar(x, y, MouseX.kr(0,2));
[x,y]
}.scope(2);
)
Reverbs
(
{
// play with the room size
var x;
x = Klank.ar(`[[200, 671, 1153, 1723], nil, [1, 1, 1, 1]], Dust.ar(2, 0.1));
x = Pan2.ar(x, -0.2);
x = [x[0], DelayC.ar(x[1], 0.01, 0.01)]; // de-correlate
FreeVerb.ar(x, 0.75, 0.9, 0.4);
}.scope;
)
(
{
// play with the room size
var x;
x = Klank.ar(`[[200, 671, 1153, 1723], nil, [1, 1, 1, 1]], Dust.ar(2, 0.1));
GVerb.ar(x, 105, 5, 0.7, 0.8, 60, 0.1, 0.5, 0.4) + x;
}.scope;
)
Delays and Buffer UGens
simple delays
N - no interpolation
L - linear interpolation
C - cubic interpolation
arguments: in, maximum delay time, current delay time, mul, add
(
// Dust randomly triggers Decay to create an exponential
// decay envelope for the WhiteNoise input source
{
z = Decay.ar(Dust.ar(1,0.5), 0.3, WhiteNoise.ar);
DelayN.ar(z, 0.1, 0.1, 1, z); // input is mixed with delay via the add input
}.scope(1, zoom: 4)
)
(
{
z = Decay.ar(Impulse.ar(2,0,0.4), 0.3, WhiteNoise.ar);
DelayL.ar(z, 0.3, MouseX.kr(0,0.3), 1, z); // input is mixed with delay via the add input
}.scope(1, zoom: 4)
)
feedback delays
arguments: in, maximum delay time, current delay time, echo decay time, mul, add
// used as an echo.
{ CombN.ar(Decay.ar(Dust.ar(1,0.5), 0.2, WhiteNoise.ar), 0.2, 0.2, 3) }.scope(1, zoom:4);
// Comb used as a resonator. The resonant fundamental is equal to
// reciprocal of the delay time.
{ CombN.ar(WhiteNoise.ar(0.02), 0.01, XLine.kr(0.0001, 0.01, 20), 0.2) }.scope(1);
{ CombL.ar(WhiteNoise.ar(0.02), 0.01, XLine.kr(0.0001, 0.01, 20), 0.2) }.scope(1);
{ CombC.ar(WhiteNoise.ar(0.02), 0.01, XLine.kr(0.0001, 0.01, 20), 0.2) }.scope(1);
// with negative feedback:
{ CombN.ar(WhiteNoise.ar(0.02), 0.01, XLine.kr(0.0001, 0.01, 20), -0.2) }.scope(1);
{ CombL.ar(WhiteNoise.ar(0.02), 0.01, XLine.kr(0.0001, 0.01, 20), -0.2) }.scope(1);
{ CombC.ar(WhiteNoise.ar(0.02), 0.01, XLine.kr(0.0001, 0.01, 20), -0.2) }.scope(1);
{ CombC.ar(Decay.ar(Dust.ar(1,0.1), 0.2, WhiteNoise.ar), 1/100, 1/100, 3) }.play;
{ CombC.ar(Decay.ar(Dust.ar(1,0.1), 0.2, WhiteNoise.ar), 1/200, 1/200, 3) }.play;
{ CombC.ar(Decay.ar(Dust.ar(1,0.1), 0.2, WhiteNoise.ar), 1/300, 1/300, 3) }.play;
{ CombC.ar(Decay.ar(Dust.ar(1,0.1), 0.2, WhiteNoise.ar), 1/400, 1/400, 3) }.scope(1, zoom:4);
allpass delay
arguments: in, maximum delay time, current delay time, echo decay time, mul, add
(
{
var z;
z = Decay.ar(Dust.ar(1,0.5), 0.1, WhiteNoise.ar);
8.do { z = AllpassL.ar(z, 0.04, 0.04.rand, 2) };
z
}.scope(1);
)
buffer playback
arguments: numChannels, buffer number, rate, trigger, start pos, loop
// read sound
b = Buffer.read(s, "sounds/a11wlk01.wav");
{ SinOsc.ar(800 + (700 * PlayBuf.ar(1,b, BufRateScale.kr(b), loop:1)),0,0.3) }.scope(1);
// loop is true
{ PlayBuf.ar(1,b, BufRateScale.kr(b), loop:1) }.scope(1);
// trigger one shot on each pulse
(
{
var trig;
trig = Impulse.kr(2.0);
PlayBuf.ar(1,b,BufRateScale.kr(b),trig,0,0);
}.scope(1);
)
// trigger one shot on each pulse
(
{
var trig;
trig = Impulse.kr(XLine.kr(0.1,100,30));
PlayBuf.ar(1,b,BufRateScale.kr(b),trig,5000,0);
}.scope(1);
)
// mouse control of trigger rate and startpos
(
{
var trig;
trig = Impulse.kr(MouseY.kr(0.5,200,1));
PlayBuf.ar(1,b,BufRateScale.kr(b),trig,MouseX.kr(0,BufFrames.kr(b)),1)
}.scope(1);
)
// accelerating pitch
(
{
var rate;
rate = XLine.kr(0.1,100,60);
PlayBuf.ar(1, b, rate, 1.0,0.0, 1.0)
}.scope(1);
)
// sine wave control of playback rate. negative rate plays backwards
(
{
var rate;
rate = FSinOsc.kr(XLine.kr(0.2,8,30), 0, 3, 0.6);
PlayBuf.ar(1,b,BufRateScale.kr(b)*rate,1,0,1)
}.scope(1);
)
// zig zag around sound
(
{
var rate;
rate = LFNoise2.kr(XLine.kr(1,20,60), 2);
PlayBuf.ar(1,b,BufRateScale.kr(b) * rate,1,0,1)
}.scope(1);
)
// free sound
b.free;
Granular Synthesis.
granulation of a buffer
arguments: numChannels, trigger, buffer number, rate, center pos, dur, pan, amp, interpolation
// read sound
b = Buffer.read(s, "sounds/a11wlk01.wav");
(
{
var trate, dur;
trate = MouseY.kr(2,200,1);
dur = 4 / trate;
TGrains.ar(2, Impulse.ar(trate), b, 1, MouseX.kr(0,BufDur.kr(b)), dur, 0, 0.1, 2);
}.scope(2, zoom: 4);
)
(
{
var trate, dur, clk, pos, pan;
trate = MouseY.kr(8,120,1);
dur = 12 / trate;
clk = Impulse.kr(trate);
pos = MouseX.kr(0,BufDur.kr(b)) + TRand.kr(0, 0.01, clk);
pan = WhiteNoise.kr(0.6);
TGrains.ar(2, clk, b, 1, pos, dur, pan, 0.1);
}.scope(2, zoom: 4);
)
// 4 channels
(
{
var trate, dur, clk, pos, pan;
trate = MouseY.kr(8,120,1);
dur = 12 / trate;
clk = Impulse.kr(trate);
pos = MouseX.kr(0,BufDur.kr(b)) + TRand.kr(0, 0.01, clk);
pan = WhiteNoise.kr(0.6);
TGrains.ar(4, clk, b, 1, pos, dur, pan, 0.1);
}.scope(4, zoom: 4);
)
(
{
var trate, dur, clk, pos, pan;
trate = MouseY.kr(8,120,1);
dur = 4 / trate;
clk = Dust.kr(trate);
pos = MouseX.kr(0,BufDur.kr(b)) + TRand.kr(0, 0.01, clk);
pan = WhiteNoise.kr(0.6);
TGrains.ar(2, clk, b, 1, pos, dur, pan, 0.1);
}.scope(2, zoom: 4);
)
(
{
var trate, dur, clk, pos, pan;
trate = LinExp.kr(LFTri.kr(MouseY.kr(0.1,2,1)),-1,1,8,120);
dur = 12 / trate;
clk = Impulse.ar(trate);
pos = MouseX.kr(0,BufDur.kr(b));
pan = WhiteNoise.kr(0.6);
TGrains.ar(2, clk, b, 1, pos, dur, pan, 0.1);
}.scope(2, zoom: 4);
)
(
{
var trate, dur, clk, pos, pan;
trate = 12;
dur = MouseY.kr(0.2,24,1) / trate;
clk = Impulse.kr(trate);
pos = MouseX.kr(0,BufDur.kr(b)) + TRand.kr(0, 0.01, clk);
pan = WhiteNoise.kr(0.6);
TGrains.ar(2, clk, b, 1, pos, dur, pan, 0.1);
}.scope(2, zoom: 4);
)
(
{
var trate, dur, clk, pos, pan;
trate = 100;
dur = 8 / trate;
clk = Impulse.kr(trate);
pos = Integrator.kr(BrownNoise.kr(0.001));
pan = WhiteNoise.kr(0.6);
TGrains.ar(2, clk, b, 1, pos, dur, pan, 0.1);
}.scope(2, zoom: 4);
)
(
{
var trate, dur, clk, pos, pan;
trate = MouseY.kr(1,400,1);
dur = 8 / trate;
clk = Impulse.kr(trate);
pos = MouseX.kr(0,BufDur.kr(b));
pan = WhiteNoise.kr(0.8);
TGrains.ar(2, clk, b, 2 ** WhiteNoise.kr(2), pos, dur, pan, 0.1);
}.scope(2, zoom: 4);
)
(
{
var trate, dur;
trate = MouseY.kr(2,120,1);
dur = 1.2 / trate;
TGrains.ar(2, Impulse.ar(trate), b, (1.2 ** WhiteNoise.kr(3).round(1)), MouseX.kr(0,BufDur.kr(b)), dur, WhiteNoise.kr(0.6), 0.1);
}.scope(2, zoom: 4);
)
// free sound
b.free;
arguments: numChannels, trigger, dur, freq, pan, envbufnum
( // using default window
{
var trigrate, winsize, trig;
trigrate = MouseX.kr(2, 120);
winsize = trigrate.reciprocal;
trig = Impulse.ar(trigrate);
GrainSin.ar(2, trig, winsize, TRand.ar(440.0, 880.0, trig), LFNoise1.kr(0.2),
-1, 0.2)
}.scope(2, zoom: 4);
)
b = Buffer.sendCollection(s, Env([0, 1, 0], [0.5, 0.5], [8, -8]).discretize, 1);
( // using user supplied window
{
var trigrate, winsize, trig;
trigrate = MouseX.kr(2, 120);
winsize = trigrate.reciprocal;
trig = Impulse.ar(trigrate);
GrainSin.ar(2, trig, winsize, TRand.ar(440.0, 880.0, trig), LFNoise1.kr(0.2),
b, 0.2)
}.scope(2, zoom: 4);
)
see also GrainFM, GrainBuf and GrainIn
Control
Filters for Controls
triggered exponential decay
arguments: in, decay time, mul, add
{ WhiteNoise.ar * Decay.ar(Impulse.ar(1), 0.9, 0.2) }.scope(1, zoom:4);
{ WhiteNoise.ar * Decay.ar(Dust.ar(3), 0.9, 0.2) }.scope(1, zoom:4);
{ SinOsc.ar(Decay.ar(Dust.ar(4), 0.5, 1000, 400), 0, 0.2) }.scope(1, zoom:4);
triggered exponential attack and exponential decay
arguments: trigger, attack time, decay time, mul, add
{ WhiteNoise.ar * Decay2.ar(Impulse.ar(1), 0.2, 0.9, 0.2) }.scope(1, zoom:4);
{ WhiteNoise.ar * Decay2.ar(Dust.ar(3), 0.2, 0.9, 0.2) }.scope(1, zoom:4);
arguments: trigger, duration
{ SinOsc.ar(Lag.ar(LFPulse.ar(2,0,0.5,800,400), MouseX.kr(0,0.5)), 0, 0.2) }.scope(1, zoom:4);
leaky integrator
{ SinOsc.ar(Integrator.ar(Dust2.ar(8), 0.99999, 200, 800), 0, 0.2) }.scope(1)
Triggers
timed duration gate
arguments: trigger, duration
// amplitude determined by amplitude of trigger
{ Trig.ar(Dust.ar(2), 0.2) * FSinOsc.ar(800, 0, 0.4) }.scope(1, zoom:4);
// amplitude always the same.
{ Trig1.ar(Dust.ar(2), 0.2) * FSinOsc.ar(800, 0, 0.4) }.scope(1, zoom:4)
delays a trigger. only delays one pending trigger at a time.
arguments: trigger, delay time
(
{
var trig;
trig = Dust.ar(2);
[(Trig1.ar(trig, 0.05) * FSinOsc.ar(600, 0, 0.2)),
(Trig1.ar(TDelay.ar(trig, 0.1), 0.05) * FSinOsc.ar(800, 0, 0.2))]
}.scope(2, zoom:4);
)
sample and hold
arguments: in, trigger
{ Blip.ar(Latch.ar(WhiteNoise.ar, Impulse.ar(9)) * 400 + 500, 4, 0.2) }.play;
{ Blip.ar(Latch.ar(SinOsc.ar(0.3), Impulse.ar(9)) * 400 + 500, 4, 0.2) }.play;
pass or hold
arguments: in, trigger
{ Blip.ar(Gate.ar(LFNoise2.ar(40), LFPulse.ar(1)) * 400 + 500, 4, 0.2) }.scope(1, zoom:4);
count triggers
arguments: trigger, reset
(
{
SinOsc.ar(
PulseCount.ar(Impulse.ar(10), Impulse.ar(0.4)) * 200,
0, 0.05
)
}.scope(2, zoom:4);
)
arguments: trigger, div, start
(
{
var p, a, b;
p = Impulse.ar(8);
a = SinOsc.ar(1200, 0, Decay2.ar(p, 0.005, 0.1));
b = SinOsc.ar(600, 0, Decay2.ar(PulseDivider.ar(p, MouseX.kr(1,8).round(1)), 0.005, 0.5));
[a, b] * 0.4
}.scope(2, zoom:4);
)
envelope generator
envelope is specified using an instance of the Env class.
{ EnvGen.kr(Env.perc, doneAction:2) * SinOsc.ar(880,0,0.2) }.play;
{ EnvGen.kr(Env.perc(1,0.005,1,4), doneAction:2) * SinOsc.ar(880,0,0.2) }.play;
{ EnvGen.kr(Env.perc, Impulse.kr(2)) * SinOsc.ar(880,0,0.2) }.play;
{ EnvGen.kr(Env.perc, Dust.kr(3)) * SinOsc.ar(880,0,0.2) }.play;
// for sustain envelopes a gate is required
z = { arg gate=1; EnvGen.kr(Env.adsr, gate, doneAction:2) * SinOsc.ar(880,0,0.2) }.play;
z.release;
(
// randomly generated envelope
z = { arg gate=1;
var env, n=32;
env = Env(
[0]++{1.0.rand.squared}.dup(n-1) ++ [0],
{rrand(0.005,0.2)}.dup(n),
\lin, n-8, 8 );
EnvGen.kr(env, gate, doneAction: 2) * LFTri.ar(220,0,0.4)
}.scope(1, zoom:4);
)
z.release;
Spectral
FFT, IFFT and the phase vocoder ugens.
FFT calculates the spectrum of a sound, puts it into a buffer, and outputs a trigger each time the
buffer is ready to process. The PV UGens process the spectrum when they receive the trigger.
IFFT converts the spectrum back into sound.
// alloc a buffer for the FFT
b = Buffer.alloc(s,2048,1);
// read a sound
c = Buffer.read(s, "sounds/a11wlk01.wav");
(
// do nothing
{
var in, chain;
in = PlayBuf.ar(1,c, BufRateScale.kr(c), loop:1);
chain = FFT(b, in);
0.5 * IFFT(chain);
}.scope(1);
)
(
// pass only magnitudes above a threshold
{
var in, chain;
in = PlayBuf.ar(1,c, BufRateScale.kr(c), loop:1);
chain = FFT(b, in);
chain = PV_MagAbove(chain, MouseX.kr(0.1,512,1));
0.5 * IFFT(chain);
}.scope(1);
)
(
// pass only magnitudes below a threshold
{
var in, chain;
in = PlayBuf.ar(1,c, BufRateScale.kr(c), loop:1);
chain = FFT(b, in);
chain = PV_MagBelow(chain, MouseX.kr(0.1,512,1));
0.5 * IFFT(chain);
}.scope(1);
)
(
// brick wall filter.
{
var in, chain;
in = PlayBuf.ar(1,c, BufRateScale.kr(c), loop:1);
chain = FFT(b, in);
chain = PV_BrickWall(chain, MouseX.kr(-1,1));
0.5 * IFFT(chain);
}.scope(1);
)
(
// pass random frequencies. Mouse controls how many to pass.
// trigger changes the frequencies periodically
{
var in, chain;
in = PlayBuf.ar(1,c, BufRateScale.kr(c), loop:1);
chain = FFT(b, in);
chain = PV_RandComb(chain, MouseX.kr(0,1), Impulse.kr(0.4));
0.5 * IFFT(chain);
}.scope(1);
)
(
// rectangular comb filter
{
var in, chain;
in = PlayBuf.ar(1,c, BufRateScale.kr(c), loop:1);
chain = FFT(b, in);
chain = PV_RectComb(chain, 8, MouseY.kr(0,1), MouseX.kr(0,1));
0.5 * IFFT(chain);
}.scope(1);
)
(
// freeze magnitudes
{
var in, chain;
in = PlayBuf.ar(1,c, BufRateScale.kr(c), loop:1);
chain = FFT(b, in);
chain = PV_MagFreeze(chain, LFPulse.kr(1, 0.75));
0.5 * IFFT(chain);
}.scope(1);
)
2. Techniques
Artificial Space
Building a sense of space into a sound by setting up phase differences between the speakers.
{ var x; x = BrownNoise.ar(0.2); [x,x] }.scope(2); // correlated
{ {BrownNoise.ar(0.2)}.dup }.scope(2); // not correlated
// correlated
{ var x; x = LPF.ar(BrownNoise.ar(0.2), MouseX.kr(100,10000)); [x,x] }.scope(2);
// not correlated
{ LPF.ar({BrownNoise.ar(0.2)}.dup, MouseX.kr(100,10000)) }.scope(2);
// correlated
(
{ var x;
x = Klank.ar(`[[200, 671, 1153, 1723], nil, [1, 1, 1, 1]], PinkNoise.ar(7e-3));
[x,x]
}.scope(2))
// not correlated
{ Klank.ar(`[[200, 671, 1153, 1723], nil, [1, 1, 1, 1]], PinkNoise.ar([7e-3,7e-3])) }.scope(2);
// two waves mixed together coming out both speakers
{ var x; x = Mix.ar(VarSaw.ar([100,101], 0, 0.1, 0.2)); [x,x] }.scope(2);
// two waves coming out each speaker independantly
{ VarSaw.ar([100,101], 0, 0.1, 0.2 * 1.414) }.scope(2); // * 1.414 to compensate for power
// delays as cues to direction
// mono
{ var x; x = LFTri.ar(1000,0,Decay2.ar(Impulse.ar(4,0,0.2),0.004,0.2)); [x,x]}.scope(2);
(
// inter-speaker delays
{ var x; x = LFTri.ar(1000,0,Decay2.ar(Impulse.ar(4,0,0.2),0.004,0.2));
[DelayC.ar(x,0.01,0.01),DelayC.ar(x,0.02,MouseX.kr(0.02, 0))]
}.scope(2);
)
(
// mixing two delays together
// you hear a phasing sound but the sound is still flat.
{ var x; x = BrownNoise.ar(0.2);
x = Mix.ar([DelayC.ar(x,0.01,0.01),DelayC.ar(x,0.02,MouseX.kr(0,0.02))]);
[x,x]
}.scope(2);
)
(
// more spatial sounding. phasing causes you to perceive directionality
{ var x; x = BrownNoise.ar(0.2);
[DelayC.ar(x,0.01,0.01),DelayC.ar(x,0.02,MouseX.kr(0.02, 0))]
}.scope(2);
)
Parallel Structures
(
{
// mixing sine oscillators in parallel
var n = 16; // number of structures to make
// mix together parallel structures
Mix.fill(n,
// this function creates an oscillator at a random frequency
{ FSinOsc.ar(200 + 1000.0.rand) }
) / (2*n) // scale amplitude
}.scope(1);
)
(
{
// mixing sine oscillators in parallel
var n = 16; // number of structures to make
// mix together parallel structures
Mix.fill(n,
// this function creates an oscillator at a random frequency
{ FSinOsc.ar(200 + 1000.0.rand + [0, 0.5]) }
) / (2*n) // scale amplitude
}.scope(2);
)
(
{
// mixing sine oscillators in parallel
var n = 16; // number of structures to make
// mix together parallel structures
Mix.fill(n,
{
var amp;
amp = FSinOsc.kr(exprand(0.1,1),2pi.rand).max(0);
Pan2.ar(
FSinOsc.ar(exprand(100,1000.0), 0, amp),
1.0.rand2)
}
) / (2*n) // scale amplitude
}.scope(2);
)
(
{
var n;
n = 8; // number of 'voices'
Mix.ar( // mix all stereo pairs down.
Pan2.ar( // pan the voice to a stereo position
CombL.ar( // a comb filter used as a string resonator
Dust.ar( // random impulses as an excitation function
// an array to cause expansion of Dust to n channels
// 1 means one impulse per second on average
1.dup(n),
0.3 // amplitude
),
0.01, // max delay time in seconds
// array of different random lengths for each 'string'
{0.004.rand+0.0003}.dup(n),
4 // decay time in seconds
),
{1.0.rand2}.dup(n) // give each voice a different pan position
)
)
}.scope(2, zoom:4);
)