240 bits per mile on synths

240 Bits per Mile but with a lot more supersaws

Log in to post a comment.

// 240 Bits per Mile (Leon Riskin), from the Five Nights at Freddy's 6 soundtrack
// see https://dittytoy.net/ditty/a5f81ae6ff for the original

function softsaw(p, formant) {
    const x = p-~~p;
    // polyblep
    const f = Math.abs(formant);
    const s = Math.max(0, Math.abs((f + 1) * (2*x - 1)) - f);
    // saw - polyblep
    return 2 * x - 1 - s*s * Math.sign(x - 0.5);
}

// from my Piano ditty, https://dittytoy.net/ditty/bc17a034bf
// modified slightly
const osc = class {
    constructor(options) {
        // The value of the note argument of the play call is retrievable via options.note.
        this.phases = new Float32Array(16).map(_ => Math.random());
        this.damping = new Float32Array(16).map(_ => 1);
        
        // frequency
        this.freq = midi_to_hz(options.note) * ditty.dt;
        
        // phasor
        this.time = Math.random();
    }
    process(note, env, tick, options) {
        // phasor times
        this.time += ditty.dt * 3;
        this.time -= this.time|0;
        let phase = Math.sin(this.time * 2 * Math.PI);
        
        // total
        let l = 0.0;
        let r = 0.0;
            
        // all saw waves
        for (let i = 0; i < this.phases.length; i++) {
            // sample wave
            const v = softsaw(
                this.phases[i],
                this.damping[i] * (options.soft_decay * env.value + options.soft_base)
            ) * env.value; 
                
            // update phase
            this.phases[i] += this.freq * (options.detune||1) * (i%2 + 1) + i * 4 * ditty.dt / this.phases.length;
            this.phases[i] -= this.phases[i]|0;
                
            // stereo
            if (i < this.phases.length / 2) l += v;
            else r += v;
        }
            
        return [l / Math.sqrt(this.phases.length), r / Math.sqrt(this.phases.length)];
    }
};

// Sounds fine:tm:, but could be better
// Piano is very hard to recreate properly
const piano = synth.def(
    osc, {
        soft_decay: 24,
        soft_base: 0,
        detune: 0.5,
        levels: [0, 1, 0, 0],
        times: [0.001, 4, 0.5],
        env: segenv,
    }
);

// closest I could get this to a saxaphone
const sax = synth.def(
    osc, {
        soft_decay: 20,
        soft_base: 10,
        vibrato: 1.2,
        detune: 1,
        levels: [0, 1, 0.7, 0],
        times: [0.05, 2, 0.2],
        env: segenv,
    }
);

// more of a violin considering how high it plays, but reasonably close
const cello = synth.def(
    osc, {
        soft_decay: 4,
        soft_base: 4,
        vibrato: 3,
        detune: 1,
        levels: [0, 1, 0.7, 0],
        times: [0.05, 0.2, 0.2],
        env: segenv,
    }
);

// === Song ===

// 240 bits per mile is indeed 240 bpm
ditty.bpm = 240;

// play the drums
function playDrums(kick, snare, duration, notes) {
    for (const note of notes) {
        // string encoding of the note
        // k = kick
        // s = snare
        // b = both
        // rest is ignored
        if (note == "k" || note == "b") kick.play(20);
        if (note == "s" || note == "b") snare.play(20);
        sleep(duration);
    }
}

// Alternate 3 notes
function alt(instr, count, a, b, low) {
    for (let i = 0; i < 4 * count; i++) { 
        instr.play([a, low, b, low].ring(i), { duration: 0.5 });
        sleep(0.5);
    }
}

// Alternate with a pattern
function altpat(instr, low, pat) {
    for (const note of pat) {
        instr.play(note, { duration: 0.5 });
        sleep(0.5);
        instr.play(low, { duration: 0.5 });
        sleep(0.5);
    }
}

// play a pattern
function pat(instr, time, pat) {
    for (const note of pat) {
        if (note > 0) instr.play(note, { duration: time });
        sleep(time);
    }
}

// play a pattern, merge notes
function patmerge(instr, time, pat) {
    const notes = [];
    for (const note of pat) {
        if (notes.length && notes[notes.length - 2] == note) notes[notes.length - 1] += time;
        else notes.push(note, time);
    }
    
    for (let i = 0; i < notes.length; i += 2) {
        if (notes[i] > 0) instr.play(notes[i], { duration: notes[i + 1] });
        sleep(notes[i + 1]);
    }
}

