There are at least three open source live-coding systems in popular languages — Sonic Pi (Ruby), Overtone (Clojure), and Tidal Cycles (Haskell) — which all share the same foundation: a system called SuperCollider. I’m going to show you how to build a basic house beat in SuperCollider.

Getting Started

You can grab the code from GitHub here:

https://github.com/gilesbowkett/basic-beat

You’ll need to install SuperCollider to use it. Once you have it, playing the beat becomes super easy, as shown in this video:

In the video, I kick off the beat by opening a file called loader.scd. As you probably guessed, it loads other files. Here’s the code:

(
"/Users/personal/code/ccrma-2018/supercollider-2018/project/synths.scd".load;
"/Users/personal/code/ccrma-2018/supercollider-2018/project/timing.scd".load;
"/Users/personal/code/ccrma-2018/supercollider-2018/project/pbinds.scd".load;
)

Of course, you won’t see this same code when you first download or fork the project. Instead of a SuperCollider file called loader.scd, you’ll have an ERB template called loader.scd.erb:

(
"<%= this_directory %>/synths.scd".load;
"<%= this_directory %>/timing.scd".load;
"<%= this_directory %>/pbinds.scd".load;
)

To turn this template into a SuperCollider file with the name of the appropriate directory, just run the Ruby installer:

> ruby installer.rb

This runs using the Ruby standard library on any Unix-like operating system. So the good news is that it doesn’t need a Gemfile on OS X, but the bad news is it won’t run on Windows. Sorry Windows users. Pull requests welcome!

Anyway, if you’re on OS X, you’ll see that this installer took a file called loader.scd.erb and created a new file called loader.scd. There’s a synths.scd.erb template as well, which the installer used the same way. At this point, you can either open loader.scd, or, in the worst-case scenario, open loader.scd -a SuperCollider.

Common Issue: Sample Rate Mismatch

If you see an error message from SuperCollider about sample rate mismatch, you can fix it (on OS X) by opening up the Audio MIDI Setup app — a standard utility included with every Mac — and ensuring that both your input and output devices are set to the same sample rate.

Pbinds & Timing

loader.scd loads three files:

  • synths.scd — SuperCollider evaluates this file to create a set of SynthDefs, which are basically functions that have the ability to create sound. Each SynthDef has a name also. The names are very useful for live-coding or testing, since for any given name, the command Synth(name) will play the synth immediately.
  • pbinds.scd — this file defines Pbinds, which are pattern binding objects.
  • timing.scd — this file defines beat-by-beat timing patterns for the Pbinds to use. These are simply arrays.

To understand Pbinds and timing, consider this UI from Ableton Live, which defines a drum pattern visually. (In fact, it defines the same drum pattern as the SuperCollider files.)

This is a very common UI for drum programming. Each row represents a drum sound, and each box represents a beat when that drum will play. There are four measures in the drum pattern, and each measure is broken up into 16 beats. (Or a more proper phrasing, in terms of music theory, might be to say that each measure has four beats, and each beat has four subdivisions.)

In this color-coded version of the Ableton image, you could say that the blue area represents a set of drum sounds, each of which is mapped to a set of beats in the green area.

The blue area is like SuperCollider’s pattern bindings in pbinds.scd, and the green area is like the arrays in timing.scd.

Let’s zero in on these two bars of ride cymbal rhythm:

It’s not hard to translate that into an array. The pattern repeats every 32 subdivisions, so our array will need 32 elements. The entire first bar is empty, so the first 16 elements can just be zeros. Since this drum pattern sequence only specifies if a drum is on or off, the array can just hold zeros and ones.

ride = [ 0, 0, 0, 0,
         0, 0, 0, 0,
         0, 0, 0, 0,
         0, 0, 0, 0,

         1, 0, 0, 1,
         0, 0, 1, 0,
         1, 0, 0, 1,
         1, 0, 1, 0 ]

The actual SuperCollider code in timing.scd looks only slightly different:

// ride timing
~rideAmps = [
  0, 0, 0, 0,
  0, 0, 0, 0,
  0, 0, 0, 0,
  0, 0, 0, 0,

  1, 0, 0, 1,
  0, 0, 1, 0,
  1, 0, 0, 1,
  1, 0, 1, 0
];

SuperCollider uses JavaScript-style // comments, and the ~ prefix indicates that a variable is global. Global variables are used extensively in native SuperCollider, mainly because when you’re live-coding music, you have different namespacing priorities than you would in most other types of programming.

The timing file uses the “amps” term (meaning “amplitudes,” or loudnesses) because of how pbinds.scd consumes this data:

Pbind(
  \instrument, \zaps,
  \dur, 1/4,
  \amp, Pseq(~rideAmps, inf)
).play(mainClock);

The \ plays the same role in SuperCollider syntax that the : does in Ruby and Clojure, so \instrument in SuperCollider means what :instrument would in Ruby or Clojure. This is an extended example of passing keyword arguments in the classic Smalltalk or Objective-C style, which Ruby popularized by faking it with hashes, and which Python now fully supports. If we had been writing Ruby, the above code would have looked like this:

p_bind = PatternBinding.new(
           instrument: :zaps,
           dur: 1/4,
           amp: PatternSequence.new(ride_amps, INFINITY)
         )
p_bind.play(main_clock)

In other words, the code sets up a pattern binding with the “zaps” instrument, passes it a sequence of amplitudes based on repeating our ~rideAmps array an infinite number of times, and sets a duration of one quarter note — which brings in music theory issues that are unfortunately a bit outside the scope of this blog post. The good news: in practice, you can scrape by for a very long time with the assumption that the duration should always be a quarter note. It’s not exactly true, but it’s close enough.

Instead of stressing out about that, let’s look at what this “zaps” thing is. That’s defined in synths.scd:

SynthDef("zaps", { arg amp = 1, modFreq = 15, carFreq = 880, modDepth = 3250;
  var car, mod, env;

  env = Env.perc(releaseTime: 0.3).kr(doneAction: 2);
  mod = Pulse.ar(freq: modFreq * [1, 1.14, 0.97, 6, 7, 8, 9, 10, 1.04, 1.2], mul: modDepth);
  car = Pulse.ar(freq: carFreq + mod * [1, 1.41, 0.99, 2.4921, 5, 6, 1.397], mul: env);
  car = Splay.ar(car);
  Out.ar(0, FreeVerb.ar(HPF.ar(car, freq: 5274), mix: 0.05, room: 0.1, damp: 0.9, mul: amp * 0.5));
}).add;

Everything passed in via the curly braces at the beginning is basically a function. The arg amp = 1, modFreq = 15 section sets up a list of named keyword arguments to the function, along with their default values. The name “zaps” and the .add at the end ensure that SuperCollider adds them to some kind of in-memory synth registry, which means you can audition the sound later just by evaluating the following code:

Synth("zaps")

In practice, this is very useful.

I created the “zaps” sound while attempting to copy the synthesis of ride cymbals in the classic TR-808 drum machine. I used two articles from Sound On Sound, the British audio engineering magazine, as my guides. It didn’t really work, but it still sounds cool. The two Pulse objects are supposed to function as modulator and carrier for FM synthesis. There’s also a high-pass filter and some reverb.

Here’s what the zaps instrument sounds like, playing this pattern:

To Be Continued

This seems like a decent stopping point for now. In the next post, I explain more about how the “zaps” sound works, but I start with an explanation of the simplest drum in the beat, and build towards the zap sound’s complexity from there.