Sending SC dynamic datas via OSC


#1

Hello, super-newbie on SC,I would like to know how to send dynamic values to my external synth (a Kyma Paca).

No problem with a fixed value, like:
(
n = NetAddr.new(“169.254.27.79”, 8000);
n.sendMsg("/volume", 100);
)

But if I want to use a time ramp like:
(
n = NetAddr.new(“169.254.27.79”, 8000);
n.sendMsg("/volume", Line.kr(0,10,100));
)
It doesn’t work, of course.

There is much more documentation for receiving than sending OSC messages, so i am kind of lost.

Also, for a better understanding of the OSC syntax, how could I send for instance the result of:

DemandEnvGen.kr(Dwhite(1000,4000,inf),Dwhite(0.01,0.02,inf),1,22);

as an OSC message?

Thanks a lot for your attention


#2

Line and DemandEnvGen etc are UGens - they are meant to be run inside of Synths and operate on audio signals, and don’t make sense run in isolated lines of code as you’re doing. For example, your n.sendMsg(...) calls send a single message (with a non-sensical result - you can’t send a UGen over OSC).

I would take some time to go over some introductory tutorials, to get a better sense of UGens, Synths, and the synthesis server.

Here’s a sketch of what your solution might look like. There are many ways you might achieve the result you’re looking for, so you might need to go through a few iterations as you learn more, until you find a way that gets you the exact results you want.

~paca = NetAddr.new("169.254.27.79", 8000);


// ... Ndef is a convenient way to create a named Synth that writes to an
//     audio or control Bus. This one is called \volume.
(
Ndef(\volume, {              
    |input=0|                // The synth has one argument - input
    VarLag.kr(input, 1.5);   // input is run through a VarLag, which ramps the value over 1.5 seconds
});
)



// Create a Routine to read from your Ndef bus and send to your ~paca
(
Routine({
	inf.do {
		var volume = Ndef(\volume).bus.getSynchronous; // Get the value from the bus your Ndef is writing to
		~paca.sendMsg("/volume", volume.postln);       // Send your volume value.
		(1/20).wait;                                   // Repeat 20 times / second. If you skip this line, you won't wait
                                                 	   // between loops - you'll loop forever and lock the app! 
	}
}).play;
)



// Okay, now if you set your Ndef(\volume)'s \input parameter,
// it will slowly ramp to that value over 1.5 seconds. Your looping 
// Routine will send the value as it changes to your ~paca.
Ndef(\volume).set(\input, 1.0);

Ndef(\volume).set(\input, 0.5);

The function inside of Ndef(\volume) is turned into a Synth - this is the place you can use UGens like the ones you were mentioning. For example, if I replace that line with the DemandEnvGen.kr(...) code, you’ll see something like what you were expecting - in fact, since your Routine is still running, replacing that code and re-running the Ndef chunk will start broadcasting the new values immediately.

In all of these cases, using .postln or .poll methods (for UGens) will help you visualize the values you’re generating and sending.

Hope this helps!


#3

Hello and thank you very much for your answer which is perfectly clear. Indeed I had some confusions regarding the synthesis server.
Unfortunatly your code line doesn’t work with the Kyma, the data is effectively correct in the post window but the Kyma doesn’t get it. No clue why. It’s still working with a fixed value. The thing also is I find this strategy (bus + routine) a little bit heavy to just send a dynamic integer.
I don’k know if you re using Max, but I found this really convenient way to send a dynamic value, it is so simple. I would love to know if there could be a way to be that simple with SC…
Thanks a lot.



