Für Elise | Grand piano

"east cost" piano synthesis. There is an Easteregg at the end.
#FurElise #piano #contest

Log in to post a comment.

// Für Elise | Piano Contest
// Grand Piano
// by srtuss 2022/12/7
//
//  In this implementation, we attempt a piano sound in a purely and simply subtractive-synthesis manner.
//  We want to keep synthesis as simple and fast as possible, because we will end up playing LOTS of synth voices simultaneously (strings
//  may keep vibrating for several seconds).
//  It is based around a synthesizer preset in FL Studio called "Grand piano", by Dave Hughes
//
//  After listening to some piano sounds in our DAW, and looking at some waveforms and spectrograms, we can make the following basic observations:
//      * Keys sound very different at different octaves. Low keys have a harsh, metallic timbre. High keys become smooth like a sinusoid.
//      * The waveforms produced are not merely pure tones, but have a shimmering quality, like subtle slow frequency modulation.
//      * The hammering action produces a brief amplitude spike, followed by a gradual decay.
//      * The vibration of low keys fades slower than high keys.
//      
//  HOW IT WORKS
//  We use a total of 4 wavetable oscillators per "key press".
//  Each wavetable holds a timbre related to a pitch-range on the piano (taken from sampled piano sounds).
//  The intensity/contribution of each oscillator varies depending on the note that is pressed.
//  Each oscillator has it's own gentle frequency modulation which creates a shimmering quality when mixed together with the other oscillators.
//  The output of the oscillators is mixed and fed through a lowpass filter.
//  The lowpass cutoff and amplitude are modulate to mimic the piano hammering action.
//  The oscillator for the highest pitches receives a burst of FM to further mimic the piano hammering action.
//  Finally the sound passes through effect processors:
//      * A chorus to increase the amount of shimmer, and to add stereo qualities. A chorus effect creates detuned copies of the
//        input signal. This is the same as multiplying the total number of oscillators, but without additional computational cost.
//      * A reverb to give the impression of a large space around the piano.
//  
//
// Forked from "Für Elise | Piano Contest" by reinder
// https://dittytoy.net/ditty/cb751b4b3b

ditty.bpm = 60;

// Simple allpass reverberator, based on this article by Spin Semiconductor:
// http://www.spinsemi.com/knowledge_base/effects.html
const reverb = filter.def(class {
    constructor(opt) {
        this.lastReturn = 0;
        this.krt = .4;
        this.delaylines = [];
        this.flt = new SVF({fc:.001, q:.5, num:1, mode: 'hp'});
        this.pred = new Delayline(4000);
        
        [263, 863, 1319, 1433, 439, 359, 887, 1399, 233, 1367, 4253, 2903].forEach((dl) => this.delaylines.push(new Delayline(dl)));
    }
    allpass(delayline, x, k) {
        var delayin = x - delayline.lastOut * k;
        var y = delayline.lastOut + k * delayin;
        delayline.clock(delayin);
        return y;
    }
    process(input, opt) {
        var inv = input[0];
        var dls = this.delaylines;
        inv = this.flt.process(this.pred.clock(inv));
        var v = this.lastReturn;
        // Let the signal pass through the loop of delay lines. Inject input signal at multiple locations.
        v = this.allpass(dls[0], v + inv, .5);
        v = this.allpass(dls[1], v, .5);
        dls[2].clock(v);
        v = dls[2].lastOut * this.krt;
        v = this.allpass(dls[3], v + inv, .5);
        v = this.allpass(dls[4], v, .5);
        dls[5].clock(v);
        v = dls[5].lastOut * this.krt;
        v = this.allpass(dls[6], v + inv, .5);
        v = this.allpass(dls[7], v, .5);
        dls[8].clock(v);
        v = dls[8].lastOut * this.krt;
        v = this.allpass(dls[9], v + inv, .5);
        v = this.allpass(dls[10], v, .5);
        dls[11].clock(v);
        v = dls[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] += dls[2].tap(111);
        ret[1] += dls[2].tap(2250);
        ret[0] += dls[5].tap(311);
        ret[1] += dls[5].tap(1150);
        ret[0] += dls[8].tap(511);
        ret[1] += dls[8].tap(50);
        ret[0] += dls[11].tap(4411);
        ret[1] += dls[11].tap(540);
        // Mix wet + dry signal.
        ret[0] = ret[0] * opt.wet + input[0] * opt.dry;
        ret[1] = ret[1] * opt.wet + input[1] * opt.dry;
        var m = (ret[0] + ret[1])*.5;
        var s = (ret[0] - ret[1])*.5;
        s *= 2;
        return [m+s, m-s];
    }
});

const chorus = filter.def(class {
    constructor(options) {
        this.lastOutput = [0, 0];
        var time = 60 / ditty.bpm;
        //time *= 3 / 4;
        time /= 2;
        var n = Math.floor(time / ditty.dt);
        this.delay = new Delayline(5000);
        this.ops = [];
        for(var i = 0; i < 9; ++i) {
            var t = i/9;
            this.ops.push({p:t, f: 1, fl: t, fr: 1-t});
        }
    }
    process(inp, options) {
        this.delay.clock(inp[0]);
        var vl = 0, vr = 0;
        for(var i = 0; i < this.ops.length; ++i) {
            var op = this.ops[i];
            var p = (Math.sin(op.p*Math.PI*2)*.5+.5)*40;
            var pi = ~~p;
            var v = lerp(this.delay.tap(pi), this.delay.tap(pi+1), p-pi);
            vl += v * op.fl;
            vr += v * op.fr;
            op.p += ditty.dt * op.f;
        }
        inp[0] = inp[0] * .5 + vl * .1;
        inp[1] = inp[1] * .5 + vr * .1;
        return inp;
    }
});

const series = (n, lambda) => {
    var r = [];
    for(var i = 0; i < n; ++i)
        r.push(lambda(i));
    return r;
}
const spectral = (amps, phas, length) => {
    var sum = 0;
    for(var i = 0; i < amps.length; ++i)
        sum += Math.abs(amps[i]);
    for(var i = 0; i < amps.length; ++i)
        amps[i] /= sum;
    var res = new Float32Array(length);
    for(var i = 0; i < amps.length; ++i) {
        var f = (i + 1) * Math.PI * 2;
        var amp = amps[i];
        var ph = phas[i];
        for(var j = 0; j < length; ++j) {
            var x = j / length;
            res[j] += Math.sin(x * f + ph) * amp;
        }
    }
    return res;
}

