// Forked from "Fork: Breakbeat experiment" by srtuss // https://dittytoy.net/ditty/34b39abc6f // Forked from "Backbeat experiment" by athibaul // https://dittytoy.net/ditty/ee9a7420a7 const TAU = 2*Math.PI; const sin = Math.sin; const rnd = () => Math.random() * 2 - 1; input.swarm_voices = 10; // min=1, max=10, step=1 input.chord_size = 4; // min=1, max=4, step=1 input.jitter = 1; // min=0, max=1, step=0.1 function sinsaw(phase, frequency) { phase = phase%1; return (1-phase) * sin(TAU*frequency*phase); } const lp = filter.def(class { constructor(opt) { this.hist0 = [0, 0]; this.hist1 = [0, 0]; } process(inp, opt) { const alpha = opt.cutoff; if (inp) { for (let i = 0; i < 2; i++) { this.hist0[i] += alpha * (inp[i] - this.hist0[i]); this.hist1[i] += alpha * (this.hist0[i] - this.hist1[i]); } return this.hist1; } } }, { cutoff: 1 }); // TODO: learn this! const hp = filter.def(class { constructor(opt) { this.l = [0, 0]; } process(inp, opt) { const fc = opt.fc || .3; this.l[0] += (inp[0] - this.l[0]) * fc; this.l[1] += (inp[1] - this.l[1]) * fc; return [inp[0] - this.l[0], inp[1] - this.l[1]]; } }); ditty.bpm = 150; class Swarm { constructor(opt) { this.voices = []; this.waveform = sinsaw; this.jtu = 0; let voice_count = 10; let pattern = [0,7]; for(let i = 0; i < voice_count; ++i) { let voice_gain = i/(voice_count-1); let bipolar_gain = voice_gain*2-1; const voice = { phase:Math.random(), frequency_multiplier:2**(pattern[i%pattern.length]/12), left:clamp01(bipolar_gain+1), right:clamp01(1-bipolar_gain), jitter:2**(rnd()*opt.detune) }; this.voices.push(voice); debug.log("f"+i, voice.left + ", " + voice.right); } } process(note, event, tick, opt) { this.jtu += ditty.dt * opt.jitter_factor * input.jitter; if(this.jtu >= 1) { this.jtu -= 1; for(let i = 0; i < this.voices.length; ++i) { this.voices[i].jitter = 2**(rnd()*opt.detune); } } const dt = midi_to_hz(note)*ditty.dt; let mix = [0,0]; for(let i = 0; i < this.voices.length; ++i) { if (i==input.swarm_voices) break; const voice = this.voices[i]; const wave = this.waveform(voice.phase, tick*.1+i*.2); mix[0] += wave * voice.left; mix[1] += wave * voice.right; voice.phase += dt * voice.frequency_multiplier * voice.jitter; } mix[0] *= event.value; mix[1] *= event.value; return mix; } } const pad = synth.def(Swarm); loop( (i) => { const padChords = [ [a4,e4,c5,es4], [c4,c3,e4,b4], [e4,e3,e4,g4], [e4,e3,g4,a4] ]; const duration = 16; const chord = padChords[i%padChords.length]; for(let j=0; j<chord.length; j++) { if (j==input.chord_size) break; pad.play(chord[j], {attack:4, release:4, duration:duration, detune: 0.03, jitter_factor: 60 }); } sleep(duration); }, {name:"pad", amp:()=>0.2*(1/input.swarm_voices)}) .connect(hp.create({fc:.0001}) );