Hysteria by MUSE

To sing along in the right places:

It's bugging me
Grating me
And twisting me around
Yeah, I'm endlessly
Caving in
And turning inside out

'Cause I want it now
I want it now
Give me your heart and your soul
And I'm breaking out
I'm breaking out
Last chance to lose control

And it's holding me
Morphing me
And forcing me to strive
To be endlessly
Cold within
And dreaming I'm alive

'Cause I want it now
I want it now
Give me your heart and your soul
I'm not breaking down
I'm breaking out
Last chance to lose control

And I want you now
I want you now
I feel my hear

Log in to post a comment.

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

ditty.bpm = 94;

// start position in song
START = 0;


// clamp11 and amp from https://dittytoy.net/ditty/989ebdaad7
let clamp11 = (x) => x < -1 ? -1 : x > 1 ? 1 : x;
const amp = filter.def(class {
    constructor(options) {
        this.amp  = options.amp || 0.5;
        // gain > 1 = overdrive
        this.gain = options.gain || 1.0;
    }

    process(inpt, options) {
        return [clamp11(this.gain*inpt[0]) * this.amp, clamp11(this.gain*inpt[1]) * this.amp];
    }
});



/* 
    Filters
*/

// from https://dittytoy.net/syntax#filters
const lowpass = filter.def(class {
    constructor(options) {
        this.hist0 = [0, 0];
        this.hist1 = [0, 0];
        this.t = 0;
    }
    process(input, options) {
        const alpha = clamp(options.cutoff, 0.01, 1);
        if (input) {
            for (let i = 0; i < 2; i++) {
                this.hist0[i] += alpha * (input[i] - this.hist0[i]);
                this.hist1[i] += alpha * (this.hist0[i] - this.hist1[i]);
            }
            return this.hist1;
        }
    }
});

const spread = filter.def(class {
    constructor(options) {
        this.size = 2 * options.delay || 256;
        this.hist = new Float32Array(this.size);
        this.write = options.delay;
        this.read = 0;
        this.width = options.width || 0.8;
    }
    process(input, options) {
        if (input) {
            // save current sample
            const x = input[0];
            this.hist[this.write++ % this.size] = x

            // add history to current 
            const spread = this.width; 
            const signal  = x + this.hist[this.read % this.size];
            const delayed = x + this.hist[(options.delay + this.read) % this.size];
            this.read++;

            const L = signal;
            const R = spread*delayed + (1.0-spread)*signal;
            const V = 0.5;
            return [V*L,V*R];
        }
    }
});

/*
    Synths
*/

const Strat = synth.def( class {
    constructor (options) {
        this.t = Math.random();
        this.i = 0;
        this.mul = options.mul;
        this.bend = options.bend || 0;
        this.coeff = options.coeff || [
            [1, 1],
        ];
        this.feedback = options.feedback || 0;
        this.last = 0;
    }
    add_fm(note, env, tick) {
        // carrier frequency
        let fc;
        if (this.bend) {
            const a = midi_to_hz(note);
            const b = midi_to_hz(note + this.bend)
            fc = lerp(a, b, this.i);
        } else {
            fc = midi_to_hz(note);
        }
        
        let x = 0;
        const fb = this.feedback * this.last;
        this.coeff.forEach(y => {
            let fm = fc * y[0] * this.mul;
            let mod = sin(TAU*this.t*fm + fb) * (Math.exp(tick * -16)*0.4+1);
            x += sin(TAU*this.t*fc*y[0] + mod) * y[1];
        });
        this.last = x;
        
        return (x / this.coeff.length) * env.value; 
    }
    process(note, env, tick, options) {
        // forward time
        this.t += ditty.dt;
        this.i += ditty.dt;

        const gain = 0.5; 
        let osc = this.add_fm(note, env, tick) * gain;
        osc *= env.value;
        
        return [osc, osc]; // left, right
    }
}); 


/* 
    Building blocks
*/
class Track {
    constructor(options) {
        this.patterns = options.patterns;
        this.pos = options.start || 0;
        this.base = options.base || 0;
        this.read = true;
        this.duration = 0.25;
        this.stop = false;
    }
    process(loopCount) {
        this.sleep = true;
        if (this.read) {
            if (this.stop) {
                sleep(1);
                return;
            }
            this.read = false;
            this.part = this.patterns[this.pos++];
            if (this.pos==this.patterns.length) this.stop = true;
            this.i = 0;
        }
    
        let x = this.part[this.i++];
        if (typeof x === 'number') {
            if (x > 24) {
                this.base = x;
                x = this.part[this.i++];
            } 
            if (x < 0) {
                this.duration = -x;
                x = this.part[this.i++];
            }
            if (typeof x === 'number') x += this.base;
        }
        if (this.i==this.part.length) {
            this.read = true;
        }

        this.play(x, loopCount);
        if (this.sleep) sleep(this.duration); // sleep in ticks
    }
}  

