Background
I’ve been making electronic music on and off for what is approaching 15 years now. Throughout that journey, I’ve used a startlingly wide variety of software:
- Digital Audio Workstations (DAWs): Logic, Cubase, Ableton Live
- Trackers: Renoise
- Graphical programming / multimedia environments: Max/MSP, PureData
- Music-orientated programming languages: SuperCollider
I’ve never been fully happy with any of them though. I usually disliked the music I was making, partly because I felt I couldn’t express what I wanted to with these tools. I spent years scrapping the music I created, except for when I was forced to submit things for school assignments (my university degree was in Music Computing).
The SuperCollider Language
For actually producing sounds, I’ve always been very happy with SuperCollider. That is, once I got my bearings with it, as the learning curve is quite steep. What I like about SuperCollider compared to both VST plugins and physical synthesizers, is that I feel far less limited in what I can do. I can translate the ideas and things I hear in my head much more readily, and I’m always learning new things.
One of SuperCollider’s best features is called multichannel expansion. Passing a list as an argument to a UGen (SuperCollider’s basic sonic building blocks: oscillators, filters etc.) will expand that UGen across multiple channels, with a different argument being used for each channel. This is amazing for creating dense and layered sounds, and I could never go back to other synthesis methods without this feature. Of course, every UGen argument can be another UGen too, much like physical modular synthesizers.
Enter Common Lisp
SuperCollider has a client-server architecture:
- scsynth is a server that does the actual audio synthesis.
- sclang or other clients send OSC messages to the scsynth server, to do things like play synths.
As a result of this, it’s possible to use other programming languages to communicate with the SuperCollider server instead of sclang. This is good, because sclang has some issues. Namely, the tooling and editor support isn’t great, and the syntax can be quite verbose compared to other languages.
I’ve tried a few SuperCollider client libraries for different languages in the past few years, and the best by far experience has been cl-collider, a Common Lisp library:
- It’s actively-maintained and supports everything I’d need from SuperCollider.
- Lisp syntax is very minimal, so it doesn’t get in your way, ideal for music work.
- I use Emacs as my editor, which makes the lisp-editing experience a joy.
- The language is extensible through macros, and lends itself to creating your own domain-specific languages in general.
Here’s what one of my Common Lisp sound definitions looks like:
(p :clouds
(rand (+ 10 (* 10 (lf-noise2.ar 20))))
(trigger (impulse.ar rand))
(env (decay2.ar trigger 0.05 0.15))
(snd (mi-plaits.ar 30 1 0.3 0.7 0.5))
(snd (* snd env))
(snd (mi-rings.ar snd 0 50 0.2 0.6 :damp 0.9))
(snd (mi-clouds.ar snd 2 (lf-noise2.ar 20) 1 1))
(snd (mi-clouds.ar snd 0.5 (lf-noise2.ar 20) 0 0)))
It’s dependent on the following macro:
(defmacro p (name &body bindings)
(let* ((final-binding (car (last bindings)))
(final-name (if (consp final-binding)
(car final-binding)
(error "invalid binding"))))
`(progn
(pushnew ,name *proxy-names*)
(proxy ,name
(let* (,@bindings
(,final-name (splay.ar ,final-name)))
,final-name)))))
This is the first actually useful macro I’ve written in Common Lisp. It does the following:
- Accept a name (e.g. :clouds) and a set of “let bindings” as arguments.
- Extract the final left-hand name from the let bindings (e.g. “snd”)
- Call the PROXY function from cl-collider, passing the name and a call to LET* with my bindings, while also appending a final binding (the call to SPLAY.AR for the aforementioned final left-hand name).
The aim here is reduce as much boilerplate as possible in my sound definitions, which lisp macros are perfect for. I’m still quite new to Common Lisp, but the flexibility here makes me excited to spend more time working with it.
A Happy Medium?
While I love using SuperCollider for sound synthesis, in the past I’ve never found an approach to actually composing music with it that has yielded satisfying results. I dislike generative and algorithmic music, and I prefer to arrange my music by hand. With SuperCollider, I would spend a long time creating compelling sounds but not being able to properly tie them together into a full piece.
At a high-level, my current approach now works like this:
- I write code in lisp to create sounds via SuperCollider.
- Using BlackHole, I’m able to route audio from the SuperCollider server, to Ableton Live audio channels, which I can be recording to while modifying my lisp code.
- When I’m satisfied with some recorded audio, I’m able to slice and arrange it, stretch it and otherwise manipulate it as I see fit with all of Live’s facilities.
Over time, I may gradually start increasing the amount of compositional work that’s being automated in lisp rather than done by hand in the DAW. For now, I’m prioritising getting things done, and I feel more productive musically than I ever have.
You can listen to my latest composition for free below: