Objects manipulation in SuperCollider and Synths generation

posted by on 2014.08.18, under Supercollider
18:

In the last post, I wrote about some object oriented techniques in Processing which simplyfied our code, and gave the possibilty to reason more abstractly. Also SuperCollider is an object oriented programming language, and in this little example below I want to explore a situation where we can really take advantage of this: it somehow belongs to the category of “things one (or I?) can’t really do in Max/Msp, Reaktor, etc.”. :)
Here’s the simple situation: suppose we want to create 100 different percussive sounds with FM synthesis, with random waveforms as carrier, random harmonics for the modulating signal, etc., and play them randomly. We don’t want to change the sounds later, just play them. So, the first strategy we could adopt is the dirty and cheap no-brainer approach, which works along the following lines: well, what I will do is to create a SynthDef, i.e. an audio graph on the server, which is “big” enough to encompass the various possibilities of sounds, store the various randomly created combinations of waveforms, frequencies, etc. in an array, and poll these data when needed. The SynthDef would look like

 SynthDef(\perc, { arg out = 0, freq = 0, decay = 0.5, pan = 0, depth = 1, waveform = 0, harm = 0;
            var sig = Select.ar(waveform, [SinOsc.ar(freq + (SinOsc.ar(harm * freq) * depth)), Saw.ar(freq + (SinOsc.ar(harm * freq) * depth))]);
        var env = EnvGen.kr(Env.perc(0.01, decay), doneAction: 2);
            Out.ar(out, Pan2.ar(LPF.ar(sig * env * 0.1, 3000), pan));
    }).add;

Setting the parameter waveform will choose among the waveforms, freq will determine the frequency, etc. A big array (or dictionary, if you prefer) with randomly generated values, and I’m done!
Well, yeah, this approach will work, but it’s quite ugly, and, more importantly, inefficient: indeed, for each Synth created, a copy is kept of the two waveforms, only one of which is heard. So, for each percussion sound, we allocate twice what we need. Remember that there is a difference between not having a sound generator on the audio server, and having one which is muted, since in the second case, the sample rate computations are performed anyways.
Let’s think about this situation from a different angle, which makes more use of the language capabilities of SuperCollider. What we really need are different percussion sounds: they are different enough to be considered not as different instances of the same SynthDefs, but rather as instances of different SynthDefs. Instead of having SuperCollider creating different Synth instances, we can tell it to create different SynthDefs! Indeed, consider the following code (assuming a given choice for the range of the parameters)

100.do({|i|
    var name;  
    name = "perc"++i;
    SynthDef(name, { arg out = 0;
                        var freq = [24, 57, 68, 29, 87, 39, 70].choose.midicps;
                        var decay =  rrand(0.1, 0.5);
                        var pan = rrand(-1.0, 1.0);
            var sig = [SinOsc, Saw].choose.ar(freq + (SinOsc.ar([0, 1, 2, 3, 4].choose*freq*rrand(1, 30))))*0.1;
        var env = EnvGen.kr(Env.perc(0.01, decay), doneAction: 2);
            Out.ar(out, Pan2.ar(LPF.ar(sig*env, 3000), pan));
    }).add;
    })
});

First, we are defining names with which we can refer to the SynthDef: this is easily done, since a name for a Synth is a string (or a symbol), and we can obtain each of them by concatenating a string (in this case “perc”) with the counter index. Next, we define different SynthDefs: the beautiful thing that makes this work in a nice way is contained in the part

[SinOsc, Saw].choose.ar()

Remember: SuperCollider is object oriented, and in most cases, even if we don’t think about it, we are dealing with objects. Indeed, when we write something like SinOsc.ar(440), we are actually creating an object of type SinOsc, and sending to it the value 440 via the method .ar (which stands for “audio rate”), which provides the necessary computations to produce audio on scsynth, the audio server. Since SinOsc, Saw, etc are an “extension” of the class UGen (more or less), we can make a list (or Collection) with objects of this type, and use the powerful collection manipulation methods provided by SuperCollider (in this case the method .choose). Since both SinOsc and Saw react in the same way to the method .ar (this is an example of “polymorphism”), we can have it follow the method .choose, and pass everything in the round brackets. In this way the SynthDef itself will only have one copy of the choosen waveform per name.
This way of reasoning might look a bit abstract at the beginning, but it’s based on a simple concept: everything that require repetitive actions is best left to a programming language! Moreover, since SuperCollider is heavily object oriented, it gives really serious manipulations possibilities, as we have seen. We used already an incarnation of this concept when we built graphical interfaces.
Finally, the percussive sounds can be played via something like this

