SuperCollider has a vast library of unit generators that can be assembled in unlimited ways, but sometimes even those aren't sufficient. You may have a need for an unusual signal processing algorithm, or you're running into efficiency problems that can be solved by condensing parts of your SynthDef into a single UGen.
UGens are defined in server plugins written in C++. Server plugins are not to be confused with quarks, which extend the SuperCollider language. UGens exist more or less independently of the interpreter and you don't need much familiarity with SC to write them.
Writing UGens is not too difficult, but it's arguably far less convenient and intuitive than the high-level tools that SuperCollider provides. You'll need a build system and a good amount of boilerplate code -- even fairly basic signal processing operations can require a lot of code. You don't have an instant live coding environment, and mistakes can easily crash the server. SuperCollider's UGens are stable and well-tested, and custom UGens are best viewed as a last resort for when the limitations of SC are impassable.
Before we proceed to the real UGens, we'll take a quick detour for the sake of completeness. A pseudo-UGen is a bit of SuperCollider code that abbreviates a certain configuration of UGens that gets used repeatedly. A pseudo-UGen is a class that superficially resembles a UGen class, but it only returns a composition of existing UGens. It has no efficiency savings, but it does save typing.
The below example has only a
.ar method, but you can just as easily have both
There are very few restrictions on what these classes can contain, but you should keep the following in mind:
A (real) UGen needs two components: a plugin for the server, and a class for the language. The class goes in an ordinary
*.sc file, and defines the interface for your UGen in the language. This class is generally just a few lines of code that ultimately call the class method UGen: -multiNew.
The server plugin is where the actual UGen behavior is defined. It is given by a dynamically loaded library written in C++, whose format is platform-dependent:
A plugin file can contain more than one UGen, and it can also define things other than UGens such as buffer fill ("/b_gen") commands.
When the server boots, it will look for plugin files in
Platform.userExtensionDir. Since sclang also looks for class files in the same location, the class file and the library file can go in the same place.
Plug-ins are loaded during the startup of the server, so it will have to be restarted after (re-)compiling a plugin. If you modify the plugin file but not the class file, you don't need to reboot the interpreter.
FAUST1 is an open source DSP language that describes real-time audio units. It can compile to SuperCollider plugins, providing an easy way to create UGens in SuperCollider.
FAUST provides a shell script useful for SuperCollider users called
faust2supercollider. This compiles a .dsp file into a class file and server plugin, which you can then drop into your extensions directory.
FAUST plugins are often quick to develop and can be painlessly ported to other environments. Unfortunately, they can't take advantage of all of the server's features, such as accessing Buffers or random number generators, and some UGens featuring very complex logic are difficult or impossible to write in FAUST. Furthermore, the FAUST compiler is quite intelligent but it might not always offer the best efficiency in its results. If a UGen you are developing hits these limitations, it is time to move on to handwritten C++.
To get an idea of the necessary ingredients for writing UGens, it's often best to poke around at complete examples. We've set up a GitHub repository at https://github.com/supercollider/example-plugins, which contains some example plugins numbered roughly by complexity. Each directory in that repository is self-contained with its own build system, so you can copy out a directory to form a starting point for your own UGens. The source codes of these plugins are heavily commented.
The first example, BoringMixer, is very minimal. The UGen is stateless and has only one calculation function, which is audio rate.
MySaw introduces states and multiple calculation functions. AnalogEcho introduces real-time memory management through internal buffers, and demonstrates how to do cubic interpolation from an array of samples.
The SC source code has a header file,
include/plugin_interface/SC_PlugIn.h, that gives you your interface to the server architecture as well as a bunch of helper functions. These are documented at Server Plugin API.
When the library is loaded the server calls a function in the library, which is defined by the
PluginLoad()macro. This entry point has two responsibilities:
Unit Generators are defined by calling a function in the InterfaceTable and passing it the name of the unit generator, the size of its C data struct, and pointers to functions for constructing and destructing it. There are 4 macros, which can be used to simplify the process.
These macros depend on a specific naming convention:
The meat of the UGen is its calculation function, which gets called every control period with the UGen object as an argument. (This is for control-rate and audio-rate UGens -- demand-rate is different.) In this function, the UGen reads from its inputs and writes to its outputs.
The calculation function is selected in the
PluginName_Ctor function with the
SETCALC macro. You can name the calculation function whatever you want, but the convention is
UGens often have multiple calculation functions, depending on the rate of the UGen itself and the rate of its inputs. For example, Phasor can be .ar or .kr, and its argument can be either .ar or .kr. So it has four calculation functions: Phasor_next_aa, Phasor_next_ak, Phasor_next_ka, and Phasor_next_kk. You don't need to be this thorough for your own UGens, however. For example, FreeVerb has only one calculation function. Who would want a control-rate reverb?
The most portable way to build plugins is using cmake2 , a cross-platform build system.
The examples in the example repository contain
Unit generator plugins are called from the real-time context, which means that special care needs to be taken in order to avoid audio dropouts.
delete. Instead you should use the real-time memory allocator via
There are two different implementations of the SuperCollider server. scsynth is the traditional server and supernova is a new implementation with support for multi-processor audio synthesis. Since the plugins in supernova can be called at the same time from multiple threads, write access to global data structures needs to be synchronized.
LOCK_macros, which are defined in SC_Unit.h. As exception, buffers in the wavetable format are not required to be locked.
In order to prevent deadlocks, a simple deadlock prevention scheme is implemented, based on the following constraints.