Tides on the Sea of Tranquillity

posted by on 2014.03.04, under Supercollider
04:

A quick post, since it is a while I’m not updating this.
Here’s a short track, “Tides on the Sea of Tranquillity”, built around one of my favourite UGens in SuperCollider: GrainBuf. GrainBuf is a buffer granulizer which is very flexible.
The idea for the track is quite simple: there are four granulizers, which get activated at a certain point, whose parameters are controlled by the loops in the Task. In particular, the position parameter for each of them is “lagged”, so that at each change it will start from the middle of the buffer, and reach the new value in a given time. Also, the spread, pitch, and pan are jittered, to give a spacey feeling. The other important part is that the rate for the grains are controlled via the .midiratio method, which allows to vary the pitch according to a temperate scale, hence giving some harmonic consistency. To end, everything is passed to a feedback delay line with a simple compressor.
One technical point: make sure you load buffers which are mono in GrainBuf, otherwise it will fail silently! Some times ago it took me ages to figure this out (I know, the documentation says so…).
Enjoy. 😉

s.boot;

(
fork({

//Define SynthDefs for the granulizers and delay

SynthDef(\granular, {arg out = 0, buff, pos = 0, spread = 0.05, dur = 0.40, p = 0.5, t_trig = 0, rate= 1, t = 300, a = 0.1, l = 1, pa = 0, att = 6, f = 100;
    var trigger = Impulse.ar(t);
    var sp = TRand.ar((-1)*spread, spread, trigger);
    var pan = TRand.ar((-1)*p, p, trigger);
    var sig = GrainBuf.ar(1, trigger, dur, buff, rate + LFNoise0.ar(f).range(-0.01, 0.01), Lag.kr(pos, l) + sp, pan:pan);
    var env = EnvGen.kr(Env.linen(att, 4, 8), gate: t_trig, doneAction:0);
    Out.ar(out, Pan2.ar(sig*env*a, pa));
}).add;

SynthDef(\del, {arg out = 0, in, feed = 0.93;
    var sig = In.ar(in, 2) + LocalIn.ar(2);
    var delL = DelayL.ar(sig[0], 2.0, 0.2);
    var delR = DelayL.ar(sig[1], 2.0, 0.5);
    LocalOut.ar([delL, delR]*feed);
    Out.ar(out, Compander.ar(sig, sig, 0.5, 1, 0.5, 0.01, 0.3));
}).add;


// Load samples in mono buffers and create a audio bus for delay
~path = PathName(thisProcess.nowExecutingPath).pathOnly;

~del = Bus.audio(s, 2);

~buff = Buffer.readChannel(s, ~path ++ "sample/sample1.wav", channels: [0]);
~buff2 = Buffer.readChannel(s, ~path ++ "sample/sample2.wav", channels: [0]);
~buff3 = Buffer.readChannel(s, ~path ++ "sample/sample3.wav", channels: [0]);
~buff4 = Buffer.readChannel(s, ~path ++ "sample/sample4.wav", channels: [0]);
~buff5 = Buffer.readChannel(s, ~path ++ "sample/sample5.wav", channels: [0]);

s.sync; //Wait for the buffers to be loaded;

y = Synth(\del, [\in: ~del]);


// Make an array of synths to be used as voices

~synths = Array.fill(3, {Synth(\granular, [\buff: ~buff, \t_trig:0, \pos: 0.5, \rate: ([0, 4, 5, 7, 9, 12, 16, 17] - 12).choose.midiratio, \spread: 0.05, \out:~del ])});

Task({

    10.do({
        ~synths[0].set(\buff, [~buff, ~buff2, ~buff3, ~buff4, ~buff5].wchoose([0.5, 0.3, 0.1, 0.08, 0.02]), \t_trig, 1, \t, rrand(10, 400), \dur, rrand(0.020, 0.40), \pos, rrand(0.0, 0.8), \l, rrand(0.4, 4), \rate, ([0, 4, 5, 7, 9, 12, 16, 17] - 12).choose.midiratio, \spread, rrand(0.005, 0.1), \p, rrand(-0.3, 0.3), \out, ~del, \pa, rrand(-0.5, 0.5), \att, rrand(6, 9), \a, rrand(0.05, 0.1), \f, rrand(50, 150));

        rrand(4, 9).wait;
    });

        30.do({
        ~synths[0].set(\buff, [~buff, ~buff2, ~buff3, ~buff4, ~buff5].wchoose([0.5, 0.3, 0.1, 0.08, 0.02]), \t_trig, 1, \t, rrand(10, 400), \dur, rrand(0.020, 0.40), \pos, rrand(0.0, 0.8), \l, rrand(0.4, 4), \rate, ([0, 4, 5, 7, 9, 12, 16, 17] - 12).choose.midiratio, \spread, rrand(0.005, 0.1), \p, rrand(-0.3, 0.3), \out, ~del, \pa, rrand(-0.5, 0.5), \att, rrand(6, 9), \a, rrand(0.05, 0.1), \f, rrand(50, 150));

        ~synths[1].set(\buff, [~buff, ~buff2, ~buff3, ~buff4, ~buff5].wchoose([0.5, 0.3, 0.1, 0.08, 0.02]), \t_trig, 1, \t, rrand(10, 400), \dur, rrand(0.020, 0.40), \pos, rrand(0.0, 0.8), \l, rrand(0.4, 4), \rate, ([0, 4, 5, 7, 9, 12, 16, 17]).choose.midiratio, \spread, rrand(0.005, 0.1), \p, rrand(-0.3, 0.3), \out, ~del, \pa, rrand(-0.5, 0.5), \att, rrand(6, 9), \a, rrand(0.01, 0.05), \f, rrand(50, 150));

        rrand(0.1, 4).wait;

    });

            20.do({
            ~synths[0].set(\buff, [~buff, ~buff2, ~buff3, ~buff4, ~buff5].wchoose([0.5, 0.3, 0.1, 0.08, 0.02]), \t_trig, 1, \t, rrand(10, 400), \dur, rrand(0.020, 0.40), \pos, rrand(0.0, 0.8), \l, rrand(0.4, 4), \rate, ([0, 4, 5, 7, 9, 12, 16, 17] - 12).choose.midiratio, \spread, rrand(0.005, 0.1), \p, rrand(-0.3, 0.3), \out, ~del, \pa, rrand(-0.5, 0.5), \att, rrand(6, 9), \a, rrand(0.05, 0.1), \f, rrand(50, 150));

            ~synths[1].set(\buff, [~buff, ~buff2, ~buff3, ~buff4, ~buff5].wchoose([0.5, 0.3, 0.1, 0.08, 0.02]), \t_trig, 1, \t, rrand(10, 400), \dur, rrand(0.020, 0.40), \pos, rrand(0.0, 0.8), \l, rrand(0.4, 4), \rate, ([0, 4, 5, 7, 9, 12, 16, 17]).choose.midiratio, \spread, rrand(0.005, 0.1), \p, rrand(-0.3, 0.3), \out, ~del, \pa, rrand(-0.5, 0.5), \att, rrand(6, 9), \a, rrand(0.05, 0.1), \f, rrand(50, 150));

            ~synths[2].set(\buff, [~buff, ~buff2, ~buff3, ~buff4, ~buff5].wchoose([0.5, 0.3, 0.1, 0.08, 0.02]), \t_trig, 1, \t, rrand(10, 400), \dur, rrand(0.020, 0.40), \pos, rrand(0.0, 0.8), \l, rrand(0.4, 4), \rate, ([0, 4, 5, 7, 9, 12, 16, 17] + 12).choose.midiratio, \spread, rrand(0.005, 0.1), \p, rrand(-0.3, 0.3), \out, ~del, \pa, rrand(-0.5, 0.5), \att, rrand(6, 9), \a, rrand(0.05, 0.1), \f, rrand(50, 150));

        rrand(0.1, 3).wait;

    });
                10.do({
            ~synths[0].set(\buff, [~buff, ~buff2, ~buff3, ~buff4, ~buff5].wchoose([0.5, 0.3, 0.1, 0.08, 0.02]), \t_trig, 1, \t, rrand(10, 400), \dur, rrand(0.020, 0.40), \pos, rrand(0.0, 0.8), \l, rrand(0.4, 4), \rate, ([0, 4, 5, 7, 9, 12, 16, 17] - 12).choose.midiratio, \spread, rrand(0.005, 0.1), \p, rrand(-0.3, 0.3), \out, ~del, \pa, rrand(-0.5, 0.5), \att, rrand(6, 9), \a, rrand(0.05, 0.1), \f, rrand(50, 150));

        rrand(1, 3).wait;

});



}).play(quant: 4);
})
)