class ElectricGuitar extends Track {
    constructor(options) {
        super(options);
        this.mul = options.mul || 0.503;
        this.pan = options.pan || 0;
        this.transpose = options.transpose || 0;
        this.feedback = options.feedback || 0;
        this.coeff = [ 
        /* [1, 1],
            [2, 0.5],
            [2.5, 0.25],
            [5.25, 0.125] */
            [1, 0.5],
            [2.01, 0.25],
            [3.02, 0.125],
            [4.03, 0.06]
        ];
    }
    strum(note, a, d, r) {
        Strat.play(note + this.transpose, {
            attack: a || 0.01, duration: d || this.duration, release: r || 0.15, 
            pan: this.pan, amp: 1, mul: this.mul,
            coeff: this.coeff, feedback: this.feedback
        });
    }
    chord(notes) {
        let string = 0;
        for (let x of notes) {
            if (x > 0) {
                this.strum(this.base + string + x);
            } else if (x == 0) { 
                // palm mute strings
                this.strum(this.base + string, 0.01, 0.05, 0.05);
            }
            string += 5;
        }
    }
    arp(notes) {
        this.sleep = false;
        for (let x of notes) {
            const [string, tone, duration, gap] = x;
            this.strum(string + tone, null, duration);
            sleep(gap);
        }        
    }
    bend(notes, a, d, r) {
        this.sleep = false;
        for (let x of notes) {
            const [string, fret, dur, bend] = x;
            Strat.play(string + fret + this.transpose, {
                attack: 0.01, duration: dur, release: 0.15, 
                pan: this.pan, amp: 1, mul: this.mul, bend: bend,
                coeff: this.coeff, feedback: this.feedback
            });
            sleep(dur);
        }
    }
    play(note) {
        if (note === null) return; // pause
        if (Array.isArray(note)) {
            // deduce type
            const type = note[0];
            const notes = note.slice(1);
            switch (type) {
                case 0:
                    this.chord(notes);
                    break;
                case 1: 
                    this.arp(notes);
                    break;
                case 2:
                    this.bend(notes);
                    break;
            }
        } else if (note > 0) {
            this.strum(note);
        }
        // sleep is done in the Track class!
    }
}

/*
    TODO: Vocals
*/

/*    
// +++ Parts +++
// intro, 1-8
// pre-verse, 9-16
// verse, 17-24

It's bugging me
Grating me
And twisting me around
Yeah, I'm endlessly
Caving in
And turning inside out

// chorus, 25-32

'Cause I want it now
I want it now
Give me your heart and your soul
And I'm breaking out
I'm breaking out
Last chance to lose control

// pre-verse2, 33-36
// verse2, 37-44

And it's holding me
Morphing me
And forcing me to strive
To be endlessly
Cold within
And dreaming I'm alive


// chorus2, 45-52

'Cause I want it now
I want it now
Give me your heart and your soul
I'm not breaking down
I'm breaking out
Last chance to lose control
// pre-solo riff, 53-56
// solo 57-72
// chorus w/ solo 73-80
// outro 81-87
*/


/*
    Electric Bass
*/
const bp1a = [a2,-0.25,0,0,10,0, 10,12,0,10, 0,7,0,8, 8,7,5,7,];
const bp1b = [e2,0,0,10,0, 10,12,0, a2,10, e2,0,12,0, a2,10,10, e2,12, a2,10,12,];
const bp1c = [d3,0,0,10,0, 10,12,0,10, 0,10,9,0, 9,8,0,8,];
const bp1d = [7, a2,0,10,0, 10,12,0,10, 0,7,0,8, 8,7,5,7];
const bp1e = [7,0,0,0, 6,0,0,0, 5,0,0,0, 4,0,0,0];
const bp3a = [
    a2,-0.5,3,   
    g3,5, -0.25,3, 5,      
    a2,3, -0.5,3, -0.25,3, 
    g3,-0.5,5, -0.25,3, 5,      
    a2,-0.5,3
];
const bp3b = [
    e2,-0.5,3,   
    d3,5, -0.25,3, 5,      
    e2,3, -0.5,3, -0.25,3, 
    d3,-0.5,5, -0.25,3, 5,      
    e2,-0.5,3
];
const bp3c = [
    a2,-0.5,5,   
    g3,7, -0.25,5, 7,      
    a2,5, -0.5,5, -0.25,5, 
    g3,-0.5,7, -0.25,5, 7,      
    a2,-0.5,5
];
const bp3d = [
    e2,-0.5,5,   
    d3,7, -0.25,5, 7,      
    e2,5, -0.5,5, -0.25,5, 
    d3,-0.5,7, -0.25,5, 7,      
    e2,-0.5,5
];
const bp3e = [    
    e2,-0.25, 0,0,10,0, 10,12,0, a2,10, e2,0,12,0, a2,10,10, e2,12, a2,10,12,
];
const bp3f = [
    e2,-0.5,5,   
    d3,7, -0.25,5, 7,
    e2,5,5,5,5,5,5,5, // add pinch harmonics here
    d3,-0.75,7
];
const bp6a = [
    e2,-0.25,
    0,0,10,0, 10,12,0,10, 0,10,9,0, 9,8,0,8, 
];
const bp6b = [
    e2,-0.25,
    7,0,10,0, 10,12,0,10, 0,10,9,0, 9,8,0,8,
];
const bp6c = [
    e2,-0.25,
    7,0,10,0, 10,12,0,10, 0,10,9,0, 9,8,0,8,
];
const bp6d = [
    e2,-0.25,
    7,0,10,0, 10,12,0,15, 0,12,0,15, 15,12,15, 
    a2,12
];
const bp7 = [
    e2,-0.25,
    0,0,10,0, 10,12,0,10, 0,10,9,0, 9,8,0,8, 
    7,0,10,0, 10,12,0,10, 0,10,9,0, 9,8,0,8, 
    7,0,10,0, 10,12,0,10, 0,10,9,0, 9,8,0,8, 
    7,0,10,0, 10,12,0,10, 0,10,9,0, 9,8,0,8, 
    7,0,10,0, 10,12,0,10, 0,10,9,0, 9,8,0,8, 
    7,0,10,0, 10,12,0,10, 0,10,9,0, 9,8,0,8, 
    -2, 0
];

