Sine with recurrence formula

It is possible to use a recurrence formula to compute sine waves efficiently. One multiplication per sample is required for a normal sine wave, two for a decaying one.
This used to be a significant time-saving technique for early computer music ; nowadays I think it has mainly fallen out of use.

Log in to post a comment.

// This synth calculates the signal
//
//      s[n] = exp(-nγT) sin(nωT),
// 
// where
//      n is the sample number,
//      T = ditty.dt is the sampling period,
//      ω = 2πf is the angular frequency,
//      γ is the decay speed,
// using the following recurrence formula:
//
//      s[n+1] = 2 exp(-γT) cos(ωT) s[n] - exp(-2γT) s[n-1].
//
// Assuming ω and γ are constant, the coefficients can be precomputed,
// thus only two multiplications are needed for each sample.
// Moreover if there is no decay, meaning that
//
//      γ = 0
// 
// then the recurrence formula can be simplified to
//
//      s[n+1] = 2 cos(ωT) s[n] - s[n-1].
//
// In that case, only one multiplication is needed for each sample.
// The recurrence formula can be derived by using the trigonometric formula:
//
//      sin(a+b) = sin(a) cos(b) + cos(a) sin(b).
//

const recurrentSine = synth.def(class {
    constructor(options) {
        const freq = midi_to_hz(options.note);
        const omega = 2 * Math.PI * freq;
        const gamma = 7/options.duration; // --> 60 dB decay in "duration"
        const T = ditty.dt;
        this.c1 = 2 * Math.exp(-gamma * T) * Math.cos(omega * T);
        this.c2 = Math.exp(-2 * gamma * T);
        // Initial conditions:
        //      s[-1] = exp(γT) sin(-ωT),
        //      s[0] = 0,
        this.s_nm1 = Math.exp(gamma*T) * Math.sin(-omega*T);
        this.s_n = 0;
    }
    process(phase,env,tick,options) {
        const s_np1 = this.c1 * this.s_n - this.c2 * this.s_nm1;
        this.s_nm1 = this.s_n;
        this.s_n = s_np1;
        return s_np1;
    }
}, {env:one, duration:3, amp:0.1});

//const nn = [c4,d4,e4,f4,g4,a4,b4,c5];
const nn = [ab2,eb3,ab3,bb3,c4,eb4,f4];
loop( (i) => {
    let octave = Math.floor(3*Math.random()) * 12;
    recurrentSine.play(nn.choose() + octave, {duration:5, pan:2*Math.random()-1});
    sleep(Math.random() + 0.5);
});