Audio production Part 5 - SuperCollider

From LXF Wiki

Table of contents

Audio and Music Production - part 5

(Original version written by Graham Morrison for Linux Format magazine issue 67.)


For the ultimate in sound manipulation and generation, SuperCollider is hard to beat. We build a simple synthesizer out of a complicated environment.


You may think that sitting down to design and program an audio generator, before you can produce any sound, may restrict that wild streak of burning creativity. Well, the reality is that designing audio tools in this way brings new ways of approaching composition, almost from the inside out. This may not suite everybody's style, but even then, you can still take advantage of the massive library of sounds available from people who have already gone to the trouble of designing their own.

Despite the fact that it may appear something of a niche market, there are in fact, several high audio specific programming languages, almost as many as there are genres of electronic music in fact. One of the most popular is called Csound (so called because of the language it was written in). Csound is the descendant of an earlier language called Music360, and was originally written by Barry Vercoe at MIT. Then there's another language is called SuperCollider.

If you had downloaded SuperCollider expecting to find the Higgs Boson, you'd be bitterly disappointed. On the other hand, if you were expecting a cutting edge, experimental audio tool, you may feel a little better. SuperCollider (often abbreviated to SC) was for many years, a commercial project for the Mac. Development was originally sponsored by its users who paid a considerable amount each for a licence. But when the author, James McCartney, finally got a job with Apple, his subsequent rise in income enabled him to release the SuperCollider source code under the GPL. The Linux version isn't identical to its Mac counterpart as the graphical components haven't been ported yet. This means you only get the sound generator, but that's more than enough to be getting on with.

Getting started with an audio oriented programming language is never going to be easy. After all, it's got none of the spontaneity of a hands-on approach. The flip-side of this is power, with the main advantage being total control over every single aspect of your creation. You can manipulate the audio right down to the bit-level if you desire. More often than not, this leads the programmer into uncharted territory, and the results can often be atonal and abstract. If you've ever heard the disjointed rhythms of Autechre, then you'll know what I mean.

It might be surprising to learn that SC is based on a client-server model. This isn't quite so unusual when you consider that this is also true for Jack. The server is supplied by the SCSynth command, and is primarily responsible for the sound synthesis. In technical terms, it's this that is responsible for the DSP calculations and managing resources. The client part of the application is supplied by a binary program called SCLang. The client-server model means that the server can be used across a network, or even in parallel with other servers.

While it is possible to edit source files manually, and then execute them as you would with other languages, most people opt to use Emacs as a kind of ad-hoc integrated environment for SuperCollider.

Installing SuperCollider

Downloading and configuring

Rather unusually, the SuperCollider team don't currently provide users with any well organised tar files for downloading their wonderful software. It can also be a little difficult to get working, which is why these instructions are needed. The only way to get a copy is directly via their CVS repository at SourceForge.

Firstly, you need to login to the CVS account:

$ cvs -d:pserver:anonymous@cvs.sourceforge.net:/cvsroot/supercollider login

Just press return when asked for a password. You can then download the current source tree (or 'check out' in CVS speak):

$ cvs -z3 -d:pserver:anonymous@cvs.sourceforge.net:/cvsroot/supercollider co SuperCollider3

This should download the necessary files and place them in a newly created SuperCollider3 directory. Next, make sure you've got a recent version of automake installed, and enter: (for some distributions, the --prefix is unnecessary):

$ ./linux/bootstrap
$ ./configure --prefix=/usr

$ make
$ make install

This should provide you with the scsynth and sclang binaries. Before any SC programs can be run, you need to copy a couple of configuration files from the CVS source tree to your home directory:

$ cp linux/examples/sclang.cfg ~/.sclang.cfg
$ cp linux/examples/sclang.sc ~/.sclang.sc

sclang.cfg needs to be edited to reflect the installation directory, as used in by the configure script. You also need to uncomment the additional libraries to enable extra functionality. For Emacs to work with SC, sclang.sc needs the location of the scsynth binary changed on the first line to where the installer has placed the binary.

SC also relies on several environmental variables to be set (this can be done by either executing the export command directly from the shell, or adding them to ~/.bash_profile). SC_LIB_DIR needs to point to your own installation:

export SC_LIB_DIR=/usr/share/SuperCollider/SCClassLibrary
export SC_JACK_DEFAULT_INPUTS="alsa_pcm:capture_1,alsa_pcm:capture_2"
export SC_JACK_DEFAULT_OUTPUTS="alsa_pcm:playback_1,alsa_pcm:playback_2"
export SC_SYNTHDEF_PATH="./synthdefs"

Finally, to be able to use SuperCollider conveniently from within Emacs, "(require 'sclang)" needs to be added to ~/.emacs.


Stage 1 - Starting the SC server

