How does scsynth _really_ works

Hi all, I’ve recently discovered about what SuperCollider is and on how it very roughly works. I’ve already heard of Sonic Pi and knew that is used scsynth under the hood, and I got really fascinated. I wanted to experiment on developing a client for scsynth sound server. So far I know that it listens for OSC messages and reproduce sound out of them, however I didn’t find much useful documentation on what kind of messages am I supposed to send and the overall mechanism in detail. If someone could explain me/point out some proper documentation would be awesome! Oh, and thank you for the amazing work on SuperCollider!

A couple of complementary approaches:

  • Follow standard SC language tutorials, especially those focusing on Synth and Group object interfaces. (Events and patterns likely to be less helpful for this purpose.) In these classes, you can look up ...Msg methods to see how the messages are constructed. E.g. Synth(\default) is a call to Synth’s *new method. The actual message is made in the newMsg method.
  • Complete documentation of server messages is found in the Server Command Reference. However this document doesn’t explain why you would use each message – so I think it’s best to start with the basics using SC lang objects, and after you’ve gotten more familiar with the workflow and design, then look at the message details.

hjh

Another reason it’s a good idea to start looking into sclang: If you plan to write an autonomous client (without sclang), you will need to correctly build a graph of UGens, sort the optimal order in which they should be evaluated through the graph, and byte-compile it. (It sounds more complicated than it is; most languages have even more resources than sclang for graph optimization.) At the very least, you have to be familiar with the SynthDef small language and the UGens.

If you choose a multi-client setup, you will need to ensure that the states of both processes are synchronized.

https://doc.sccode.org/Reference/Synth-Definition-File-Format.html

Thanks to @jamshark70 and @smoge for the answers. So the best thing to do would be to start to learn sclang and get familiar with it and its constructs, such as nodes, UGens etc. and then start to study the server command reference, am I right?

Not necessarily, but I think the easy and clear way to know how the client and server communicate and how the synthdef is put together is to read Sclang documentation and how it does those things. Maybe check other clients, too; there are several clients.

Yeah, unfortunately it doesn’t seem that there is some kind of “guide” or “tutorial” to help understand how OSC messages are formed or to build alternative clients, but with enough dedition I hope it’s doable even for someone that basically has no experience with SuperCollider and this kind of stuff (me :smiley: )

1 Like

For this part, I suppose it’s a two-part question: 1. What are the messages that a new client needs to construct? (Server Command Reference). For example, to play a new Synth: Server Command Reference | SuperCollider 3.12.2 Help. The message needs to begin with a command path – this can’t change – for “new synth” it must be /s_new. Then the document explains that you need a “synth definition name” (another string), an ID number (integer), add action (0-4), target ID, and then a series of pairs for synth arguments. In sclang, OSC messages are written as arrays, and internally converted into OSC bundles, e.g.

['/s_new', 'mySynthDef', 1000, 0, 1, 'freq', 440, 'amp', 0.2]

… which means: Create a new synth (/s_new) based on definition ‘mySynthDef’, with ID 1000, at the head (0) of group 1, with control input values freq = 440 and amp = 0.2.

['/s_new', 'mySynthDef', 1000, 0, 1, 'freq', 440, 'amp', 0.2].asRawOSC in sclang will show you the correct OSC format, as numeric bytes. This isn’t directly readable, but you can use it to check messages that your client generates.

“Synth definition,” as smoge pointed out, is a BIG concept. It’s probably best initially to create some SynthDefs in sclang, save them as .scsyndef files, and /d_load or /d_loadDir them. That way, you can try things out with the messaging without having to create hundreds of UGen classes and the logic to interconnect and optimize the graph.

So that’s part 1 of question. Part 2 would be the mechanics of formatting the OSC packet. This depends on the language you will use. Most languages have an OSC support library. For example, in Processing, the above message would be:

OscMessage msg = new OscMessage("/s_new");
msg.add("mySynthDef");
msg.add(1000);
msg.add(0);
msg.add(1);
msg.add("freq");
msg.add(440);
msg.add("amp");
msg.add(0.2);

Now… as you said, you’re not already familiar with scsynth architecture. So you’ll also have to take some time to understand the resources that the server provides, and good vs bad ways of using those resources. In some ways, this is the harder part. That’s why I suggested starting with the Getting Started tutorials. To make your own client, you need to understand at least up to Getting Started 13 (buffers). “But I don’t want to use sclang” – right, but “how to play a note” / “how to isolate and mix channels” / “how to organize nodes so that effects come after sources” / “how to load a sample” are all concepts where it will benefit you a lot to have done it first in sclang, then transfer that knowledge to another environment. Otherwise it’s throwing OSC at the wall and hoping something sticks.

And – ask questions here :+1:

hjh

1 Like

Thank you for the extensive answer! I agree with you, I think the best way to achieve what I want would be to first of all get familiar with sclang and how does it all work, then start to read raw OSC message that sclang sends to the server and study how they’re formed. I actually was wondering whether there is some kind of documentation/reference that lists all the synth parameters of /s_new (like frequency, amplitude etc.)

From the standpoint of the messages, synth parameters are completely arbitrary – there is no pre-definition of parameters for server messages.

Synth inputs, and their default values, and what happens to them – these are all defined in a SynthDef. There’s a Getting Started chapter about this.

The only thing that matters is that the parameter names you use in a message should match names in the SynthDef being played. (Non-matching names are just ignored silently.)

(There are some reserved argument names defined in the default Event prototype as well, but I’d suggest for your purpose that you don’t need to concern yourself with Events – so don’t worry about these.)

There are some conventional parameter names: freq, amp (0.0-1.0), pan (-1.0 to 1.0), out (output bus index) – pretty much everything else is up to you. You don’t have to use these conventions at all, though – just that your code might play nicer with others if you do.

hjh

1 Like

Out of curiosity, why? Is there something sclang lacks, or is this more of a learning project?

I was curios in how all this system worked, and I also was interested in implementing some sort of scsynth client to create some sort of enviroment where you can live code music (something a bit like Sonic PI, though never as big - yes I know, quite ambitious project, but it’s mostly for learning purpose and I don’t even know if I’ll ever be able to work it out)

1 Like

Is it true if I want rock solid timing I need to use Tdefs and systemclock?

Is it true if I want rock solid timing I need to use Tdefs and systemclock?

I’m not sure what you mean by “rock solid” exactly, but SystemClock and TempoClock use the same time source and Tdef is just a Routine wrapper. I don’t see how this would be more “solid” than a plain Routine + TempoClock, for example.

Side note: SystemClock and TempoClock are pure sclang concepts and have little to do with scsynth (= the topic of this thread :wink: )

Sorry I thought it was scsynth territory, rock solid timing I guess like clock jitter, trigger latency. But since it is not apart of the topic I’ll save it for a diff day

No problem. You can just open a new topic.