fork{
inf.do({
    i = rrand(0, 99);
    Synth("perc"++i);
    rrand(0.1, 0.4).wait;
})
}

Here’s the complete code

s.boot;

(

var reverb, delay;
reverb = Bus.audio(s, 2);
delay = Bus.audio(s, 2);


SynthDef(\reverb, {arg out = 0, in;
    var sig = In.ar(in, 2);
    sig = FreeVerb.ar(sig);
    Out.ar(out, sig);
}).add;

SynthDef(\delay, {arg out = 0, in;
    var sig = In.ar(in, 2) + LocalIn.ar(2);
    LocalOut.ar(DelayL.ar(sig, 0.5, 0.1)*0.8);
    Out.ar(out, sig);
}).add;

fork{
   
100.do({|i|
    var name;  
    name = "perc"++i;
    SynthDef(name, { arg out = 0;
                        var freq = [24, 57, 68, 29, 87, 39, 70].choose.midicps;
                        var decay =  rrand(0.1, 0.5);
                        var pan = rrand(-1.0, 1.0);
            var sig = [SinOsc, Saw].choose.ar(freq + (SinOsc.ar([0, 1, 2, 3, 4].choose*freq*rrand(1, 30))))*0.1;
        var env = EnvGen.kr(Env.perc(0.01, decay), doneAction: 2);
            Out.ar(out, Pan2.ar(LPF.ar(sig*env, 3000), pan));
    }).add;
    });
   

s.sync;

        Synth(\reverb, [\in: reverb]);
    Synth(\delay, [\in: delay]);
   
    inf.do({
    i = rrand(0, 99);
        Synth("perc"++i, [\out: [reverb, delay].choose]);
    rrand(0.1, 0.4).wait;
})
}
)

I have added some reverb and delay to taste.
Notice that this code illustrate only the tip of the iceberg concerning what you can achieve with an object oriented approach to structure your code.
No audio this time, since it is not that interesting. :)

comment

Hey, I was wondering. I’m really interested in SC and it’s capabilities, but I don’t know “what” I can actually do with it. Also, it seems a bit intimidating. I’m learning PD know and it looks quite easier… Why should I learn SC? :)

mr. dood ( 18/08/2014 at 5:55 pm )

    The learning curve in SuperCollider is indeed a bit steeper than PD, but it’ll pay off later, if you are interested in more elaborated algorithmic music. For instance, I don’t think it is possible to replicate the code in this post in Pure Data, because there, as far as I know, you have no algorithmic way to create your audio graph, which is created rather by graphically connecting lines. As in many things in life, though, it all depends on what you want to achieve. I currently use SuperCollider, Reaktor, Max4Live, Pure Data (rarely): they each inspire different approaches to composition, and sound design, which is a good thing.
    Anyway, concerning “what” you can do with SuperCollider, well, what you can’t? :)


    https://www.youtube.com/watch?v=LWRU41sFTGI

    me ( 18/08/2014 at 6:17 pm )

It is technically possible to algorithmically create an audio graph in pd, but it’s definitely swimming against the tide.

Certainly no harm in learning pd – it’s not going to stop you getting into something like SuperCollider later if you find yourself surrounded by spaghetti or wanting to do things that pd can’t express so well.

Peter ( 19/08/2014 at 10:39 pm )

    Indeed, that’s exactly what I did. :)
    Out of curiosity, could you point me to some resources concerning how to algorithmically create an audio graph in Pd?

    me ( 19/08/2014 at 11:10 pm )

Please Leave a Reply

Current ye@r *

TrackBack URL :

pagetop