Installation is via the latest CVS version (see the installation box). After which, Emacs can be launched with the SuperCollider interface by typing ‘emacs -sclang'. For those unfamiliar with the way Emacs works, now may be a good time to run through our LXF Emacs tutorial starting in LXF64. After Emacs has loaded, the interface is separated into two buffer sections, with the top one being labelled SCWorkspace, the lower one SCLang. Code is entered into the workspace area, and SC status messages should be displayed in the lower area, known as the post buffer window.

To start, enter this into the workspace buffer:

s = Server.local.boot;

To execute this line from within Emacs, either press Control-c Control-c, or select 'Evaluate Line' from the SCLang menu. The post buffer should show that the server has created the Jack/Alsa connections and should spontaneously appear in qjackctl's Connection window. The final output in the post buffer window should be an auspicious "SuperCollider 3 server ready.." message. This means that the server has successfully started (assigned to 's') and is now waiting for us to send some sound generating code its way.


Stage 2 - Making a preliminary sound

SuperCollider's is an object oriented language with a syntax that's a cross between C++ and Smalltalk. It borrows from C++ for its basic object model, while relying on Smalltalk for the dynamic binding of methods and its syntax for list control structures. To see how a typical structure looks, we'll use the SynthDef object to create a sound. To get a simple sine-wave generated in SC, simply enter this into the Emacs SCWorkspace:

SynthDef("my_first_sine", { Out.ar(0, SinOsc.ar(440,0,1,0)) }).play(s);

For obvious reasons, make sure that the volume is turned down on your equipment. The above line can be executed by typing Control-c control-c, or by selecting SCLang->Evaluate Line from the menu, as before. This should generate a fairly low sine wave, oscillating at 440Hz - which is the A above middle C on a piano keyboard (otherwise known as concert pitch). What we've just done is create a synth object called "my_first_sine".

SynthDef defines a synthesizer class to be passed on to the server. The synth class from which my_first_sine was dynamically created contains the instructions for generating audio. The play() postfix takes two arguments, server and duration. We've used 's' to point the synth at our server from Stage 1, and omitted the duration to make the sound last indefinitely.

SinOsc is generates a sine wave, and takes four arguments; frequency, phase, multiply and add. 'Frequency' is the number of oscillations per second (hertz), 'phase' is the angle (or the position in the cycle) of the waveform, 'multiply' is the amplitude and 'add' will let you add a separate generator to the oscillator's output.

The 'add' argument is actually deceptively simple, as you can use it to embed other oscillators. In the above example, if you replaced the last '0' in SinOsc with SinOsc.ar(220,0,1,0), you'd get the original 'A' tone at 440Hz, mixed with an 'A' an octave lower at 220Hz.


Stage 3 - Generating an envelope

As you can see from the previous stage, methods can be embedded into what are effectively function calls to control the resulting sound. This is very similar to the control voltages used when patching a modular synthesizer. It's in this way that you can add an envelope to control the volume of the sound over time. This is achieved by using the Line object; basically a method of generating linear output. This is done by replacing the original SynthDef with:

SynthDef("my_first_sine", { Out.ar(0, SinOsc.ar(440,0,Line.kr( 1,0,2) ,0)) }).play(s);

As you can see, the Line method has replaced the '1' as the multiply argument for SineOsc, and takes start, end, and duration as its own arguments. Evaluating this line now should produce a tone that starts loud but fades to silence over two seconds. 'ar', as used by Out and SinOsc, forces the audio rate, which is the sampling frequency as apposed to 'kr' (used with Line) for the control rate. This is all to do with the clock used to calculate intervals, and while the audio needs the maximum resolution, control signals can happily use a lower one. This reduces CPU overhead, as there's no need for such a high detail in control signals. As an example, SC generates a single control signal for every 64 audio samples.

The Line method is only capable of implementing either an attack or decay envelope, which isn't very useful. For the more traditional Attack-Decay-Sustain-Release (ADSR) envelope, we need to use two separate classes. Firstly, there's the Env class for storing the envelopes shape, followed by EnvGen for generating the control signal for playback. It's possible to use anything to generate the envelope data, from oscillators to tables of data, but SC has several preset envelopes that cover the most common requirements. This is defined using the Env.shape function. As we're not triggering notes from an external source, the closest to an ADSR envelope without the sustain component is called 'linen'. I'm not sure why either.

We need to add the envelope as a variable to maintain clarity, so this next snippet of code should go before the SynthDef. The comma seperated values represent the ADSR times for the envelope:

e = Env.linen(0.2,0.8,0.4,0.2);

The envelope is then used to generate a control signal using EnvGen in the SynthDef line, with the second value simply being a gate, or trigger, value:

SynthDef("my_first_sine", { Out.ar(0, SinOsc.ar(440,0,EnvGen.kr(e, 1),0)) }).play(s);