const bass = [
    // intro, 1-8
    bp1a, bp1b, bp1c, bp1d,
    bp1a, bp1b, bp1c, bp1d,
    // pre-verse, 9-16
    bp1a, bp1b, bp1c, bp1d,
    bp1a, bp1b, bp1c, bp1d,
    // verse, 17-24
    bp1a, bp1b, bp1c, bp1d,
    bp1a, bp1b, bp1c, bp1e,
    // chorus, 25-32
    bp3a, bp3b, bp3c, bp3d,
    bp3a, bp3b, bp3c, bp3e, 
    // pre-verse2, 33-36
    bp1a, bp1b, bp1c, bp1d,
    // verse2, 37-44
    bp1a, bp1b, bp1c, bp1d,
    bp1a, bp1b, bp1c, bp1e,
    // chorus2, 45-52
    bp3a, bp3b, bp3c, bp3d, 
    bp3a, bp3b, bp3c, bp3f, 
    // pre-solo riff, 53-56
    bp6a, bp6b, bp6c, bp6d,
    // solo 57-72
    bp1a, bp1b, bp1c, bp1d,
    bp1a, bp1b, bp1c, bp1d,
    bp3a, bp3b, bp3c, bp3d,
    bp3a, bp3b, bp3c, bp3d,
    // chorus w/ solo 73-80
    bp3a, bp3b, bp3c, bp3d,
    bp3a, bp3b, bp3c, bp3f,
    // outro 81-87
    bp7
];

loop( class extends Track {
    play(note) {
        const A = 0.01;
        const D = 0.01;
        const S = 0.3;
        const R = 0.1;
        if (1) Strat.play(note, {
            attack: A, decay: D, sustain: S, duration: this.duration, release: R, pan: 0, amp: 30,
            mul: 0.997, // 1.99763,
            coeff: [
                [1.0003, 0.5],
                [2.0002, 0.3],
                [4.0001,0.2],
            ],
            feedback: 2.8
        });
        if (1) Strat.play(note, {
            attack: A, decay: D, sustain: S,duration: this.duration, release: R, pan: 0, amp: 30,
            mul: 1.01, // 1.99763,
            coeff: [
                [0.5003, 0.6],
                [1.0002, 0.25],
                [2.0001,0.1]
            ],
            feedback: 2.5
        });
    }
}, { 
    amp: 30, name: 'Electric bass', patterns: bass, start: START,
})
.connect(amp.create({ gain: 40 }))
.connect(lowpass.create( { cutoff: 0.35 } ))
.connect(amp.create({ gain: 1.5 }))
;


/*
    Main guitar
*/
const gp1 = [
    -4,0
];

