Microtonal Nonsense 🎩

If the note falls on the beat in the right key, you can play any note in between.

Log in to post a comment.

// k=kick,s=snare,h=hihat
let rythms = [
    {name: "clean",         r:"----h-hhk---k---k---k---k---k---k---k---k---k---k---k---k-----hh"},
    {name: "basic",         r:"k---k--hk---k---k---k-hhk-s-s-hh"},
    {name: "swing",         r:"k-h-k-shk-h-k-shk-h-k-shk-h-k-shk-h-k-shk-h-k-shk-h-k-shk-h-k-s-"},
    {name: "backbeat",      r:"k---h---k-s-h---k---h---k-s-h-s-k---h---k-s-h---k--sh---k-s-h---"},
    {name: "afro",          r:"k-h-s-h-k-h-s-h-k-h-s-h-k-h-s-h-k-h-s-h-k-h-s-h-k-h-s-h-k-h-s-h-"},
    {name: "brush",         r:"k---h---s---h---k---h---s---h---k---h---s---h---k---h-s-h-h-h---"},
    {name: "edm",           r:"k-h-s-h-k-h-s-h-k-h-s-h-k-h-shhhk-h-s-h-k-h-s-h-k-h-s-h-k-h-shhh"},
    {name: "hip hop",       r:"k-h-h-h-s-h-h-h-k-h-k-h-s-h-h-h-k-h-h-h-s-h-k-h-k-h-k-h-s-h-h-h-"},
    {name: "drum bass",     r:"k---h---s---h-----k-h---s---h---k---h---s---h-----k-h--ks--hh---"},
    {name: "bossa-nova",    r:"k---s--hk--h-s--k---s--hk--h-s--k---s--hk--h-s--k---s--hk-sh-s--"},
    {name: "trap",          r:"k---h-h-s---h-h-k--kh-h-s--hh-h-k---h-h-s-h-h-h-k--kh-h-s-hhhhh-"},
    {name: "bass solo",     r:"k-k-k-k-k-kkk-k-k-k-k-kk"},
    {name: "kkhhss",        r:"k-k-h-h-s-s-"},
    {name: "hithat solo",   r:"h-hhhhhhhhh-"},
    {name: "snare solo",    r:"s-s-s-s-s-ss"},
    {name: "breath",        r:"----"},
    {name: "breath",        r:"--------"},
    {name: "breath",        r:"------------"},
    {name: "breath",        r:"----------------"},
];
const jazziness = 1/3; // min=0, max=1, step=0.05
const step = 0.45;
const melodyChords = [
    scale(c3, chords['major'], 2), 
    scale(a4, chords['minor'], 2), 
    chord(c3, chords['major']), 
    chord(f3, chords['major7']),
    chord(g3, chords['sus4']),
    chord(a4, chords['min7']),
];
console.log(melodyChords)

const tri = p => Math.abs(p % 1 - .5) * 4 - 1;
const osc = synth.def((phase, env) => tri(phase) * env.value);
const kick = synth.def( (phase, env, tick, options) => Math.sin(phase * 2 * Math.PI * (1.5 - tick * 4)) * env.value);
const hihat  = synth.def( (phase, env, tick, options) => (Math.random() * 2 - 1) * Math.pow(1 - env.progress, options.pow) * env.value, { attack: 0.0, release: 0.4, amp: 0.5, pow: 10, env: adsr2 });
const snare = synth.def((phase, env) => (Math.random() * 2 - 1) * env.value * 0.2, { attack: 0.001, release: 0.4 });


// delay / reverb from <https://dittytoy.net/ditty/827b1b3e63>
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((1-Math.random()*Math.random()) * (options.length || 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;
    }
});

// source: <https://dittytoy.net/ditty/24373308b4>
const fract = (x) => x - Math.floor(x);
const triangle = x => Math.abs(fract(x + .5) - .5) * 2;
var phaser = filter.def(class {
    constructor(opt) {
        this.stages = [[], []];
        this.n = 16;
        this.lastOut = [0, 0];
        this.p = 0;
        this.feedback = 0.5;
        this.speed = 0.25;
        this.mix = .25;
        for(var i = 0; i < this.n; ++i) {
            this.stages[0].push({z: 0, ap: 0});
            this.stages[1].push({z: 0, ap: 0});
        }
    }
    __allpass(s, input, a) {
        var z = input - a * s.z;
        s.ap = s.z + a * z;
        s.z = z;
        return s.ap;
    }
    process(inv, opt) {
        //inv[0] = inv[1] = Math.random() - .5;
        var vl = inv[0] + clamp(this.stages[0][this.n-1].ap * this.feedback, -1, 1);
        var vr = inv[1] + clamp(this.stages[1][this.n-1].ap * this.feedback, -1, 1);
        var lfo = (2**triangle(this.p))*.5-1.4;
        this.p += ditty.dt * this.speed;
        for(var i = 0; i < this.n; ++i) {
            vl = this.__allpass(this.stages[0][i], vl, lfo);
            vr = this.__allpass(this.stages[1][i], vr, lfo);
        }
        vl = lerp(inv[0], vl, this.mix);
        vr = lerp(inv[1], vr, this.mix);
        return [vl, vr];
    }
});