Monitoring

Seeing what you hear

Whereas the Mac version of SC features many graphical elements to provide some visual feedback for your creations, the Linux version currently does not. (Note: This is no longer true. See: http://www.sciss.de/swingOSC/). This can be remedied to a certain extent by installing an excellent Jack plugin called 'Meterbridge'. This is basically a tool that provides five graphical meters for visualising audio.

Firstly, the PPM, or Peak Program level Meter (1), displays the peak amplitude of a signal. The VU meter (2), typically found on mixing consoles around the world, is designed to measure the volume of an audio signal, but, as they allow levels over the sampling threshold, aren't too useful in the digital domain. This is rectified by the Digital Peak Meter, or DPM (3), which as the name suggests, shows the digital peak of a signal rather than an averaged 'volume'. The jellyfish (4) (JF) monitor is so called because the audio signal looks a little like a jellyfish when connected. It's used in stereo to check for phase problems that may reduce the audio signal at certain frequencies when played on mono equipment. Finally, there's an oscilloscope (5)(SCO), perhaps most useful for synthesis as it displays the current waveform of the audio signal in real time.

Starting an instance of Meterbridge with four unconnected oscilloscopes is as easy as typing this on the command line:

$ meterbridge -t sco x x x x


Stage 4 - Triggering an Envelope

You can now start to see why SuperCollider is so powerful. What we're basically doing is using the equivalent of synthesizer modules, as functions in a programming language. It's all getting a little too much to fit neatly on to a single line though, so why not abstract this synth a little to make it easier to read. The synth definition can be refined to:

(
SynthDef("my_first_sine", { arg gate; 
   var env, amp;
   env = Env.adsr(0.2,0.8,0.2);
   amp = EnvGen.kr(env, gate);
   Out.ar(0, SinOsc.ar(440,0,amp,0))
 }).writeDefFile;
s.sendSynthDef("my_first_sine");
)

play(s) has been replaced with writeDefFile, which stores the synthesizer definition, before being sent to the SCServer with s.sendSynthDef("my_first_sine"). You can see that the previous 'linen' envelope has been replaced with adsr, and the reason for this is that we can now provide a gate trigger as if we were actually manually opening the gate (i.e. playing a key) on a keyboard. We've used the gate value within the envelope to let the server know when the envelope can be triggered and released. Once the definition has been saved and sent to the server (using the evaluation commands from stage 1), the synth can be triggered by calling the definition with a true or false value for the gate:

a = Synth("my_first_sine");
a.set("gate", 1);
a.set("gate", 0);

As a reminder, to generate sound in Emacs, first select the whole SynthDef, from the first opening bracket to the final closing one and either press Control-c Control-c or select SCLang->Evaluate Region from the menu. Then do the same for the a = Synth line, and when you come to the first a.set line, you should hear the sound generated from the synthesizer. But it won't stop. The sound will go through the Attack and Decay phases of the envelope, but while the gate is open, it will stay on the sustain stage, producing a constant low tone.

From Emacs, if you 'evaluate' the final a.set("gate", 0) command, this will close the gate and push the envelope to the Release stage, creating the fade-out you would hear as you release the key.


Stage 5 - Modulation

As we're creating a synthesizer in the shape of a classic synth, the next stage is to modulate the signal to produce some interest. You should already have an idea of how this can be achieved. What we need to do is modify the above code to add another variable (mod) and define this as some form of oscillator. To keep it simple I've chosen another sine wave at a frequency of a single oscillation per second:

var env, amp, mod, max=2;
mod = SinOsc.kr(1,0,10,0);

This sets the mod variable to contain a stream of values that oscillate between -10 and +10, once per second. The max variable is known as the index of modulation, and is used as a scaling factor. This effectively means that the value should modulate between peak amplitudes of +20 and -20 from the base frequency of 440Hz:

Out.ar(0, SinOsc.ar(440+(mod*max),0,amp,0))

This should produce a wobble in the pitch of the sine wave, similar to tremolo, and can be used in all kinds of ways. As an example of how complicated this can start to get, you could let the modulator's frequency stray into the audio frequency range:

mod = SinOsc.kr(220,0,10,0);

And you could do the same with the frequency range itself:

mod = SinOsc.kr(220,0,110,0);

The distorted frequencies that can be heard are the result of what are known as sidebands, which are frequencies related to the original signal. This added complexity is the early stages of frequency modulation synthesis found on Yamaha's groundbreaking FM synthesizers. FM maps the relationship between the carrier (the 440Hz A tone in this case) and the sideband frequencies produced through modulation. It's worth experimenting with these values, and you can also modify the phase of the SinOsc function for interesting results.

SuperCollider Programming Primitives

This is just a small selection of the total SC is capable of...

Oscillators

Wavetable Oscillator		Osc.ar(table, freq, phase, mul, add)
Sine Oscillator			SinOsc.ar(freq, phase, mul, add)
Fast Sine Oscillator		FSinOsc.ar(freq, mul, add)
Band limited Sawtooth		Saw.ar(freq, mul, add)
Band limited Pulse		Pulse.ar(freq, duty, mul, add)
White Noise				WhiteNoise.ar(mul, add)
Pink Noise				PinkNoise.ar(mul, add)


Filters

General Purpose Resonator	Resonz.ar(in, freq, bandwidth, mul, add)
One Pole filter			OnePole.ar(in, cutoff, multiply, add)
Two Pole Filter			TwoPole.ar(in, freq, radius, mul, add)
Resonant Low Pass			RLPF.ar(in, freq, res, mul, add)
Resonant High Pass		RHPF.ar(in, freq, res, mul, add)
Butterworth Low Pass		LPF.ar(in, freq, mul, add)
Butterworth High Pass		HPF.ar(in, freq, mul, add)
Butterworth Band Pass		BPF.ar(in, freq, rq, mul, add) 
Butterworth band reject		BRF.ar(in, freq, rq, mul, add)


Controls

Break Point Envelope		EnvGen.ar(ref, dur, mul, add, scale, bias, time)
Slew					Slew.ar(in, up, dn, mul, add)
Timed Trigger			Trig.at(in, dur)
Sample and Hold Latch		Latch.ar(in, trig)
Gate					Gate.ar(in, trig)
Line					Line.ar(start, end, dur, mul, add)
Exponential Line			XLine.ar(start, end, dur, mul, add)
Sequencer				Sequencer.ar(sequence, clock, mul, add)


Stage 6 - Filters

Now that our synth is sounding almost normal, the next stage is to add some filtering to the sound. To make the filter effect more pronounced, we need to change the sine wave oscillator (which has only a single harmonic) to something more complex such as white noise, which by contrast, contains every single harmonic. This is the same as we did with the Alsa Modular Synthesizer back in LXF64.

This can be easily done by changing the line that starts with Out.ar to:

Out.ar(0, WhiteNoise.ar(amp,0))


When evaluated, the synth should now produce a burst of noise, hopefully still under the control of our previous envelope. We now need to add the filter, and we'll use the same kind of Low Pass filter we used in the original Alsa Modular Synth tutorial. As this is an object oriented language, it's as simple as placing the filter where the oscillator was, and using the oscillator as the input for the filter. This would make the line from above look like this:

Out.ar(0, LPF.ar(WhiteNoise.ar(amp,0),500,1,0)

Easy! The low pass filter is using the white noise oscillator as the sound source (which is still using the same envelope from before), with a cut-off frequency of 500Hz, and an amplitude of one. When opening the gate, this should create a sound like the sound of the sea in a large shell. If the filter is working, it should sound far more muffled than before - the equivalent of putting your hand over part of the aforementioned sea shell. The most obvious next step is to use the modulator from the previous stage to vary the cut-off frequency. This involves adding the mod variable again and assigning the sine wave control signal to it, before finally adding it to the cut-off frequency in the synth. If all has gone well, the complete synthesizer should look like this:

s = Server.local.boot;

(
SynthDef("my_first_sine", { arg gate; 
   var env, amp, mod;
   env = Env.adsr(0.2,0.8,0.2);
   amp = EnvGen.kr(env, gate);
   mod = SinOsc.kr(1,0,500,0);
   Out.ar(0, LPF.ar(WhiteNoise.ar(amp,0),500+mod,1,0))
 }).writeDefFile;
s.sendSynthDef("my_first_sine");
)

a = Synth("my_first_sine");
a.set("gate", 1);
a.set("gate", 0);
s.freeAll

The final command is for when you sometimes get strange UDP errors. They're basically caused by conflict with currently running servers. Executing s.freeAll should free the resources and re-enable the server.

We've only scratched the surface. SuperCollider is capable of so much more than simple synthesis. Looking through the list of unit generators can give you a good understanding of what SC is capable of. Among the usual suspects, you'll find formant and granular synthesis, assorted effects and more mathematical functionality than you'll ever need. SC can also operate on streams of audio, either from RAM or directly from disk, making it perhaps the world's most versatile sampler. The sequencer also plays a big part, where you can control note and tone generation with almost the same degree as the sound synthesis itself.

Where to go from here...

SuperCollider marks the end to this series of audio tutorials. But don't fear! There's still plenty of uncharted territory to explore. For a good place to start, there's an exhaustive list of Linux audio applications available at http://sound.condorow.net/one-page.html.

If you need a specific recommendation, try http://freewheeling.sourceforge.net for an excellent live audio looping tool, designed to rival similar commercial applications. Don't forget, if you have a comment, or even a piece of your own music, send it in!