Guides | Server > Nodes | External Control > OSC

Node Messaging

Messaging Nodes with OSC

Introduction

The most direct and fast way to send commands to the server is to send messages to the Server object, if you are within sc-lang. If you are in a shell you can use sendOSC (available from CNMAT).

this messaging scheme is explained in detail in:

Messaging nodes

When creating nodes on the server (synths and groups) the only things we need to know are the nodeID and the server (its address to be precise).

In order to communicate with a synth, one sends messages with its nodeID. If you do not intend to communicate with the node after its creation (and the node will cause itself to end without external messaging), the node id can be set to -1, which is the server's equivalent to nil.

As soon as you want to pass around the reference to a certain node, assuming that you might not have only one server, it can be useful to create a Synth or Group object. These objects also respond to messages, and when needed can be used to obtain the state of the server side node.

see Node, Synth, and Group help for more detailed helpfiles on node objects.

the equivalent of

n = s.nextNodeID;
s.sendMsg("/s_new", "default", n);
s.sendMsg("/n_free", n);

is

n = Synth("default");
n.free;

when passing arguments:

n = s.nextNodeID;
s.sendMsg("/s_new", "default", n, 0, 0, "freq", 850);
s.sendMsg("/n_set", n, "freq", 500);
s.sendMsg("/n_free", n);

it is

n = Synth("default", [\freq, 850]);
n.set(\freq, 500)
n.free;

note that Symbols may be used instead of Strings:

n = s.nextNodeID;
s.sendMsg(\s_new, \default, n, 0, 0, \freq, 850);
s.sendMsg(\n_set, n, \freq, 500);
s.sendMsg(\n_free, n);

and:

n = Synth(\default, [\freq, 850]);
n.set(\freq, 500)
n.free;

The answer to the question of whether one should work with node objects or directly with messages depends to some extent on context, and to some extent is a matter of personal taste.

The encapsulation of node objects results in a certain generalization, meaning that other compound objects can respond to the same messages and thus exploit polymorphism. They also provide a certain level of convenience, keeping track of indexes and IDs, etc.

In certain cases, such as for granular synthesis it is recommended to use messages directly, because there is no benefit to be gained from the node objects (i.e. no need to message them) and they add cpu load to the client side.

(
SynthDef("grain", {
    Out.ar(0, Line.kr(0.1, 0, 0.01, doneAction: Done.freeSelf) * FSinOsc.ar(12000))
}).send(s);
)

(
Routine({
    20.do({
        s.sendMsg("/s_new", "grain", -1);
        0.01.wait;
    })
}).play;
)

In cases where you need to keep track of the synth's state, it is advisable to use node objects and register them with a NodeWatcher. (see helpfile)

Apart from such cases it is a matter of taste whether you want to use the combination of message and a numerical global representation or an object representation. The two can be mixed, and certain advantages of the object style can be accessed when using messaging style. For instance Server.nextNodeID allows one to use dynamically assigned IDs in messaging style. As a gross generalization, it is probably fair to say that object style is more convenient, but messaging style is more efficient, due to reduce client-side CPU load.

NOTE: IMPORTANT: If you wish to have the functionality of the default_group (e.g. problem free use of Server's record and scope functionality) you should treat ID 1 (the default_group) as the root of your node tree rather than ID 0 (the RootNode). See default_group for more details.

Note that Function-play and SynthDef-play return a synth object that can be used to send messages to.

x = { arg freq=1000; Ringz.ar(Crackle.ar(1.95, 0.1), freq, 0.05) }.play(s);
x.set(\freq, 1500);
x.free;

Argument lists in node messages

Several node messages accept lists of values to map onto the controls of Synth nodes, as in some of the examples already given:

s.sendMsg(\s_new, \default, n, 0, 0, \freq, 850);
n = Synth(\default, [\freq, 850]);

Argument lists generally appear as alternating pairs, with the control identifier preceding the value. Usually the control identifier is a name, as above, but it could also be an integer index. (Using integers is slightly faster for the server, but it makes the code harder to read and can introduce bugs if the SynthDef structure changes.) One way to find out control indices is to .add the SynthDef into a SynthDescLib, then get the list of all controls out of the SynthDesc.

    (
        SynthDef(\controlList, { |out = 0, freq = 440, amp = 0.1, detune = #[0.999, 1.001], gate = 1|
            var    sig = Mix(Saw.ar(freq * (detune ++ [1]), amp)),
            env = EnvGen.kr(Env.adsr, gate, doneAction: Done.freeSelf);
            Out.ar(out, (sig * env) ! 2);
        }).add;

        SynthDescLib.global[\controlList]
    )

Prints:

SynthDesc 'controlList'
Controls:
ControlName  P 0 freq control 440
ControlName  P 1 amp control 0.10000000149012
ControlName  P 2 detune control 0.9990000128746
ControlName  P 3 ? control 1.00100004673
ControlName  P 4 gate control 1
ControlName  P 5 out control 0
   O audio 0 2

The list shows that the 'freq' control has index 0, 'amp' is 1 and so on. Detune is defined as an "array argument," occupying indices 2-3.

SynthDefs with a large number of controls may need a little extra code to print the entire list.

SynthDescLib.global[\controlList].controls.do(_.postln); ""

Prior to SuperCollider 3.3, the only way to set array arguments by name was using n_setn (or Node's setn method). Beginning in version 3.3, array arguments can be included in s_new or n_set messages transparently.

Messaging style

note the characters $[ and $] delimiting the array in the list:

n = s.nextNodeID;
s.sendMsg(\s_new, \controlList, n, 0, 0, \detune, $[, 0.95, 1.005, $], \freq, 220);
s.sendMsg(\n_set, n, \gate, 0);

Object style

the Node object automatically inserts $[ and $] for you:

n = Synth(\controlList, [\detune, [0.95, 1.005], \freq, 220]);
n.set(\detune, [0.99, 1.01]);
n.release;

Event style

Supplying an array for an argument in an event already has another meaning: multichannel expansion, in which a separate node is created for each array item. If all items of the array should be sent to the same node, then the array argument should be enclosed in another array level:

(instrument: \controlList, freq: 220, detune: [[0.95, 1.005]], sustain: 2).play;