August 5, 2019

SuperCollider tutorial: Easily render generative compositions as sound files using NRT

alt

One of the many powerful features of SuperCollider is it’s ability to render sounds offline. This is called Non-Realtime Synthesis (NRT). NRT is for example useful for fast, offline processing of sounds, doing sound analysis or rendering generative compositions.

NRT works like this (normally): First you write a list of server OSC messages (stored in a Score usually) which will tell the (offline) server what to do at what point in time when you decide to render it. These are in the format [beat, [osc_command]]. An example: Making a Synth using synthdef \boring_sine at beat number 2 looks like this in such a OSC form: [2.0, [\s_new, \boring_sine, 1001, 0, 0]]

All of the possible server osc message commands are documented here: Server Command Reference

Creating such a list manually is naturally time consuming and does not feel very smart. It is also a very awkward way of writing music.

Fortunately, there are ways of doing this in a more efficient and musical way.

Recording event patterns

One of my favourite techniques is to simply convert Event Patterns (such as Pbind, Pmono, etc.) to scores using the .asScore method.

The process can be divided into the following steps:

  1. Make a SynthDef and store it on your system using .store

  2. Write an event pattern

  3. Convert the event pattern to a Score object using .asScore

  4. Render the Score to a sound file on your system

First step is to make a SynthDef. SynthDefs are sort of recipes for sound patches that the server uses to make sound. In this case, it will be a very boring sine, aptly named \boring_sine. Note the use of the .store method here. This will save the synthdef as a file on your system and make it available to the NRT process later on.

SynthDef.new(\boring_sine, {|freq, dur|
// A percussive envelope
var env = EnvGen.ar(Env.perc, gate: 1, timeScale:dur);

// A sine wave
var sig = SinOsc.ar(freq);

// Apply envelope to sine wave and output
Out.ar(0, env * sig)
}).store;

We will keep the pattern super simple: Random scale degrees played using our \boring_sine synth, each of which a quarter of beat in duration.

The total duration of the pattern will be infinite for now (the length of this will automatically be truncated by the Score conversion process).

p = Pbind(
\instrument, \boring_sine,
\dur, 0.25,
\degree, Pwhite(0,10)
);

And now, let us convert this to a score:

p = p.asScore(60); // Duration of 60 beats

Finally, we render the score

(
// Destination path and file name
~outFile = "~/Desktop/yo.wav";

// Render the score as wav file
p.recordNRT(outputFilePath: ~outFile.asAbsolutePath, headerFormat: "WAV");
)

Lets make this interesting: Iteration

Rendering one random melody is quite nice, but let us exploit the fact that our pattern chooses random scale degrees every time we play it and combine that functionality with iteration to make 10 (or any number) of rendered random melodies.

First, let us wrap what we wrote up until this point in a function that we can call as often as we want.

~renderMelody = { |pattern, destinationPath|

// Convert pattern to score
var patscore = pattern.asScore(60); // Duration of 60 beats

// Render the score as a wav file
patscore.recordNRT(outputFilePath: destinationPath.asAbsolutePath, headerFormat: "WAV");
};

Let us keep the pattern as is

p = Pbind(
\instrument, \boring_sine,
\dur, 0.25,
\degree, Pwhite(0,10)
);

And then render 10 versions of it

// Now make 10 different melodies and save them to your desktop as wave files
10.do{|index|
~renderMelody.value(p, "~/Desktop/boringMelody" ++ index ++ ".wav");
};
#tutorial #supercollider