Risset Bell in SuperCollider

Update: Moved the code into a pair of gists. (2013-07-05)

Trying to get my feet wet with SuperCollider, I decided to implement a version of Risset’s bell using additive synthesis. There’s an example in the help files for Pd (also described on Miller Puckette’s site) that I used for reference.

Here’s my first take. I defined a Synth, and broke out a function to handle each partial for the additive synthesis. Env.perc makes a great envelope shape for a bell. I’m taking all the details–the tunings, relative amplitudes, and relative durations–from the Pd example.

The biggest trouble spot I ran into was the doneAction argument on EnvGen.kr. The doneAction is “an integer representing an action to be executed when the env is finished playing.” The common choice here would be 2, which means that the server should free the enclosing Synth as soon as the envelope has completed.

When I used a 2 I observed that the sound would cut off abruptly while still playing. It took me a bit to realize what was going wrong, but it eventually dawned on me that once the shortest partial had finished (remember, they each have different durations), it was triggering the doneAction and the server was freeing the entire Synth including all the other partials that I wanted to be still sounding.

To prove my hunch, I changed the doneAction argument to 0, or “do nothing”. Sure enough, the bell played all the way to completion. But that also left a Synth and a bunch of UGens sitting around on the server. These would continue to build up with every strike of the bell. Obviously this wasn’t acceptable.

    partials = Array.new(11);

    addPartial = { |amplitude, rel_duration, rel_freq, detune|
                    Env.perc(0.05, dur*rel_duration, amplitude*0.1, -4), 
                    doneAction: 0
                ) * SinOsc.ar(freq*rel_freq+detune)

    addPartial.(1, 1, 0.56, 0);
    addPartial.(0.67, 0.9, 0.56, 1);
    addPartial.(1, 0.65, 0.92, 0);
    addPartial.(1.8, 0.55, 0.92, 1.7);
    addPartial.(2.67, 0.325, 1.19, 0);
    addPartial.(1.67, 0.35, 1.7, 0);
    addPartial.(1.46, 0.25, 2, 0);
    addPartial.(1.33, 0.2, 2.74, 0);
    addPartial.(1.33, 0.15, 3, 0);
    addPartial.(1, 0.1, 3.76, 0);
    addPartial.(1.33, 0.075, 4.07, 0);

            Env.perc(0.05, dur, curve: -4),
            doneAction: 2
        ) * Mix.new(partials)


a = (
type: \note, instrument: \risset_bell, freq: 500, dur: 10.0 ); a.play; // play the note

After some searching I came upon a solution in Mark Polishook’s tutorial. He suggests wrapping the entire series of partials in an envelope, and put the done action on that overarching envelope. To make this happen I had to do a bit of refactoring: I threw the partials into an array and then mixed them down inside of the overlapping envelope. I left the doneAction of 0 on the individual partials, and gave the new envelope a doneAction of 2.

This was just the trick I needed. The bell rings loud and clear all the way to the end, and all the pieces are neatly cleaned up afterwards.