Ditty by Ditty

A "Ditty-style"(??) cover. Who even needs a DAW? :)

Log in to post a comment.

// 20KB Ditty cover of "Camel by camel" by Sandy Marton (1984)
// by srtuss
//
// Again, this song was first created in FL Studio and then converted into JS code.
// This time the synths employ a slow-update technique, which means that all the costly code that creates parameter/option automation
// of any kind, executes only at a rate of 100Hz instead of at audio rate (44100Hz) which gives a noticeable performance boost. This is
// sufficient for most parameter automation, while it adds only minimal additional code!
// 
// On lower-end devices there still will be occasional performance hiccups when the orchestra-hit synth or the vocal synth triggers.
// Although, this seems to only happen the first time that the synth gets triggered, and the playback runs smoothly from there on.

class Delayline {
    constructor(n) {
        this.n = ~~n;
        this.p = 0;
        this.lastOut = 0;
        this.data = new Float32Array(n);
    }
    process(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];
    }
}

const echo = filter.def(class {
    constructor(opt) {
        this.lastOut = [0, 0];
        var division = opt.division || 3/4;
        var pan = clamp01((opt.pan || 0)*.5+.5);
        var sidetime = (opt.sidetime || 0) / ditty.dt;
        var time = 60 * division / ditty.bpm;
        this.fb = clamp(opt.feedback || 0, -1, 1);
        this.kl = 1-pan;
        this.kr = pan;
        this.wet = opt.wet || .5;
        this.stereo = isFinite(opt.stereo) ? opt.stereo : 1;
        var n = ~~(time / ditty.dt);
        this.delay = [new Delayline(n), new Delayline(n)];
        this.dside = new Delayline(~~sidetime);
    }
    process(inv, opt) {
        this.dside.process(inv[0]);
        var l = this.dside.lastOut * this.kl;
        var r = inv[1] * this.kr;
        var nextl = l + this.delay[1].lastOut * this.fb;
        var nextr = r + this.delay[0].lastOut * this.fb;
        this.lastOut[0] = inv[0] + this.delay[0].lastOut * this.wet;
        this.lastOut[1] = inv[1] + this.delay[1].lastOut * this.wet;
        this.delay[0].process(nextl);
        this.delay[1].process(nextr);
        if(this.stereo != 1) {
            var m = (this.lastOut[0] + this.lastOut[1])*.5;
            var s = (this.lastOut[0] - this.lastOut[1])*.5;
            s *= this.stereo;
            this.lastOut[0] = m+s;
            this.lastOut[1] = m-s;
        }
        return this.lastOut;
    }
}, {sidetime: .01, division: 1/2, pan: .5, wet: .5, feedback: .6, stereo: 2});

// Simple allpass reverberator, based on this article:
// http://www.spinsemi.com/knowledge_base/effects.html
const reverb = filter.def(class {
    constructor(opt) {
        this.lastReturn = 0;
        this.density = opt.density;
        this.delaylines = [];
        // Create several delay lines with random lengths
        [263,863,1319,1433,439,359,887,1399,233,1367,4253,2903].forEach((dl) => this.delaylines.push(new Delayline(dl)));
        this.tap = [111,2250,311,1150,511,50,4411,540];
        this.dry = 1-opt.mix;
        this.wet = opt.mix*.25;
        this.pred = new Delayline(opt.pred / ditty.dt);
    }
    allpass(delayline, x, k) {
        var delayin = x - delayline.lastOut * k;
        var y = delayline.lastOut + k * delayin;
        delayline.process(delayin);
        return y;
    }
    process(input, options) {
        var inv = input[0] + input[1];
        if(this.pred.n > 0)
            inv = this.pred.process(inv);
        var v = this.lastReturn;
        var dls = this.delaylines;
        // 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].process(v);
        v = dls[2].lastOut * this.density;
        v = this.allpass(dls[3], v + inv, .5);
        v = this.allpass(dls[4], v, .5);
        dls[5].process(v);
        v = dls[5].lastOut * this.density;
        v = this.allpass(dls[6], v + inv, .5);
        v = this.allpass(dls[7], v, .5);
        dls[8].process(v);
        v = dls[8].lastOut * this.density;
        v = this.allpass(dls[9], v + inv, .5);
        v = this.allpass(dls[10], v, .5);
        dls[11].process(v);
        v = dls[11].lastOut * this.density;
        this.lastReturn = v;
        // Tap the delay lines at randomized locations and accumulate the output signal.
        var ret = [0, 0];
        ret[0] += dls[2].tap(this.tap[0]);
        ret[1] += dls[2].tap(this.tap[1]);
        ret[0] += dls[5].tap(this.tap[2]);
        ret[1] += dls[5].tap(this.tap[3]);
        ret[0] += dls[8].tap(this.tap[4]);
        ret[1] += dls[8].tap(this.tap[5]);
        ret[0] += dls[11].tap(this.tap[6]);
        ret[1] += dls[11].tap(this.tap[7]);
        // Mix wet + dry signal.
        ret[0] = ret[0] * this.wet + input[0] * this.dry;
        ret[1] = ret[1] * this.wet + input[1] * this.dry;
        // 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;
    }
}, {mix: .1, density: .5, pred: .01});