----------begin_max5_patcher----------
547.3ocwVEsbbBBE8Y8qfgmsVAzUSdquzehNcxftraIihV.S2zL4euBnc213
txlXadQFtbgy8btW35SgAvx1CLEDbK3KfffmBCBrlLFBFmG.anGppoJqavFl
RQ2yfQt0zrCZqcTRbD.kL9MYZ8ZtfU01KrNgGM1Q0UeiK1emjUocfShSF1HI
MNKBTjalPJhS.ecbG7sVPZKu+Cn7oydWqPKnML6ReRxo0SqH5a3hZlVcJpCF
a60SVQmbHJ9OsGBxDENyNO0O1wbwGD96XQoer1gIzX34vPymHO0OA6GCr3Ex
mQm.IwvkTnhT6vfNOLfwmQhlUGHypC3yR3n0iz6paGP8XdS1PsrZyBzESvlg
rKxVR9QUSNTNnYx6XBZoKdSlSJPWuTTRE6+GWCzIYcLwVvG+rj88kpDHY23Q
kPFze5idmJ80fRvtknKNG6AcIWCeOa51lpirUslxz0g7m6cyhr3hEy0HWtNC
cQxieEO+8+NY2usSYpwQatIFmkFiI4wHDn301xHMwUWjUbodF3zqrmw7ULIW
tmw7BlMdrj5u5yZojw9ephp1dY0T1XpgG3Hs1xTZtfp4shScx4yrIJuAxCbH
4q.PYd.jIk8lAh3izQxVCj7gSlanqCRHePB8VQB6Amdo34J3occOvjpQmsXL
7.w8sRyz7H6TtvM09mIPI6A9j+1eL.RkC240CW36kt6UGJ1.cascKSJ541af
gFjeN7W.0HRb1C
-----------end_max5_patcher-----------

#4

Is ~paca set to the right address?

Also, the Ndef(\volume).set(\input, 1.0) lines are important. These are what change the value.

You don’t have a choice about that. The Routine handles the timing (similar to the timing parameter of a [snapshot~]). No Routine, no timing. So you cannot do without the Routine.

And you must run UGens like Line in the server. They are not client-side calculations. So the only way to use UGens this way is to run them in the server, query the data in the language, and send the OSC message from the language.

hjh


#5

Start with the ~paca.sendMsg(...) code and try to get that to work. It could be something obvious like a port number or how you’re formatting your message - it could also be more subtle networking stuff (e.g. is the Paca expecting UDP packets?)


There are simpler / more high-level ways of doing this in SuperCollider. Using the Connection quark for example, you can do:

Ndef(\volume, { VarLag.kr(\input.kr, 1.5) });

~updater = BusUpdater(Ndef(\volume).bus);
~updater.signal(\value).connectToUnique(
	~paca.methodSlot("sendMsg('/volume', value)"));

or even this:

~volume = SynthControlValue().valueFunc_({
     |in|
     VarLag.kr(in, \lag.kr(1.5))
});
~volume.signal(\value) --> ~paca.methodSlot("sendMsg('/volume', value)");

(I haven’t released the functionality from the latter code in the official library yet, so only I get to do that version :slight_smile: )

Out-of-the-box, SuperCollider is both lower-level than Max/MSP, and much much broader in scope. The initial code I posted has a few more moving pieces, but it is I think relatively easy to understand - there’s not much hidden trickery (except for Ndef) and you can more or less see how each thing connects to the others. You can encapsulate that code in a function and never have to touch it again - or, you can rely on a library like Connection to do some of the heavy lifting stuff for you.


#6

Echoing scztt’s comment – I should have said last night that the usual way to handle this kind of thing more conveniently is to create helper functions or objects – kind of like abstractions in Max. If it feels “heavy” to do it this way, then wrap the heavy-ness into something else, and then invoke the whole thing with shorter, simpler commands.

For example, in the ddwCommon quark, I have KrBusWatcher, modeled after NodeWatcher. It handles all of the routine timing and server communication, so all you have to do is register the bus, and add a dependent to act on the values. I’ll replace the network communication with a slider, but the concept is the same.

// ~paca = NetAddr.new("169.254.27.79", 8000);
z = EZSlider(nil, Rect(800, 200, 400, 50), "lfo", [-1, 1]);

s.boot;

// run the LFO
c = Bus.control(s, 1);
~lfo = { LFTri.kr(0.1) }.play(outbus: c);

// hook up the bus to the slider -- that's it, 2 lines
KrBusWatcher.register(c);
c.addDependant { |what, values| defer { z.value = values[0] } };

// you would write something like
// c.addDependant { |what, values| ~paca.sendMsg("/volume", values[0]) };

// oh, you can make it update faster
KrBusWatcher.newFrom(s).updateFreq = 6;

// cleanup after you're finished
~lfo.free;
KrBusWatcher.unregister(c);
c.releaseDependants.free;

(In my own work, I use another of my quark extension classes, GenericGlobalControl, which can play LFO signals, and then I can automatically sync the language-side value and GUIs just by calling the method watch – which, from the usage perspective, is not heavy at all.)

hjh