// Wavetable data, encoded as amplitudes and phases of each harmonic
var op1 = {a:[2,0,2,3,4,12,4,6,8,7,21,6,13,8,25,17,13,9,17,20,7,14,19,17,12,31,12,13,20,10,29,9,12,15,14,9,22,27,19,13,26,12,
27,14,11,23,21,17,17,14,34,16,31,11,15,26,34,20,23,27,26,26,14,17,30,20,25,27,10,15,14,12,20,29,26,21,37,21,24,23,19,28,37,16,
26,39,26,19,31,36,22,24,27,33,31,30,32,35,21,31,28,25,28,30,24,33,35,26,37,36,43,29,39,45,34,36,42,33,37,45,45,31,34,51,37,37,
38,31],p:[186,220,171,225,174,196,224,242,168,228,135,98,218,143,203,-45,120,224,117,-22,142,163,241,-14,170,125,194,115,28,
-51,77,220,49,77,122,222,87,135,171,-40,120,129,90,-20,203,171,-87,204,44,103,157,266,192,167,-40,18,20,120,193,261,142,171,
151,-64,95,220,101,151,141,23,140,-78,136,138,167,172,150,250,77,28,173,89,60,248,35,-66,93,139,218,195,-83,84,192,-45,263,140,
203,-65,145,32,258,146,251,-14,161,-36,222,104,-90,162,257,95,-34,150,201,20,102,120,251,255,33,121,133,39,-23,190,146,-13]};
var op2 = {a:[1,0,3,4,11,5,6,5,9,7,14,7,19,8,12,10,16,13,17,9,10,16,22,15,13,15,10,13,15,9,22,13,13,28,16,20,20,22,19,14,18,
15,27,15,17,18,24,17,20,19,29,25,27,22,16,20,30,23,24,19,22,24,22,22,25,24,23,21,10,15,20,19,26,26,36,25,28,21,33,24,26,26,35,
30,23,28,26,21,26,34,27,23,41,30,25,26,34,33,31,35,28,29,29,35,25,39,27,35,37,42,32,31,29,43,28,27,32,42,41,29,32,38,42,41,42,
30,48,38],p:[213,204,195,204,67,206,117,-58,183,-54,103,9,-54,121,-68,22,245,259,240,35,155,189,188,-2,142,161,-76,118,60,-61,
103,-28,-17,37,32,236,74,85,97,-17,4,119,169,47,-51,167,261,157,-43,3,159,255,253,125,-1,-22,99,35,251,206,36,94,90,-67,205,
181,115,145,80,39,58,-90,153,119,96,94,40,-79,81,101,62,57,39,255,12,230,8,124,-18,-84,-47,114,204,54,-48,142,232,-78,75,-29,
-79,97,265,35,66,268,243,65,5,120,-68,121,23,126,260,91,69,94,-74,-76,111,119,102,-9,-3,237,198,10]};
var op3 = {a:[0,8,13,22,14,21,20,19,23,21,28,31,29,30,28,26,36,38,35,35,35,44,39,41,39,45,34,39,40,37,42,43,47,40,52,35,51,39,
47,36,45,41,51,38,44,47,56,45,48,45,55,48,51,48,44,36,46,54,48,42,51,62,47,52,48,46,52,76,49,41,53,47,59,51,46,52,45,45,50,46,
48,77,58,41,50,54,59,54,62,54,47,55,63,59,50,42,72,47,47,45,57,52,59,51,50,77,55,45,66,46,57,49,53,49,66,55,45,68,50,43,57,62,
57,67,56,47,62,53],p:[0,0,0,30,38,98,243,-84,-29,-59,-42,29,183,21,115,124,209,-30,187,97,218,-6,128,-11,264,259,152,226,217,
246,7,19,191,228,223,253,42,-81,1,27,208,219,175,193,128,53,-28,-11,-75,169,131,-56,-36,-64,219,202,209,-66,1,75,-83,133,74,
17,122,168,-63,134,-62,-39,32,208,86,171,-2,207,179,204,209,-47,127,255,102,-13,-83,-85,177,-39,32,-7,-78,17,198,254,169,220,
-30,-87,143,85,106,250,59,-69,152,151,62,155,-10,-27,72,-17,246,32,148,245,124,-63,-14,45,-20,6,229,-40,192,-4,69,12]};
var op4 = {a:[0,14,15,22,21,25,29,31,45,44,41,36,53,48,50,53,62,48,53,52,68,60,60,57,59,47,74,50,63,50,60,56,66,61,62,67,69,
74,58,67,70,63,84,71,57,81,78,74,74,73,73,61,71,71,80,57,88,71,69,59,73,68,77,63,60,81,71,77,77,62,78,76,72,105,60,79,77,66,
79,69,77,94,80,63,84,79,83,89,76,64,72,73,94,89,78,72,96,76,81,89,89,90,81,80,72,80,103,88,87,85,97,86,86,103,93,87,97,100,96,
86,89,97,87,96,93,85,91,84],p:[180,203,193,176,185,187,183,214,174,221,210,164,-67,-86,-29,-68,-57,253,178,258,234,268,-14,
209,58,192,236,195,-15,200,-11,192,253,190,176,-26,-29,265,4,246,197,-9,259,-19,-2,32,-53,125,196,184,-11,184,5,184,253,178,
52,186,-18,184,-10,196,-49,183,-5,242,-24,29,221,3,206,-27,10,99,2,202,-11,172,29,-1,26,127,11,179,58,168,11,239,1,175,5,173,
195,154,-14,180,64,-9,179,154,13,-39,1,161,-8,178,-32,19,6,-11,-57,6,175,265,169,15,55,56,154,180,167,163,17,-15,2,140,180,174]};

const wtables = (wf) => {
    var r = [];
    for(var j = 0; j < 4; ++j) {
        r.push(spectral(
            series(128 >> j, (i) => 10**(wf.a[i]/-20)),
            series(128 >> j, (i) => wf.p[i]*Math.PI/180),
            512));
    }
    return r;
};

var wfOp1 = wtables(op1);
var wfOp2 = wtables(op2);
var wfOp3 = wtables(op3);
var wfOp4 = wtables(op4);

const ilerp = (a, b, x) => clamp01((x-a) / (b-a))