Audio clip: Adobe Flash Player (version 9 or above) is required to play this audio clip. Download the latest version here. You also need to have JavaScript enabled in your browser.

Ambient soundscapes – A variation on theme

posted by on 2013.08.22, under Supercollider
22:

It’s always a good practice to take a simple idea, and look at it from a different perspective. So, I went back to the ambient code of my last post, and built a little variation. If you check that code, you’ll see that the various synths are allocated via a routine in a scheduled way, and it would go on forever. The little variation I came up with is the following: instead of allocating a synth via an iterative procedure which is independent of the synths playing, we will allocate new synths provided the amplitude of the playing synth exceeds a certain value. In plain English: “Yo, synth, every time your amplitude is greater than x, do me a favor darling, allocate a new synth instance. Thanks a lot!”. But how do we measure the amplitude of a synth? Is that possible? Yes, it is: the UGen Amplitude does exactly this job, being an amplitude follower. Now we are faced with a conceptual point, which I think it’s quite important, and often overlooked. SuperCollider consists a two “sides”: a language side, where everything related to the programming language itself takes place, and a server side, where everything concerning audio happens, and in particular UGens live. So, when you allocate a synth, the client side is sending a message to the audio server with instructions about the synth graph (i.e. how the various UGens interact with each others) to create. Since Amplitude lives on the server side, we need to find a way to send a message from the server side to the client side. Since in this case the condition we have, i.e. amplitude > x, is a boolean condition*, and we want to allocate a single synth every time the condition holds, we can use the UGen SendTrig, which will send an OSC message to the client side, which in turn will trigger the allocation of a new synth via an OSC function. We have almost solved the problem: indeed, SendTrig will send triggers continuosly (either at control rate or audio rate), while we need only 1 trigger per condition. For this, we can use a classic “debouncing” technique: we measure the time from a trigger to the previous one via the Timer UGen, check if “enough” time has passed, and only then allow a new trigger. The value chosen here is such that only one trigger per synth should happen. I say should, because in this case I end up getting two triggers per synth, which is not what I want… I don’t know really why this happens, but in the code you find a shortcut to solve this problem: simply “mark” the two triggers, and choose only the second one, which is accomplished by the “if” conditional in the OSC function. Of course, this little example opens the way to all cool ideas, like controlling part of synths or generate events via a contact microphone, etc. etc.