// filtered sawtooth function form athibaul's ditties:
function softclip(x) {
    return x<-1?-1:x>1?1:1.5*(1-x*x/3)*x;
}
function varsaw(p, formant) {
    let x = p-~~p;
    return (x - 0.5) * softclip(formant*x*(1-x));
}
const fract = (x) => x - Math.floor(x);
const triangle01 = x => Math.abs(fract(x + .5) - .5) * 2;
const triangle11 = x => Math.abs(fract(x + .75) - .5) * 4 - 1;
class Analog {
    constructor(opt) {
        var def = {nunison:1,spread:.9,detune:.1,flt1:.5,flt2:.02,fm12:0,fshimmer:0,pw:.3};
        for(const x in def)
            if(!isFinite(opt[x]))
                opt[x]=def[x];
        this.ops = [];
        var vol = opt.nunison > 1 ? 1 / opt.nunison : 1;
        for(var i = 0; i < opt.nunison; ++i) {
            var t = opt.nunison > 1 ? i / (opt.nunison-1) : .5;
            var x = t*2-1;
            var pan = x * opt.spread;
            this.ops.push({
                pha1:Math.random(),
                pha2:Math.random(),
                pitch: 2 ** (Math.sin(x * 12) * opt.detune / 12),
                fl: (pan>0?1-pan:1) * vol,
                fr: (pan<0?1+pan:1) * vol
            });
        }
        this.tshimmer = 0;
        this.fshimmer = opt.fshimmer;
        this.timbre1 = opt.timbre1;
        this.timbre2 = opt.timbre2;
        this.tupd = 0;
        this.kupd = 100 * ditty.dtick;
        this.pw = opt.pw;
    }
    process(note, env, tick, opt) {
        if(this.tupd <= 0) {
            this.tupd += 1;
            var pitch = opt.pitch||0;
            this.pinc = midi_to_hz(note + pitch) * ditty.dt;
            this.o1flt = opt.flt1;
            this.o2flt = opt.flt2;
            this.fm12 = opt.fm12;
        }
        this.tupd -= this.kupd;
        if(this.tshimmer >= 1) {
            for(var i = 0; i < this.ops.length; ++i) {
                var op = this.ops[i];
                op.pitch = 2 ** ((Math.random()*2-1) * opt.detune / 12);
            }
            this.tshimmer -= 1;
        }
        this.tshimmer += ditty.dt * this.fshimmer;
        
        var vl=0, vr=0;
        for(var i = 0; i < this.ops.length; ++i) {
            var op = this.ops[i];
            var fbase = this.pinc * op.pitch;
            var osc1 = varsaw(op.pha1, this.o1flt / fbase);
            if(this.timbre1==1)
                osc1 = triangle11(op.pha1);
            else if(this.timbre1==2)
                osc1 -= varsaw(op.pha1+this.pw, this.o1flt / fbase);
            
            var osc2 = varsaw(op.pha2, this.o2flt / fbase);
            vl += osc1 * op.fl;
            vr += osc1 * op.fr;
            op.pha1 += fbase * (1+osc2*this.fm12);
            op.pha2 += fbase * .99;
        }
        return [vl*env.value, vr*env.value];
    }
};