const gp2 = [
    e3,-0.125,
    6.0,6.042,6.083,6.125,6.167,6.208,6.25,6.292, 6.333,6.375,6.417,6.458,6.5,6.542,6.583,6.625, 6.667,6.708,6.75,6.792,6.833,6.875,6.917,6.958, 7.0,7.042,7.083,7.125,7.167,7.208,7.25,7.292
];
const gp3 = [
    e3,-0.125,
    7.333,7.375,7.417,7.458,7.5,7.542,7.583,7.625,7.667,7.708,7.75,7.792,7.833,7.875,7.917,7.958,8.0,8.042,8.083,8.125,8.167,8.208,8.25,8.292,8.333,8.375,8.417,8.458,8.5,8.542,8.583,8.625
];
const gp4 = [
    e3,-0.125,
    8.667,8.708,8.75,8.792,8.833,8.875,8.917,8.958,9.0,9.042,9.083,9.125,9.167,9.208,9.25,9.292,9.333,9.375,9.417,9.458,9.5,9.542,9.583,9.625,9.667,9.708,9.75,9.79
];
const gp5 = [
    e3,-0.125,
    10.0,10.042,10.083,10.125,10.167,10.208,10.25,10.292,
    10.333,10.375,10.417,10.458,10.5,10.542,10.583,10.625,
    10.667,10.708,10.75,10.792,10.833,10.875,10.917,10.958,
    11.0,11.042,11.083,11.125, 
    12,13,14,15,16,17,18,19,
];
/* jshint elision: true */
const gp6 = [
    a3,
    -0.5,[0,12,,14],-0.25,[0,12,,14],[0,12,,14],-0.5,[0,12,,14],[0,12,,14],[0,12,,14],
    // string, fret, duration, bend
    b4,-0.25,12,
    [2, [b4,12,0.25,0.5]],
    b4,-0.5,12,10,
];
const gp7 = [
    a3,
    -0.5,[0,11,,13],-0.25,[0,11,,13],[0,11,,13],-0.5,[0,11,,13],[0,11,,13],[0,11,,13],
    b4,-0.25,12,
    [2, [b4,12,0.25,0.5]],
    b4,-0.5,12,10,
];
const gp8 = [
    a3,
    -0.5,[0,8,,10],-0.25,[0,8,,10],[0,8,,10],-0.5,[0,8,,10],[0,8,,10],[0,8,,10],
    b4,-0.25,12,
    [2, [b4,12,0.25,0.5]],
    b4,-0.5,12,10,
];
const gp9 = [
    a3,
    -0.5,[0,7,,9],-0.25,[0,7,,9],[0,7,,9],-0.5,[0,7,,9],[0,7,,9],[0,7,,9],
    b4,12,-1,12,
];
const gp10 = [
    b3,-1,10,-3,null
];
const rake = [0,0,0,0];
const gp11 = [
    d3,-0.25,
    rake,rake,rake,rake,rake,rake,rake,rake,
    rake,rake,rake,rake,rake,rake,rake,rake,
];
const pause = [-4,null];
const cC = [0,3,5,5]; //,4];
const cG = [0,3,5,5]; //,4];
const cDm = [0,5,5,7]; //,7];
const cAm = [0,5,7,7]; //,5];
const gp12 = [
    a3,
    -0.5,cC,
    -0.25,cC,cC,cC,cC,cC,
    -2.25,cC
];
const gp13 = [
    e3,
    -0.5,cG,
    -0.25,cG,cG,cG,cG,cG,
    -2.25,cG
];
const gp14 = [
    e3,
    -0.5,cDm,
    -0.25,cDm,cDm,cDm,cDm,cDm,
    -2.25,cDm
];
const gp15 = [
    e3,
    -0.5,cAm,
    -0.25,cAm,cAm,cAm,cAm,cAm,
    -2.25,cAm
];
const gp16 = [
    e3,-0.25,
    0,0,10,7, 10,12,0,15, 0,12,0,15, 15,12,15,
    a3,12
];
const gp17 = [
    e3,-1, [0,5,7,7],
    a3,-0.25, 10,12,0,10, 0,7,0,8, 8,7,5,7
];
const gp18 = [
    e3,-0.25,
    0,0,10,0, 10,12,0,15, 0,12,0,15, 15,12,15,a3,12
];
const gp19 = [
    d4,-0.25,
    0,0,10,0, 10,12,0,10, 0,10,9,0, 9,8,0,8
];
const gp20 = [
    d3,-0.25,
    7,a3,0,10,0, 10,12,0,10, 0,7,0,8, 8,7,5,7
];
const gp21 = [
    a3,-0.25, 
    0,0,10,0, 10,12,0,10, 0,7,0,8, 8,7,5,7
];
const gp22 = [
    e3,
    -0.5,cAm,
    -0.25,cAm,cAm,cAm,cAm,cAm,
    // bak -2.25,cAm,
    -1.25,cAm,
    [2, [a3, 12, 1,  -6]],
];
const gp23a = [
    e3,-0.25, 
    0,0,10,0, 10,12,0,10, 0,10,9,0, 9,8,0,8,
];
const gp23b = [
    e3,-0.25, 
    7,0,10,0, 10,12,0,10, 0,10,9,0, 9,8,0,8,
];
const gp23c = [
    e3,-0.25, 
    7,0,10,0, 10,12,0,15, 0,12,0,15, 15,12,15,a3,12
];
const gp24 = [
    g4,-0.25,
    14,14,b4,17,17, g4,14,14,b4,18,18, g4,14,14,b4,18,18, 17,17,g4,14,b4,17
];
const gp25 = [
    g4,-0.25,
    13,13,b4,17,17, g4,13,13,b4,18,18, g4,13,13,b4,18,18, 17,17,g4,13,b4,17
];
const gp26 = [
    g4,-0.25,
    14,14,b4,18,18, g4,14,14,e5,17,17, g4,14,14,b4,18,18, g4,14,14,b4,17,17
];
const gp27 = [
    b4,-0.25,
    17,g4,14,14,14, b4,15,d4,15,15,15, b4,13,d4,14,14,14, b4,12,d4,12,12,12 
];
const gp28 = [ // bar 65
    [1, [b4,13,2.5,0.5],[g4,12,2.0,0.5],[d4,14,1.5,1]],
    b4,-1,
    // bak: 13,
    [2, [b4,12,1,0.5]],
    -0.5,12,10
];
const gp29 = [ // bar 66
    [1, [b4,12,2.5,0.5],[g4,12,2.0,0.5],[d4,12,1.5,1]],
    b4,-1,12,-0.5,12,-0.25,13,15
];
const gp30 = [ // bar 67
    [1, [b4,15,2.5,0.5],[g4,14,2.0,0.5],[d4,15,1.5,1]],
    b4,-1,
    // bak: 15,
    [2, [b4,13,1,1]],
    -0.5,13,12
];
const gp31 = [ // bar 68
    [1, [b4,13,2.5,0.5],[g4,14,2.0,0.5],[d4,14,1.5,1]],
    b4,-1,15,-0.5,13,12
];
const gp32 = [ // bar 69
    [1, [b4,13,2.5,0.5],[g4,12,2.0,0.5],[d4,14,1.5,1]],
    b4,-1.5,12,-0.5,10
];
const gp33 = [ // bar 70
    [1, [b4,12,2.5,0.5],[g4,12,2.0,0.5],[d4,12,1.5,1]],
    b4,-1,12,-0.5,13,
     // bend: string, fret, duration, bend
    [2, [b4, 12, 1,  1.5]],
];
const gp34 = [ // bar 71
    b4,-0.5,15,
    // bend: string, fret, duration, bend
    -0.25,rake,14,
    d4,-1, 14,
    b4,-0.5,15,13,12
];
const gp35 = [
    [1, [b4,13,2.5,0.5],[g4,14,2.0,0.5],[d4,14,1.5,1]],
    e3,-0.25,pause,rake,rake,rake,rake,rake,rake,rake
];
const gp36 = [
    e3,-0.25,
    0,0,10,0, 10,12,0,10, 0,10,9,0, 9,8,0,8, 
    7,0,10,0, 10,12,0,10, 0,10,9,0, 9,8,0,8, 
    7,0,10,0, 10,12,0,10, 0,10,9,0, 9,8,0,8, 
    7,0,10,0, 10,12,0,10, 0,10,9,0, 9,8,0,8, 
    7,0,10,0, 10,12,0,10, 0,10,9,0, 9,8,0,8, 
    7,0,10,0, 10,12,0,10, 0,10,9,0, 9,8,0,8, 
    -2, 0
];

