Fork: Fork: Breakbeat experiment

I was fascinated by the pad sound in @srtuss's fork of @athibaul's Breakbeat experiment, so I forked it, reduced it to the bare pad sound, renamed the variables to better understand what's going on and finally added some sliders for fun :)

Log in to post a comment.

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