Arcane FM

I began experimenting with Dittytoy and especially FM synthesis and ended up with this rendition of my Amstrad CPC cover of Zetrix/Arcane's Amiga cover (youtube.com/watch?v=5vy6g42ezby) of The Last Hero by Future Freak (csdb.dk/sid/?id=12652)

Log in to post a comment.

// Constants
const TAU = Math.PI * 2;
const sin = Math.sin;
const exp = Math.exp;

// Envelopes
my_env = adsr.create( { attack: 0.1, decay: 0.2, release: 0.2, duration: 10 });

// Synths
const square = synth.def( (phase, env, tick, options) => Math.sign(sin(phase*TAU)) * env.value);
const saw = synth.def( (phase, env, tick, options) => ((phase % 1)*2-1) * env.value);
const sinus = synth.def( (phase, env, tick, options) => sin(phase*TAU) * env.value);
const fm = synth.def( class {
    constructor (options) {
        this.phase = 0;
        this.t = 0
        // index of modulation     
        this.iom = options.iom || 13.7; 
        this.mul = options.multiplier || 0.5;
    }
    process(note, env, tick, options) {
        // carrier frequency
        const fc = midi_to_hz(note);
        // modulator frequency
        const fm = fc * this.mul;
        // forward time
        this.t += ditty.dt;
        const osc = sin(
            TAU*this.t*fc + this.iom*sin(TAU*this.t*fm) 
        ) * env.value;
        
        // swing panning 
        const swing = tick * 1.6128;
        const x = TAU*swing;
        const m = 0.5;
        const L = m - sin(x) * m;
        const R = m - sin(x+Math.PI) * m;
        return [osc * L,  osc * R]; // left, right
    }
}); 

class ST128 {
    constructor(options) {
        this.synth = options.synth;
        this.notes = options.notes;
        this.max = this.notes.length;
        this.adsr = options.adsr || { 
            attack: 0.01, release: 0.25, duration: 0.125, amp: 1.0 };
        this.speed = options.speed || 0.05;
        this.transpose = options.transpose || 0;
        this.arps = options.arps || [];
        this.pos = 0; 
    }
    process(loopCount) {
        let tpl = this.notes[this.pos++];
        if (this.pos >= this.max) this.pos = 0;

        this.pause = tpl[0];
        this.note = tpl[1];
        this.adsr.duration = this.pause * (this.speed * 0.95);
            
        if (this.note > 0)
            this.synth.play(this.note + this.transpose, this.adsr);
        sleep(this.pause * this.speed);
    }
}


