/* 

==========================================================

DX490a - Summer 2010

Instructor: Stelios Manousakis

==========================================================

Class 2.1:

Server <-> language communication

Contents:

• Open Sound Control

• Client-Server communication

• Sending messages from sclang to scsynth

- direct and indirect

- message bundles

• Sending messages from scsynth to sclang

- Poll and .poll

- SendTrig

- SendReply

- Bus.control.get

==========================================================

*/



// ================= OPEN SOUND CONTROL (OSC) =================

// OSC is a communication protocol developed by Matthew Wright at CNMAT UC Berkley in 1998. It is optimized for networking technology, and has a client-server messaging architecture that uses a dynamic URL-style symbolic scheme, bundling messages that need to be sent simultaneously together. It is a very general protocol, initially developed for controlling digital sound and as a non-keyboard-centric successor of MIDI, but is currently used in many non-audio applications as well. The protocol is open, fast, efficient and flexible with up to 32-bits of resolution. You can create your own tags, and you can bundle messages together to ensure they are evaluated at the same time; a time-tag is also enclosed to control timed execution of messages precisely.

// Have a look at the specifications of the protocol here:

"open http://opensoundcontrol.org/spec-1_0".unixCmd 





// ================= CLIENT-SERVER COMMUNICATION =================

// In SuperCollider the client sclang (or any other client) requests and the server scsynth provides information. The two applications communicate through a network. In the case of the SuperCollider application this network exists inside the same machine, but it is possible to connect remote machines on the same Local-Area-Network if you know their IP addresses, or through the internet, if you know their IP addresses and have access to a web server! All commands are received via TCP or UDP (networking protocols) using a simplified Open Sound Control (OSC) version. OSC is used to communicate both ways: sclang -> scsynth and scsynth -> sclang




// ====== SENDING MESSAGES FROM SCLANG TO SCSYNTH ====== 

// sclang can send OSC messages to the server directly, or indirectly. 

// - Direct communication involves sending raw OSC messages 'by hand', using the .sendMsg method of the server and other similar formats. This is the way in which other client applicationos need to communicate with the server as well, if one does not use sclang.


// - Inderect communication, or object style communication is provided by numerous objects inside the language, which allow you to operate using a higher-level syntax, and take the responsibility of internally translating those messages to raw OSC data.


// For example, having this SynthDef

s.boot;

(

SynthDef(\test, {arg outbus = 0, freq = 220;

Out.ar(outbus, SinOsc.ar(freq, 0, 0.25));

}).send(s)

)

// the direct method for creating an instance is:

s.sendMsg("/s_new", \test, 100, 1, 0);

//  100 is the node ID

// 1 is the action (0: addToHead, 1: addToTail)

// 0 is the 'target' ID

// to change the frequency

s.sendMsg("/n_set", 100, \freq, 200);

// to free the node:

s.sendMsg("/n_free", 100)


// indirect methods are much easier, though add a slight overhead:

a = Synth(\test).play

a.run(false); // stop it

a.run(true); // start again

a.set(\freq, 200)

a.free; // free the server resources


// {}.play is a very indirect method construct, creating a SynthDef for you and sending the appropriate messages to the Server

{SinOsc.ar(220, 0, 0.25)}.play




// ------ Message Bundles ------

// Timing is crucial in music, so many times it is very useful to bundle messages together to ensure timing


SynthDef(\sine, {arg outBus = 0, freq = 400, amp = 1, dur = 1;

var env, src;

env = EnvGen.kr(Env([0, 1, 0], [0.5, 0.5], \sin), timeScale: dur, levelScale: amp, doneAction: 2);

src = SinOsc.ar(freq, 0, env);

Out.ar(outBus, Pan2.ar(src, Rand.new(-0.7, 0.7)));

}).send(s);


s.sendMsg(\s_new, \sine, s.nextNodeID, 0, 1);

s.sendBundle(1, [\s_new, \sine, s.nextNodeID, 0, 1]);


(

var basefreq = 400;

s.sendBundle(0.1, 

[\s_new, \sine, s.nextNodeID, 0, 1, \freq, basefreq, \dur, 1, \amp, 0.5],

[\s_new, \sine, s.nextNodeID, 0, 1, \freq, basefreq * 0.99, \dur, 0.8, \amp, 0.4],

[\s_new, \sine, s.nextNodeID, 0, 1, \freq, basefreq * 1.97, \dur, 2, \amp, 0.3],

[\s_new, \sine, s.nextNodeID, 0, 1, \freq, basefreq * 2.01, \dur, 2.3, \amp, 0.1],

[\s_new, \sine, s.nextNodeID, 0, 1, \freq, basefreq * 2.59, \dur, 0.9, \amp, 0.05],

[\s_new, \sine, s.nextNodeID, 0, 1, \freq, basefreq * 3.99, \dur, 4.1, \amp, 0.02]

);

)



/*

 Have a look at these files:

[NodeMessaging]

[Server-Command-Reference] // a list of all server commands and their arguments

[bundledCommands] // on sending messages to the server together as a bundle

*/



// ====== SENDING MESSAGES FROM SCSYNTH TO SCLANG ====== 

// At times it can be very useful to know what happens inside a SynthDef. There are a few different ways to do that:


// ------ 1| Poll UGen or .poll method  ------

// This UGen and method allows you to monitor things inside a SynthDef by printing values in the post window. This should only be used during programming for debugging, as it can add a considerable amount of overhead, especially if it's triggered very often.

s.boot;

{ Poll.kr(Impulse.kr(10), Line.kr(0, 1, 1), \test) }.play(s); // the UGen

{ SinOsc.ar(375, 0, 1).poll(Impulse.ar(20), \test2) }.play(s); // the method .poll can be called to all UGens



// ------ 2| SendTrig UGen (for triggers) + OSCresponder ------

// Send a trigger message back to sclang, upon receiving a trigger. An OSCresponder (client-side network responder) has to be setup to receive the messages. Note that the OSCresponder's command-name has to be "/tr".


{ SendTrig.kr(Impulse.kr(1.0),0,0.9) }.play;

// register to receive this message

(

r = OSCresponderNode(s.addr,'/tr',{ arg time, responder, msg;

"bang!".postln;

[time,responder,msg].postln;

}).add

);

r.remove; // don't forget to remove the responder once you're done



// ------ 3| SendReply UGen (for values) + OSCresponder ------

// Same idea as SendTrig, except this time for floats, ints or arrays instead just triggers - and you need to provide a trigger UGen yourself. The OSCresponder's command-name is up to you to set.


{SendReply.kr(Impulse.kr(1.0), 'the_answer', [40, 41, 42, 43] + SinOsc.kr, 1905)}.play(s);

// register to receive this message

(

r = OSCresponderNode(nil, 'the_answer', { |t, r, msg| 

"received!".postln;

[t, r, msg].postln }).add;

)

r.remove; 



// ------ 4| Control Bus  .get ------

// Lastly, you can query a control bus for its value with the .get method


a = Bus.control(s, 1); // a single channel control bus on the default server

{Out.kr(a, LFSaw.kr(0.1))}.play; // run a test synth

a.get({arg val; val.postln}); //ask for the bus' value from the language


// If you look inside the source of the Bus class, you'll see that the .get method uses an OSCresponder:

get { arg action; 

action = action ? { |vals| "Bus % index: % values: %.\n".postf(rate, index, vals); };

OSCpathResponder(server.addr,['/c_set',index], { arg time, r, msg;

action.value(msg.at(2)); r.remove }).add;

server.listSendMsg(["/c_get",index]);

// Note that there is an inherent latency involved in this process, as sclang asks scsynth for a value and then scsynth responds.