Busses#

In many instances, you might simply wish to create a synth that delivers sound directly to your speaker. For this purpose, a single SynthDef, representing the entire signal-flow graph, is often sufficient.

However, in numerous scenarios, we may want to construct multiple SynthDef rather than one extensive SynthDefs, enabling us to connect each synth with its unique function. This would be a more modular solution. For example, one synth might be dedicated to applying reverberation to the sound generated by all other synths. Embedding the code for reverberation in each synth would be considered poor practice. Using a more modular implementation requires a good understanding of the signal-flow, i.e., where is the signal created and where does it flow in which order.

SuperCollider uses a node tree for organizing this flow and we can visualize it via Server.local.plotTree; or s.plotTree; The signal flows from the head of the tree to its tail. This wording seems confusing, since one would expect the terms root and leafs. Just remember that the order goes from head to tail because it is important if you create your synth on the audio server.

Note Tree

In SuperCollider the signal flows from the head of the node tree to its tail.

If our synth does not represent the whole signal flow graph, there has to be a possibility to read the signal and to somehow manipuate it. For this purpose there are so called busses. In fact, when we use the Out unit generator we actually write a signal to a Bus.

Bus

A Bus in SuperCollider is simply a place or a location (on the server) to which signals can be written and from which signals can be read. It is a location where audio (or control signals) can exist where multiple processes can share it.

When we boot the audio server, by default SuperCollider creates a bunch of busses for us. By evaluating s.options.numAudioBusChannels we can see how many audio busses are available. With my configuration, I have 1024 audio busses ready.

We can address busses by their identification number which starts by 0. On my machine and most certainly your machine, bus 0 and 1 are the output busses. That is the reason why write Out.ar(0, sig); to generate sound. A stereo signal requires two busses and the Out unit generator takes care of that.

Let’s construct a simple example using three synths:

  1. an impulse,

  2. a percussive saw wave triggered by the impulse, and

  3. a reverb effect.

The reverb effect should process the signal in the last stage. Therefore we have to positioning it at the tail. The impulse should be at the head, and the saw wave should be in between. At this point it is not necissary that you fully understand these SynthDefs. When ever \impuse outputs an impulse, i.e., trigger, TExpRand spits out a value between 40 and 100. It is a so called demand unit generator. I use Dust as a random impulse. Its frequency determines the density of the randomly generated impulses.

(
SynthDef(\impulse,{
    Out.ar(\out.ir(0), Dust.ar(\freq.kr(2)));
}).add;

SynthDef(\saw, {
    var sig, env, in, freq;
    in = In.ar(\in.ir(1));
    freq = TExpRand.ar(40, 100, in).midicps.round;
    sig = Saw.ar(freq!2);
    env = EnvGen.ar(Env.perc, gate: in);
    sig = LPF.ar(sig, \freq.kr(440));
    sig = sig * \amp.kr(1.0) * env;
    Out.ar(\out.ir(0), sig);
}).add;

SynthDef(\reverb,{
    var sig, in;
    in = In.ar(\in.ir(1), numChannels: 2);
    sig = FreeVerb.ar(in, room: 1.0, damp: 0.4);
    Out.ar(\out.ir(0), sig);
}).add;
)

The In unit generator read a signal from a bus and its counterpart Out writes the signal to a bus. Note that our default values would not work because each synth would write to bus 0. In the following, I use Bus objects that are handy to organize busses client-side. However, these objects have no effect on the server, i.e., they do not acutally create new busses but manage bus numbers. If we execute the following, we do not hear any sound:

(
~b_impuse = Bus.audio(s, 1);
~b_fx = Bus.audio(s, 2);

Synth(\impulse, [\out: ~b_impuse]);
Synth(\saw, [\in: ~b_impuse, \out: ~b_fx]);
Synth(\reverb, [\in: ~b_fx, \out: 0]);
)

If we plot the node tree we can see why. \reverb is at the head while \impulse is at the tail. Therefore \reverb reads first and since there is nothing there, it reads zeros. Then \saw reads also only zeros and \impulse does not write to the output bus. The overall result is silence.

We can fix this by changing the order in which we create our synths.

(
~b_impuse = Bus.audio(s, 1);
~b_fx = Bus.audio(s, 2);

Synth(\reverb, [\in: ~b_fx, \out: 0]);
Synth(\saw, [\in: ~b_impuse, \out: ~b_fx]);
Synth(\impulse, [\out: ~b_impuse]);
)

The glitches in the sound result from the frequency changes before the previous saw impulse has dissipated. We can also use extra arguments when creating a synth to control where it will be added. The following approach will work as well:

(
~b_impuse = Bus.audio(s, 1);
~b_fx = Bus.audio(s, 2);

Synth(\impulse, [\out: ~b_impuse]);
Synth(\saw, [\in: ~b_impuse, \out: ~b_fx], addAction: \addToTail);
Synth(\reverb, [\in: ~b_fx, \out: 0], addAction: \addToTail);
)

We can also add a synth relative to another synth. The following also works just fine:

(
~b_impuse = Bus.audio(s, 1);
~b_fx = Bus.audio(s, 2);

~impulse = Synth(\impulse, [\out: ~b_impuse]);
Synth(\saw, [\in: ~b_impuse, \out: ~b_fx], target: ~impulse, addAction: \addAfter);
Synth(\reverb, [\in: ~b_fx, \out: 0], addAction: \addToTail);
)

Here we add \saw after (with respect to the head) ~imuplse.

Another possibility is to use Groups which are containes in the node tree that can contain multiple synths. This is useful if you have a lot of synth where some are independent. For example, if you only apply at most one effect to any signal, you could put all effect synths into the same Group.

Reset Busses

To reset the bus numbering, you can call s.newBusAllocators;.

Out will add the signal to the bus. If we use ReplaceOut, it will override the current signal on the bus. Let me show you what happens if we read and write to the same bus, i.e. bus 0.

(
Synth(\reverb, [\in: 0, \out: 0]);
Synth(\saw, [\in: 0, \out: 0]);
Synth(\impulse, [\out: 0]);
)

It still works but we can hear all three synths. Especially the impulse, the short click, stands out here. If we replace all Out by ReplaceOut within our synth definitions, the above code creates the desired result.

(
SynthDef(\impulse,{
    // since it is the first signal, Out or ReplaceOut will work
    Out.ar(\out.ir(0), Dust.ar(\freq.kr(2)));
}).add;

SynthDef(\saw, {
    var sig, env, in, freq;
    in = In.ar(\in.ir(1));
    freq = TExpRand.ar(40, 100, in).midicps.round;
    sig = Saw.ar(freq!2);
    env = EnvGen.ar(Env.perc, gate: in);
    sig = LPF.ar(sig, \freq.kr(440));
    sig = sig * \amp.kr(1.0) * env;
    ReplaceOut.ar(\out.ir(0), sig);
}).add;

SynthDef(\reverb,{
    var sig, in;
    in = In.ar(\in.ir(1), numChannels: 2);
    sig = FreeVerb.ar(in, room: 1.0, damp: 0.4);
    ReplaceOut.ar(\out.ir(0), sig);
}).add;
)
(
Synth(\reverb, [\in: 0, \out: 0]);
Synth(\saw, [\in: 0, \out: 0]);
Synth(\impulse, [\out: 0]);
)