In case you haven’t heard, the SuperCollider Book is now available. Congratulations to everyone involved in bringing this to press.
The publisher has been kind enough to allow my chapter on SuperCollider’s server internals to be posted online as a preview chapter here. I think this is great, because understanding SuperCollider’s server internals will be of interest not just to SuperCollider developers and users, but to anyone involved in creating dynamic real-time audio software.
I first became interested in the internal workings of SuperCollider during the early days of AudioMulch development. At that time, SuperCollider was a closed-source project and information about how it was implemented was fairly scarce. Luckily, SuperCollider’s creator James McCartney was active on the music-dsp mailing list and was open to answering questions and explaining how things worked under the hood. Later, after SC became an open source project I performed initial work on porting scsynth to Windows, and in the process had a good look at what was going on in there. One thing that really interested me was how SuperCollider supported re-patching the audio signal processing graph in real-time without glitches or interruptions to the audio stream. The answer turned out to be simple and elegant.
Real-time dynamic audio graph manipulation
In SuperCollider, the dynamic audio processing graph (the graph of Nodes) is a linked data structure that is traversed at each audio buffer period to compute audio data. Modifications to the dynamic Node graph are achieved by posting asynchronous commands to the real-time audio processing thread via a lock-free queue. These commands are interpreted at the start of each buffer period. They cause the Node graph to be modified, changing the signal flow and/or adding and deleting processing Nodes as necessary. When required, dynamic memory allocation is performed in the audio thread using a special-purpose thread-specific real-time memory allocator. A general mechanism, also based on lock-free command queues, is provided to execute non-real-time operations (such as file i/o) asynchronously in a separate thread.
Precompiled Graphs and Unit calculation functions
In a slightly confusing case of overloaded terminology, scsynth defines a Node subclass called Graph, which evaluates a precompiled schedule of Units (unit generators). That is: scsynth has a dynamic graph of Nodes, some of which are Graphs whose purpose is to evaluate precompiled graphs of Units. Got it? The diagram above from the book chapter should help.
Allocating and evaluating Graph objects is relatively efficient because Unit instances are stored sequentially in a single memory block, and a Graph’s evaluation loop involves calling each Unit’s mCalc calculation function pointer in sequence. One interesting by-product of this design is that a Unit can change its calculation function pointer at runtime — this can be used to implement a Unit with different states. For example, an envelope unit generator whose final state returns a constant could set its mCalc member to a function that simply returns the constant. Thus less conditional logic need be executed for each Unit at each time step.
These mechanisms are two of the many aspects of the scsynth implementation described in the full book chapter available here. I cover the implementation and usage of these mechanisms in detail, along with the concurrency structure of scsynth including threading and inter-thread communication. The chapter is illustrated with a number of class and sequence diagrams. I encourage you to check it out, and of course, to get the book, which is full of insights and examples of how people have built whole artistic worlds using SuperCollider.