posted by on 2018.04.21, under Supercollider

Here’s a little exercise concerning voicings management in SuperCollider. The idea is very simple: we have a collection of samples we would like to trig randomly, but a retrig is allowed only if the whole sample has finished playing. To do this we have to keep track of active Synths (or “voices”), in order to avoid retriggering those. This role is played by the array ~voices: indeed, the index of the array identifies the buffer to be played, while a value of 0 or 1 denotes an available or unavailable voice, respectively. At the moment of the instantiation of a Synth on the server, SuperCollider allows us to assign a function to be executed when the given synth is free, which in our case sets the ~voices value corresponding to the given buffer to 0. In the infinite loop cycle we can then check for the value of ~voices at a random position i: if this value is 0, we create a new Synth with the corresponding buffer, and set the correspondent voice number to 1. Otherwise, we continue with the inf cycle. By changing the values in the rrand function you can decide how sparse the various instances will be.
You can use this technique with any type of SynthDef, in order to have a fixed voice system which does not allow retriggering or voice stealing. Also, the way I have done it is not the most elegant one: you can search for NodeWatcher (a way to monitor Nodes on the server) for an alternative approach.
Here’s the code


SynthDef(\voice, {|buff, trig = 1, out = 0, amp = 1|

    var sig = PlayBuf.ar(2, buff, 1, trig, doneAction: 2);

    Out.ar(out, sig *  amp);


SynthDef(\reverb, {|in = 0|
    var sig = In.ar(in, 2);
    sig = CombC.ar(sig, 0.5, 0.5, 3);
    sig = FreeVerb.ar(sig, 0.5, 0.5, 0.7);
    Out.ar(0, sig);



var samplePath;
var ind;

    //Setting up reverb line

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

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

~voices = [];

~buffers = [];

//Loading buffers
    samplePath = thisProcess.nowExecutingPath.dirname ++ "/sounds/*";
    ~buffers = samplePath.pathMatch.collect {|file| Buffer.read(s, file, 0, 44100 * 9);};


    ~voices = ~voices.add(0);

    ind = Prand(Array.fill(~buffers.size, {|i| i}), inf).asStream;

        i = ind.next;
        z = ~voices[i];

            if( (z == 0),  {

            x = Synth(\voice, [\buff: ~buffers[i], \out: ~rev, \amp: rrand(0.8, 1.0)]);
                x.onFree({~voices[i] = 0});
            ~voices[i] = 1;
            }, {});
        rrand(0.1, 0.6).wait;



All the samples have to be in a folder called “sounds” inside the same folder your .scd file is. I have used some few piano samples from Freesounds.org, since I wanted to achieve a minimalist piano atmosphere. Here’s how it sounds

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.


Awesomeness, tried this at home 😉

You might want to move the “rrand…wait” two lines upwards to preserve density in case you have many long-tailed samples. It can get really sparse after all the voices start.. Though it could spin your cpu after all the voices start :) So I used a separate check to see if all the indices are 1 and then also wait a bit.

prewaves ( 04/05/2018 at 1:34 pm )

    At the beginning I had an equal density of voices, but I was looking for something that was indeed more sparse and minimalist.

    me ( 06/05/2018 at 9:35 am )

Oh yeah, and for really short samples, theoretically, the synth could end up free by the time you register the callback. To prevent this, I’d write

x = Synth.newPaused(…);

prewaves ( 04/05/2018 at 1:46 pm )

    I had not thought of very short samples, but that’s a nice fix, thanks!

    me ( 06/05/2018 at 9:36 am )

Please Leave a Reply

Current ye@r *

TrackBack URL :