loop((_) => {
    if (Math.random() > 0.5) {
        // smol break
        const pause = [4,8].choose()
        sleep(step * pause);
        debug.log(`M`, "⬛".repeat(pause) + " breath");
    } else if (Math.random() > 0.75) {
        // good notes bar
        const duration = [4,8].choose();
        const mode = ["random","order"].choose();
        debug.log(`M`,"πŸŸ₯".repeat(duration)  + " " + mode + " good");
        const notes = melodyChords.choose();
        for(let i=0;i<duration;i++) {
            osc.play(mode === "order" ? notes[i%notes.length] : notes.choose(), {amp:0.5});
            sleep(step);
        }
    }  else if (Math.random() > 0.75) {
        // bad duration bar, kinda going upwards 
        const duration = [4,8,16].choose();
        debug.log(`M`,"🟦".repeat(duration) + " up bad");
        const notes = melodyChords.choose();
        for(let i=0;i<duration;i++) {
            sine.play(lerp(notes.at(0), notes.at(-1) + 12, Math.random() * i/duration), {amp:0.5});
            sleep(step);
        }
    } else {
        const notes = melodyChords.choose();
        const mode = ["random","order"].choose();
        const sequence = Array.from({ length: 16 }, (_, i) => 
          i % 4 === 0 
            // notes in key, on count
            ? (mode === "order" ? notes[i%notes.length] : notes.choose()) + 12
            : (Math.random() < jazziness
                // microtones
                ? lerp(notes.at(0), notes.at(-1)+12, Math.random()) 
                : null)
        );
        
        if (Math.random() > 0.5) {
            // dont play on the 2, 3 or 4
            let idx = 1+Math.random()*3|0;
            sequence[idx*4] = null;
        }
        if (Math.random() > 0.5) {
            // "good" note 2e, 3e or 4e
            let idx = Math.random()*4|0;
            sequence[idx*4+2] = notes.choose() + 12;
        }
        
        debug.log(`M`, sequence.map((note,idx) => note == null ? "⬛": Number.isInteger(note) ?  "πŸŸ₯" : "🟦").join("")+  " "+mode + " melody");
        
        for (let i=0; i<sequence.length; i++) {
            let note = sequence[i];
            if (note) (Number.isInteger(note) ? osc : sine).play(note, {amp:0.5});
            sleep(step);
        }
    }
}, { name: 'M' }).connect(reverb.create({length: 3500}));

const beatMap = {
    "-":"⬛",
    "k": "πŸ₯",
    "h": "🎩",
    "s": "πŸ₯’",
}

function getVis({view, visStart, i}) {
    // not sure why this indicator ⏺️ doesnt work
    return view.split("").map((beat, idx) => (visStart + idx === i) ? "⏺️" : beatMap[beat]).join("")
}

loop((_) => {
    const current = _%2===0 ? rythms[0] : rythms.choose();
    const rythm = current.r;
    for(let i=0;i<rythm.length;i++) {
        let totalVis = 16;
        let visStart = Math.floor(i / totalVis) * totalVis;
        let currentView = rythm.slice(visStart, visStart + totalVis);
        debug.log(`B`, getVis({view:currentView, visStart, i}) + " " + current.name);
        
        let beat = rythm[i%rythm.length];
        if (beat === "k") {
            const bassNote = [c2,c2,g2,e2].choose();
            sine.play(bassNote, {amp:.5});
            osc.play(bassNote-12, {amp:.5, release: 0.4*4});
            kick.play(c2, { attack: 0.025, release: 0.25, amp: 1.5 });
        }
        else if (beat === "h") hihat.play(null, { amp: Math.random(), pow: 7+Math.random()*3, pan: lerp(-1,1,Math.random())});
        else if (beat === "s") snare.play(d4, {pan: [-0.25, 0.25].choose(), release: lerp(0.3, 0.4, Math.random())});
        sleep(step/2);
    }
}, { name: 'B' }).connect(phaser.create()).connect(reverb.create({length: 450}));