const main_guitar = [
    // intro, 1-8
    gp1,gp1,gp1,gp1,
    gp2,gp3,gp4,gp5,
    // pre-verse, 9-16
    gp6,gp7,gp8,gp9,
    gp6,gp7,gp8,gp9,
    // verse, 17-24
    gp10,pause,pause,pause,
    pause,pause,pause,gp11,
    // chorus, 25-32
    gp12,gp13,gp14,gp15,
    gp12,gp13,gp14,gp16,
    // pre-verse2, 33-36
    gp6,gp7,gp8,gp9,
    // verse2, 37-44
    gp17,gp18,gp19,gp20,
    gp21,gp18,gp19,gp11,
    // chorus2, 45-52
    gp12,gp13,gp14,gp15,
    gp12,gp13,gp14,gp22,
    // pre-solo riff, 53-56
    gp23a,gp23b,gp23b,gp23c,
    // solo 57-72
    gp24,gp25,gp26,gp24, // 57-60
    gp24,gp25,gp26,gp27, // 61-64
    
    gp28,gp29,gp30,gp31, // 65-68
    gp32,gp33,gp34,gp35, // 69-72
    // chorus w/ solo 73-80
    gp12,gp13,gp14,gp15,
    gp12,gp13,gp14,gp15,
    // outro 81-87
    gp36
];

loop(ElectricGuitar, { 
    name: 'Main guitar', patterns: main_guitar, start: START,
    amp: 1.2, pan: -0.5, mul: 0.495, feedback: 3
})
.connect(spread.create({ delay: 2000, width: 0.8 }))
.connect(amp.create({ gain: 47.456 })) // overdrive
//.connect(lowpass.create( { cutoff: 0.7 } ))
.connect(amp.create({ gain: 1.75 }))
;


/*
    2nd Guitar 
*/
const odp1 = [
    b4,-0.5,10,-0.25,[0,0,0],[0,0,0],-0.5,10,-0.25,[0,0,0],10,[0,0,0],[0,0,0],13,-0.75,12,-0.5,10
];
const odp2 = [
    b4,-0.5,9,-0.25,[0,0,0],[0,0,0],-0.5,9,-0.25,[0,0,0],9,[0,0,0],[0,0,0],13,-0.75,12,-0.5,10
];
const odp3 = [
    g4,-0.5,10,-0.25,[0,0,0],[0,0,0],-0.5,10,-0.25,[0,0,0],10,[0,0,0],[0,0,0],b4,13,-0.75,12,-0.5,10
];
const odp4 = [
    g4,-0.5,9,-0.25,[0,0,0],[0,0,0],-0.5,9,-0.25,[0,0,0],9,[0,0,0],[0,0,0],b4,12,-0.25,13,-0.5,12,-0.5,10
];
const odp5 = [
    -2,null,
    b4,-0.5,null,15,13,12
];
const odp6 = [
    e5,-0.5,12,b4,13,g4,-1.5,12,
    e5,-0.5,12,10,8
];
const odp7 = [
    e5,-0.5,10,b4,12,g4,-1.5,12,
    e5,-0.5,10,10,12
];
const odp8 = [
    e5,-0.5,13,b4,15,g4,-1.5,14,
    e5,-0.5,13,-0.5,15,-0.5,13
];
const odp9 = [
    e5,-0.5,12,b4,13,e5,-1.5,12,
    e5,-1,10,d4,-0.5,14
];
const odp10 = [
    e5,-0.5,10,b4,12,g4,-1.5,12,
    e5,-0.5,10,12,10
];
const odp11 = [
    -3,null,
     // bend: string, fret, duration, bend
    [2, [g3, 12, 1,  -4]],
];

const git2 = [
    // intro, 1-8
    gp1,gp1,gp1,gp1,
    gp2,gp3,gp4,gp5,
    // pre-verse, 9-16
    gp11,gp11,gp11,gp11,
    gp11,gp11,gp11,gp11,
    // verse, 17-24
    pause,pause,pause,pause,
    pause,pause,pause,pause,
    // chorus, 25-32
    pause,pause,pause,pause,
    pause,pause,pause,pause,
    // pre-verse2, 33-36
    odp1,odp2,odp3,odp4,
    // verse2, 37-44
    pause,pause,pause,pause,
    pause,pause,pause,pause,
    // chorus2, 45-52
    pause,pause,pause,pause,
    pause,pause,pause,pause, // odp11,
    // pre-solo riff, 53-56
    pause,pause,pause,pause,
    // solo 57-72
    pause,pause,pause,pause,
    pause,pause,pause,pause,
    pause,pause,pause,pause,
    pause,pause,pause,odp5,
    // chorus w/ solo 73-80
    odp6,odp7,odp8,odp9,
    odp6,odp10,odp8,odp9,
    // outro 81-87
    gp36
];