const piano = synth.def(class {
    constructor(opt) {
        this.ops = [];
        var f = midi_to_hz(opt.note);
        var wti = Math.log2(f * 512 * ditty.dt);
        wti = ~~clamp(wti, 0, 3);
        this.ops.push({p: Math.random(), f: f, wt: wfOp1[wti], pmod: -.01, pmods: 3, amp: ilerp(c7, d2, opt.note)*.5});
        this.ops.push({p: Math.random(), f: f, wt: wfOp2[wti], pmod: .01, pmods: 4, amp: ilerp(c7, d2, opt.note)*.5});
        this.ops.push({p: Math.random(), f: f, wt: wfOp3[wti], pmod: .03, pmods: 3.2222, amp: ilerp(c0, c3, opt.note)*ilerp(c6, f4, opt.note)});
        this.ops.push({p: Math.random(), f: f, wt: wfOp4[wti], pmod: -.03, pmods: 2.99, amp: ilerp(f4, c6, opt.note)+ilerp(c2, c1, opt.note)*.7});
        this.flt = new SVF({mode:'lp', num: 3, q: 1});
        this.cutoff = .3 + Math.random() * .02;
        this.t = 0;
    }
    process(note, env, tick, opt) {
        var v = 0;
        for(var i = 0; i < this.ops.length; ++i) {
            var op = this.ops[i];
            if(i == 3)
                op.p += op.f * ditty.dt * (2**(Math.sin(Math.min(this.t*900,2.5)*Math.PI*2) * 1));
            else
                op.p += op.f * ditty.dt;
            while(op.p >= 1)
                op.p -= 1;
            var ph = op.p + Math.sin(tick*op.pmods) * op.pmod;
            var w = (ph*512) & 511;
            var opv = lerp(op.wt[w], op.wt[(w+1)&511], ph*512-~~(ph*512));
            
            var vol = 1;
            if(i == 3) {
                vol = Math.exp(this.t * -10) * .5 + .5;
            }
            
            v += opv * vol * op.amp;
        }
        this.flt.q = lerp(.5,1,Math.exp(this.t * -2.));
        if(this.cutoff > .1)
            this.cutoff *= .99994;
        else
            this.cutoff *= .999995;
        this.flt.fc = this.cutoff;
        v = this.flt.process(v);
        v *= (1-clamp01((this.t - .022)*1000)*.33);
        this.t += ditty.dt;
        return v * env.value;
    }
}, { release: 1});

const numWorkers = 2; // increase number of workers when performance is low
const fürEliseData = fürElise();

for (let i=0; i<numWorkers; i++) {
    loop( () => {
        fürEliseData.notes.forEach( (n, index) => {
            sleep(second_to_tick(n.dt));
            
            if (index % numWorkers === i % numWorkers) {
                
                const duration = n.sustain ? Math.max(n.duration, Math.min(n.duration + 0.5, n.sustainMaxDuration)) : n.duration;
                piano.play(n.midi, { 
                    amp: n.velocity, 
                    duration: second_to_tick(duration), 
                    pan: (n.midi-50)/50 - .5
                });
                
            }
        });
    
    }, { name: `Piano ${i}`, sync: fürEliseData.duration, amp: 1.5 })
        .connect(chorus.create())
        .connect(reverb.create({wet:.1, dry:.8}));;
}

const applause = synth.def(class {
    constructor(opt) {
        this.l = 1;
        this.t = 0;
        this.fo = 60;
        this.flt = new SVF({mode:"bp", fc:.04, num: 2, q: 1.3});
        this.flt2 = new SVF({mode:"bp", fc:.08, num: 2, q: 1.3});
    }
    process(note, env, tick, opt) {
        var v = (Math.random()-.5) * Math.exp(this.t * -this.fo);
        this.t += ditty.dt;
        if(this.t > this.l) {
            this.t = 0;
            this.l = lerp(.05 + Math.random() * .05, lerp(.01, .04, Math.random()), env.value);
            this.fo = lerp(50, 100, Math.random());
            this.flt2.fc = lerp(.08, .1, Math.random());
        }
        return (this.flt.process(v) + this.flt2.process(v) * 0.2) * env.value;
    }
});

loop(() => {
    sleep(145);
    applause.play(c3, { attack: 2, release: 4,  duration: 10, pan: -1 });
    applause.play(c3, { attack: 2, release: 4,  duration: 10, pan: 1 });
    sleep(20);
}, { name: '...', sync: fürEliseData.duration, amp: 1.5 }).connect(reverb.create({wet:.5,dry:.5}));

//
// Use this data. The data contains each note's velocity, duration, and time and whether the sustain pedal is active.
// The data is based on a midi file by: https://www.patreon.com/rousseau
// You can find the recording of the midi file here: https://youtu.be/wfF0zHeU3Zs 
//

