Hi Nathan,
this is a very interesting topic. Here’s my two cents.
My armchair critique is that the option to schedule a bundle rather than send an immediate OSC message should have been options in Synth.new
, Synth:set
, and any other method that sends OSC.
I think this would clutter the interface. To be honest, I find the s.bind
solution rather elegant. I always assumed it was well known, but apparently it isn’t…
Another approach could have been to enable/disable scheduling on a per-“thread” basis.
The two-process model of SuperCollider is occasionally touted as a benefit, but to my understanding one reason they were separated was because multithreading within a process was not universally and reliably supported on consumer hardware at the turn of the millennium.
I doubt that. scsynth
has always used multithreading (network thread, NRT thread, audio callback). I rather think the idea was to seperate the two components as much as possible – the total opposite of SuperCollider 2. I also think that multi-client setups and networking have been an important part of the design.
Note there is also an “internal” Server that runs directly in the sclang process. I think it has been the default at one point in time, but I may be wrong.
As a result, SuperCollider users are burdened with many practical issues as a consequence of inter-process communication. The latency-accuracy tradeoff is one of them.
Even if the Server ran in the same process, you would still need to schedule OSC bundles with latency. (I will go more into details at the end of this post.)
This assumes that language and audio processing run in seperate threads, i.e. they are not tightly synchronized. While this is always true for SuperCollider, it is not necessarily true for all platforms. A prominent example is Pd: the message system and DSP run in the same thread. Moreover, Pd offers two different schedulers:
- “polling scheduler”: messaging + DSP runs in a dedicated thread; the audio callback just reads/writes audio samples to/from a lockfree FIFO
- “callback scheduler”: messaging + DSP runs directly in the audio callback
Clock drift is another.
Very true. But note that sclang could follow the audio clock – even if it runs on a seperate process. For example, the server may ask the sclang scheduler to advance by sending a message or posting to a process-shared semaphore. Note that this does not mean that sclang would be in sync with the audio clock, it would still run independently, but always trying to catch up. (The idea is very similar to Pd’s polling scheduler.)
(Also, on Windows I get OSC messages completely dropped sometimes, especially for rapid music. Maybe Server.default.options.protocol = \tcp
would help, but it breaks my server meter.)
Yep, UDP packet loss because Windows uses a ridiculously small socket receive buffer by default. I should really fix this, but I always forget… Here’s a reminder to myself: Increase UDP socket receive buffer size · Issue #5993 · supercollider/supercollider · GitHub
pretty much every sufficiently complex real-time audio platform follows some kind of client-server model. But should they be separate processes? Probably not in this decade.
IMO, server-client is very specific to SuperCollider. I would rather think in terms like
- UI
- interpreter/language/scheduler
- audio engine.
In typical audio software, all three live in the same process, but there are outliers. Pd, for example, uses a dedicated process for the GUI.
VST3 plugins are an interesting case: they suggest a clean seperation between UI and audio processing to enforce thread safety, but as a consequence the two components may also run in separate processes, or even on different machines (= remote FX processing). Some plugins even run the audio processing on dedicated hardware.
(Some have argued that sclang surviving when scsynth crashes is a perk, but I don’t consider any situation where the server crashes to be a benefit.)
Yeah, I never bought this argument. If the Server crashes, I usually have to restart my project anyway. On the other hand, it totally makes sense to have the IDE in a seperate process, as we don’t want to lose our (unsaved) code on a language/server crash.
I would be interested if someone could explain the exact factors that cause timing nondeterminism in the sending/receiving of OSC messages. I don’t know quite enough about computer architecture, nor the internals of sclang timing, to offer a good explanation for that.
[/quote]
Generally, the very fact that scheduling and audio processing run independently requires that messages are scheduled in advance. The exact amount of delay depends on at least 3 factors:
-
network jitter: only relevant with actual network connections; negligible with localhost (in the order of microseconds)
-
language jitter: each operation in the language takes time – and more importantly: different amounts of time. For example, if you write a loop, the elapsed system time between each iteration will vary by some degree. Some iterations may do more work than others, or another Routines gets scheduled in between, or there is a garbage collector pause, etc.
-
hardware buffer size: Generally, audio is always processed in blocks. If you want OSC messages to be interpreted in between blocks, you have to schedule them in advance; otherwise messages would only be interpreted at block boundaries.
Scsynth uses a blocksize of 64 samples by default. However, the audio callback often uses a buffersize that is larger than the Server blocksize. For example, if the hardware buffersize is 256 samples, the audio callback executes 4 Server ticks in a row as fast as possible; as a consequence, OSC messages might be interpreted at an interval of 256 samples in the worst case. Generally, if you want to avoid late OSC bundles, the delay must be larger than the hardware buffersize duration.