Additive Synthesis with Snd and Supercollider


Published on 2023-07-07 by Kenneth Flak

Back to Tech Research


Table of Contents

The Snd Editor and Scheme

I have been fascinated by the insanely powerful wave editor snd for a long time, but never found the time to dive into it and learn it properly. First of all, it's modelled on emacs, which I am not very familiar with, and, secondly, I would also have to learn the programming language Scheme to get the most out of it. The Scheme syntax is very simple, but also very counter-intuitive if you are used to working in a one of the many languages using C-like syntax. For example, if you want to do a simple addition in SuperCollider, you would do:

1 + 1; // => 2

In Scheme, the same thing is written like this:

(+ 1 1) ; => 2

There are a bunch of advantages to the Scheme approach: to sum up n numbers, you can just add them to the list:

(+ 1 2 3 4 5 6 7 8)

No need for this kind of stuff:

1 + 2 + 3 + 4 + 5 + 6 + 7 + 8;

Of course, in SuperCollider I would rather do:

(1..8).sum;

But that's besides the point right now. Suffice to say that experienced Schemers claim near spiritual experiences from digging deep enough into the language, and I will definitely want to learn more about it in the near future.

Analysing the Sound

Nuff procrastination, let's get to the point: using snd to create some lovely sounds in SuperCollider! I started out by recording a trumpet playing a concert Bb. The raw waveform looks like this:

Raw waveform of the trumpet note

If you uncheck the little w down in the left corner and check the f, you will get an FFT view of the sound. In order to get what we need for this particular exercise, you will also have to go to Options->Transform options. Change type to Fourier, size to 8192 or something in that area, depending on how precise resolution you need. Under the display section tick the sonogram and db boxes. You will probably also want to change the spectrum start/end to zoom in on the relevant frequency ranges. Depending on your sound you might also want to untick the log freq option in the display section to get a linear view of the frequencies.

This is the result:

Spectral analysis of the note

Cool. This is a nice, graphical representation of the sound, but how does it help us in recreating it? Enter the verbose cursor! If you have set this option in the snd preferences (Options->Preferences->cursor options->report cursor location as it moves) you can click anywhere in the display and get a reasonably precise estimate of the frequency and amplitude at that point. So all you have to do is to click on the different bands at their darkest points, and you can get an overview of the harmonic structure of the sound you have recorded. Extremely useful!

EDIT 26 July 2023: Extracting the data programmatically

After writing this blog post I started digging into whether there's a better way to do this than fiddling around with the mouse like some cave-dwelling computer illiterate. Turns out there is! What's more, it's relatively simple, once you know how:

  1. Select the snippet you want to analyze and export it to a new sound.
  2. Check the f box to get an fft analysis.
  3. Set the transform options to Fourier and sonogram
  4. Issue the command (peaks "peaks.txt") in the Listener.

Voila! You just exported the FFT peaks to a text file in your home directory. Now you can use these in the next section, just leave out the peaks that are too small or below and above certain frequencies. You definitely don't want the lowest bins, and probably anything above 10.000 hz will not be too useful either.

Building a SynthDef in SuperCollider

Let's put this into use to create something nice in SuperCollider. The aim of this is not to recreate the recordings in all its details, but rather to use it as a starting point for some nice, usable, additive synthesis with a vague resemblance to the original sound. Synthesising a trumpet sound using only sine waves is possible, but not particularly interesting, especially not if you have access to the real thing. You would also have to use an inordinate amount of oscillators and some very, very clever programming to get a life-like result.

With that out of the way, let's get to it!

s.boot; // make sure the server is running

(
SynthDef(\additiveBrass, {
    var freq = \freq.kr(440);
    
    // The frequencies of the first 11 harmonics
    var baseFreqs = [
        233, 480, 717, 957, 1190, 1423, 1652, 1889, 2119, 2362, 2835
    ];

    // Calculate the ratios. We want to preserve our harmonic structure
    // no matter which pitch we play back the synth.
    var freqs = baseFreqs.size.collect({|i|
        freq * (baseFreqs[i] / baseFreqs[0]);
    });

    // Modulate the frequencies a little bit. Nobody likes a static sine
    // wave.
    var freqMod = freqs.collect({
        SinOsc.kr(rrand(0.01, 0.1)).range(0.99, 1.01)
    });

    // the amplitudes of the first 11 harmonics
    var amps = [
        -33, -29, -30, -32, -32, -37, -37, -44, -52, -49, -60
    ].dbamp;

    // modulate these as well.
    var ampMod = amps.collect({|i, idx|
        SinOsc.kr(rrand(0.01, 0.1)).range(0.5, 1.0); 
    });

    // Overall volume envelope
    var env = Env.asr( 
        \attack.kr(0.01), 1, \release.kr(0.01)
    ).kr(
        gate: \gate.kr(1), doneAction: \da.kr(2)
    );

    var amp = amps * ampMod * 6.dbamp * \amp.kr(0.dbamp) * AmpCompA.kr(freq) * env;

    var sig = SinOsc.ar(freqs * freqMod) * amp;

    // Making sure the fundamental is in the middle of the stereo image.
    // Step 1: separate it from the rest
    var fundamental = sig[0];
    // Step 2: scramble the overtones, so that they can exist anywhere in
    // the stereo image.
    var overtones = sig[1..];
    overtones = overtones.scramble;
    // Step 3: mix them together again.
    sig = fundamental + overtones;
    // Step 4: mix down the resulting 11 channels to 2 channels. Read up
    // on multichannel expansion in SuperCollider if the reason for this
    // is unclear
    sig = Splay.ar(sig);

    Out.ar(\out.kr(0), sig);
}).add;
)

Creating an Ambient Soundscape

We're going to use the Pattern and JitLib libraries in SuperCollider to create a nice and soothing soundscape. Enjoy!

(
// set up the playback algorithm
Pdef(\additiveBrass,
    Pbind(
        \instrument, \additiveBrass,
        \scale, Scale.minorPentatonic,
        \degree, Pwhite(0, 4),
        // I use the Pwrand object to select randomly from a list
        // with a weighted probability. In this case the lower octaves
        // have a higher probability of occuring.
        \octave, Pwrand((2..5), [0.5, 0.4, 0.3].normalizeSum, inf),
        \attack, Pwhite(10, 20),
        \release, Pkey(\attack) * 2,
        \dur, Pkey(\attack),
        \legato, 1,
        \db, Pwhite(-6, 0),
    )
);
// A great way of setting up an effect chain for a Pdef that I learned 
// from Fredrik Olofsson https://fredrikolofsson.com/. 
// Step 1: Wrap the Pdef in an Ndef instead of playing it back directly.
Ndef(\additiveBrass, Pdef(\additiveBrass)).play;
// Step 2: use the Ndef.filter method to apply some signal processing on
// the Pdef's output.
Ndef(\additiveBrass).filter(2, {|in|
    var verb, mix=0.3, sig;
    sig = in;
    // The JPverb is a part of SC3-plugins. Highly recommended.
    verb = JPverb.ar(in, size: 1, t60: 2);
    sig = (1 - mix) * sig + (verb * mix);
})
)