ok well here is an example of:
- a synth writing to a control bus
- client reading from that control bus, and sending the value to a midi device
- making another synth and using it to modulate the first, through a 2nd bus
here it is verbosely with lots of comments
MIDIClient.init; // initialize MIDI
m = MIDIOut(0); // get the first output device
Routine { // use a Routine (aka a thread) for synchronization with the server
s = Server.default;
s.boot;
s.sync; // wait for the server to be ready
// a control-rate bus which our synth will write to
~lfo_out_bus = Bus.control(s, 1);
// make a new synthdef (a control-rate sine)...
~lfo = SynthDef.new(\lfo, {
arg hz=1, out, mul=1, add=0;
Out.kr(out, SinOsc.kr(hz, mul:mul, add:add));
}).play(target:s, ///... and play it on the server...
args: [ //... with some arguments...
\out, ~lfo_out_bus.index // ... including the target bus
]
);
// now there are different things we could do to read the bus.
// here, we'll just explicitly read values from it in an endless loop.
inf.do {
~lfo_out_bus.get(
// getting a bus value is asnychronous, so the method takes a callback...
{
arg val; // ...whose argument is the bus value.
var scaled; // the value is in [-1, 1], so we want to rescale it
scaled = (val+1)/2 * 127;
postln(scaled);
m.control(1, 10, scaled); // send it out on CC 10, channel 1 (
/// or - gotcha! - 2 if receiver uses 1-based indexing.)
}
);
0.1.wait; // initiate a read every 100 milliseconds
// (we could go faster but this is so we don't choke on the print statements)
}
}.play; // make everything go
/// if we want to, say, modulate the LFO speed with another lfo,
/// another control bus is the way to do it
// this is a bit hacky, but we'll just make another routine here...
Routine {
8.wait; // ... and use it to wait for some arbitrary time for the earlier stuff to happen
~lfo_hz_bus = Bus.control(s, 1); // make another bus
~lfo.map(\hz, ~lfo_hz_bus); // map it to the synth
~lfo_mod = Synth.new(\lfo, // we already sent this def to the server
args: [
\hz, 0.2, // slower
\add:1.1, // scaled up to [0.1, 2.1]
\out, ~lfo_hz_bus.index // and sending to the modulation bus
],
target: ~lfo, addAction:\addBefore // make sure the modulator executes before its destination
);
}.play; // make it go
and here it is written more compactly. with faster polling.
MIDIClient.init;
m = MIDIOut(0);
Routine {
s = Server.default;
s.boot;
s.sync;
~lfo_out_bus = Bus.control(s, 1);
~lfo = SynthDef.new(\lfo, {
arg hz=1, out, mul=1, add=0;
Out.kr(out, SinOsc.kr(hz, mul:mul, add:add));
}).play(s, [\out, ~lfo_out_bus.index]);
s.sync;
~lfo_hz_bus = Bus.control(s, 1);
~lfo.map(\hz, ~lfo_hz_bus);
~lfo_mod = Synth.new(\lfo, [\hz, 0.2, \add:1.1, \out, ~lfo_hz_bus.index], ~lfo, \addBefore);
inf.do {
~lfo_out_bus.get({ |val| m.control(1, 10, (val+1)/2 * 127); });
0.01.wait;
}
}.play;
many tasks in SC can be solved with multiple techniques. the other main way that i’d get stuff from server->client is with SendTrig
in a synth and OscFunc
(or OSCdef
) in sclang. this is appropriate when you want the synth to initiate the transmission instead of polling it.
(side note, i believe there are some newer syntax shortcuts that don’t require you to use .map
at all, but i guess my brain is too fossilized to learn or remember them.)
HTH
oh and in keeping with the Questions philosophy, here is the “solution” on github, more memoriously