const lead = synth.def(Analog, {nunison:4, detune:.2, attack: .01, fm12:20, amp: 1.3,
    pitch: (tick, opt)=>triangle11(Math.max(tick-.25,0)*3)});
const arpbass = synth.def(Analog, {nunison:4, detune:.2, attack: .01, fm12:(tick,opt)=>20+(ditty.tick%16)*4, amp: 1.3,
    pitch: (tick, opt)=>triangle11(Math.max(tick-.25,0)*3)});
const lead2 = synth.def(Analog, {nunison:4, detune:.2, attack: .01, timbre1:1, amp: 1,
    pitch: (tick, opt)=>12+triangle11(Math.max(tick-.25,0)*3)});
const bass = synth.def(Analog, {nunison:1, detune:.01, attack: .001, timbre1:2, sustain: 1, amp: 1, fm12:0,
    flt1:(tick, opt)=>2**Math.max(-tick * 24, -5)});
const pad = synth.def(Analog, {nunison:4, detune:.3, attack: .01, fm12:1, amp: 1, flt1: .15,
    pitch: 0, spread: 1, fshimmer: 30});
const bell = synth.def((ph, env, tick, opt) => Math.sin(Math.PI*2*(ph+Math.sin(Math.PI*2*ph*3) * Math.exp(tick*-20))) * env.value);
var conga = synth.def((ph, env, tick, opt) => Math.sin(ph * Math.PI * 2) * env.value, {note: hz_to_midi(251), amp: .8, duration: .013, release: .1});
var crash = synth.def((ph, env, tick, opt) => (Math.random()-.5) * env.value, {amp: .8, duration: .013, release: 1.5});
const orchhit = synth.def(Analog, {nunison:5, detune:.2, attack: .001, timbre1:0, decay: .1, sustain: .4, amp: 2.5, release: .1, fm12:15,
    flt1:(tick, opt)=>.01+2**(-tick * 20)});

ditty.bpm = 110;

loop( (lc) => {

    const pat0 = {
        p:[69,71,72,72,69,71,72,74,72,71,72,69,69,71,72,74,72,71,72,69,71,67,69],
        x:[.25,.25,.5,.5,.25,.25,.25,.25,.25,.25,.5,.75,.25,.25,.25,.5,.25,.25,.5,.25,.5,.25,.5],
        d:[.198,.198,.115,.458,.198,.198,.104,.24,.146,.135,.469,.198,.125,.208,.24,.458,.198,.198,.448,.146,.24,.115,.188],
        s:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
    };
    const pat1 = {
        p:[69,71,72,74,60,60,62,65,64,62,60,60,62,65,64,62,67],
        x:[.25,.25,.25,3.25,.25,.25,.5,.5,.25,6.25,.25,.25,.5,.5,.25,.5,1.75],
        d:[.219,.219,.219,.219,.208,.208,.208,.396,.208,1.542,.208,.208,.208,.396,.208,.167,1.385],
        s:[0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1]
    };
    const pat2 = {
        p:[57,58,61,61,57,58,61,62,61,58,57,60,60,62,65,64,62,57,58,61,61,57,58,61,62,61,58,57,60,60,62,65,64,62,67],
        x:[.25,.25,.5,.5,.25,.25,.25,.25,.25,.25,1,.25,.25,.5,.5,.25,2.25,.25,.25,.5,.5,.25,.25,.25,.25,.25,.25,1,.25,.25,.5,.5,.25,.5,1.75],
        d:[.25,.25,.125,.406,.25,.25,.125,.219,.219,.219,.469,.208,.208,.208,.396,.208,1.542,.25,.25,.125,.406,.25,.25,.125,.219,.219,.219,.469,.208,.208,.208,.396,.208,.167,1.385],
        s:[0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1]
    };
    const pat3 = {
        p:[57,58,61,61,57,58,61,61,57,58,61,61,57,58,61,57,58,61,62,61,58,57],
        x:[.25,.25,.5,.5,.25,.25,.5,.5,.25,.25,.5,.5,.25,.25,.5,.25,.25,.25,.25,.25,.25,1],
        d:[.25,.25,.125,.406,.25,.25,.125,.406,.25,.25,.125,.406,.25,.25,.406,.25,.25,.125,.219,.219,.219,.469],
        s:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
    };
    const insts = [lead, lead2];
    var pats = [pat3,pat3,pat0,pat0,pat0,pat0,pat1,pat0,pat0,pat1,pat2,pat0,pat0,pat0,pat0];
    var seq = pats[lc%pats.length];
    for (let i=0; i < seq.p.length; i++) {
        insts[seq.s[i]].play(seq.p[i]+2, { duration: seq.d[i], release: .01});
        bell.play(seq.p[i]+14, { duration: seq.d[i], release: .01, amp: .25});
        sleep(seq.x[i]);
    }

}, { name: 'lead' }).connect(echo.create());

