Differences between sc2 and sc3 architecture
In sc2, all synthesis takes place in one master synth process. The language is tightly integrated into this process. For each block of audio calculations (one cycle), the interpreter is called to handle scheduling and flow of control. Then control passes back to the synth process.
In sc3, there is a strict division between synthesis (which takes place on the server) and the language (the client). Both run in separate applications, and the only communication between them takes place using Open Sound Control messages sent over a network protocol (usually UDP, but TCP/IP may also be used).
Why the change? Let's place some advantages and disadvantages side by side:
|sc2||Code has a unified feel, which is easier to read and understand.||The monolithic aspect of the coding style makes it difficult to modularize code, which in turn makes it difficult to debug.|
|Integration means that the language can control synthesis processes with no latency, and vice versa–a trigger being synthesized can instantiate a complex synth process that depends on executing code in the language.||Stability: if a synth process crashes, it takes the language down with it, and vice versa.|
|sc3||Separation gives the user more freedom regarding the client. You can use the server without using the SuperCollider language. All that's required is a program that sends correctly formatted OSC messages to the server at the right time. Networked applications become easier to conceive and implement.||The use of network messaging introduces latency and additional complexity into some techniques that were very easy in sc2.|
|Multiple processes can be started and stopped independently, without requiring all synthesis to be stopped. Resources can be allocated dynamically while synthesis is happening (in many cases, not possible in sc2). This opens up possibilities for real-time live coding and other live performance techniques that were much clumsier to implement in sc2.||Code is more fragmentary: many small pieces have to be prepared before they can be started. It can be more difficult when reading sc3 code to see how the little pieces fit together. Simple things can be more difficult to do (but, on the other hand, some things that were very difficult to do in sc2 are much simpler in sc3).|
|The architecture encourages modularization, simplifying testing and debugging.|
|Synthesis "instruments" (more correctly, synthdefs) can be stored and reused much more easily.|
|Stability: a crash on one side does not bring down the other side.|
sc3 architecture basics:
Server:Roughly (very roughly) corresponds to Synth in sc2. It's the master controller for all synth processes. The interpreter variable s is usually assigned the server being used at the time. On startup, it's assigned to Server.local.
Synth processes are created and destroyed from pre-compiled templates called synthdefs. A synthdef is a fixed arrangement of unit generators (UGens) compiled in the language and sent to the server. This is a simple synthdef:
SynthDef("sine", Out.ar([0, 1], SinOsc.ar(440, 0, 0.5)) );
The book explains the syntax and how the unit generators fit together, so I won't cover that here.
Using this synthdef as shown above doesn't really do anything, because nothing tells it where to send the synthdef. So usually you'll see something more like the following:
SynthDef("sine", Out.ar([0, 1], SinOsc.ar(440, 0, 0.5)) ).send(s);
This compiles the synthdef into a binary format and sends it as a message to the server. The server stores it in memory and can create a theoretically unlimited number of instances of it (though there is a practical limit of CPU usage).
The instance is created when the server receives the /s_new message, and destroyed on /n_free:
s.sendMsg("/s_new", "sine", 1000, 0, 0); // 1000 is the synth ID s.sendMsg("/n_free", 1000); // freeing node ID 1000 kills the sine synth
This could be written with slightly fewer keystrokes as:
s.sendMsg(\s_new, "sine", 1000, 0, 0); // 1000 is the synth ID s.sendMsg(\n_free, 1000); // freeing node ID 1000 kills the sine synth
Nodes:Synthdef instances are called nodes. As shown above, nodes can be created and destroyed freely. The above kind of node, that makes sound, is called a Synth.
There's another kind of node, called a group. Groups don't make sound, but they hold other nodes (both groups and synths). Generally, when you send a command to a group, the command will be applied to all the members of the group.
Nodes are evaluated in a specific order. In many cases, this order is not important, but if one node reads the output of another node, then the order is critical. See the help file called "Order-of-execution" for more details about how the order should be arranged, and the commands to use to get it that way.
Addressing nodes:Nodes have numeric IDs. If you're using direct server messaging (as in the s_new and n_free messages above), you have to give the node ID so the server knows who you're trying to talk to.
There are client-side objects that manage node IDs for you and construct messages: Synth, Group. Buffer and Bus are similar objects that allocate IDs for buffers and buses and help you talk to them as well.
It's very important to remember that the synth object in sc3 is not the same as the synth object in sc2. In sc2, Synth is the synthesis process. In sc3, Synth is a placeholder that exists only on the client side. Its only function is to hold bookkeeping information and construct server messages. There's not a one-to-one correlation. A Synth object can exist after the server-side node has stopped, and a Synth can be created on the client side and held reserved until it's time can to make the server-side node.
Buses:You often want to share audio among multiple nodes. This is done using audio buses. The simple synthdef above already uses them–buses 0 and 1, which represent the left and right channels of the hardware output.
The first few buses (depending on your hardware and on the server option settings) are reserved for hardware output. The next few buses serve as hardware inputs. By default, the hardware buses are apportioned like this:
|Local server||0-7 hardware out||8-15 hardware in|
|Internal server||0-1 hardware out||2-3 hardware in|
The defaults can be changed using the ServerOptions object found in aServer.options.
Above these ranges, the buses are internal buses. Audio can be written to and read from them with no effect on the hardware.
There's also a set of control buses, which represent control rate (kr) data. (Audio and control rate will be discussed in the text.) This allows multiple synth nodes to share the same control values very easily.
Buses will be used in many of the examples from the text.
Buffers:Buffers hold audio samples to be played using PlayBuf or BufRd, wavetables to be synthesized using Osc, COsc, VOsc, or VOsc3, or other data of your choosing.
In sc2, soundfiles would be read into a buffer within the language. This was okay because the language and the synthesizer ran in the same process. In sc3, the data storage takes place only on the server side. The buffer object merely represents the buffer. It does not hold the data.
If you say:
b = Buffer.read(s, "sounds/a11wlk01.wav");
You might think the variable b now holds the contents of the sound file. It doesn't. Essentially all it contains is the ID for the buffer on the server side. Executing this command performs the following steps:
- Get the next available buffer ID from the server object (note, the server itself doesn't assign buffer IDs; there's a client-side object representing the server that handles all of this).
- Create a new buffer object with this ID.
- Send a message to the server telling it to read the file and place it into buffer #(ID).
- Return the buffer object to the user.
This is meant to be nothing more than a quick tour, a foretaste of some features to be aware of as you read the text and the translated examples. Many details will become clearer as you see the usage in the example. Further details can be found in help files for the various objects.