function fürElise() {
    const data = {notes:[76,.2,1.02,.496,75,.15,1.22,.496,76,.15,1.39,.512,75,.15,1.55,.512,76,.16,1.71,.543,71,.15,1.87,.528,74,.16,2.03,.52,72,.19,2.19,.535,69,.17,2.36,.52,45,.21,2.36,.52,52,.29,2.54,.496,57,.1,2.71,.528,60,.15,2.88,.52,64,.14,3.05,.496,69,.16,3.2,.551,40,.13,3.36,.551,71,.14,3.36,.559,52,.17,3.54,.528,56,.07,3.71,.488,64,.17,3.88,.504,68,.15,4.03,.543,71,.18,4.21,.535,45,.21,4.36,.52,72,.27,4.38,.583,52,.2,4.57,.457,57,.16,4.74,.52,64,.13,4.94,.496,76,.17,5.1,.575,75,.15,5.28,.575,76,.18,5.44,.551,75,.17,5.6,.575,76,.17,5.77,.551,71,.15,5.94,.504,74,.16,6.09,.52,72,.2,6.25,.504,45,.18,6.42,.52,69,.15,6.42,.496,52,.28,6.6,.504,57,.1,6.78,.496,60,.18,6.93,.504,64,.15,7.1,.512,69,.19,7.27,.528,40,.1,7.42,.535,71,.16,7.43,.583,52,.19,7.6,.496,56,.08,7.8,.488,64,.19,7.98,.488,72,.18,8.17,.543,71,.2,8.36,.496,69,.49,8.56,.48,45,.23,8.58,.496,52,.26,8.82,.425,57,.43,9.07,.472,76,.18,9.61,.488,75,.18,9.79,.52,76,.17,9.97,.528,75,.18,10.13,.575,76,.17,10.3,.559,71,.17,10.45,.512,74,.17,10.61,.551,72,.19,10.77,.559,45,.2,10.94,.512,69,.17,10.94,.528,52,.32,11.12,.504,57,.13,11.31,.504,60,.17,11.47,.504,64,.16,11.64,.52,69,.19,11.8,.543,40,.12,11.95,.535,71,.16,11.97,.606,52,.17,12.15,.528,56,.09,12.32,.48,64,.19,12.5,.52,68,.17,12.66,.535,71,.21,12.83,.535,72,.27,13,.591,45,.22,13.01,.48,52,.23,13.22,.488,57,.23,13.4,.52,64,.14,13.59,.512,76,.17,13.77,.591,75,.18,13.93,.575,76,.18,14.11,.567,75,.16,14.27,.583,76,.2,14.42,.535,71,.17,14.59,.528,74,.16,14.77,.551,72,.17,14.92,.543,69,.18,15.08,.528,45,.18,15.1,.504,52,.3,15.28,.512,57,.13,15.46,.512,60,.17,15.63,.512,64,.15,15.8,.504,69,.18,15.97,.535,40,.13,16.14,.543,71,.17,16.14,.583,52,.19,16.32,.52,56,.09,16.51,.48,64,.21,16.68,.504,72,.17,16.86,.575,71,.18,17.04,.488,69,.3,17.22,.504,45,.23,17.24,.488,52,.24,17.44,.496,57,.17,17.65,.512,71,.21,17.81,.551,72,.19,17.98,.598,74,.2,18.16,.598,76,.24,18.34,.622,48,.21,18.36,.606,55,.31,18.55,.528,60,.11,18.72,.528,67,.2,18.91,.551,77,.19,19.07,.606,76,.23,19.25,.528,43,.14,19.41,.543,74,.19,19.42,.606,55,.2,19.6,.52,59,.1,19.77,.488,65,.19,19.96,.528,76,.21,20.15,.551,74,.24,20.32,.543,45,.2,20.49,.528,72,.17,20.5,.559,52,.35,20.68,.52,57,.14,20.87,.512,64,.2,21.05,.48,74,.21,21.23,.559,72,.19,21.41,.535,71,.18,21.58,.535,40,.13,21.59,.559,52,.08,21.79,.512,64,.05,21.99,.52,64,.18,22.17,.512,76,.17,22.36,.591,64,.2,22.52,.496,76,.11,22.74,.465,76,.26,22.95,.512,88,.15,23.17,.543,75,.22,23.38,.488,76,.16,23.58,.512,75,.18,23.77,.504,76,.18,23.94,.512,75,.18,24.1,.543,76,.18,24.29,.543,75,.16,24.45,.528,76,.17,24.61,.543,75,.15,24.77,.567,76,.19,24.93,.528,75,.18,25.09,.528,76,.18,25.27,.543,71,.18,25.44,.504,74,.17,25.61,.528,72,.2,25.78,.528,45,.2,25.93,.504,69,.2,25.94,.528,52,.3,26.13,.48,57,.13,26.31,.52,60,.16,26.47,.52,64,.16,26.64,.504,69,.17,26.8,.551,40,.11,26.96,.551,71,.16,26.96,.598,52,.2,27.14,.528,56,.08,27.32,.488,64,.18,27.48,.528,68,.18,27.64,.543,71,.21,27.81,.543,45,.21,27.98,.496,72,.28,27.99,.591,52,.21,28.19,.457,57,.19,28.38,.496,64,.15,28.56,.504,76,.17,28.73,.559,75,.18,28.91,.559,76,.19,29.08,.551,75,.18,29.24,.575,76,.2,29.41,.551,71,.17,29.58,.528,74,.19,29.74,.551,72,.19,29.91,.543,45,.18,30.08,.543,69,.18,30.08,.535,52,.3,30.25,.52,57,.11,30.43,.512,60,.19,30.59,.512,64,.16,30.77,.48,69,.2,30.94,.512,40,.14,31.09,.559,71,.17,31.1,.575,52,.21,31.29,.52,56,.09,31.48,.48,64,.2,31.65,.512,72,.19,31.82,.575,71,.19,32,.512,69,.31,32.19,.48,45,.26,32.2,.488,52,.22,32.44,.417,57,.19,32.63,.48,71,.22,32.8,.543,72,.2,32.97,.583,74,.19,33.16,.559,48,.24,33.33,.559,76,.23,33.33,.63,55,.3,33.53,.512,60,.11,33.71,.52,67,.22,33.87,.575,77,.21,34.05,.614,76,.23,34.22,.559,43,.14,34.38,.535,74,.19,34.39,.622,55,.23,34.56,.512,59,.09,34.76,.52,65,.18,34.93,.528,76,.2,35.1,.559,74,.22,35.26,.559,45,.2,35.43,.543,72,.19,35.43,.575,52,.36,35.63,.512,57,.13,35.83,.528,64,.21,36.01,.496,74,.21,36.18,.567,72,.19,36.36,.528,40,.13,36.52,.567,71,.18,36.53,.559,52,.07,36.71,.528,64,.07,36.91,.535,64,.18,37.08,.535,76,.16,37.27,.559,64,.19,37.43,.504,76,.1,37.64,.504,76,.27,37.83,.504,88,.15,38.05,.559,75,.23,38.24,.52,76,.15,38.45,.551,75,.18,38.65,.512,76,.17,38.82,.551,75,.18,38.98,.567,76,.18,39.16,.551,75,.15,39.32,.543,76,.19,39.49,.575,75,.16,39.66,.575,76,.21,39.81,.504,75,.18,39.99,.567,76,.21,40.16,.551,71,.17,40.34,.535,74,.17,40.51,.551,72,.19,40.67,.543,45,.21,40.84,.535,69,.2,40.84,.528,52,.3,41.03,.488,57,.12,41.2,.488,60,.18,41.35,.488,64,.17,41.53,.535,69,.19,41.69,.543,40,.16,41.85,.551,71,.17,41.86,.591,52,.22,42.05,.496,56,.11,42.24,.425,64,.19,42.4,.496,68,.17,42.57,.528,71,.22,42.74,.528,45,.23,42.92,.528,72,.23,42.93,.583,52,.23,43.12,.449,57,.22,43.29,.496,64,.14,43.47,.504,76,.19,43.64,.575,75,.16,43.82,.559,76,.18,43.99,.535,75,.16,44.16,.551,76,.17,44.33,.559,71,.16,44.5,.528,74,.19,44.67,.551,72,.2,44.84,.535,45,.21,45.01,.528,69,.17,45.01,.528,52,.28,45.19,.465,57,.11,45.36,.496,60,.17,45.52,.496,64,.17,45.69,.504,69,.2,45.86,.535,40,.12,46.02,.543,71,.18,46.04,.567,52,.2,46.21,.496,56,.09,46.39,.465,64,.19,46.56,.457,72,.2,46.73,.551,71,.19,46.92,.488,45,.26,47.11,.504,69,.44,47.11,.496,52,.24,47.33,.449,57,.15,47.57,.496,64,.1,48.03,.535,58,.09,48.03,.52,60,.09,48.03,.512,72,.09,48.04,.543,65,.08,48.28,.559,57,.08,48.28,.543,60,.08,48.28,.535,72,.08,48.28,.591,60,.1,48.49,.535,58,.1,48.5,.567,72,.13,48.5,.551,55,.1,48.51,.512,64,.07,48.51,.575,67,.08,48.52,.528,65,.45,48.69,.598,69,.33,48.78,.591,53,.25,48.81,.622,72,.38,48.82,.638,57,.18,49.03,.575,60,.2,49.22,.559,57,.17,49.41,.543,60,.26,49.6,.535,77,.18,49.61,.622,57,.11,49.8,.551,76,.04,49.88,.559,53,.28,49.98,.551,76,.35,49.99,.654,58,.2,50.19,.543,74,.4,50.38,.583,62,.19,50.4,.559,58,.2,50.58,.575,82,.17,50.78,.614,62,.23,50.8,.559,58,.11,50.99,.559,81,.04,51.08,.559,53,.23,51.17,.551,81,.2,51.18,.693,64,.22,51.37,.535,79,.13,51.39,.575,58,.17,51.56,.551,55,.21,51.57,.512,53,.21,51.57,.472,77,.22,51.57,.567,64,.19,51.77,.528,76,.22,51.79,.543,58,.27,51.97,.512,74,.18,51.97,.583,53,.21,51.97,.409,55,.21,51.97,.488,64,.14,52.18,.52,72,.23,52.18,.575,53,.25,52.36,.535,70,.4,52.38,.606,57,.19,52.58,.528,60,.19,52.79,.535,69,.18,52.79,.543,57,.15,52.98,.488,70,.07,53.05,.559,69,.09,53.14,.504,60,.21,53.16,.504,67,.1,53.25,.575,57,.18,53.34,.504,69,.11,53.35,.488,70,.08,53.45,.583,53,.3,53.55,.543,72,.74,53.58,.614,57,.18,53.77,.528,60,.21,53.94,.551,57,.2,54.14,.559,60,.23,54.33,.551,74,.24,54.33,.622,57,.14,54.53,.528,75,.14,54.53,.654,52,.29,54.73,.512,76,.45,54.73,.646,57,.19,54.93,.543,60,.21,55.13,.559,57,.14,55.31,.551,76,.18,55.32,.606,77,.18,55.51,.669,50,.16,55.51,.52,62,.23,55.51,.614,53,.05,55.71,.496,69,.19,55.71,.559,55,.26,55.91,.622,72,.67,55.93,.677,64,.2,56.13,.543,55,.29,56.33,.591,65,.16,56.54,.606,55,.27,56.73,.504,74,.21,56.73,.638,65,.07,56.94,.528,71,.08,57.01,.551,60,.07,57.14,.661,64,.06,57.14,.614,72,.08,57.15,.622,79,.07,57.27,.614,67,.08,57.37,.583,79,.07,57.48,.614,69,.07,57.59,.535,79,.06,57.7,.551,67,.07,57.78,.567,65,.07,57.79,.535,71,.08,57.81,.551,79,.08,57.93,.543,67,.08,58.01,.559,64,.08,58.02,.567,72,.07,58.03,.606,79,.1,58.14,.535,67,.08,58.23,.614,62,.08,58.24,.512,74,.08,58.24,.614,65,.06,58.25,.48,79,.08,58.36,.496,67,.09,58.46,.717,64,.1,58.47,.598,60,.15,58.47,.614,76,.09,58.48,.622,79,.1,58.6,.551,84,.13,58.7,.654,83,.12,58.82,.535,57,.35,58.92,.646,53,.33,58.92,.591,81,.11,58.93,.567,79,.1,59.05,.559,77,.07,59.15,.551,76,.11,59.27,.551,59,.17,59.38,.559,74,.13,59.38,.559,55,.17,59.38,.543,79,.14,59.51,.575,77,.16,59.6,.543,74,.1,59.74,.559,60,.09,59.86,.638,64,.09,59.86,.543,72,.1,59.87,.685,79,.08,60,.63,67,.07,60.12,.535,79,.08,60.23,.646,69,.08,60.34,.504,79,.09,60.47,.622,65,.08,60.56,.575,67,.07,60.57,.551,71,.09,60.57,.543,79,.08,60.7,.512,64,.08,60.8,.575,67,.08,60.8,.583,72,.09,60.81,.63,79,.12,60.93,.575,65,.07,61.03,.543,67,.07,61.03,.583,62,.07,61.04,.52,74,.09,61.05,.638,79,.08,61.17,.504,64,.12,61.27,.614,60,.16,61.27,.685,76,.11,61.28,.622,79,.12,61.41,.575,84,.13,61.51,.646,83,.13,61.64,.528,53,.38,61.73,.606,57,.39,61.73,.638,81,.12,61.75,.598,79,.1,61.87,.559,77,.08,61.97,.551,76,.12,62.08,.551,55,.51,62.21,.543,59,.47,62.21,.559,74,.13,62.21,.575,79,.14,62.33,.583,77,.16,62.44,.535,74,.13,62.59,.559,59,2.41,62.72,.409,56,2.35,62.73,.543,76,.1,62.73,.535,77,.16,62.86,.512,76,.12,63.01,.496,75,.15,63.14,.543,76,.13,63.3,.496,71,.18,63.45,.48,76,.15,63.64,.37,75,.17,63.83,.496,76,.21,64.03,.441,71,.23,64.24,.433,76,.24,64.48,.362,75,.3,64.74,.402,76,.72,65.09,.362,71,.26,66.38,.496,76,.32,66.64,.433,75,.41,66.97,.378,76,.74,67.41,.291,71,.28,68.76,.488,76,.21,69.03,.457,75,.17,69.24,.512,76,.16,69.43,.441,75,.14,69.61,.504,76,.16,69.76,.551,75,.15,69.91,.559,76,.16,70.07,.551,75,.16,70.23,.559,76,.16,70.39,.543,75,.18,70.55,.567,76,.2,70.72,.559,71,.15,70.91,.504,74,.16,71.07,.551,72,.18,71.23,.543,45,.18,71.38,.504,69,.17,71.39,.543,52,.24,71.56,.472,57,.09,71.72,.488,60,.16,71.88,.504,64,.15,72.03,.528,69,.19,72.19,.535,40,.11,72.33,.559,71,.16,72.34,.559,52,.19,72.51,.488,56,.07,72.69,.441,64,.19,72.86,.512,68,.17,73.02,.52,71,.18,73.2,.496,45,.22,73.34,.449,72,.26,73.38,.559,52,.2,73.57,.417,57,.16,73.75,.472,64,.14,73.92,.528,76,.17,74.09,.575,75,.15,74.26,.591,76,.16,74.42,.575,75,.18,74.57,.598,76,.16,74.73,.63,71,.14,74.9,.543,74,.17,75.05,.559,72,.18,75.2,.583,45,.19,75.34,.535,69,.14,75.36,.559,52,.24,75.54,.488,57,.1,75.7,.504,60,.16,75.84,.504,64,.12,76.01,.535,69,.18,76.17,.591,40,.11,76.33,.567,71,.16,76.33,.622,52,.19,76.5,.504,56,.09,76.67,.465,64,.18,76.83,.535,72,.16,77,.606,71,.18,77.17,.496,69,.29,77.35,.496,45,.24,77.37,.496,52,.2,77.56,.465,57,.16,77.73,.488,71,.19,77.9,.543,72,.18,78.06,.614,74,.18,78.23,.583,48,.22,78.38,.535,76,.21,78.4,.614,55,.35,78.58,.496,60,.14,78.75,.504,67,.18,78.91,.567,77,.19,79.08,.622,76,.21,79.25,.551,43,.13,79.4,.559,74,.17,79.41,.654,55,.21,79.59,.488,59,.08,79.77,.496,65,.18,79.92,.528,76,.19,80.09,.598,74,.22,80.26,.559,45,.18,80.42,.567,72,.16,80.43,.591,52,.3,80.61,.512,57,.1,80.79,.52,64,.22,80.95,.528,74,.2,81.13,.622,72,.19,81.3,.559,40,.11,81.45,.638,71,.15,81.47,.559,52,.07,81.65,.512,64,.05,81.84,.48,64,.17,82.02,.48,76,.14,82.2,.551,64,.21,82.34,.512,76,.08,82.56,.425,76,.23,82.74,.472,88,.14,82.96,.559,75,.19,83.16,.543,76,.13,83.35,.528,75,.19,83.53,.559,76,.18,83.71,.535,75,.16,83.88,.543,76,.16,84.05,.535,75,.15,84.21,.528,76,.16,84.37,.551,75,.15,84.53,.543,76,.17,84.69,.52,75,.18,84.85,.528,76,.18,85.04,.543,71,.15,85.22,.496,74,.17,85.38,.52,72,.17,85.54,.559,45,.2,85.69,.528,69,.18,85.7,.551,52,.24,85.88,.488,57,.1,86.05,.496,60,.2,86.19,.52,64,.14,86.36,.543,69,.19,86.52,.575,40,.16,86.65,.591,71,.16,86.7,.638,52,.18,86.88,.52,56,.09,87.03,.543,64,.2,87.21,.528,68,.19,87.36,.575,71,.15,87.55,.535,45,.21,87.71,.52,72,.26,87.71,.622,52,.2,87.89,.504,57,.17,88.06,.528,64,.13,88.24,.543,76,.17,88.4,.638,75,.15,88.56,.583,76,.18,88.73,.583,75,.16,88.9,.575,76,.18,89.06,.591,71,.17,89.22,.543,74,.16,89.39,.559,72,.18,89.56,.551,45,.15,89.73,.543,69,.16,89.73,.543,52,.26,89.9,.488,57,.09,90.07,.52,60,.16,90.23,.512,64,.14,90.4,.52,69,.16,90.56,.559,40,.11,90.71,.575,71,.17,90.72,.598,52,.2,90.89,.504,56,.09,91.07,.488,64,.17,91.23,.528,72,.22,91.41,.614,71,.24,91.58,.535,45,.07,91.77,.575,69,.36,91.77,.559,45,.07,91.96,.52,45,.08,92.15,.528,45,.09,92.33,.535,45,.09,92.53,.52,45,.09,92.7,.512,45,.11,92.9,.543,70,.46,92.91,.591,73,.47,92.91,.701,64,.43,92.91,.559,67,.4,92.91,.567,45,.09,93.1,.535,45,.09,93.3,.535,45,.1,93.49,.543,45,.09,93.67,.543,45,.08,93.85,.512,45,.09,94.05,.496,74,.41,94.06,.598,65,.36,94.06,.512,69,.32,94.06,.48,45,.09,94.26,.449,45,.08,94.45,.504,45,.08,94.64,.52,45,.08,94.82,.535,73,.09,94.83,.622,76,.11,94.83,.614,45,.09,95.01,.528,77,.1,95.01,.717,74,.11,95.02,.63,74,.47,95.21,.693,68,.45,95.21,.638,77,.45,95.21,.591,45,.09,95.22,.575,45,.09,95.41,.504,45,.09,95.6,.52,45,.09,95.79,.504,77,.21,95.98,.693,68,.2,95.98,.598,74,.25,95.98,.575,45,.08,95.99,.488,45,.08,96.19,.512,45,.08,96.41,.543,69,.8,96.41,.559,76,.73,96.41,.575,72,.76,96.41,.575,45,.08,96.61,.528,45,.09,96.78,.512,45,.07,96.97,.52,45,.07,97.16,.52,45,.08,97.34,.528,74,.31,97.56,.717,45,.09,97.56,.598,65,.38,97.56,.654,38,.09,97.57,.638,38,.08,97.77,.488,45,.07,97.78,.496,38,.08,97.96,.504,45,.08,97.96,.512,45,.07,98.16,.512,38,.07,98.16,.512,64,.1,98.34,.575,45,.07,98.34,.551,72,.08,98.34,.724,38,.08,98.35,.543,38,.08,98.52,.512,45,.07,98.53,.543,62,.08,98.53,.661,71,.07,98.53,.685,45,.1,98.72,.575,69,.57,98.72,.701,39,.09,98.72,.638,60,.57,98.72,.654,66,.59,98.73,.646,39,.08,98.93,.606,45,.1,98.93,.496,45,.1,99.11,.512,39,.08,99.13,.567,39,.07,99.3,.575,45,.08,99.3,.512,39,.07,99.51,.622,45,.08,99.51,.512,69,.22,99.51,.661,60,.19,99.52,.685,45,.08,99.71,.535,39,.07,99.71,.669,60,.16,99.91,.701,45,.09,99.91,.622,69,.19,99.91,.724,40,.08,99.92,.654,45,.08,100.11,.504,40,.07,100.12,.559,45,.07,100.32,.551,64,.17,100.32,.63,40,.07,100.33,.575,72,.14,100.33,.732,40,.08,100.52,.567,45,.07,100.52,.535,62,.19,100.71,.654,44,.06,100.72,.693,71,.16,100.72,.74,40,.07,100.73,.575,40,.09,100.9,.543,44,.05,100.92,.504,60,.84,101.11,.701,69,.89,101.11,.724,45,.09,101.12,.677,33,.1,101.13,.661,45,.08,101.32,.559,45,.08,101.52,.52,45,.07,101.72,.512,45,.08,101.92,.551,45,.07,102.12,.52,64,.6,102.33,.591,67,.58,102.34,.575,73,.63,102.34,.677,70,.61,102.34,.575,45,.09,102.34,.567,45,.08,102.55,.504,45,.09,102.74,.52,45,.08,102.94,.528,45,.09,103.14,.535,45,.08,103.33,.512,45,.09,103.53,.52,65,.33,103.53,.575,69,.29,103.53,.535,74,.38,103.53,.669,45,.09,103.74,.528,45,.08,103.93,.512,45,.08,104.12,.528,45,.08,104.31,.543,73,.1,104.32,.638,76,.13,104.32,.638,45,.09,104.51,.543,77,.1,104.52,.685,74,.09,104.52,.693,77,.55,104.7,.559,45,.1,104.71,.567,74,.55,104.71,.638,45,.09,104.92,.551,45,.09,105.1,.528,45,.08,105.3,.496,74,.32,105.52,.63,77,.3,105.52,.63,45,.08,105.52,.551,45,.08,105.73,.52,46,.1,106.01,.575,74,.74,106.01,.606,77,.71,106.01,.614,46,.07,106.25,.512,46,.07,106.45,.575,46,.08,106.65,.575,46,.07,106.85,.543,46,.07,107.04,.512,46,.1,107.25,.598,67,.32,107.27,.559,75,.24,107.27,.677,46,.08,107.47,.52,46,.07,107.66,.551,46,.06,107.86,.512,46,.08,108.05,.575,74,.13,108.06,.638,65,.08,108.06,.52,46,.07,108.24,.559,63,.08,108.26,.591,72,.08,108.27,.575,46,.09,108.45,.583,70,.41,108.45,.669,65,.44,108.45,.606,62,.41,108.46,.638,46,.07,108.66,.504,46,.07,108.84,.504,46,.08,109.04,.535,62,.21,109.22,.614,69,.23,109.22,.583,46,.08,109.23,.512,65,.2,109.23,.559,46,.07,109.43,.512,68,.52,109.63,.677,62,.48,109.63,.606,65,.51,109.64,.551,47,.11,109.64,.614,47,.09,109.85,.488,47,.09,110.05,.465,47,.08,110.26,.457,68,.19,110.47,.591,47,.09,110.47,.52,62,.21,110.47,.559,65,.18,110.48,.512,47,.08,110.68,.465,69,.63,110.94,.583,48,.55,110.94,.543,60,.56,110.94,.567,64,.58,110.95,.496,71,.56,112.29,.591,64,.59,112.3,.535,52,.61,112.3,.441,56,.53,112.3,.48,57,.13,113.59,.622,33,.08,113.6,.677,60,.14,113.75,.528,64,.05,113.9,.496,69,.09,114.03,.559,72,.13,114.15,.583,76,.14,114.28,.575,60,.14,114.39,.543,57,.15,114.4,.535,64,.14,114.4,.512,74,.14,114.4,.575,72,.09,114.53,.575,71,.09,114.64,.551,60,.53,114.77,.551,64,.53,114.77,.543,69,.1,114.77,.52,57,.52,114.78,.543,72,.11,114.9,.551,76,.06,115.03,.559,81,.08,115.16,.63,84,.1,115.27,.622,88,.14,115.39,.638,86,.14,115.51,.559,60,.14,115.52,.559,64,.14,115.52,.543,57,.14,115.53,.551,84,.1,115.63,.598,83,.09,115.73,.567,81,.1,115.85,.591,64,.54,115.87,.575,57,.53,115.88,.591,60,.54,115.88,.567,84,.1,115.98,.598,88,.07,116.1,.606,93,.07,116.23,.622,96,.13,116.35,.638,100,.2,116.48,.693,98,.19,116.61,.63,57,.14,116.63,.583,64,.14,116.63,.551,60,.13,116.64,.575,96,.14,116.74,.685,95,.17,116.88,.638,94,.16,117.03,.677,60,.2,117.04,.591,64,.21,117.04,.559,57,.21,117.04,.614,93,.14,117.2,.559,92,.15,117.35,.646,91,.15,117.5,.559,90,.18,117.65,.583,89,.18,117.81,.543,88,.17,117.98,.528,87,.17,118.16,.559,86,.18,118.34,.528,85,.2,118.52,.543,84,.19,118.72,.551,83,.24,118.91,.496,82,.22,119.14,.496,81,.23,119.37,.512,80,.21,119.6,.512,79,.28,119.82,.496,78,.29,120.08,.496,77,.26,120.38,.488,76,.25,120.65,.441,75,.25,120.93,.472,76,.21,121.2,.48,71,.2,121.41,.488,74,.17,121.61,.52,72,.22,121.79,.52,45,.22,121.96,.488,69,.2,121.96,.496,52,.23,122.16,.441,57,.1,122.33,.472,60,.14,122.48,.496,64,.14,122.65,.504,69,.18,122.81,.551,40,.12,122.96,.551,71,.14,122.97,.575,52,.16,123.14,.504,56,.07,123.31,.472,64,.16,123.46,.504,68,.18,123.61,.551,71,.18,123.78,.528,45,.2,123.94,.512,72,.26,123.94,.575,52,.22,124.13,.441,57,.15,124.31,.488,64,.12,124.49,.488,76,.16,124.66,.591,75,.15,124.84,.535,76,.16,125,.543,75,.17,125.15,.575,76,.18,125.32,.559,71,.13,125.49,.504,74,.15,125.64,.528,72,.18,125.8,.551,69,.16,125.96,.543,45,.17,125.98,.512,52,.26,126.16,.48,57,.09,126.32,.496,60,.16,126.47,.496,64,.15,126.64,.512,69,.16,126.8,.543,40,.12,126.95,.551,71,.16,126.96,.575,52,.19,127.14,.504,56,.06,127.31,.472,64,.18,127.45,.52,72,.18,127.62,.591,71,.22,127.8,.496,45,.21,127.96,.496,69,.24,127.98,.52,52,.2,128.17,.457,57,.17,128.34,.48,71,.2,128.51,.543,72,.17,128.67,.591,74,.19,128.83,.583,76,.22,129,.622,48,.2,129.01,.591,55,.3,129.2,.48,60,.11,129.38,.52,67,.18,129.55,.559,77,.18,129.71,.606,76,.23,129.88,.528,43,.13,130.03,.528,74,.16,130.04,.583,55,.21,130.21,.488,59,.08,130.38,.488,65,.19,130.55,.512,76,.21,130.72,.543,74,.2,130.9,.535,45,.17,131.05,.52,72,.15,131.07,.551,52,.28,131.24,.496,57,.11,131.42,.528,64,.2,131.58,.52,74,.2,131.76,.598,72,.17,131.94,.543,40,.11,132.09,.575,71,.17,132.1,.575,52,.06,132.29,.528,64,.07,132.48,.52,64,.16,132.66,.496,76,.13,132.83,.559,64,.17,133.01,.52,76,.1,133.18,.528,76,.26,133.38,.512,88,.15,133.59,.606,75,.2,133.79,.559,76,.14,133.97,.543,75,.17,134.15,.559,76,.17,134.31,.551,75,.16,134.48,.52,76,.16,134.63,.52,75,.16,134.79,.567,76,.14,134.97,.528,75,.16,135.13,.512,76,.17,135.29,.512,75,.17,135.45,.52,76,.16,135.63,.504,71,.16,135.78,.496,74,.17,135.95,.535,72,.2,136.11,.504,45,.2,136.28,.504,69,.17,136.29,.504,52,.29,136.47,.496,57,.1,136.64,.512,60,.18,136.8,.512,64,.19,136.97,.528,69,.18,137.14,.551,71,.15,137.3,.591,40,.11,137.31,.567,52,.21,137.48,.528,56,.08,137.65,.504,64,.21,137.81,.551,68,.18,137.97,.575,71,.2,138.14,.535,45,.19,138.29,.504,72,.29,138.3,.622,52,.22,138.47,.512,57,.16,138.65,.535,64,.14,138.83,.512,76,.17,138.98,.575,75,.15,139.15,.575,76,.17,139.32,.551,75,.17,139.46,.575,76,.18,139.64,.559,71,.17,139.8,.512,74,.18,139.96,.543,72,.2,140.14,.528,45,.2,140.29,.512,69,.16,140.31,.528,52,.33,140.49,.512,57,.1,140.69,.512,60,.16,140.86,.504,64,.19,141.04,.504,69,.19,141.21,.52,40,.13,141.39,.591,71,.2,141.39,.583,52,.24,141.58,.528,56,.19,141.8,.512,62,.7,142.03,.559,72,.43,142.31,.598,71,.55,142.67,.535,60,2.89,143.19,.559,69,2.89,143.19,.551,45,2.89,143.2,.441,33,2.85,143.2,.496],sustainPedal:[1.09,2.21,2.42,3.22,3.42,4.31,4.53,6.32,6.51,7.27,7.48,8.61,8.8,10.89,11.08,11.86,12.06,12.97,13.17,15.05,15.24,16.04,16.22,17.27,17.43,18.36,18.54,19.36,19.53,20.49,20.66,21.46,21.64,25.86,26.03,26.81,26.99,27.95,28.13,30.07,30.22,30.95,31.1,32.23,32.4,33.3,33.46,34.3,34.47,35.43,35.57,36.45,36.59,40.81,40.98,41.77,41.94,42.91,43.08,44.95,45.12,45.93,46.09,47.09,47.31,48.73,48.93,49.93,50.1,51.14,51.32,52.35,52.51,53.55,53.7,54.58,54.79,55.44,55.59,55.85,56.02,57.13,58.55,58.78,59.47,59.91,61.38,61.63,68.96,71.29,71.48,72.19,72.41,73.25,73.47,75.2,75.44,76.19,76.4,77.38,77.61,78.35,78.52,79.25,79.47,80.39,80.6,81.31,81.53,85.62,85.82,86.5,86.73,87.66,87.87,89.61,89.84,90.57,90.79,91.63,91.82,92.92,93.04,94.06,94.22,95.21,95.39,96.41,96.58,97.53,97.71,98.7,98.87,99.83,99.99,100.65,100.76,101.03,101.22,103.54,103.67,104.71,104.84,105.96,106.15,107.24,107.41,108.46,108.6,109.59,109.78,110.94,111.29,111.58,112.58,113.24,113.61,121.89,122.1,122.83,123.03,123.9,124.07,125.88,126.09,126.85,127.04,127.96,128.14,128.98,129.17,129.89,130.07,131,131.17,131.95,132.14,136.2,136.41,137.13,137.34,138.17,138.39,140.24,140.45,141.33,141.48,143.23,143.67,145.98]};
    const ret = { notes: [], duration: 160 };

    for (let i=0, ip=0, n=data.notes, p=data.sustainPedal, sustain=false; i<n.length; i+=4) {
        if (n[i+2] > p[ip]) ip++, sustain=!sustain;
        ret.notes.push({
            midi: n[i+0], 
            duration: n[i+1], 
            time: n[i+2], 
            dt: i>0?n[i+2]-n[i-2]:n[i+2],
            velocity: n[i+3], 
            sustain, 
            sustainMaxDuration: p[ip]-n[i+2],
        });
    }

    return ret;
}

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;
        }
        return this.lastOut;
    }
    tap(offset) {
        var x = this.p - offset - 1;
        x %= this.n;
        if(x < 0) {
            x += this.n;
        }
        return this.data[x];
    }
}

