// 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}));