// intro base pattern
// 64 beats
function bass1(instr) {
    alt(instr, 4, ds4, c4, g3);
    alt(instr, 4, ds4, c4, gs3);
    alt(instr, 4, ds4, c4, f3);
    alt(instr, 2, ds4, c4, gs3);
    alt(instr, 2, f4, d4, as3);
    
    alt(instr, 4, g4, ds4, c4);
    alt(instr, 4, gs4, ds4, c4);
    alt(instr, 4, g4, ds4, as3);
    alt(instr, 4, g4, d4, b3);
}

// 64 beats
function bass2(instr) {
    alt(instr, 4, gs4, ds4, c4);
    alt(instr, 4, gs4, f4, c4);
    alt(instr, 4, g4, ds4, c4);
    alt(instr, 4, g4, ds4, as3);
    
    alt(instr, 4, gs4, ds4, c4);
    alt(instr, 4, gs4, f4, c4);
    
    // join the crazy bit
    patmerge(instr, 1/3, [a4, fs4, d4, a4, fs4, d4, a4, fs4, d4, a4, fs4, d4]);
    patmerge(instr, 1/3, [a4, fs4, d4, a4, fs4, d4, a4, fs4, d4, a4, fs4, d4]);
    
    // even crazier
    patmerge(instr, 0.25, [b4, g4, d4, b3, g3, d3, c3, d3, g3, b3, d4, g4, b4, d5]);
    patmerge(instr, 0.25, [b4, g4, d4, b3, g3, d3, c3, d3, g3, b3, d4, g4, b4, d5]);
    patmerge(instr, 0.25, [b4, g4, d4, b3]);
}

// 64 beats
function bass3(instr) {
    alt(instr, 4, ds4, c4, g3);
    alt(instr, 4, ds4, c4, gs3);
    alt(instr, 4, ds4, c4, a3);
    alt(instr, 2, ds4, c4, gs3);
    sleep(0.5);
    pat(instr, 0.5, [c4, ds4, f4, fs4, f4, ds4, f4]);
    
    
    alt(instr, 4, ds4, c4, g3);
    alt(instr, 4, ds4, c4, gs3);
    alt(instr, 4, ds4, c4, f3);
    alt(instr, 2, ds4, c4, gs3);
    alt(instr, 2, f4, d4, as3);
}

// 64 beats
function bass4(instr) {
    alt(instr, 4, ds4, c4, g3);
    alt(instr, 2, ds4, c4, gs3);
    alt(instr, 2, d4, b3, g3);
    alt(instr, 4, ds4, c4, g3);
    alt(instr, 2, ds4, c4, gs3);
    alt(instr, 2, d4, b3, g3);
    
    alt(instr, 4, ds4, c4, g3);
    alt(instr, 2, ds4, c4, gs3);
    alt(instr, 2, d4, b3, g3);
    alt(instr, 4, ds4, c4, g3);
    alt(instr, 2, ds4, c4, gs3);
    alt(instr, 2, d4, b3, g3);
}

// 64 beats
function lead1(instr) {
    altpat(instr, g4, [c5, c5, d5, ds5, c5, c5, d5, ds5]);
    altpat(instr, gs4, [c5, c5, d5, ds5, c5, c5, d5, ds5]);
    altpat(instr, a4, [c5, c5, d5, ds5, c5, c5, d5, ds5]);
    altpat(instr, gs4, [c5, c5, d5, ds5]);
    altpat(instr, as4, [d5, d5, ds5, f5]);
    
    patmerge(instr, 0.5, [c5, 0, c6, 0, as5, 0, g5, 0, f5, f5, ds5, 0, d5, ds5, 0, d5, d5, d5]);
    patmerge(instr, 0.5, [c5, 0, d5, 0, ds5, gs5, gs5, gs5, c5, c5, d5, 0, ds5, 0]);
    patmerge(instr, 1, [g5, as4, ds5, g5, gs5]);
    patmerge(instr, 0.5, [g5, f5, f5, ds5, ds5, d5, d5, d5]);
    patmerge(instr, 0.5, [b4, b4, d5, b4, f5, b4, g5, b4, f5, b4, ds5, b4, d5, b4]);
}