// State Variable Filter based on an article by cytomic Sound Music Software
// https://cytomic.com/files/dsp/SvfLinearTrapOptimised2.pdf
function processSVF(s, input, a1, a2, a3, k) {
    var v1, v2, v3;
    v3 = input - s.ic2eq;
    v1 = a1 * s.ic1eq + a2 * v3;
    v2 = s.ic2eq + a2 * s.ic1eq + a3 * v3;
    s.ic1eq = 2 * v1 - s.ic1eq;
    s.ic2eq = 2 * v2 - s.ic2eq;
    s.lp = v2;
    s.bp = v1;
    s.hp = input - k * v1 - v2;
    s.ap = s.lp + s.hp - k * s.bp;
}
class SVF {
    constructor(opt)
    {
        this.stages = [];
        this.mode = opt ? opt.mode || 'lp' : 'lp';
        this.fc = 0;
        this.q = 1;
        this.num = opt ? opt.num || 1 : 1;
        // g parameter determines cutoff
        // k parameter = 1/Q
        for(var i = 0; i < this.num; ++i) {
            this.stages.push({lp:0, bp:0, hp:0, ap:0, ic1eq:0, ic2eq:0});
        }

        this.q = opt && isFinite(opt.q) ? opt.q : 1;
        this.fc = opt && isFinite(opt.fc) ? opt.fc : .25;
    }
    process(input)
    {
        if(this.fc != this._fc) {
            this._fc = this.fc;
            this._q = this.q;
            var fc = this.fc * .5;
            if (fc >= 0 && fc < .5) {
                this.g = Math.tan(Math.PI * fc);
                this.k = 1 / this.q;
                this.a1 = 1 / (1 + this.g * (this.g + this.k));
                this.a2 = this.g * this.a1;
                this.a3 = this.g * this.a2;
            }
        }
        if(this.q != this._q) {
            this._q = this.q;

            this.k = 1 / this.q;
            this.a1 = 1 / (1 + this.g * (this.g + this.k));
            this.a2 = this.g * this.a1;
            this.a3 = this.g * this.a2;
        }

        for(var i = 0; i < this.num; ++i) {
            processSVF(this.stages[i], input, this.a1, this.a2, this.a3, this.k);
            processSVF(this.stages[i], input, this.a1, this.a2, this.a3, this.k);
            input = this.stages[i][this.mode];
        }
        return input;
    }
}