// 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());