// 64 beats
function lead2(instr) {
    patmerge(instr, 0.5, [c5, 0, c5, c5, d5, ds5, ds5, c5, 0, c5, 0, c5, d5, d5, ds5, ds5]);
    patmerge(instr, 0.5, [c5, 0, c5, c5, d5, ds5, ds5, c5, 0, c5, 0, c5, ds5, ds5, d5, d5]);
    
    patmerge(instr, 0.5, [c5, 0, c5, c5, d5, ds5, ds5, c5, 0, c5, 0, c5, d5, d5, ds5, ds5]);
    patmerge(instr, 0.5, [g5, g5, as4, as4, ds5, g5, g5, gs5, gs5, g5, g5, f5, f5, ds5, ds5, 0]);
    
    patmerge(instr, 0.5, [gs5, gs5, c5, 0, gs5, gs5, c5, 0, as5, as5, gs5, g5, 0, f5, f5, gs5]);
    patmerge(instr, 0.5, [0, gs5, c5, 0, gs5, gs5, c5, 0, as5, as5, gs5, g5, 0, f5, f5, f5]);
    
    // the wild one
    // 24 * 1/3 = 8 beats 
    patmerge(instr, 1/3, [fs5, fs5, d5, a5, fs5, d5, as5, fs5, d5, c6, fs5, d5, as5, fs5, d5, a5, fs5, d5, g5, fs5, d5, fs5, fs5, d5]);
    
    // the even wilder bit
    patmerge(instr, 0.25, [g5, d5, b4, g4, d4, b3, g3, b3, d4, g4, b4, d5, g5, b5]);
    patmerge(instr, 0.25, [g5, d5, b4, g4, d4, b3, g3, b3, d4, g4, b4, d5, g5, b5]);
    patmerge(instr, 0.25, [g5, d5, b4, g4]);
}

// 64 beats
function lead3(instr) {
    pat(instr, 0.5, [ds5, g4, c5, g4, ds5, g4, c5, g4, ds5, g4, c5, ds5, g4, f5, g4, ds5]);
    pat(instr, 0.5, [gs4, ds5, c5, gs4, ds5, gs4, c5, gs4, ds5, gs4, c5, ds5, gs4, f5, gs4, ds5]);
    pat(instr, 0.5, [a4, ds5, c5, a4, ds5, a4, c5, a4, ds5, a4, c5, ds5, a4, f5, a4, ds5]);
    pat(instr, 0.5, [gs4, ds5, c5, gs4, ds5, gs4, c5, ds5, 0, c5, ds5, f5, fs5, f5, ds5, f5]);
 
    pat(instr, 0.5, [c5, g4, c5, g4, d5, ds5, g4, g5, g4, c5, g4, c5, d5, g4, ds5, g4]);
    pat(instr, 0.5, [gs5, gs4, c5, gs4, d5, ds5, gs4, gs5, gs4, c5, gs4, c5, d5, gs4, ds5, gs4]);
    pat(instr, 0.5, [a5, a4, c5, a4, d5, ds5, a4, a5, a4, c5, a4, c5, d5, a4, ds5, a4]);
    pat(instr, 0.5, [gs5, gs4, ds5, gs4, c5, gs5, gs4, g5, as4, f5, as4, f5, ds5, as4, d5, as4]);
}

// 64 beats
function lead4(instr) {
    pat(instr, 0.5, [0, g4]); // 1
    pat(instr, 1/3, [d5, ds5, d5, c5, g4, ds4, g4, c5, ds5]); // 3 -- 4
    pat(instr, 0.25, [g5, f5, ds5, d5, c5, d5]); // 1.5 -- 5.5
    pat(instr, 0.5, [c5]); // 0.5 -- 6
    pat(instr, 0.25, [g5, ds5, c5, g4]); // 1 -- 7
    pat(instr, 0.5, [0, g4]); // 1 -- 8
    
    pat(instr, 1/3, [gs5, ds5, c5, gs5, ds5, c5, gs5, ds5, c5, as5, ds5, c5]); // 12
    pat(instr, 1/3, [g5, d5, b4, f5, d5, b4, ds5, d5, b4, bs4, b4, g4]); // + 12 * 1/3 = 8
    
    pat(instr, 1/3, [c5, g5, ds5, g5, ds5, c5, ds5, c5, g4, c5, g4, ds4]); // 12
    pat(instr, 1/3, [c4, ds4, g4, c5, ds5, g5, c6, ds6, g6, ds6, c6, ds6]); // + 12 * 1/3 = 8
    
    pat(instr, 0.25, [g5, 0]); // 0.5
    patmerge(instr, 0.5, [g5, f5, 0, ds5, f5, 0, ds5, ds5, ds5]); // 4.5 -- 5
    patmerge(instr, 0.25, [d5, 0, d5, d5, c5, 0, c5, c5, b4, 0, b4, b4]); // 3 -- 8
    
    patmerge(instr, 0.25, [c5, c5, c5, c5, g5, 0, g5, g5, f5, 0, f5, f5, ds5, ds5]); // 3.5 -- 3.5
    patmerge(instr, 0.25, [c5, c5, c5, 0, c5, c5, g5, 0, g5, g5, f5, 0, f5, f5, ds5, 0, ds5, ds5]); // 4.5 -- 8 
    
    pat(instr, 1, [c5]); // 1
    patmerge(instr, 0.25, [g5, 0, g5, g5, f5, 0, f5, f5, ds5, ds5, c5, c5, c5, 0, c5, c5]); // 4 -- 5
    patmerge(instr, 0.25, [g5, 0, g5, g5, f5, 0, f5, f5, ds5, 0, ds5, ds5]); // 3 -- 8
    
    pat(instr, 1, [c5]); // 1
    patmerge(instr, 0.25, [g5, 0, g5, g5, f5, 0, f5, f5, ds5, ds5, c5, c5, c5, 0, c5, c5]); // 4 -- 5
    patmerge(instr, 0.25, [g5, 0, g5, g5, f5, 0, f5, f5, ds5, 0, ds5, ds5]); // 3 -- 8
    
    pat(instr, 1, [c5]); // 1
    patmerge(instr, 0.25, [g5, 0, g5, g5, f5, 0, f5, f5, ds5, ds5]); // 2.5 -- 3.5
    pat(instr, 4.5, [c5]); // 4.5 -- 8
}

