// srtuss, 2023 ditty.bpm = 60; // 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(2), new Delayline(2)]; 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}); input.pitch = 2; // min=0.25, max=12, step=0.25 const TAU = Math.PI * 2; const wave1 = p => Math.sin(p * TAU * input.pitch); const wave0 = p => Math.cos(clamp((p+10) % 1, 0, .5) * TAU * 2) - .5; const sy = synth.def((p,e,t,o) => wave1(p) * e.value); const noise = synth.def( (phase, env, tick, options) => (Math.random() * 2 - 1) * env.value, { env: adsr2 } ); const kick = synth.def( (phase, env, tick, options) => Math.sin(phase * 2 * Math.PI * (1.5 - tick * 4)) * env.value); const square = synth.def( (phase, env, tick, options) => (phase % 1 < 0.5 ? 1 : -1) * env.value, { env: adsr2 }); const shuffle = arr => arr.sort(_ => -0.5+Math.random()); const pick = (a,b) => Math.random()>0.5?a:b; loop( (lc) => { var chords = [ [a4, a5, c6, e6, c7], [a4, a5, b5, e6, a6], [e4, g5, b5, e6, b6], [e4, g5, b5, d6, b6], [f4, f5, a5, c6, a6], [f4, d5, f5, a5, f6], [d4, d5, g5, a5, c7], [d4, d5, fs5, a5, d7] ]; var tp = -18 + (lc&1)*12 - 12; shuffle(chords); chords.forEach(chord => { shuffle(chord); chord.forEach(note => sy.play(note + tp, {duration: 4, attack: .1, release: 1, amp: .1})); pick(square,sy).play(chord[Math.random()*chord.length|0] + tp - 24, {duration: 32, attack: .5, release: 2, amp: .1}); let speed = pick(0.25, 0.5); for(let i = 0; i < 32; ++i) { if (i === 16) speed = pick(0.25, 0.5); sy.play(chord[Math.random()*chord.length|0] + tp, {duration: 0, attack: 0.01, release: .1, amp: .5, pan: Math.random() - .5}); sleep(speed); } }); }, { name: 'synth'}) .connect(reverb.create()); const kickLoops = ['x---x---x-x-x-xx--------', 'x---x---x------xx---', 'x-------', 'x-x-----','------------']; let kickLoop = kickLoops.choose(); loop( (lc) => { kickLoop.ring(lc) == 'x' && kick.play(c2, { attack: 0.025, release: 0.3, amp: 1.5 }); if (lc === kickLoop.length-1) kickLoop = kickLoops.choose(); }, { name: 'kick', sync: 0.5}) .connect(reverb.create()); const hithatLoops = ['--x---x---x---xx', '--x---x---x-x-x---', '--x-----', 'xxx-----','------------']; let hihatLoop = hithatLoops.choose(); loop( (lc) => { hihatLoop.ring(lc) == 'x' && noise.play(c3, { attack: 0.0125, release: 0.075, amp: .03 + Math.random() * 0.05 }); if (lc === hihatLoop.length-1) hihatLoop = hithatLoops.choose(); }, { name: 'hi-hat', sync: 0.5}) .connect(reverb.create());