Network communication with Open Sound Control - Programming for Musicians and Digital Artists: Creating music with ChucK (2015)

Programming for Musicians and Digital Artists: Creating music with ChucK (2015)

Appendix D. Network communication with Open Sound Control

Open Sound Control (OSC) is a popular network protocol for interconnecting digital media software systems. In ChucK, OSC is used to connect instances of ChucK software running on different computers. This is an easy way to link up different ChucK programs (or multiple copies of the same ChucK program) for collaborative music programming, such as in a laptop orchestra. OSC can also be used to connect ChucK to other audiovisual programming environments, like Processing (processing.org), Max/MSP (cycling74.com), or others.

From a practical level, OSC consists of messages passed from a client application to a server application. These messages consist of an address and zero or more arguments, for example:

/bass/play 58 1.0

/texture/alpha/amp 0.8

/jam/message "davetronic" "slow tempo to 90 bpm!"

Resembling a file path, a valid OSC address is any sequence of alphanumeric components separated by forward slashes and beginning with an initial forward slash. Each argument can be a floating-point number, integer, or string. (There are a few additional argument types, but these three are the most common.) Every message also has a typetag, which is a string indicating what the type of each argument is: the character at index n indicates the nth argument’s type, where i corresponds to an int, f is float, and s is string. For example, a typetag of siff means the OSC message’s arguments are a string, int, float, and float, in that order. Usually, the typetag can be inferred from the arguments, but there are times when you must work with it explicitly.

An OSC server must specify a port to listen to, which is simply an integer between 1024 and 65535 (ports below 1024 are possible but are usually reserved for system services and only accessible with administrator privileges on a system). A given port can be listened to by only one server at a time. An OSC client connects to a server by its hostname and port.

You can send messages via the OscOut class. The following code sends the message /bass/play 58 1.0:

OscOut oout;

("localhost", 6449) => oout.dest;

oout.start("/bass/play").add(58).add(1.0).send();

After declaring an instance of OscOut, you set the hostname and port of the server you wish to connect to. "localhost" corresponds to “this computer;” that is, the machine that the program is running on, and 6449 is a port chosen arbitrarily—whatever it is, this must match the port used by the server you wish to connect with.

.start() begins a message by providing the OSC address, and .add() can be called multiple times to add arguments as desired. .add() accepts any of the aforementioned OSC argument types: float, int, or string. .send() takes the supplied address and any arguments and sends the message to the set destination. .start() and .add() both return the same OscOut object, allowing calls to be chained. Thus, the following code snippets accomplish the same thing:

oout.start("/bass/play").add(58).add(1.0).send();

// same as:

oout.start("/bass/play");

oout.add(58);

oout.add(1.0);

oout.send();

Some more examples of sending:

// send: /texture/alpha/amp 0.8

oout.start("/texture/alpha/amp").add(0.8).send();

// send: /jam/message "davetronic" "slow tempo to 90 bpm!"

oout.start("/jam/message").add("davetronic").add("slow tempo to 90

bpm!").send();

Receiving messages takes a bit more work but is still relatively straightforward. The OscIn class manages starting a server and handling incoming OSC messages. For example, to receive the /bass/play message you sent earlier, you would use the following program:

OscIn oin;

6449 => oin.port;

"/bass/play" => oin.addAddress;

OscMsg msg;

while(true)

{

oin => now;

while(oin.recv(msg))

{

<<< msg.address, ",", msg.typetag >>>;

<<< msg.getInt(0), msg.getFloat(1) >>>;

}

}

That’s a lot of new code, so let’s look at it bit by bit. First, you declare your OscIn object, set the port to listen to (of course, this should match the port used by whatever is sending the message), and set the address of the message you want to listen for by ChucKing it to .addAddress(). You also declare an OscMsg object, which you’ll need later.

In the while(true) loop, ChucKing the OscIn to now will wait until a message matching the specified pattern is received. The inner while loop—while (oin.recv(msg))—pulls off any incoming messages, one by one, processing them in the loop’s body. The address of the message can be retrieved by getting the .address member of OscMsg, and similarly the typetag is available from .typetag (note that these are member variables rather than functions). You can retrieve the arguments themselves using OscMsg’s .getInt(), .getFloat(), and .getString() functions. If the nth argument of the OscMsg is an int, then .getInt(n) will contain that value; likewise for float arguments and .getFloat(n), as well as for string arguments and .getString(n). Because you happen to know that /bass/play has two arguments, an int followed by a float, you can easily pull out these values with .getInt(0) and .getFloat(1). From here, you could map the arguments to whichever musical parameters you like.

This is all fine when you know what arguments to expect from the OSC messages, but you must be cleverer when a message might have a variable number of arguments or no arguments:

OscIn oin;

6449 => oin.port;

"/bass/play" => oin.addAddress;

OscMsg msg;

while(true)

{

oin => now;

while(oin.recv(msg))

{

<<< msg.address, ",", msg.typetag >>>;

for(int n; n < msg.numArgs(); n++)

{

if(msg.typetag.charAt(n) == 'i') // integer

<<< "arg", n, ":", msg.getInt(n) >>>;

else if(msg.typetag.charAt(n) == 'f') // float

<<< "arg", n, ":", msg.getFloat(n) >>>;

else if(msg.typetag.charAt(n) == 's') // string

<<< "arg", n, ":", msg.getString(n) >>>;

}

}

}

Here, you iterate through each argument, determining the type of argument n from the nth character of the typetag; msg.numArgs() returns the number of arguments in the message. Usually you don’t want a given message to have arguments of arbitrary number and type, but this technique can be useful for some types of interactions. For example, an additive synthesizer might accept a variable number of amplitudes for the sinusoids it generates, which could be sent over OSC by a sequence of float arguments.

OscIn can also handle messages with different addresses—you might have guessed this, because the function for setting an address is called .addAddress() instead of just “address.” Calling .addAddress() multiple times will cause OscIn to listen for messages that have any of the supplied addresses. These messages will come in through .recv() as before, but you need to check the value of each OscMsg’s .address member to see the address of an individual message, for example:

OscIn oin;

6449 => oin.port;

"/bass/play" => oin.addAddress;

"/texture/alpha/amp" => oin.addAddress;

"/jam/message" => oin.addAddress;

OscMsg msg;

while(true)

{

oin => now;

while(oin.recv(msg))

{

if(msg.address == "/bass/play")

{ <<< "got bass" >>>; } // do one or more things

else if(msg.address == "/texture/alpha/amp")

{ <<< "got texture" >>>; } // do another thing

else if(msg.address == "/jam/message")

{ <<< "got jam" >>>; } // do something else

}

}

Finally, what if you want to receive all OSC messages coming in, even if you don’t know what address they’ll have beforehand? OscIn has you covered with the .listenAll() method. Calling this will cause any incoming message to trigger the OscIn object:

OscIn oin;

6449 => oin.port;

oin.listenAll();

OscMsg msg;

while(true)

{

oin => now;

while(oin.recv(msg))

{

<<< "got message:", msg.address, msg.typetag >>>;

}

}

In this way, you could process the addresses using ChucK’s string object utility functions (see appendix B) to route messages with any level of intricacy you can think of.

Using ChucK’s OscOut and OscIn classes, digital artists can create a variety of network interactions, from simple on/off messages to complex OSC server/routers. Armed with these mechanisms, ChucK programs are able to communicate with other media arts software programs, interaction devices, and combinations of these running on multiple networked computers.