// https://dittytoy.net/ditty/ee9a7420a7 const PI = 3.1415926; const sin = Math.sin; const cos = Math.cos; const abs = Math.abs; const rnd = () => Math.random() * 2 - 1; //effects class Delayline { constructor(n) { this.n = ~~n; this.p = 0; this.lastOut = 0; this.data = new Float32Array(n); } clock(input) { this.lastOut = this.data[this.p]; this.data[this.p] = input; if(++this.p >= this.n) { this.p = 0; } } tap(offset) { var x = this.p - offset - 1; x %= this.n; if(x < 0) { x += this.n; } return this.data[x]; } } function allpass(delayline, x, k) { var delayin = x - delayline.lastOut * k; var y = delayline.lastOut + k * delayin; delayline.clock(delayin); return y; } // Simple allpass reverberator, based on this article: // http://www.spinsemi.com/knowledge_base/effects.html const reverb = filter.def(class { constructor(options) { this.lastReturn = 0; this.krt = .7; this.delaylines = []; // Create several delay lines with random lengths for(var i = 0; i < 12; ++i) { this.delaylines.push(new Delayline(10 + Math.floor(Math.random() * 5000))); } } process(input, options) { var inv = input[0] + input[1]; var v = this.lastReturn; // Let the signal pass through the loop of delay lines. Inject input signal at multiple locations. v = allpass(this.delaylines[0], v + inv, .5); v = allpass(this.delaylines[1], v, .5); this.delaylines[2].clock(v); v = this.delaylines[2].lastOut * this.krt; v = allpass(this.delaylines[3], v + inv, .5); v = allpass(this.delaylines[4], v, .5); this.delaylines[5].clock(v); v = this.delaylines[5].lastOut * this.krt; v = allpass(this.delaylines[6], v + inv, .5); v = allpass(this.delaylines[7], v, .5); this.delaylines[8].clock(v); v = this.delaylines[8].lastOut * this.krt; v = allpass(this.delaylines[9], v + inv, .5); v = allpass(this.delaylines[10], v, .5); this.delaylines[11].clock(v); v = this.delaylines[11].lastOut * this.krt; this.lastReturn = v; // Tap the delay lines at randomized locations and accumulate the output signal. var ret = [0, 0]; ret[0] += this.delaylines[2].tap(111); ret[1] += this.delaylines[2].tap(2250); ret[0] += this.delaylines[5].tap(311); ret[1] += this.delaylines[5].tap(1150); ret[0] += this.delaylines[8].tap(511); ret[1] += this.delaylines[8].tap(50); ret[0] += this.delaylines[11].tap(4411); ret[1] += this.delaylines[11].tap(540); // Mix wet + dry signal. ret[0] = ret[0] * .1 + input[0]; ret[1] = ret[1] * .1 + input[1]; // Slight stereo widening: var m = (ret[0] + ret[1]) * .5; var s = (ret[1] - ret[0]) * .5; ret[0] = m + s * 1.5; ret[1] = m - s * 1.5; return ret; } }); function sinsaw(p,f) { p = p%1; return (1-p) * sin(2*PI*f*p); } const tri = (p) => 2*abs((p/*+.25*/)%1 - 0.5) - .5; function softsat(x) { x = clamp(x,-1,1); return x*(3-x*x)/2; } const ssaw = synth.def( (p,e,t,o) => sinsaw(p+o.pho,o.f)*e.value, {attack:1e-3, release:0.2, duration:0.0, f:5, pho: 0} ); const kick = synth.def( (p,e,t,o) => softsat(o.a*tri(o.f0*t*(2*o.d-t))*clamp01(o.d-t)), {f0:500,d:0.2,a:6} ); const hp = filter.def(class { constructor(opt) { this.l = [0, 0]; } process(inp, opt) { this.fc = opt.fc || .3; this.l[0] += (inp[0] - this.l[0]) * this.fc; this.l[1] += (inp[1] - this.l[1]) * this.fc; return [inp[0] - this.l[0], inp[1] - this.l[1]]; } }); const lowpass = filter.def(class { constructor(options) { this.hist0 = [0, 0]; this.hist1 = [0, 0]; } process(input, options) { const alpha = options.cutoff; if (input) { for (let i = 0; i < 2; i++) { this.hist0[i] += alpha * (input[i] - this.hist0[i]); this.hist1[i] += alpha * (this.hist0[i] - this.hist1[i]); } return this.hist1; } } }, { cutoff: 1 }); ditty.bpm = 144; function pumper(x, opt) { // Ensure the pumpRate is passed or defaults to 0.5 if not provided let pumpRate = tick_to_second(opt.pumpRate) || 0.5; // Use timeStep for updating time (simulating the passage of time) let time = opt.time || 0; // Calculate amplitude using a sine wave for the pumping effect let amplitude = Math.sin(2 * Math.PI * time * pumpRate) * 0.5 ; debug.log ( "amp: ", amplitude ) debug.log ( "x: ", x ) debug.log ( "xamp: ", x + amplitude / amplitude ) // Apply the amplitude modulation to the input signal return x + amplitude / amplitude ; } loop( (i) => { if(!i) sleep(0); //let f = 5**(1-cos(2*PI*i/64)); let f = 10**((i/64)%1); let amp = 3 - abs((i%4)-2); f *= amp/3; //ssaw.play(a3, {f:2+Math.random(), amp:amp, pan:rnd()*.1, release: .1}); sine.play(a1, {f:2+Math.random(), amp:amp, release: .1}); //ssaw.play(a2, {f:f+rnd(), amp:amp, pan:rnd()}); //ssaw.play(a1, {f:2 + f/4, amp:amp}); sleep(0.25); }, { name: 'bass', amp: .2 }); const wtf = filter.def(class { constructor() { } process(inp) { let am = 1; inp[0] = tri(inp[0]*am)/am*.2; inp[1] = tri(inp[1]*am)/am*.2; return inp; } }); loop( (i) => { if(!i) sleep(0); let pattern = "k...k.s.k...k.ks";//"k.......k......." if (ditty.ticks > 32){ pattern = "k...k.s.k...k.ks";//"k.....s.k.....ks" } if(pattern.charAt(i%16) == "k") { let r = 1.0 + 0.1*rnd(); let dm = 1; kick.play(c4, {f0:20,d:1.0*dm,a:1.0}); // bass //sine.play((t) => a1 + 3*(0.5-t), {attack:0.0, release:0.2, duration:0.5}); kick.play(c4, {f0:300*r,d:0.2*dm,a:6}); // thump kick.play(c4, {f0:1e8*r,d:0.01*dm,a:20}); // click kick.play(c4, {f0:1e7*r,d:0.05*dm,a:3}); // click, softer } else if (pattern.charAt(i%16) == "s") { let r = 1.0 + 0.5*rnd(); kick.play(c3, {f0:300*r,d:0.2,a:1}); // thump //kick.play(c4, {f0:1e7,d:0.2,a:3*r,amp:0.1}); // noise //kick.play(c4, {f0:6e7*r,d:0.02,a:30,amp:0.2}); // click } sleep(0.25); }, {name:'kick', amp:.5}); loop( (i) => { if(!i) sleep(32); let pattern = "..S.....S...."; if(pattern.charAt(i%16) == "S") { let r = 1.0 + 0.1*rnd(); kick.play(c4, {f0:600*r,d:0.15,a:2}); // thump kick.play(c4, {f0:1e7,d:0.15,a:6*r,amp:0.3}); // noise kick.play(c4, {f0:6e7*r,d:0.05,a:30,amp:0.7}); // click } else if (pattern.charAt(i%16) == "s") { let r = 1.0 + 0.5*rnd(); kick.play(c4, {f0:300*r,d:0.2,a:1}); // thump kick.play(c4, {f0:1e7,d:0.2,a:3*r,amp:0.1}); // noise kick.play(c4, {f0:6e7*r,d:0.02,a:30,amp:0.2}); // click } sleep(0.25); }, {name:"snare"}); loop( (i) => { if(!i) sleep(0); let pattern = 's.......k.......'; for(let j=0; j<pattern.length; j++) { let r = 1.0 + 0.2*rnd(); switch(pattern.charAt(j)) { case 'k': kick.play(c4, {f0:200*r,d:0.3,a:20,amp:0.3,pan:rnd()}); // thump kick.play(c4, {f0:1e6*r,d:0.2,a:3,amp:0.1,pan:rnd()}); // noise break; case 's': kick.play(c4, {f0:300*r,d:0.3,a:20,amp:0.07,pan:rnd()}); // thump kick.play(c4, {f0:1.5e6*r,d:0.3,a:3,amp:0.15,pan:rnd()}); // noise break; } let d = 0.25; sleep(d); } }, {name:"backbeat"}).connect(hp.create()).connect(reverb.create()); loop((i) => { if(!i) sleep(32);//32 let pattern = '..oc..o...o...o.';//'ccc.cc..c..co.c.c.' if (ditty.ticks > 96){ pattern = '..oc..o...o...o.'; } for(let j = 0; j < 16; ++j) { switch(pattern.charAt(j)) { case 'c': kick.play(c4, {f0:5e6,d:0.05,a:30,amp:0.2,pan:rnd()*.5}); // click, softer break; case 'o': kick.play(c4, {f0:1.5e6,d:.2,a:30,amp:0.2,pan:rnd()*.5}); // click, softer break; } sleep(0.25); } }, {name:"hat"}).connect(hp.create()); input.droneSpeed1 = 0.4; input.droneSpeed2 = 0.07; let pianoData = [24,25,26,27,28,29,30,31,32,33,34,35]; const piano = synth.def( class { constructor(options) { // The value of the note argument of the play call is retrievable via options.note. this.phase = 0; this.c = 0 } process(note, env, tick, options) { this.phase += ditty.dt * midi_to_hz(note) / (pianoData.length * input.speed1); let index = Math.round(clamp01(this.phase) * pianoData.length); let value = clamp01(pianoData[index] / 120); if (isNaN(value)) return 0; return [ Math.cos(index * (60*input.speed2)) * value, Math.sin(index * (60*input.speed2)) * value, ]; } } ); loop( (i) => { if(!i) sleep(32); piano.play(c3, { amp: 0.5, duration: 1, pan: 0.5 + rnd * (-1) }); sleep(0.25); }, { name: `drone synth`, amp: 0.25 })/* everything sounds nicer with fx */.connect(reverb.create()); class Swarm { constructor(opt) { this.op = []; this.wf = sinsaw; this.jtu = 0; let nu = 10; let pat = [0,7]; for(let i = 0; i < nu; ++i) { let k = i/(nu-1); let pa = k*2-1; this.op.push({ p:Math.random(), f:2**(pat[i%pat.length]/12), ml:clamp01(pa+1), mr:clamp01(1-pa), jt:2**(rnd()*opt.det) }); } } process(n, e, t, opt) { this.jtu += ditty.dt*opt.jtf; if(this.jtu >= 1) { this.jtu -= 1; for(let i = 0; i < this.op.length; ++i) this.op[i].jt = 2**(rnd()*opt.det); } let dt = midi_to_hz(n)*ditty.dt; let mix = [0,0]; for(let i = 0; i < this.op.length; ++i) { let op = this.op[i]; mix[0] += this.wf(op.p, t*.1+i*.2) * op.ml; mix[1] += this.wf(op.p, t*.1+i*.2) * op.mr; op.p += dt * op.f * op.jt; } mix[0] *= e.value; mix[1] *= e.value; return mix; } } const pad = synth.def(Swarm); loop( (i) => { let padChords = [ [a1,c5,es5] ]; let du = 64; let ch = padChords.ring(i); for(let j=0; j<ch.length; j++) { pad.play(ch[j], {attack:.1,decay:.1,sustain:.5,release:8,duration:0, det:0.4,jtf:(x,t)=>800*Math.exp(-x*.5)}); } sleep(du); }, {name:"fx", amp: .1}).connect(hp.create({fc:.05})); let pump = pumper(0.0001, { pumpRate: 1, time: ditty.dt }); loop( (i) => { let padChords = [ [a4,e4,c5,es4], [c4,c3,e4,b4], [a4,e4,c5,es4], [e4,e3,g4,a4] ]; let du = 16; let ch = padChords.ring(i); for(let j=0; j<ch.length; j++) { //let rrr = Math.sin(j*646+i) * 32 + a5; pad.play(ch[j], {attack:4,release:4,duration:du, det:0.03,jtf:60}); } sleep(du); }, {name:"pad", amp:0.1}).connect(hp.create({fc:.0001})).connect( lowpass.create( { cutoff: () => pump}));