loop( (lc) => {
    sleep(16);
    for(var j = 0; j < 64; ++j) {
        const seq = {
            p:[0,0,12,0,0,12,0,12],
            x:[.5,2,.5,.25,1.25,1,.75,.75,.25],
            d:[.1,.1,.2,.1,.1,.2,.2,.1,.1]
        };
        for (let i=0; i < seq.p.length; i++) {
            arpbass.play(seq.p[i]+e2, { detune: .02, duration: seq.d[i], release: .1});
            sleep(.25);
        }
    }
}, { name: 'lead_low' });

loop( (lc) => {
    const pat0 = {
        p:[38,38,38,38,38,38,38,50,38,38,38,36,38,38,38,50,36,38],
        x:[1.5,.25,1.75,.25,.75,.5,.25,2.75,1.5,.25,1.75,.25,.75,.5,.25,1.25,.75,.75],
        d:[.344,.198,.344,.198,.344,.344,.198,.344,.344,.198,.344,.198,.344,.344,.177,.354,.344,.344]
    };
    const pat1 = {
        p:[45,57,45,57,57,41,53,41,53,53,43,55,43,55,55,38,50,38,41,53,43,45,57,45,57,57,41,53,41,41,53,53,43,55,43,55,55,38,50,38,41,53,43],
        x:[.5,.5,.25,.5,.25,.5,.5,.25,.5,.25,.5,.5,.25,.5,.25,.5,.25,.25,.25,.5,.25,.5,.5,.25,.5,.25,.5,.25,.25,.25,.5,.25,.5,.5,.25,.5,.25,.5,.25,.25,.25,.5,.25],
        d:[.427,.427,.177,.427,.177,.427,.427,.177,.427,.177,.427,.427,.177,.427,.177,.427,.177,.177,.177,.427,.177,.427,.427,.177,.427,.177,.427,.177,.177,.177,.427,.177,.427,.427,.177,.427,.177,.427,.177,.177,.177,.427,.188]
    };
    const pats = [,pat0,pat0,pat1,pat0,pat1,pat1,,,];
    var seq = pats[lc%pats.length];
    if(seq) {
        for (let i=0; i < seq.p.length; i++) {
            bass.play(seq.p[i]+2-12, { duration: seq.d[i], release: .01});
            sleep(seq.x[i]);
        }
    }
    else
        sleep(16);
}, { name: 'bass' }).connect(echo.create());


let sat = x => Math.max(Math.min(x, 1), -1);
var kick = synth.def((ph, env, tick, opt) => sat(Math.sin(Math.sqrt(ph) * 8 + ph * .5) * 2 * env.value), {note: c4, attack: .001, release: .2, duration: .1, amp: .6});
var snare = synth.def((ph, env, tick, opt) => sat(((Math.random() - .5) + Math.sin(Math.sqrt(ph) * 15 + ph * 3) * .5) * env.value), {note: c4, amp: 1.1, attack: 0, release: .3});
var clhat = synth.def((ph, env, tick, opt) => (Math.random()-.5) * env.value, {release: .05, amp: .5});
var ohat = synth.def((ph, env, tick, opt) => (Math.random()-.5) * env.value, {release: .2, amp: .6});
var woodblock = synth.def((ph, env, tick, opt) => Math.sin(ph * Math.PI * 2) * env.value, {note: hz_to_midi(537.8), amp: .4, duration: .013, release: .036});
var clave = synth.def((ph, env, tick, opt) => Math.sin(ph * Math.PI * 2) * env.value, {note: hz_to_midi(2100), amp: .4, duration: .013, release: .036});