loop(class extends ElectricGuitar {
    strum(note, a, d, r) {
        super.strum(note - 0.02, a, d, r);
    }
}, { 
    name: '2nd Guitar', patterns: git2, start: START,
    amp: 1, pan: 0.5, mul: .497, feedback: 2.5
})
.connect(amp.create({ gain: 47.11 })) // overdrive
//.connect(lowpass.create( { cutoff: 0.5 } ))
.connect(amp.create({ gain: 0.85 }))
;

/*
    Bass Drum
*/
const Bassdrum = synth.def(class {
    constructor(options) {
        this.t = 0; Math.random();
        this.noise = 0.05;
    }
    fm(fc, fm, iom, t) {
        return sin(fc*TAU*t + iom*sin(fm*TAU*t));
    }
    process(note, env, tick, options) {
        const f = 120;
        this.t += ditty.dt;
        
        const t = this.t;
        let sig = 0;

        // noise click
        sig += Math.random(1) * this.noise * Math.exp(-300*t);

        // body
        const df = -f *5.5* t;
        const fb = f + df;
        sig += this.fm(fb, fb, 1, t) * (1-this.noise) * Math.exp(-40*t);
 
        return [sig,sig];
    }
});

const bdp1  = "................";
const bdp2  = "x...............";
const bdp3  = "x...x...x...x...";
const bdp4  = "x.x..x..x.x..x..";
const bdp5  = "x.x..x..x.x.....";
const bdp6  = ".xx..x.xx..x.x..";
const bdp7  = "x...x...x...x.x.";
const bdp8  = ".xx.xx.xx.xx.x..";
const bdp9  = "x...x...x....x..";
const bdp10 = ".x.x.......x....";
const bdp11 = "x.x..x...xx.xx..";
const bdp12 = "x...x.......x.x.";
const bdp13 = "x.x.x.x.x.x.x.x.";
const bdp14 = "x..x..x.x..x..x.";
const bdp15 = "x";

const bd = [
    // intro, 1-8
    bdp1,bdp1,bdp1,bdp1,
    bdp2,bdp2,bdp2,bdp2,
    // pre-verse, 9-16
    bdp3,bdp3,bdp3,bdp3,
    bdp3,bdp3,bdp3,bdp3,
    // verse, 17-24
    bdp4,bdp4,bdp4,bdp5,
    bdp4,bdp4,bdp4,bdp6,
    // chorus, 25-32
    bdp7,bdp7,bdp7,bdp7,
    bdp7,bdp7,bdp7,bdp8,
    // pre-verse2, 33-36
    bdp3,bdp3,bdp3,bdp9,
    // verse2, 37-44
    bdp4,bdp4,bdp4,bdp5,
    bdp4,bdp4,bdp4,bdp6,
    // chorus2, 45-52
    bdp7,bdp7,bdp7,bdp7,
    bdp7,bdp7,bdp7,bdp10,
    // pre-solo riff, 53-56
    bdp3,bdp3,bdp4,bdp11,
    // solo 57-64
    bdp3,bdp3,bdp3,bdp3,
    bdp3,bdp3,bdp3,bdp8,
    // solo 65-68,
    bdp7,bdp7,bdp7,bdp7,
    bdp7,bdp7,bdp7,bdp12,
    // chorus w/ solo 73-80
    bdp7,bdp7,bdp7,bdp7,
    bdp7,bdp7,bdp7,bdp10,
    // outro 81-87
    bdp3,bdp3,bdp4,bdp4,
    bdp13,bdp14,bdp15
];

loop( class extends Track {
    play(note) {
        if (note == 'x') {            
            Bassdrum.play(1, {amp: Math.random()*0.1+0.9});
        }     
    }
},
{ 
    amp: 1.0, name: 'Bass drum', patterns: bd, start: START
})
.connect(amp.create({ amp: 2.5 }))
;


/*
    Snare Drum
*/

const Snare = synth.def(class {
    constructor(options) {
        this.t1 = 0; // + Math.random();
        this.t2 = 0; // - Math.random();
    }
    fm(fc, fm, iom, t) {
        return sin(fc*TAU*t + iom*sin(fm*TAU*t));
    }
    snare(f, t) {
        let sig = 0;
        // click
        sig += this.fm(f*10, 0, 0, this.t1*t) * 0.4 * Math.exp(-100*this.t1);

        // body
        const df = -f*2.5 * this.t2;
        const fb = f+df;
        sig += this.fm(fb, fb*3, 2.5, this.t2*t) * 0.8 * Math.exp(-60*this.t2);

        // noise
        sig += Math.random(1) * 0.3 * Math.exp(-15*this.t1);
        return sig;
    }
    process(note, env, tick, options) {
        const f = midi_to_hz(note);
        this.t1 += ditty.dt;
        this.t2 += ditty.dt;

        let l,r = 0;
        l = this.snare(f, 1.01);
        r = this.snare(f, 0.99);

        return [l,r];
    }
});

