// 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})
);