// srtuss, 2024 // procedural music ditty.bpm = 140; // 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}); // useful waveforms... const TAU = Math.PI * 2; const wave0 = p => Math.sin(p * TAU); const wave1 = p => Math.cos(clamp((p+10) % 1, 0, .5) * TAU * 2) - .5; const sy2 = synth.def((p,e,t,o) => wave1(p+(o.phase||0)) * e.value); const sy = synth.def((p,e,t,o) => wave0(wave1(p+(o.phase||0)) * (.8+Math.sin(ditty.time)*.2)) * e.value); const rand = () => Math.random()*2-1; 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 saw = synth.def((p,e,t,o) => varsaw(p+(o.pha||0), ditty.sampleRate / midi_to_hz(o.note)) * e.value); // filters... let comb = filter.def(class { constructor() { this.d = new Delayline(100); this.d2 = new Delayline(100); } process(inp) { let po = (Math.sin(ditty.tick * 0.2) * .5 + .5) * 80; let u = inp[0] - this.d.sample(po) * .3; let v = inp[1] - this.d2.sample(po) * .3; this.d.clock(u); this.d2.clock(v); return [u, v]; } }); class SVF { constructor(opt) { this.lp=this.bp=this.hp=this.ic1eq=this.ic2eq=0; } process(v0, fc, q) { let s = this; fc = clamp(fc, 0, .499); let g = Math.tan(Math.PI * fc); let k = 1 / q; s.a1 = 1 / (1 + g * (g + k)); s.a2 = g * s.a1; s.a3 = g * s.a2; let v1, v2, v3; v3 = v0 - s.ic2eq; v1 = s.a1 * s.ic1eq + s.a2 * v3; v2 = s.ic2eq + s.a2 * s.ic1eq + s.a3 * v3; s.ic1eq = 2 * v1 - s.ic1eq; s.ic2eq = 2 * v2 - s.ic2eq; s.lp = v2; s.bp = v1; s.hp = v0 - k * v1 - v2; return s; } } class SVF24 { constructor() { this.a = new SVF(); this.b = new SVF(); } process(v0, mode, fc, q) { return this.b.process(this.a.process(v0, fc, q)[mode], fc, q)[mode]; } } let eq = filter.def(class { constructor() { this.l = new SVF24(); this.r = new SVF24(); } process(inp, opt) { return [this.l.process(inp[0], 'hp', opt.fc, 1), this.r.process(inp[1], 'hp', opt.fc, 1)]; } }) // song arrangement... let soff = 4*56*0+4*40*0; function songc(start, end) { let x = Math.max((ditty.tick+soff) / 4 - start, 0); if(x >= end - start) x = 0; return x; } function songr(start, end) { let x = Math.max((ditty.tick + soff) / 4 - start, 0) / (end-start); if(x >= 1) x = 0; return x; } function song(start, end) { let x = (ditty.tick + soff) / 4 - start; return x >= 0 && x < end - start; } let scal = scale(a4, scales['minor'], 6); loop( (lc) => { let chords = [[0, 2, 4, 6, 3, 2, 7]]; var tp = -5 + ((Math.ceil(songc(23.99, 48)/2) * 7) % 12); let rt = 0;//(lc*3) % 7; chords.forEach(chord => { // bass for(let i = 0; i < 3; ++i) { let x = (i&1?1:-1); if(song(12, 48)+song(56, 64)) sy2.play(scal[rt + chord[0]] + tp - 36 + i * .06, {duration: 7, attack: .1, release: 1, amp: .2, phase:Math.random(), pan: x*.2}); } // chip-arp if(lc % 4 == 0 && lc) saw.play((x,y)=>scal[rt + chord[(~~(x*16))%7]] + tp, {duration: 1, release: 1.5, amp: .2}); // lead if(song(40, 52)) { let x = (ditty.tick+soff)>>3; let xx = (x*(x+1)*3)%7; let bend = [12,-12].choose(); sy2.play((x,y)=>scal[rt + xx] + tp + Math.sin(x*15)*clamp(x-.4,0,.5) + clamp(1-x*2,0,1)*bend, {attack: .2, duration: 8, amp: .2}); } let div = 32; for(let i = 0; i < div; ++i) { // chord pulses if(song(48, 64)) { if([1,1,1,1,0,0,1,1][i%8]) { for(let j = 0; j < 4; j+=2) { sine.play(scal[(rt + chord[j%chord.length])] + tp + 24, {attack: .003, release: .04, amp: .3}); } } } // synth bass if(song(8, 48)) sy.play(scal[rt + chord[0]] + tp - 24 + ((0xA4 >> (i%8)) & 1) * 12, {duration: 0, attack: 0.0001, release: .1, amp: .5, pan: Math.random() - .5}); let n = scal[rt + chord[i % chord.length] + ((i >> 3)&1) * 0] + tp; // synth arp if(song(0, 72)) sy2.play(n, {duration: 0, attack: 0.0001, release: .1, amp: .5, pan: rand()*.5}); sleep(8/div); } }); }, { name: 'synth', amp: 1.3 }) .connect(reverb.create({mix: .4})) .connect(eq.create({fc: ()=>20 * 2**(songr(14, 16)*.7*10)*ditty.dt})) .connect(comb.create()); let hihat = synth.def((p, e, t, o) => rand() * e.value, {release: .02, amp: .15}); loop( (lc) => { for(let i = 0; i < 4; ++i) { if(song(16, 40)+song(56, 64)) hihat.play(c4, {release: .01 + Math.random() * .03, pan: rand()*.5}); sleep(.25); } }, { name: 'hat', amp: 1.3 }); let kick = synth.def((p, e, t, o) => wave0(p+.2) * e.value, {release: .4}); loop( (lc) => { if(song(16, 40)+song(56, 64)) kick.play(e1); sleep(1); }, { name: 'kick', amp: 1.3 });