Showing posts with label music synthesis. Show all posts
Showing posts with label music synthesis. Show all posts

Tuesday, February 24, 2009

Cracking Supercollider

SuperCollider is an amazing language, if you are in the super genius club who can understand the extensive documentation or pursue graduate studies at CCRMA. I've been out of audio synth for a while, but recently found myself needing to apply a recursive delay line to an audio recording I'm working on for my music project, Midday Veil.

SuperCollider dates from the mid-90's, and is thus a peer to Ruby, sharing influences like Smalltalk and Lisp. As languages go, it's pretty nice, though the compiler leaves something to be desired - inexplicably, variables must be declared before use at the top of a function, much like in C. Other than minor gripes like that, it's a pretty nifty package: functions as first-class objects, native arrays and maps, garbage collection, dynamic typing, and classical OOP features to boot. However, there's a lot more to the platform than just the language, and the learning curve is quite steep. Much like Ruby, some of the syntax is "optional" or has multiple ways to express the same thing; functions need a "value" message sent to them to be executed. Also, most of even the basic language-level concepts are mixed in with advanced platform-specific concepts like OSC messaging, client-server internals, audio buffers, and so on, so getting your feet wet takes a lot of immersion in the concepts to understand SuperCollider's terse yet expressive syntax and impressive power. Getting started with Processing, which uses a much uglier language, is a cakewalk by comparison.

It doesn't help that the "introductory tutorial" launches first thing into a detailed description of how to use the platform to send OSC messages over the Internet, then SynthDefs, then SynthDescLibs, and then instantiating both them and their UDP equivalents, none of which, it turns out, are required to get started, when you really just want to figure out how to play a sound file from your hard disk and feed it through some reverb. Oh no, you don't get to that until you've drudged through page after page of detailed docs, explaining each concept in depth before moving on to the next incremental step. Memo to documentation writers: people (especially people making music) need to be able to sit down and play something after their first lesson. You don't come home from your first piano lesson with a stack of reading on Cristofori and the development of the modern soundboard, told that maybe you can start playing after you've written a 200-page treatise on the inner mechanics of the thing. Oh sure, that's all useful to understand before too long, but to start off you want to learn just enough of a subset of the thing to feel like you can begin to be creative with it. For an example of just how jargony the whole thing feels, I treat you to an excerpt from page 3 of the docs:

When notated as code in client programs, the engines have two essential parts: a name and a function. In the jargon of SuperCollider, the function is called a ugenGraphFunc. The term ugenGraphFunc derives from the notion of a graph, which is the data structure that SuperCollider uses to organize unit generators. SuperCollider constructs the graph for you from the code it finds in your function which means that don't have to know how a graph works or that it even exists.

Ok, so basically it's a parse tree with a funny name. I assumed something like that was under the hood, but if I don't have to know this, why the hell are you telling me on page 3 of the documentation?

With that in mind, I present to you my first hard-won miniature piece of software in SuperCollider, which I wrote from scratch with scant help from the docs:

(
 r = { |input, gain, decayTime = 0.5, decayFactor = 0.9|
 var output, buf;
 if (gain > 0.1, {
         buf = Buffer.alloc(s, 44100, 2);
         output = input + r.(BufDelayC.ar(
             buf,
             input,
             decayTime,
             gain
             ), gain * decayFactor)
     }, {
         output = 0
     });
 output;
 }
)


(
 var filepath = "remember-pans-cropped.aiff";
 var synth = Buffer.cueSoundFile(s, filepath, 0, 2);
 {
 r.value(DiskIn.ar(2, synth), 1, 0.5, 0.9);
 }.play;
)

Ok, it ain't much, but it's mine and I'm proud of it. I'll probably look at that example a few months from now and cringe knowing all the mistakes I just made, but know what? Hell with it. This is music, and if you leak a bit of memory here or there on your own machine before you learn how to fix it, well, that's not too steep a price for some wicked tunes. Start with what you want, then learn just enough to get yourself there.