// athibaul's reverb filter with added modulation class Delayline { constructor(n) { this.n = n; this.p = 0; this.data = new Float32Array(n); } get end() { return this.data[this.p]; } tap(offset) { var x = this.p + offset; x = ~~x; x %= this.n; return this.data[x]; } sample(offset) { if(offset < 0) offset = 0; var p = offset; var pi = ~~p; return lerp(this.tap(pi), this.tap(pi+1), p-pi); } clock(input) { var end = this.data[this.p]; this.data[this.p] = input; if(++this.p >= this.n) { this.p = 0; } return end; } } const ISQRT2 = Math.sqrt(0.5); const SQRT2 = Math.sqrt(2); const SQRT8 = Math.sqrt(8); const ISQRT8 = 1/SQRT8; const fibodelays = [1467,1691,1932,2138,2286,2567,3141,3897]; const fiboshort = [465, 537, 617, 698, 742, 802, 963, 1215]; let meandelay = fibodelays[3] * ditty.dt; const reverb = filter.def(class { constructor(options) { this.outgain = 0.3; this.predl = [new Delayline(1000), new Delayline(1000)]; this.dls = []; this.s0 = new Float32Array(8); for(let i=0; i<8; i++) { this.dls.push(new Delayline(fibodelays[i])); this.dls[i].i = i; this.s0[i] = 0; } this.a0 = clamp01(2*Math.PI*options.cutoff*ditty.dt); this.dls_nr = []; for(let i=0; i<4; i++) { this.dls_nr.push(new Delayline(fiboshort[i*2])); } } process(inpt, options) { let s0 = this.predl[0].clock(inpt[0]), s1 = this.predl[1].clock(inpt[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].end; this.dls_nr[i].clock(u1); s0 = u0; s1 = u1p; } let v0 = (s0 + s1); let v1 = (s0 - s1); let rt60 = options.rt60; let loopgain = 10 ** (-3*meandelay / rt60) * ISQRT8; let higain = 10 ** (-3*meandelay / options.rtHi) * ISQRT8; let mod = 10; let v = this.dls.map( (dl) => dl.sample((1 + Math.sin(ditty.tick * (3 + dl.i * 2))) * mod)); 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]; let lopass = this.s0[i]; this.dls[i].clock(lopass * loopgain + hipass * higain); this.s0[i] += this.a0 * hipass; } return [lerp(inpt[0], v[0], options.mix), lerp(inpt[1], v[2], options.mix)]; } }, {mix:1, rt60:10, cutoff:20000, rtHi:0.9}); ditty.bpm = 140; class SVF { constructor() { this.b = this.l = this.h = 0; } process(input, f, r) { this.h = input - this.b * (1-r) - this.l; this.b += this.h * f; this.l += this.b * f; return this; } } class SVF2 { constructor() { this.a = new SVF(); this.b = new SVF(); } process(input, f, r) { return this.b.process(this.a.process(input, f, r).l, f, r).l; } } input.tune = .73; // min=.5,max=2,step=.01 class Hihat { constructor() { this.osc = [245, 306, 365, 415, 437, 619]; this.oscp = []; this.osc.forEach(o => this.oscp.push(Math.random())); this.bp = new SVF(); this.hp = new SVF(); } process(f, env, t, o) { let v = 0; let tun = input.tune * ditty.dt; for(let i = 0; i < this.osc.length; ++i) { this.oscp[i] += this.osc[i] * tun; while(this.oscp[i] >= 1) this.oscp[i] -= 1; v += this.oscp[i] > 0.5 ? .16 : -.16; } v = this.bp.process(v, .5, .5).b; v += Math.random() * .2; v = clamp(v*2, -.2, .2) * env.value; v = this.hp.process(v, .5, .0).h; return v; } } let hihat = synth.def(Hihat); class Bassdrum { constructor(opt) { this.ph = 0; this.flt = [new SVF(), new SVF(), new SVF()]; this.freq = midi_to_hz(opt.note); this.body = 0; } process(note, env, tick, opt) { let tattack = .1; let fattack = .1; let attack = clamp01((tattack - tick) / tattack); this.body = Math.sin((this.ph+this.body*-0.0) * Math.PI * 2); let v = this.body; this.ph += ditty.dt * (this.freq + attack * 100); //v *= (.5 + Math.random() * .5); //v += Math.random(); let attack2 = clamp01((fattack - tick) / fattack) * opt.velocity; v = this.flt[0].process(v, .5, attack2 * .2 + .025).l; v += (Math.random()-.5) * attack2 * .1; v = clampWave(v * clamp(env.value, 0., 1) * 1.2); this.t += ditty.dt; return v * opt.velocity; } } const kick = synth.def(Bassdrum, { attack: 0.001, decay: .1, sustain: .2, release: 0.1, velocity: 1, duration: .5 }); loop( (lc) => { kick.play(c2); sleep(1); kick.play(c2); sleep(1); kick.play(c2); sleep(1); if(lc % 2) { kick.play(c2); sleep(.5); kick.play(c2); sleep(.5); } else { kick.play(c2); sleep(1); } }, { name: 'k' }); loop( () => { hihat.play(f4, { attack: 0.001, release: 0.05 }); sleep(.25); hihat.play(f4, { attack: 0.001, release: 0.02 }); sleep(.25); hihat.play(f4, { attack: 0.001, release: .1, duration: .1 }); sleep(.25); hihat.play(f4, { attack: 0.001, release: 0.02 }); sleep(.25); }, { name: 'h' }); const wrap01 = p => p < 0 ? p % 1 + 1 : p % 1; function softclip(x) { return x<-1?-1:x>1?1:1.5*(1-x*x/3)*x; } function varsaw(p, formant) { let x = wrap01(p); return (x - 0.5) * softclip(formant*x*(1-x)); } let bob = synth.def((p,e,t,o) => varsaw(p + o.phase, ditty.sampleRate / midi_to_hz(o.note) * (o.c * Math.exp(-t * 20) + .01)) * e.value); let saw = synth.def((p,e,t,o) => varsaw(p + o.phase, ditty.sampleRate / midi_to_hz(o.note)) * e.value); function softclip(x) { return x<-1?-1:x>1?1:1.5*(1-x*x/3)*x; } class Unison { constructor(opt) { this.tupd = 0; this.semitone = .2; let n = 16; this.phas = new Float32Array(n); this.mul = [new Float32Array(n), new Float32Array(n)]; this.dt = new Float32Array(n); let a = 2 ** (n * -.02 - 1); for(let i = 0; i < n; ++i) { this.phas[i] = Math.random(); let t = i / (n-1); let x = 1 / Math.sqrt(1+2*(t*t-t)); this.mul[0][i] = t * x * a; this.mul[1][i] = (1-t) * x * a; } } process(note, env, tick, opt) { if(this.tupd <= 0) { this.tupd += 1; this.freq = midi_to_hz(note) * ditty.dt; for(let i = 0; i < this.phas.length; ++i) { let mul = 2 ** (Math.sin(ditty.tick*4+i)*this.semitone / 12); this.dt[i] = this.freq * mul; } } this.tupd -= ditty.dt * 60; let vl = 0, vr = 0; for(let i = 0; i < this.phas.length; ++i) { let x = this.phas[i]; let v = (x - .5) * softclip(1 / this.dt[i] * x * (1-x)); //v += Math.sin(v * Math.PI * (4 - tick) * 8) * .1; vl += v * this.mul[0][i]; vr += v * this.mul[1][i]; this.phas[i] = (this.phas[i] + this.dt[i]) % 1; } return [vl * env.value, vr * env.value]; } }; class Synth { constructor(opt) { this.osc = new Unison(opt); this.svf = [new SVF2(), new SVF2()] } process(note, env, tick, opt) { let s = this.osc.process(note, env, tick, opt); s[0] = this.svf[0].process(s[0], .7 * Math.exp(-tick * 4) + .01, 0); s[1] = this.svf[1].process(s[1], .7 * Math.exp(-tick * 4) + .01, 0); return s; } } let bass = synth.def(Unison); let lead = synth.def(Synth); loop((lc) => { let bar = [a0,a0,a0,a0,a0,a0,a0,b0,c1,c1,c1,c1,g0,g0,g0,g0]; let pat = 0xA4; let n = bar[(lc>>1)&15] + 12-4 + 12 * (1&(pat>>(lc&7))); bob.play(n, {phase: Math.random(), duration: .1 + Math.random() * .1, release: .01, c: Math.sin(lc*.1) * .4 + .5, pan: -1}); bob.play(n, {phase: Math.random(), duration: .1 + Math.random() * .1, release: .01, c: Math.sin(lc*.1) * .4 + .5, pan: 1}); sine.play(n, { duration: .2, release: .01, amp: .5}); sleep(.25); }, {amp: .8}); loop( (lc) => { let bar = [a0,a0,a0,a0,a0,a0,a0,b0,c1,c1,c1,c1,g0,g0,g0,g0]; sleep(.5); let n = bar[(lc*2)&15]+24-4; bass.play(n, { attack: .01, amp: 1, duration: .2, release: .1 }); sleep(.5); }, { name: 'multi2', amp: 1.5 }).connect(reverb.create({mix: .1})); loop( (lc) => { const seq1 = { x:[.5,,1,.5,1.5,.5,.5,.25,.75,.5,.75,.75,.5,.5,,1,.5,1.5,.5,.5,.25,.75,.5,.25,.25,.5,.5,.5], d:[.281,.073,.865,.271,.354,.219,.219,.281,.406,.24,.531,.354,.354,.281,.073,.865,.271,.354,.219,.219,.281,.406,.24,.354,.354,.354,.354,.354], v:[.76,.67,.84,.72,.79,.8,.75,.72,.76,.76,.84,.84,.84,.76,.67,.84,.72,.79,.8,.75,.72,.76,.76,.84,.84,.84,.84,.84], tp:-30-4, p:"jnomjjhjhfcagjnomjjhjhfcefcg", }; const seq2 = { x:[.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.135,.115,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.25,.135,.115,.25,.25], d:[.115,.115,.115,.115,.115,.115,.115,.115,.115,.115,.115,.115,.115,.115,.115,.115,.115,.177,.115,.177,.115,.177,.115,.177,.219,.177,.219,.177,.115,.073,.073,.115,.177,.115,.115,.115,.115,.115,.115,.115,.115,.115,.115,.115,.115,.115,.115,.115,.115,.115,.177,.115,.177,.115,.177,.115,.177,.219,.177,.219,.177,.115,.073,.073,.115,.177], v:[.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84,.84], tp:-30-4, p:"jcochcjcgchcafchjfofhfjffjhjjmmfoocochcjcgchcmecojfofhfjffjhjjmmfo", }; const seq = [seq1, seq1, seq2, seq2][lc % 4]; for (let i=0; i < seq.p.length; i++) { let n = seq.p.charCodeAt(i)+seq.tp; lead.play(n, { attack: .01, duration: seq.d[i], release: .1 }); sleep(seq.x[i]||0); } }, { name: 'multi' }).connect(reverb.create({mix: .2 }));