Single block execution: SynthDef and Synth

I want to execute this as a single block, but it gives me an error. It seems so simple that I have no idea what is wrong here?

(
SynthDef.new(\name, {"whatever"} ).add;
a = Synth(\name);
)

SynthDefs are not ready immediately in the server.

Two ways:

// easy, but doesn't .add
(
a = SynthDef.new(\name, {"whatever"} ).play;
)

// or:
(
fork {
    SynthDef.new(\name, {"whatever"} ).add;
    s.sync;
    a = Synth(\name);
};
)

hjh

Okay, thanks. So the Synthdef(if not immediately played) and Synth into variable are two asynchronous processes? hmmm . . . but it works, yes.

Yeah, the subtlety here is that for s.sync to work it has to be in a Routine because it must give up the global interpreter lock so that the OSC reply of the server to sync can actually be processed in the interpreter’s context. So what happens when you issue that fork cmd is that:

  1. You receive a Routine as a result of your command, which ends immediately, releasing the global interpreter lock.

  2. Then the scheduled Routine that was just created will run on the clock at some point, where (after it acquires the interpreter lock) it sets up an OSCFunc responder (in NetAddr.makeSyncResponder), then sends the /sync OSC to the server, and then hangs (yields) waiting for the response. The hang in this Routine will again release the global interpreter lock (I’m like 99% sure this happens in schedRunFunc), allowing OSC processing to occur.

  3. That server-response OSC processing is initiated by a socket callback that re-renters the interpreter acquiring its global lock (on the C++ side in localServerReplyFunc) that eventually calls the responder that was set up in makeSyncResponder. This actually happens from the context of the main thread (Process.mainThread) with “interrupts disabled”, meaning this.canCallOS if false in the actual OSCFunc context. What makeSyncResponder does is then to signal, i.e. reschedule your Routine that you forked. Then the OSCFunc actually ends, releasing the global interpreter lock again.

  4. Your forked Routine is now scheduled run again it its own (Thread) context, which happens in a 4th pair of interpreter-lock acquire and release pairs.

So, yeah, that little bit of code goes in and out of the the interpreter 4 times for the “magic” to happen: once for the cmd-RELP, once for the sync setup in the forked Routine, once for the OSC socket callback that enters the OSCFunc, and finally one more time for the awaken Routine after it was signaled from the OSCFunc. It’s noteworthy that Process.mainThread runs both steps 1 and 3 above, which is another reason why the REPL must return immediately at 1.

1 Like

Within the server, /d_recv (receive a SynthDef) is asynchronous – it will finish sometime later. Quickly, but not instantly.

/s_new (new synth) is synchronous – it will finish completely before the next server command is processed.

Whether a command is synchronous or asynchronous is discussed in the Server Command Reference help file.

Unless you’re an extremely advanced user, you don’t have to worry about the details of the OSC receipt. It may be interesting background, but whether you knew all that or not, you would still use it exactly the same.

In sum, you need to wait for the def to be ready. Normal functions don’t wait for anything. So it’s necessary to use a context that can pause and resume. This is a Routine. Simple as that, basically, for practical purposes.

hjh

1 Like