using generalized Fibonacci numbers + Hadamard rotation
Log in to post a comment.
// Artificial reverberation based on the technique shown here:
// http://msp.ucsd.edu/techniques/latest/book-html/node111.html
// TODO Add non-recirculating delays for the early reflections
// TODO Add filters to increase high-frequency damping
input.reverbMix = 0.7; // min=0, max=1, step=0.01
input.reverbDuration = 8; // min=0.1, max=10, step=0.1
class Delayline {
constructor(n) {
this.n = n;
this.p = 0;
this.data = new Float32Array(n);
}
current() {
return this.data[this.p]; // using lastOut results in 1 sample excess delay
}
clock(input) {
this.data[this.p] = input;
if(++this.p >= this.n) {
this.p = 0;
}
}
}
const ISQRT2 = Math.sqrt(0.5);
const SQRT2 = Math.sqrt(2);
const SQRT8 = Math.sqrt(8);
const ISQRT8 = 1/SQRT8;
// const fibodelays = [1410,1662,1872,1993,2049,2114,2280,2610]; // X^8 = X+1
const fibodelays = [1467,1691,1932,2138,2286,2567,3141,3897]; // X^8 = X+2
const fiboshort = [465, 537, 617, 698, 742, 802, 963, 1215]; // X^8 = X+2
let meandelay = fibodelays[3] * ditty.dt;
const reverb = filter.def(class {
constructor(options) {
this.outgain = 0.3;
this.dls = [];
this.s0 = new Float32Array(8); // Lowpass filter memory
for(let i=0; i<8; i++) {
this.dls.push(new Delayline(fibodelays[i]));
this.s0[i] = 0;
}
this.a0 = clamp01(2*Math.PI*options.cutoff*ditty.dt);
this.dls_nr = []; // Non-recirculating delay lines
for(let i=0; i<4; i++) {
this.dls_nr.push(new Delayline(fiboshort[i*2]));
}
}
process(input, options) {
// First stage: non-recirculating delay lines
let s0 = input[0], s1 = input[1];
for(let i=0; i<4; i++) {
let u0 = 0.6*s0 + 0.8*s1, u1 = 0.8*s0 - 0.6*s1;
let u1p = this.dls_nr[i].current();
this.dls_nr[i].clock(u1);
s0 = u0;
s1 = u1p;
}
let v0 = (s0 + s1);
let v1 = (s0 - s1);
//let s0 = -0.6*input[0] + 0.8*input[1];
//let s1 = 0.8*input[0] + 0.6*input[1];
// Second stage: recirculating delay lines
let rt60 = options.rt60;
let loopgain = 10 ** (-3*meandelay / rt60) * ISQRT8;
let higain = 10 ** (-3*meandelay / options.rtHi) * ISQRT8;
let v = this.dls.map( (dl) => dl.current());
// Fast Walsh-Hadamard transform
// https://formulasearchengine.com/wiki/Fast_Walsh%E2%80%93Hadamard_transform
let w = [v[0]+v[4], v[1]+v[5], v[2]+v[6], v[3]+v[7],
v[0]-v[4], v[1]-v[5], v[2]-v[6], v[3]-v[7]];
let x = [w[0]+w[2], w[1]+w[3], w[0]-w[2], w[1]-w[3],
w[4]+w[6], w[5]+w[7], w[4]-w[6], w[5]-w[7]];
let y = [x[0]+x[1], x[0]-x[1], x[2]+x[3], x[2]-x[3],
x[4]+x[5], x[4]-x[5], x[6]+x[7], x[6]-x[7]];
y[0] += v0;
y[2] += v1;
for(let i=0; i<8; i++) {
let hipass = y[i] - this.s0[i];
this.dls[i].clock(this.s0[i] * loopgain + hipass * higain); // Mix lowpass and hipass in different amounts
this.s0[i] += this.a0 * hipass;
}
return [lerp(input[0], v[0], options.mix),
lerp(input[1], v[2], options.mix)];
}
}, {mix:0.2, rt60:1, cutoff:5000, rtHi:0.8});
const impulse = synth.def( (phase, env, tick, options) => (tick <= 1.5 * ditty.dt) ? 1.0 : 0.0);
const noise = synth.def( (phase, env) => (Math.random() - 0.5)*env.value, {env:adsr, attack:0.01, release:0.1, amp:0.1});
ditty.bpm = 60;
// Impulse response
/*
loop( () => {
impulse.play();
sleep(1);
}).connect(reverb.filter({mix:() => input.reverbMix, rt60:() => input.reverbDuration}));
*/
loop( () => {
noise.play(c, {pan:Math.random()*2-1});
sleep(1);
}, {name:"noise"}).connect(reverb.create({mix:() => input.reverbMix, rt60:() => input.reverbDuration}));
// Not quite a sine wave
const osc = synth.def((phase, env) => {
let x = (phase % 1);
return 20*x*(1-x)*(x-0.5) * env.value;
});
const mynotes = scale(e4, scales["aeolian"]);
loop( () => {
let nn = mynotes.choose() + (12 * Math.floor(Math.random()*2 - 0.2));
let drop = 2*Math.random();
osc.play((tick) => nn + drop / (50*tick+1), {atk:0.01, rel:0.1, pan:Math.random()*2-1, amp:0.3});
sleep(Math.random() * 0.5);
}, {name:"drops"}).connect(reverb.create({mix:() => input.reverbMix, rt60:() => input.reverbDuration}));