loop( (lc) => {
    var pats = ["x...", "xxxx.x.xx.xx.x..", "x... ..."];
    var seq = [0,0,0,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,,,,,,,,,];
    var pat = pats[seq[lc%seq.length]];
    for(var i = 0; i < 16; ++i) {
        if(pat && pat[i%pat.length] == 'x')
            kick.play(c4);
        sleep(.25);
    }
}, { name: 'kick' });

loop( (lc) => {
    var pats = [" ...x...", " ...x... x..x..x", " ...x... x..xxxx"];
    var seq = [,,,,0,0,0,1,0,0,0,2,0,0,0,2,0,0,0,1,0,0,0,2,0,0,0,2,,,,,,,,2];
    var pat = pats[seq[lc%seq.length]];
    for(var i = 0; i < 16; ++i) {
        if(pat && pat[i%pat.length] == 'x')
            snare.play(c4);
        sleep(.25);
    }
}, { name: 'snare' });

loop( () => {
    clhat.play(c4);
    sleep(.25);
}, { name: 'hat' });

loop( () => {
    var pat = "..xx....xxx..x....xx....xxx..x..";
    for(var i = 0; i < pat.length; ++i) {
        if(pat[i] == 'x')
            clave.play(c4);
        sleep(.25);
    }
}, { name: 'clave' });

loop( () => {
    var pat = "....x......x........x......x..x.";
    for(var i = 0; i < pat.length; ++i) {
        if(pat[i] == 'x')
            woodblock.play(c4);
        sleep(.25);
    }
}, { name: 'woodblock' });

loop( () => {
    var pat = "xxxx..xx.x.xxx.x";
    for(var j = 0; j < 4; ++j) {
        for(var i = 0; i < pat.length; ++i) {
            if(pat[i] == 'x')
                conga.play(c4);
            sleep(.25);
        }
    }
}, { name: 'conga', sync: 144 });

loop( () => {
    var pat = "..............................x.";
    for(var i = 0; i < pat.length; ++i) {
        if(pat[i] == 'x')
            crash.play(c4);
        sleep(.25);
    }
}, { name: 'crash', sync: 144 }).connect(reverb.create({dry:.7,wet:.3}));

loop( () => {
    const seq = {
        p:[59,59,59,59,59,59,59,59,59],
        x:[4.5,.25,.25,1.25,.25,.5,.25,.5,.75],
        d:[.969,.188,.188,.438,.188,.188,.188,.188,.719]
    };
    sleep(7.5);
    for (let i=0; i < seq.p.length; i++) {
        orchhit.play(seq.p[i]-24, { duration: seq.d[i]});
        orchhit.play(seq.p[i], { duration: seq.d[i]});
        orchhit.play(seq.p[i]-12, { duration: seq.d[i]});
        sleep(seq.x[i]);
    }
}, { name: 'orchhit', sync: 144 }).connect(reverb.create({mix: 1, density: .1, pred:0 }));

loop( (lc) => {
    sleep(112)
    const seq = {
        p:[57,76,69,53,72,65,55,74,67,50,69,62,57,76,69,53,77,72,55,79,74,81,50,74],
        x:[0,0,2,0,0,2,0,0,2,0,0,2,0,0,2,0,0,2,0,0,2,0,0,2],
        d:[1.8,1.8,1.8,1.8,1.8,1.8,1.8,1.8,1.8,1.8,1.8,1.8,1.8,1.8,1.8,1.8,1.8,1.8,1.8,1.8,1.8,1.8,1.8,1.8]
    };
    for(var j = 0; j < 2; ++j) {
        for (let i=0; i < seq.p.length; i++) {
            pad.play(seq.p[i]+14, { duration: seq.d[i], attack: .4, release: 2});
            sleep(seq.x[i]);
        }
    }
}, { name: 'pad', sync: 144, amp: .8 }).connect(echo.create());

