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