// snare-drum patterns
const sp1  = "................";
const sp2  = "....x.......x...";
const sp2a = "....x.......x..x";
const sp3  = "....x.......xxxx";
const sp4  = "x.....x.....x...";
const sp5  = "....x....x..x...";
const sp6  = "....x....x....x.";
const sp7  = "x..x..x..x..x.x.";
const sp8  = "....x.....xxx.x.";
const sp9  = "x...xxxxxx.x....";
const sp10 = "....x...x.....x.";
const sp11 = "x..x..x..x....x.";
const sp12 = ".xx.xx.x.xx.....";

const snare = [
    // intro, 1-8
    sp1,sp1,sp1,sp1,
    sp2,sp2,sp2,sp3,
    // pre-verse, 9-16
    sp2,sp2,sp2,sp3,
    sp2,sp2,sp2,sp3,
    // verse, 17-24
    sp2,sp2,sp2,sp3,
    sp2,sp2,sp2,sp4,
    // chorus, 25-32
    sp5,sp5,sp5,sp6,
    sp5,sp5,sp5,sp7,
    // pre-verse2, 33-36
    sp2,sp2,sp2,sp8,
    // verse2, 37-44
    sp2,sp2,sp2,sp3,
    sp2,sp2,sp2,sp4,
    // chorus2, 45-52
    sp5,sp5,sp5,sp6,
    sp5,sp5,sp5,sp9,
    // pre-solo riff, 53-56
    sp2,sp2,sp2,sp10,
    // solo 57-72
    sp2,sp2,sp2,sp3,
    sp2,sp2,sp2,sp11,
    sp5,sp5,sp5,sp6,
    sp5,sp5,sp5,sp9,
    // chorus w/ solo 73-80
    sp5,sp5,sp5,sp6,
    sp5,sp5,sp5,sp9,
    // outro 81-86
    sp2,sp2,sp2,sp2a,sp2,
    sp12
];

loop( class extends Track {
    play(note) {
        if (note == 'x') {
            Snare.play(23, { 
                pan: -0.15
            }); 
        }         
    }
},
{ 
    amp: 2.8, pan: 0, name: 'Snare drum', patterns: snare, start: START
})
.connect(lowpass.create({ cutoff: 0.6 }))
;


/*
    Ride cymbal
*/
const Ride = synth.def( class {
    constructor (options) {
        this.t = Math.random();
        this.iom = 65.51;
        this.mul = 3.8;
    }
    add_fm(note) {
        // carrier frequency
        const fc = midi_to_hz(note);
        // modulator frequency
        const fm = fc * this.mul;
        let mod = sin(TAU*this.t*fm);
        return sin(TAU*this.t*fc + this.iom*mod);
    }
    add_noise() {
        return sin(TAU*Math.random());
    }
    process(note, env, tick, options) {
        // forward time
        this.t += ditty.dt;

        let osc = this.add_fm(note) * 0.2;
        osc += this.add_noise() * 0.2;
        
        osc = (osc * 0.5) * env.value;
        //debug.probe("ride", 50*osc, 10, 2);

        return [osc, osc]; // left, right
    }
}); 

// "Make the ride crash while playing it on downbeats"
const rp1 = "................";
const rp2 = "..x.x.x.x.x.x.x.";
const rp3 = "x.x.x.x.x.x.x.x.";
const rp4 = "x.x.x.x.x.x.....";
const rp5 = "..x.............";

const ride = [
    // intro, 1-8
    rp1,rp1,rp1,rp1,
    rp1,rp1,rp1,rp1,
    // pre-verse, 9-16
    rp2,rp3,rp3,rp4,
    rp2,rp3,rp3,rp4,
    // verse, 17-24
    rp1,rp1,rp1,rp1,
    rp1,rp1,rp1,rp1,
    // chorus, 25-32
    rp2,rp3,rp3,rp4,
    rp2,rp3,rp3,rp1,
    // pre-verse2, 33-36
    rp3,rp3,rp3,rp4,
    // verse2, 37-44
    rp1,rp1,rp1,rp1,
    rp1,rp1,rp1,rp1,
    // chorus2, 45-52
    rp2,rp3,rp3,rp4,
    rp2,rp3,rp3,rp1,
    // pre-solo riff, 53-56
    rp1,rp1,rp1,rp1,
    // solo 57-72
    rp2,rp3,rp3,rp4,
    rp2,rp3,rp3,rp1,
    rp2,rp3,rp3,rp4,
    rp2,rp3,rp3,rp5,
    // chorus w/ solo 73-80
    rp2,rp3,rp3,rp4,
    rp2,rp3,rp3,rp1,
    // outro 81-87
    rp1,rp1,rp1,rp1,rp1,rp1,rp1
];

loop( class extends Track {
    play(note) {
        if (note == 'x') {            
            Ride.play(79, { 
                amp: Math.random()*0.1+0.9,
                attack: 0, decay: 2, sustain: 0, release: 0, curve: -5
            }); 
        }
    }
},
{ 
    amp: 1.6, pan: 0.6, name: 'Ride cymbal', patterns: ride, start: START,
})
.connect(lowpass.create({ cutoff: 0.6 }))
.connect(amp.create({amp: 1.5}))
;


/*
    Percussion helpers
*/

function add_fm(t, fc, options) {
    // modulator frequency
    const fm = fc * options.mul;
    const mod = sin(TAU*t*fm);
    const osc = sin(TAU*t*fc + options.iom*mod);
    return osc;
}

function add_noise(t, fc, options) {
    const osc = sin(TAU * Math.random());
    return osc;
}


/*
    Open Hi-Hat
*/