// Song data
const tracks = [
[[640, 0], [320, 0], [320, 0], [1280,0]] ,
[[640, 0], [30, 48], [20, 48], [30, 48], [30, 48], [20, 48], [20, 48], [10, 48], [30, 48], [20, 48], [30, 48], [30, 48], [20, 48], [20, 48], [10, 48], [30, 56], [20, 56], [30, 56], [30, 56], [20, 56], [30, 56], [30, 51], [20, 51], [20, 51], [10, 51], [30, 46], [20, 46], [30, 46], [30, 48], [20, 48], [30, 48], [30, 48], [20, 48], [20, 48], [10, 48], [30, 48], [20, 48], [30, 48], [30, 48], [20, 48], [20, 48], [10, 48], [30, 56], [20, 56], [30, 56], [30, 56], [20, 56], [30, 56], [30, 51], [20, 51], [20, 51], [10, 51], [30, 46], [20, 46], [30, 46], [30, 48], [20, 48], [30, 48], [30, 48], [20, 48], [20, 48], [10, 48], [30, 48], [20, 48], [30, 48], [30, 48], [20, 48], [20, 48], [10, 48], [30, 56], [20, 56], [30, 56], [30, 56], [20, 56], [30, 56], [30, 51], [20, 51], [20, 51], [10, 51], [30, 46], [20, 46], [20, 46], [10,0]] ,
[[660, 0], [40, 72], [10, 72], [30, 72], [40, 72], [40, 72], [40, 72], [10, 72], [30, 72], [40, 72], [40, 72], [40, 80], [10, 80], [30, 80], [40, 80], [10, 80], [30, 80], [40, 75], [40, 75], [40, 70], [10, 70], [30, 70], [40, 72], [10, 72], [30, 72], [40, 72], [40, 72], [40, 72], [10, 72], [30, 72], [40, 72], [40, 72], [40, 80], [10, 80], [30, 80], [40, 80], [10, 80], [30, 80], [40, 75], [40, 75], [40, 70], [10, 70], [30, 70], [40, 72], [10, 72], [30, 72], [40, 72], [40, 72], [40, 72], [10, 72], [30, 72], [40, 72], [40, 72], [40, 80], [10, 80], [30, 80], [40, 80], [10, 80], [30, 80], [40, 75], [40, 75], [40, 70], [10, 70], [10, 70]] ,
[[320, 60], [160, 68], [80, 67], [80, 65], [1920,0]] ,
[[10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 92], [10, 84], [10, 87], [10, 84], [10, 92], [10, 84], [10, 87], [10, 84], [10, 92], [10, 84], [10, 87], [10, 84], [10, 92], [10, 84], [10, 87], [10, 84], [10, 91], [10, 82], [10, 87], [10, 82], [10, 91], [10, 82], [10, 87], [10, 82], [10, 89], [10, 82], [10, 86], [10, 82], [10, 89], [10, 82], [10, 86], [10, 82], [10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 92], [10, 84], [10, 87], [10, 84], [10, 92], [10, 84], [10, 87], [10, 84], [10, 92], [10, 84], [10, 87], [10, 84], [10, 92], [10, 84], [10, 87], [10, 84], [10, 91], [10, 82], [10, 87], [10, 82], [10, 91], [10, 82], [10, 87], [10, 82], [10, 89], [10, 82], [10, 86], [10, 82], [10, 89], [10, 82], [10, 86], [10, 82], [10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 92], [10, 84], [10, 87], [10, 84], [10, 92], [10, 84], [10, 87], [10, 84], [10, 92], [10, 84], [10, 87], [10, 84], [10, 92], [10, 84], [10, 87], [10, 84], [10, 91], [10, 82], [10, 87], [10, 82], [10, 91], [10, 82], [10, 87], [10, 82], [10, 89], [10, 82], [10, 86], [10, 82], [10, 89], [10, 82], [10, 86], [10, 82], [10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 91], [10, 84], [10, 87], [10, 84], [10, 92], [10, 84], [10, 87], [10, 84], [10, 92], [10, 84], [10, 87], [10, 84], [10, 92], [10, 84], [10, 87], [10, 84], [10, 92], [10, 84], [10, 87], [10, 84], [10, 91], [10, 82], [10, 87], [10, 82], [10, 91], [10, 82], [10, 87], [10, 82], [10, 89], [10, 82], [10, 86], [10, 82], [10, 89], [10, 82], [10, 86], [10, 82]] ,
[[1280, 0], [40, 79], [20, 77], [10, 79], [40, 72], [10, 72], [20, 75], [20, 77], [40, 79], [20, 77], [10, 82], [50, 79], [20, 75], [20, 77], [40, 79], [20, 77], [10, 79], [40, 70], [10, 70], [20, 75], [20, 77], [30, 79], [30, 80], [20, 82], [30, 79], [30, 77], [10, 75], [10, 77], [40, 79], [20, 77], [10, 79], [40, 72], [10, 72], [20, 75], [20, 77], [40, 79], [20, 77], [10, 82], [50, 79], [20, 75], [20, 77], [40, 79], [20, 77], [10, 79], [40, 70], [10, 70], [20, 75], [20, 77], [30, 79], [30, 80], [20, 82], [30, 79], [30, 77], [10, 75], [10, 77]] ,
[[1425, 0], [160, 77], [160, 77], [320, 77], [160, 77], [160, 77], [160, 77], [15,0]] ,
];

const song = [
    {
        name: 'Bass 1', synth: saw, notes: tracks[1], transpose: -12, adsr: {
            attack: 0.01, decay: 0.7, sustain: 0.25, amp: 0.6128
        } 
    },{
        name: 'Arps 1', synth: sinus, notes: tracks[2], adsr: {
            attack: 0.01, decay: 0.5, sustain: 0.0, amp: 0.464
        }
    },{ 
        name: 'Bass 2', synth: fm, notes: tracks[3], transpose: -12, adsr: { 
            attack: 0.01, decay: 4, sustain: 0.0, amp: 0.368 
        }, 
    },{
        name: 'Arps 2', synth: fm, iom: 4.23, notes: tracks[4], adsr: {
            attack: 0.01, decay: 0.5, sustain: 0.3, amp: 0.464
        } 
    },{
        name: 'Lead', synth: saw, notes: tracks[5], adsr: {
            attack: 0.01, decay: 0.5, sustain: 0.2, amp: 0.6128
        } 
    }
];

// Play it again, Arnold!
ditty.bpm = 120;
for (let track of song) {
    loop(ST128, track);
}