Boot server from another client

Hello everyone,
I am trying to develop a Pharo Smalltalk frontend for SuperCollider.
At the moment I am able to quit the server, load a synthdef and play it from Pharo.
I would like to know if I can also boot the server from my frontend, as I have not found any info about that.

also I would like to know what are DATA[] in this message
‘[ “/d_recv”, DATA[508], DATA[56] ]’ that is sent to the server when I evaluate
{SinOsc.ar(440,0.0,0.4)}.play
as I would like to send a similar message from my frontend.

thanks!

1 Like

are you familiar with Rohan Drape’s ( @rdd ) work on Smalltalk and Haskell front end? might be a good resource!

There are several layers to this. You might not need to implement all of them.

Easy layer: To start a server process, build a string with the commandline options, append it to the end of "scsynth ", and issue this as a shell command (whatever is the Pharo equivalent of unixCmd).

Next layer up: How do you know it’s running? After this, start sending /status messages at regular intervals. When the server is up and running, it will send a /status.reply message back for each one (see Server Command Reference | SuperCollider 3.12.2 Help). This is also how you know if the server has gone unresponsive (not getting /status.reply anymore).

Next layer up: Logging in for notifications. After you’re getting /status.reply-es, you can /notify (Server Command Reference | SuperCollider 3.12.2 Help). If you expect only one client per server (which is the usual case), this is pretty simple.

Superhero layer 1: Multi-client (remote servers). If the server was booted with maxLogins > 1, then it may allow multiple users (1, 2, 4, 8, 16 or 32). In that case, the /done /notify clientID maxLogins reply to the /notify request will tell your client 1/ clientID = this login’s sequential number and 2/ maxLogins set at boot time. Your client can use this information to arrange non-conflicting ranges for node, bus and buffer IDs (in the SC class library, the Server:newAllocators method handles this). Some more details are here (although the method documentation states incorrectly that you must set the server options maxLogins to match the remote server – this isn’t true – the value retrieved from the remote server will override anything that you set here).

Multi-client is optional; most users will never need it. In 20 years, I’ve never used it in a production situation. You could choose not to deal with this, at least initially.

Superhero layer 2: Scoping. If you will eventually want to implement an oscilloscope and/or frequency scope, then you would need to open a shared memory interface to the server. In SC, the mechanics are in C primitives (functions prConnectSharedMem() and prDisconnectSharedMem() in OSCData.cpp, which refer to a C++ class defined in common/server_shm.hpp). Most SC users, including myself, aren’t expert enough in C for this (note, this means I am not qualified to answer questions about shm details!), so you could simply choose not to implement scopes.

hjh

1 Like

Oh, forgot this.

The first DATA block is: Synth Definition File Format | SuperCollider 3.12.2 Help

The second DATA block is a ‘/s_new’ message encoded in OSC format – this is specific to {}.play where you want the sound immediately. If you’re only sending a SynthDef, the first DATA block is enough.

hjh

thank you very much for the detailed description about server boot. it is really useful.

about the second DATA block, I don’t understand what you mean by " message encoded in OSC format "
how can I do that encoding by myself?

thanks and have a nice day

peace.
d

If you can “load a synthdef and play it from Pharo” then you must already be encoding the messages as OSC – because the server understands only OSC – there’s no other way to communicate with the server.

hjh

yes, I am already sending OSC message, but that DATA[56] looks clumsy, does it just means ‘/s_new’ ?

DATA[size] is only the way that the server’s dumpOSC mode prints embedded OSC messages blobs. It is not an accurate representation of the packet’s complete contents.

Going back to the origin – /d_recv documentation:

/d_recv
Receive a synth definition file.

bytes buffer of data.
bytes an OSC message to execute upon completion. (optional)

In the SC client, bytes should be supplied as an Int8Array.

The first “bytes” refers to the binary representation of a SynthDef – this is produced by the message asBytes. When you add a SynthDef, it includes these steps (but folds them into one operation, so you don’t normally think about them). We can unpack them, though, for explanatory purposes.

// create a synthdef object
(
d = SynthDef(\demo, { |out = 0, freq = 440, amp = 0.1, gate = 1|
	var eg = EnvGen.kr(Env.asr, gate, doneAction: 2);
	Out.ar(out, (SinOsc.ar(freq) * amp * eg).dup);
});
)

d.asBytes;
-> Int8Array[ 83, 67, 103, 102, 0, 0, 0, 2, 0, 1, 4, 100, 101, 109, 111, 0, 0, 0, 7, 63, -128, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, ... ]

d.asBytes.size;
-> 445

SynthDef bytes can get rather large – it would be wasteful for dumpOSC to print the entire contents.

In SC, you pack an OSC message by writing an array with the command path as the first item, and arguments beginning with the second item. Normally we just sendMsg but you can also generate binary OSC:

['/d_recv', d.asBytes].asRawOSC

-> Int8Array[ 47, 100, 95, 114, 101, 99, 118, 0, 44, 98, 0, 0, 0, 0, 1, -67, 83, 67, 103, 102, 0, 0, 0, 2, 0, 1, 4, 100, 101, 109, 111, 0, 0, 0, 7, 63, -128, 0, 0, 0, 0, 0, 0 ... ]

The other bytes is a second message (written also as an array), to run after the new SynthDef is ready. Normally this will be /s_new, because, what do you want to do after adding a new SynthDef? Probably hear it.

e = Synth.basicNew(\demo, s).newMsg(args: [freq: 220, amp: 0.15]);
-> [ 9, demo, 1000, 0, 1, freq, 220, amp, 0.15 ]

['/d_recv', d.asBytes, e].asRawOSC;

If you sendRaw this message to a running server, then it should play.

s.sendRaw(['/d_recv', d.asBytes, e].asRawOSC);

… which normally we elide with a sendMsg or just SynthDef().play, but it gets down to this under the hood.

So this is what your client needs to do – basically everywhere that there is a “completionMessage” (you’ll see this a lot with Buffer methods), it should be handled like e here.

TBH I haven’t looked at the C primitive for asRawOSC, so I’m not sure exactly how it’s converting the embedded sub-message array (e in the example). But it’s all there in the source code, and it should be straightforward to translate to Pharo’s OSC library.

hjh

1 Like

ps. smalltalk supercollider has grown up a little and is now more independently minded… it can encode unit generator graphs and command messages and send them to the synthesiser all by itself! but… it’s for squeak… also it’s stored in “simple object machine” format…

1 Like

Thank you very much for the detailed explanation. Now everything looks clear to me and it’s time to start working on it :blush:

That’s sounds good, where canI find the Smalltalk super collider for squeak?

…where can I find the Smalltalk super collider for squeak?

It’s at:

http://rd.slavepianos.org/t/stsc3

There’s a Makefile to construct a filein but it needs Haskell and various libraries, so I’ve copied the current Squeak code to:

http://rd.slavepianos.org/tmp/Sc3-Filein-For-Squeak.st

See Sc3HelpGraph for some unit generator graphs organised by author (jmcc, f0, &etc.).

Ps. Also, lots of the help files are in a SuperCollider like notation with a .stc suffix, which you’d need a translator to run. It’s included but you need Haskell to build it… (There are binaries for Debian and MacOs at the gitlab and github mirrors.)

Pps. It used to work in Pharo, so there’d be some things hiding in the history if you wanted to poke about.