Church bell

A church bell in C.

Log in to post a comment.

// A simple bell sound


// Partial frequencies of the bell
// Adapted from https://en.wikipedia.org/wiki/Bell#Bell_tuning
// in semitones relative to middle C
var partials = [-12,0,3,7,12,16.5,17,19,20.8,23,24.5,26,26.5,26.25,27.5,28.25,29,30,30.8];

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});


// Add a delayline for a slight Doppler/chorus effect due to the bell's side-to-side motion

// Delayline adapted from srtuss
class Delayline {
    constructor(n) {
        this.n = ~~n;
        this.p = 0;
        this.lastOut = 0;
        this.data = new Float32Array(n);
    }
    clock(input) {
        this.lastOut = this.data[this.p];
        this.data[this.p] = input;
        if(++this.p >= this.n) {
            this.p = 0;
        }
    }
    tap(offset) {
        var x = this.p - offset - 1;
        x %= this.n;
        if(x < 0) {
            x += this.n;
        }
        // Add linear interpolation
        let ix = Math.floor(x), fx = x%1;
        return (1-fx) * this.data[ix] + fx * this.data[(ix+1)%this.n];
    }
}


// A variable delay
const oscDelay = filter.def(class {
    constructor(options) {
        this.n = Math.floor(options.maxDelay / ditty.dt);
        this.dline = new Delayline(this.n);
        this.phase = options.initPhase;
    }

    process(input, options) {
        this.phase += 6.28 * options.lfoHz * ditty.dt;
        let x = 0.5 + 0.4 * Math.cos(this.phase); // current delay
        x *= this.n; // current delay in samples
        
        this.dline.clock(input[0]);
        let sig = this.dline.tap(x);
        
        return [sig,sig];
    }
}, { maxDelay: 6e-3, initPhase: 0, lfoHz: 0.3});


loop((i) => {
    if(i==0) {
        sleep(1);
    }
    if(i < 10) {
        for(let j=0; j<partials.length; j++) {
            let r = Math.random();
            let a0 = 0.1 * 2**(-0.6*partials[j]/12);
            recurrentSine.play(69+partials[j], {duration:a0*100, curve:-10, amp:(t)=>a0*Math.cos(t+6.3*r)});
        }
    }
    sleep(5 + 0.3*Math.random());
}).connect(oscDelay.create());