s.boot;
(
~out=Bus.audio(s,2);

//Synth definitions

SynthDef(\pad,{arg out=0,f=#[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],d=0,a=0.1,amp=0.11;
    var sig=0;
    var sig2=SinOsc.ar(f[15],0,0.08);
    var env=EnvGen.kr(Env.linen(6,7,10),doneAction:2);
    var amplitude;
    var trig;
    var trigCon;
    var timer;
   
    sig=Array.fill(16,{|n| [SinOsc.ar(f[n]+SinOsc.ar(f[n]*10,0,d),0,a),VarSaw.ar(f[n]+SinOsc.ar(f[n]*10,0,d),0,0.5,a),LFTri.ar(f[n]+SinOsc.ar(f[n]*10,0,d),0,a),WhiteNoise.ar(0.001)].wchoose([0.33,0.33,0.33,0.01])});
    sig=Splay.ar(sig,0.5);

    amplitude=Amplitude.kr((sig+sig2)*env);
    trig=amplitude > amp;
    timer=Timer.kr(trig);
    trigCon=(timer>4)*trig;
    SendTrig.kr(trigCon,0,Demand.kr(trigCon,0,Dseq([0,1])));

    Out.ar(out,((sig+sig2)*env)!2);
}).add;

SynthDef(\out,{arg out=0,f=0;
    var in=In.ar(~out,2);
    in=RLPF.ar(in,(8000+f)*LFTri.kr(0.1).range(0.5,1),0.5);
    in=RLPF.ar(in,(4000+f)*LFTri.kr(0.2).range(0.5,1),0.5);
    in=RLPF.ar(in,(10000+f)*LFTri.kr(0.24).range(0.5,1),0.5);
    in=in*0.5 + CombC.ar(in*0.5,4,1,14);
    in=in*0.99 + (in*SinOsc.ar(10,0,1)*0.01);
    Out.ar(out,Limiter.ar(in,0.9));
}).add;

//Choose octaves;
a=[72,76,79,83];
b=a-12;
c=b-12;
d=c-12;

//Define the function that will listen to OSC messages, and allocate synths

o = OSCFunc({ arg msg, time;
    [time, msg].postln;
    if(msg[3] == 1,{Synth(\pad,[\f:([a.choose,b.choose] ++ Array.fill(5,{b.choose}) ++ Array.fill(5,{c.choose}) ++ Array.fill(4,{d.choose})).midicps,\a:0.1/rrand(2,4),\d:rrand(0,40),\amp:rrand(0.10,0.115),\out:~out]);});
},'/tr', s.addr);
)

//Set the main out
y=Synth(\out);

Synth(\pad,[\f:([a.choose,b.choose] ++ Array.fill(5,{b.choose}) ++ Array.fill(5,{c.choose}) ++ Array.fill(4,{d.choose})).midicps,\a:0.1/rrand(2,4),\d:rrand(0,40),\amp:rrand(0.10,0.115),\out:~out]);

o.free;
y.free;

s.quit;

Here’s how it sounds. Notice that the audio finished on itself, there was no interaction on my side.

Audio clip: Adobe Flash Player (version 9 or above) is required to play this audio clip. Download the latest version here. You also need to have JavaScript enabled in your browser.

*Technically speaking, it’s not a boolean condition, since there are no boolean entities at server side. Indeed, amplitude > amp is a Binary Operator UGen… For this type of things, the Supercollider Book is your dearest friend.

pagetop