// 64 beats
function pad1(instr) {
    pat(instr, 8, [c3, gs2, f2]);
    pat(instr, 4, [gs2, as2]);
    pat(instr, 8, [c3, gs3, ds3, g2]);
}

// 64 beats
function pad2(instr) {
    pat(instr, 2, [gs2, gs3, ds3, c3]);
    pat(instr, 2, [f3, c3, gs3, f3]);
    pat(instr, 2, [c3, c4, g3, ds3]);
    pat(instr, 2, [ds3, ds4, as3, g3]);
    
    pat(instr, 2, [gs2, gs3, gs2, gs3]);
    pat(instr, 2, [f2, f3, f2, f3]);
    pat(instr, 2, [fs2, fs3]);
    pat(instr, 1, [fs2, a2, d3, fs3]);
    pat(instr, 8, [g3]);
}

// 64 beats
function pad3(instr) {
    pat(instr, 8, [c3, gs2, f2]);
    pat(instr, 4, [gs2]);
    pat(instr, 0.5, [as2, c3, ds3, f3, fs3, f3, ds3, f3]);
    pat(instr, 8, [c3, gs2, f2]);
    pat(instr, 4, [gs2, as2]);
}

// 64 beats
function pad4(instr) {
    pat(instr, 8, [c3]);
    pat(instr, 4, [gs2, g2]);
    pat(instr, 8, [c3]);
    pat(instr, 4, [gs2, g2]);
    
    pat(instr, 8, [c3]);
    pat(instr, 4, [gs2, g2]);
    pat(instr, 8, [c3]);
    pat(instr, 4, [gs2, g2]);
}

// === Loops ===

loop(() => {
    sleep(64);
    lead1(piano);
    lead2(piano);
    lead3(piano);
    lead4(piano);
}, { name: "Lead" });

loop(() => {
    bass1(cello);
    bass1(cello);
    bass2(cello);
    bass3(cello);
    bass4(cello);
}, { name: "Bass" });

loop(() => {
    pad1(sax);
    pad1(sax);
    pad2(sax);
    pad3(sax);
    pad4(sax);
}, { name: "Pad" });

// Animate the car
const road = "⬛⬛⬛⬛⬛⬛⬛⬛⬛";
const top = "⬛⬛⬛🟪⬛🟪⬛⬛⬛";
const bottom = "⬛⬛⬛🟪⬛🟪⬛⬛⬛";
const frames = [
    "⬜⬜⬛🟪🟪🟪🟪⬛⬛",
    "⬜⬛⬛🟪🟪🟪🟪⬛⬜",
    "⬛⬛⬛🟪🟪🟪🟪⬜⬜",
    "⬛⬛⬛🟪🟪🟪🟪⬜⬛",
    "⬛⬛⬛🟪🟪🟪🟪⬛⬛",
    "⬛⬛⬛🟪🟪🟪🟪⬛⬛",
    "⬛⬛⬛🟪🟪🟪🟪⬛⬛",
    "⬛⬛⬜🟪🟪🟪🟪⬛⬛",
    "⬛⬜⬜🟪🟪🟪🟪⬛⬛",
];
    
loop(() => {
    // print the car
    const rate = 4;
    debug.log(1, road);
    debug.log(2, top);
    debug.log(3, frames.ring(ditty.tick * rate));
    debug.log(4, bottom);
    debug.log(5, road);
    sleep(1 / rate);
}, { name: "Car" });