// State Variable Filter based on an article by cytomic Sound Music Software
// https://cytomic.com/files/dsp/SvfLinearTrapOptimised2.pdf
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;
    }
    _run(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;
    }
    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) {
            this._run(this.stages[i], input, this.a1, this.a2, this.a3, this.k);
            this._run(this.stages[i], input, this.a1, this.a2, this.a3, this.k);
            input = this.stages[i][this.mode];
        }
        return input;
    }
}

const tenor = [
    {f:[650,1080,2650,2900,3250], a:[0,-6,-7,-8,-22]},
    {f:[400,1700,2600,3200,3580], a:[0,-14,-12,-14,-20]},
    {f:[290,1870,2800,3250,3540], a:[0,-15,-18,-20,-30]},
    {f:[400,800,2600,2800,3000], a:[0,-10,-12,-12,-26]},
    {f:[350,600,2700,2900,3300], a:[0,-20,-17,-14,-26]}
];

const soprano = [
    {f:[800,1150,2900,3900,4950], a:[0,-6,-32,-20,-50]},
    {f:[350,2000,2800,3600,4950], a:[0,-20,-15,-40,-56]},
    {f:[270,2140,2950,3900,4950], a:[0,-12,-26,-26,-44]},
    {f:[450,800,2830,3800,4950], a:[0,-11,-22,-22,-50]},
    {f:[325,700,2700,3800,4950], a:[0,-16,-35,-40,-60]}
];

const voice = synth.def(class {
    constructor(opt) {
        this.f = [];
        for(var i = 0; i < 5; ++i)
            this.f.push(new SVF({mode:'bp', num:Math.floor(18-i*4), q:1}));
        this.p = 0;
    }
    process(note, env, tick, opt) {
        var k = Math.floor(opt.vowel);
        var inp = (Math.random()-.5) * .8;
        inp += (this.p-~~this.p)*2-1;
        this.p += ditty.dt * midi_to_hz(note - clamp01((.1-tick)*50)*.4 + Math.sin(tick*11)*.2);
        var v = 0;
        for(var i = 0; i < 5; ++i) {
            var freq = lerp(opt.vowels[k].f[i],opt.vowels[(k+1)%5].f[i], opt.vowel-k);
            var ampdb = lerp(opt.vowels[k].a[i],opt.vowels[(k+1)%5].a[i], opt.vowel-k);
            freq *= 2**opt.scale;
            this.f[i].fc = freq * ditty.dt;
            v += this.f[i].process(inp) * 10**(ampdb/20)
        }
        return v*env.value;
    }
});

loop(()=>{
    sleep(80);
    for(var j = 0; j < 2; ++j) {
        const seq = {
            a:[.01,.01,.05,.01,.01],
            p:[50,47,52,50,47],
            x:[.75,.75,.5,.75,1.25],
            d:[.667,.417,.354,.625,.375],
            vowel:[1,(tick,opt)=>lerp(3,4,clamp01(tick*3)),(tick,opt)=>clamp01(tick*5+.2),1,(tick,opt)=>lerp(3,4,clamp01(tick*3))],
            amp:[(tick,opt)=>(1-Math.exp(Math.abs(tick-.1)*-40)),1,(tick,opt)=>(1-Math.exp(Math.abs(tick-.1)*-40)),(tick,opt)=>1-Math.exp(Math.abs(tick-.1)*-40),1]
        };
        for (let i=0; i < seq.p.length; i++) {
            voice.play(seq.p[i], { duration: seq.d[i], attack:seq.a[i], decay: .1, sustain: .8, release: .05, vowels: tenor, vowel: seq.vowel[i], scale:0.3, amp: seq.amp[i]});
            voice.play(seq.p[i]+12, { duration: seq.d[i], attack:seq.a[i], decay: .1, sustain: .8, release: .05, vowels: soprano, vowel: seq.vowel[i], scale:0.2, amp: seq.amp[i]});
            sleep(seq.x[i]);
        }
        sleep(4);
    }
}, {name: "vocals", sync: 144, amp: 3}).connect(reverb.create({mix:.5}));