const OpenHiHat = synth.def(class {
    constructor(options) {
        this.t = Math.random();
        this.env_fm = adsr.create( { 
            attack: 0.0, decay: 0.05, sustain: 0, release: 0, curve: -3
        });
        this.env_n =  adsr.create( { 
            attack: 0.01, decay: 0.15, sustain: 0, release: 0, curve: -5
        });
    }
    process(note, env, tick, options) {
        this.t += ditty.dt;
        const f = midi_to_hz(note);

        let osc = add_fm(this.t, f, options) * this.env_fm.value * 0.4;
        osc += add_noise(this.t, f, options) * this.env_n.value * 0.1;
        osc *= 0.7;
        //debug.probe( "OpenHiHat", osc, 0.5 );
        return [osc, osc]; // left, right
    }
});

const ohp1 = "................";
const ohp2 = "..x...x...x...x.";
const ohp3 = "..x...x...x.....";
const ohp4 = "........x.......";

const openhihat = [
    // intro, 1-8
    ohp1,ohp1,ohp1,ohp1,ohp1,ohp1,ohp1,ohp1,
    // pre-verse, 9-16
    ohp2,ohp2,ohp2,ohp2,ohp2,ohp2,ohp2,ohp3,
    // verse, 17-24
    ohp2,ohp2,ohp2,ohp2,ohp2,ohp2,ohp2,ohp1,
    // chorus, 25-32
    ohp4,ohp4,ohp4,ohp4,ohp4,ohp4,ohp4,ohp1,
    // pre-verse2, 33-36
    ohp2,ohp2,ohp2,ohp3,
    // verse2, 37-44
    ohp2,ohp2,ohp2,ohp2,ohp2,ohp2,ohp2,ohp1,
    // chorus2, 45-52
    ohp4,ohp4,ohp4,ohp4,ohp4,ohp4,ohp4,ohp1,
    // pre-solo riff, 53-56
    ohp1,ohp1,ohp1,ohp1,
    // solo 57-72
    ohp2,ohp2,ohp2,ohp3,ohp2,ohp2,ohp2,ohp1,
    ohp4,ohp4,ohp4,ohp4,ohp4,ohp4,ohp4,ohp1,
    // chorus w/ solo 73-80
    ohp4,ohp4,ohp4,ohp4,ohp4,ohp4,ohp4,ohp1,
    // outro 81-87
    ohp1,ohp1,ohp1,ohp1,ohp1,ohp1,ohp1,
];

loop( class extends Track {
    play(note) {
        if (note == 'x') {  
            OpenHiHat.play(58.5, { 
                iom: 2.4, mul: 31.5,
            });
        }
    }
},
{ 
    amp: 1.2, pan: -0.3, name: 'Open Hi-Hat', patterns: openhihat, start: START,
})
;


/*
    Closed Hi-Hat
*/

const ClosedHiHat = synth.def(class {
    constructor(options) {
        this.t = Math.random();
        this.env_fm = adsr.create( { 
            attack: 0.01, decay: 0.3, sustain: 0, release: 0, curve: -9
        });
        this.env_n =  adsr.create( { 
            attack: 0.01, decay: 0.2, sustain: 0, release: 0, curve: -7
        });
    }
    process(note, env, tick, options) {
        this.t += ditty.dt;
        const f = midi_to_hz(note);

        let osc = add_fm(this.t, f, options) * this.env_fm.value * 0.4;
        osc += add_noise(this.t, f, options) * this.env_n.value * 0.15;
        osc *= 0.5;
        return [osc, osc]; // left, right
    }
});

const chp1  = "................";
const chp2  = "..x.x.x.x.x.x.x.";
const chp2a = "x.x.x.x.x.x.x.x.";
const chp3  = "x.x.x.x.........";
const chp4  = ".x..............";
const chp5  = "x...............";

const closedhh = [
    // intro, 1-8
    chp1,chp1,chp1,chp1,chp1,chp1,chp1,chp1,
    // pre-verse, 9-16
    chp1,chp1,chp1,chp1,chp1,chp1,chp1,chp1,
    // verse, 17-24
    chp1,chp1,chp1,chp1,chp1,chp1,chp1,chp1,
    // chorus, 25-32
    chp1,chp1,chp1,chp1,chp1,chp1,chp1,chp1,
    // pre-verse2, 33-36
    chp1,chp1,chp1,chp1,
    // verse2, 37-44
    chp1,chp1,chp1,chp1,chp1,chp1,chp1,chp1,
    // chorus2, 45-52
    chp1,chp1,chp1,chp1,chp1,chp1,chp1,chp1,
    // pre-solo riff, 53-56
    chp2,chp2a,chp2a,chp3,
    // solo 57-72
    chp1,chp1,chp1,chp1,chp1,chp1,chp1,chp1,
    chp1,chp1,chp1,chp1,chp1,chp1,chp1,chp1,
    // chorus w/ solo 73-80
    chp1,chp1,chp1,chp1,chp1,chp1,chp1,chp4,
    // outro 81-87
    chp2,chp2a,chp2a,chp2a,chp2a,chp5
    
];

loop( class extends Track {
    play(note) {
        if (note == 'x') {         
            ClosedHiHat.play(95, { 
                iom: 2.4, mul: 31.5,
            });
        }
    }
},
{ 
    amp: 1.5, pan: -0.7, name: 'Closed Hi-Hat', patterns: closedhh, start: START,
})
;