const{random,sin,PI,floor,abs}=Math;
mtof=x=>440*2**((x-69)/12)
fract=x=>x-floor(x);
// reused from my implementation of the Schroeder algorithm
class CircularBuffer{constructor(size){this.buffer=Array(size).fill(0);this.bufferLength=size;this.head=this.tail=0}cubicInterpolation(y0,y1,y2,y3,mu){var a0=0;var a1=0;var a2=0;var a3=0;var mu2=mu*mu;a0=-.5*y0+1.5*y1-1.5*y2+.5*y3;a1=y0-2.5*y1+2*y2-.5*y3;a2=-.5*y0+.5*y2;a3=y1;return a0*mu*mu2+a1*mu2+a2*mu+a3}getSample(inValue){this.tail=this.head-inValue;if(this.tail>this.bufferLength-1){this.tail-=this.bufferLength}else if(this.tail<0){this.tail+=this.bufferLength}return this.buffer[this.tail]}write(inValue){this.head=(this.head+=1)%this.bufferLength;this.buffer[this.head]=inValue}readCubic(index){var mu=index-Math.floor(index);return this.cubicInterpolation(this.getSample(Math.floor(index)+1),this.getSample(Math.floor(index)),this.getSample(Math.floor(index)-1),this.getSample(Math.floor(index)-2),mu)}}class AllPass{constructor(inValue,inG,sr){this.delayLength=inValue;this.g=inG;this.delayedSample=this.feedForwardSample=0;this.buffer=new CircularBuffer(sr)}process(sample){this.delayedSample=this.buffer.readCubic(this.delayLength);this.buffer.write(sample+this.delayedSample*this.g);this.feedForwardSample=sample*-this.g;return this.delayedSample+this.feedForwardSample}setFeedback(i){this.g=i}}
class Reverb {
constructor (SR,delays,decay=.5,dry=1.,wet=.5,enableLowpass=true,cutoff=.4) {
this.SR = SR;
this.options = {decay:decay,dry:dry,wet:wet};
this.t = Array(delays.length).fill(0);
this.bufs = delays.map(x=>Array(x).fill(0));
this.alls = delays.map(x=>new AllPass(x,random()/4+.25,SR))
this.eLP = enableLowpass;
this.l = 0;
this.rl = 0
this.cutoff = cutoff;
}
changeOptions (opt) {
for (let i=0;i<Object.values(opt).length;i++) {
this.options[Object.keys(opt)[i]] = Object.values(opt)[i];
}
}
process (x) {
for (let i=0;i<this.bufs.length;i++) {
this.t[i] = (this.t[i]+1)%this.bufs[i].length;
this.bufs[i][this.t[i]] = (i==0?x:this.bufs[i-1][this.t[i-1]])+this.alls[i].process(this.bufs[i][this.t[i]]*(this.options.decay))||0
}
let o;
if (this.eLP) {
this.l = this.bufs[this.bufs.length-1][this.t[this.bufs.length-1]]*this.cutoff+this.l*(1-this.cutoff);
this.rl = x*this.cutoff+this.rl*(1-this.cutoff);
o = this.l;
}
else {
o=this.bufs[this.bufs.length-1][this.t[this.bufs.length-1]]-x;
}
return (o-(this.eLP?this.rl:0))*this.options.wet+x*this.options.dry
}
}
class Synth {
constructor (SR,m1,m2,s) {
this.pc = 0;
this.note = null;
this.SR = SR;
this.m1 = m1; this.m2 = m2; this.s = s;
}
Note (x) {this.note = x;}
process () {
this.pc++;
let s = sin(this.pc/this.SR*mtof(this.note)*2*this.m1[0]+sin(this.pc/this.SR*mtof(this.note)*2*this.m2[0])*this.m2[1])*this.m1[1];
switch(this.s) {
case 0:
break;
case 1:
s=fract(s)*2-1;
break;
case 2:
s=(fract(s)>.5)*2-1
break;
case 3:
s=(abs(fract(s)*2-1))*2-1
break;
case 4:
s=(abs(sin(s))*2-1)
}
return s*.1**(this.pc/this.SR*8)
}
}
let p=[], q=[], m=0, r=[],o=0;
let init = false;
function w(t,sr) {
if (init == false) {
init = true;
p=[new Synth(8e3,[0,0],[0,0])];
q=[0,];
m=0;
p[0].Note(0);
r=[new Reverb(48e3,[5481,9547,12287,13256],.4,1.,.6,true,.5),new Reverb(48e3,[5485,9549,12288,13257],.4,1.,.6,true,.5)];
o=0;
}
m++;
if (random()<.0001) {
p.push(new Synth(sr,[(random()*16|0)/4,1],[(random()*16|0)/4,(random()*16|0)/4],random()*5|0));
q.push(m);
p[p.length-1].Note(69+(+'0357'[m/sr/4&3]));
}
p= q.map((x,i)=>{
if ((m-x)>12000 && p[i]!=null) {
return null
}
else {
return p[i]
}
});
for (let i=p.length-1;i>=0;i--) {
if (m-q[i]>49000) {
p.splice(i,1);
q.splice(i,1);
}
}
y=p.map(x=>x==null?0:x.process());
let out = (!y.length?[0]:y).reduce((a,b)=>a+b)/4
o+=(out-o)*.01;
return [r[0].process(out-o),r[1].process(out-o)]
}
synth.def( class {
constructor (options) {
this.phase = 0;
}
process(note, env, tick, options) {
this.phase += 48000/ditty.sampleRate;
return w(this.phase,ditty.sampleRate);
}
}).play(0, { env: one} );