//Weird ripple sound.
f=x=>((2+.5-((t/24e4)%.5))*sin(cos(x))),
rf16=x=>f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(x)))))))))))))))),
(rf16(t/1e3)/2)-.25//Started: 19 Aug 2024 13:56:32. Finished: 14:19:48.
//Moving points connected with lines to make a waveform.
rand=Math.random;
pToLine=(a,b,x)=>a.y+((b.y-a.y)*(x-a.x)/(b.x-a.x));
function makeChordSynth(count=5){
let p=[];
for(let i=0;i<count;i++){
p.push({x:rand(),y:rand(),vx:(rand()*2)-1,vy:(rand()*2)-1});
}
p.sort((a,b)=>a.x-b.x);
function step(speed=1){
for(let i=0;i<p.length;i++){
p[i].x+=p[i].vx*speed;
if(p[i].x>1){p[i].x=1-(p[i].vx*speed);p[i].vx*=-1;}
else if(p[i].x<0){p[i].x=0-(p[i].vx*speed);p[i].vx*=-1;}
p[i].y+=p[i].vy*speed;
if(p[i].y>1){p[i].y=1-(p[i].vy*speed);p[i].vy*=-1;}
else if(p[i].y<0){p[i].y=0-(p[i].vy*speed);p[i].vy*=-1;}
}
p.sort((a,b)=>a.x-b.x);
}
function wave(x){
//p must be in order first
//p positions must be within the range [0,1] (but this returns in range [-1,1])
if(x>=0&&x<=p[0].x){return(pToLine({x:p[p.length-1].x-1,y:p[p.length-1].y},p[0],x)*2)-1;}
if(x>=p[p.length-1].x&&x<=1){return(pToLine(p[p.length-1],{x:p[0].x+1,y:p[0].y},x)*2)-1;}
for(let i=0;i<p.length-1;i++){
if(x>=p[i].x&&x<=p[i+1].x){ //This could be improved? (By going backwards and only using one comparison?)
return(pToLine(p[i],p[i+1],x)*2)-1;
}
}
}
return(t)=>{
step(1e-6+(sin(t/20)*1e-5));
return(wave((t*60)%1)+wave((t*90)%1)+wave((t*200)%1)+wave((t*302)%1)+wave((t*480)%1))*.3;
};
}
a=[
//[synth,speed,LFO]
[makeChordSynth(4),1.61,.2],
[makeChordSynth(6),1.2,.6],
[makeChordSynth(12),.401,.5]
];
return(t)=>{
let m=[0,0];
for(let i=0;i<a.length;i++){
m[0]+=a[i][0](t*a[i][1])*sin(t*a[i][2]);
m[1]+=a[i][0](t*a[i][1])*cos(t*a[i][2]);
}
return[m[0]/2,m[1]/2];
};(Edited 4 minutes later.)
let mod=(n,m)=>(n%m+m)%m,
arr0=l=>new Array(l).fill(0),
FIR=(c=[1])=>{
let l=c.length,a=arr0(l);
return{
p:x=>{ //process
let o=0;
a.unshift(x);a.pop();
for(let i=0;i<l;i++){o+=c[i]*a[i];}
return o;
},
c:c //array itself
};
},pwm=(a,t)=>mod(t,1)>=a?1:-1,
cho=t=>(pwm(.3+(sin(t)*.2),t*340)+
pwm(.5+(sin(t*2.2)*.4),t*338)+
pwm(.3+(sin(t/2)*.2),t*265)-
pwm(.5+(sin(t*2.2)*.4),t*268)-
pwm(.3+(sin(t/3)*.2),t*257)-
pwm(.5+(sin(t*2.2)*.4),t*256)+
pwm(.3+(sin(t/2)*.2),t*230)+
pwm(.5+(sin(t*2.2)*.4),t*228)+
pwm(.3+(sin(t)*.2),t*180)+
pwm(.6+(sin(t*2.2)*.4),t*178)-
pwm(.3+(sin(t)*.2),t*170)-
pwm(.7+(sin(t*2.2)*.4),t*169)+
sin(t*PI*818)-sin(t*PI*1370))/8,
fc=(fun,cd=32e3)=>{
let fa=[],f,ct=0;
for(let l=64,i=0;i<l;i++)
fa.push((sin((i/l)*PI)**3)/30);
f=FIR(fa);
return t=>{
let o=f.p(fun(t));
ct++;if(ct>=cd){
let n=1+random()*20;
for(let l=fa.length,i=0;i<l;i++){
f.c[i]=(sin((i/l)*PI)**n)/30;
}
ct=0;
}
return o;
};
},fca=[[fc(cho),1],[fc(cho,8e3),1.5],[fc(cho,16e3),2.25]];
return t=>{
let o=0;
for(let i=0;i<fca.length;i++){
o+=fca[i][0](t*fca[i][1]);
}
return o/2;
};(Edited 9 minutes later.)
let arr0=l=>new Array(l).fill(0),
delay=length=>{
let a=arr0(length),i=0;
return x=>{
a[i]=x;
i=(i+1)%length;
return a[i];
};
},
reverb=()=>{
let s=72,d=[],init=()=>{
let p=300+(random()*5000);
for(let i=0;i<s;i++){
d[i]=[
delay(4+floor(random()*p)),
(.1+(random()*.23))*(random()<.5?1:-1)
];
}
};init();
return{
p:x=>{
let o=x;
for(let i=0;i<s;i++){
o+=d[i][0](i===0?x:o)*d[i][1];
}
return o;
},
i:init
};
};
let c={c:5e3,t:0,a:0,ar:true},r=reverb(),a=arr0(c.c);
c.c2=c.c;
return t=>{
let m=0;
c.t++;
if(c.t>=c.c2){c.t=0;
r.i();
m++;
c.c2=c.c/(1+floor(random()*2));
}
c.a++;
if(c.a>=c.c*4){c.a=0;
c.ar=random()<.3;
}
m=r.p(m);
if(c.ar){a[c.a]=m;}else{
m=a[c.a%c.c]+a[floor(c.a/2)%c.c]+a[floor(c.a/4)];
}
return m;
};(Edited 8 minutes later.)
var sampleRate=32000,
PI=Math.PI,TAU=PI*2;
class Filter{ //generated by o1
constructor(fs=sampleRate,freq=1000,Q=0.707){
this.fs=fs; //sampling frequency
this.g=0; //coefficient
this.R=1; //damping factor (computed from Q)
this.S1=0;this.S2=0; //state variables
this.type=0; //filter type parameter (continuous LP/BP/HP control, 0 to 2)
this.freq=freq; //cutoff frequency
this.Q=Q; //Q(uality) factor
}
setParams(freq,Q,type){
this.freq=freq;this.Q=Q;this.type=type;
var wd=TAU*freq, //digital angular frequency
T=1/this.fs, //sampling period
wa=(2/T)*Math.tan(wd*T/2); //pre-warping the frequency for bilinear transform
this.g=wa*T/2; //compute filter coefficient
this.R=1/(2*Q); //compute damping factor from Q
}
process(x){
//calculate the intermediate value 'v' using the state variables
var v=(x-this.S2-this.R*this.S1)/(1+this.g*this.R+this.g*this.g),
//update the state variables for the next sample
S1_next=this.g*v+this.S1,
S2_next=this.g*S1_next+this.S2,
//compute the filter outputs
LP=S2_next, //low-pass
BP=S1_next, //band-pass
HP=x-this.R*S1_next-S2_next; //high-pass
//update the state variables
this.S1=S1_next;this.S2=S2_next;
//blend the outputs based on the 'type' parameter
return this.type<=1?
LP*(1-this.type)+BP*this.type: //between low-pass and band-pass
BP*(2-this.type)+HP*(this.type-1); //between band-pass and high-pass
}
}
//GPT-4o mini's miniFilter
function miniFilter(){this.z=0;this.fc=0.5;this.q=1;this.a=0;this.b=0;this.set_fc(0.5);this.set_q(1);}
miniFilter.prototype.set_fc=function(fc){this.fc=fc<0?0:fc>1?1:fc;this.a=Math.sin(PI*this.fc)/(2*this.q);};
miniFilter.prototype.set_q=function(q){this.q=q<0.1?0.1:q>10?10:q;};
miniFilter.prototype.run=function(x){this.b=this.a*(x-this.z)+this.z;this.z=this.b;return this.b;};
/* This is what a real/working filter looks like:
function DiodeFilter(){
this.k=0;
this.A=0;
this.z=[0,0,0,0,0];
this.ah;this.bh;this.fc;
this.set_q(0);
this.set_hpf(0.5);
this.set_fc(.5);
}
DiodeFilter.prototype.set_hpf=function(fc){
var K=fc*PI;
this.ah=(K-2)/(K+2);
this.bh=2/(K+2);
};
DiodeFilter.prototype.reset=function(){if(this.k<17)this.z=[0,0,0,0,0];};
DiodeFilter.prototype.set_q=function(q){
this.k=20*q;this.A=1+.5*this.k;
};
DiodeFilter.prototype.set_fc=function(cutoff){
cutoff*=cutoff;
this.fc=cutoff<=0
?.02
:(cutoff>=1?.999:cutoff);
};
function clip(x){return x/(1+Math.abs(x));}
DiodeFilter.prototype.run=function(x){
var a=PI*this.fc;
a=2*Math.tan(.5*a); //dewarping, not required with 2x oversampling
var ainv=1/a,
a2=a*a,
b=2*a+1,
b2=b*b,
c=1/(2*a2*a2-4*a2*b2+b2*b2),
g0=2*a2*a2*c,
g=g0*this.bh,
//current state
s0=(a2*a*this.z[0]+a2*b*this.z[1]+this.z[2]*(b2-2*a2)*a+this.z[3]*(b2-3*a2)*b)*c,
s=this.bh*s0-this.z[4],
//solve feedback loop (linear)
y5=(g*x+s)/(1+g*this.k),
//input clipping
y0=clip(x-this.k*y5);
y5=g*y0+s;
//compute integrator outputs
var y4=g0*y0+s0,
y3=(b*y4-this.z[3])*ainv,
y2=(b*y3-a*y4-this.z[2])*ainv,
y1=(b*y2-a*y3-this.z[1])*ainv;
//update filter state
this.z[0]+=4*a*(y0-y1+y2);
this.z[1]+=2*a*(y1-2*y2+y3);
this.z[2]+=2*a*(y2-2*y3+y4);
this.z[3]+=2*a*(y3-2*y4);
this.z[4]=this.bh*y4+this.ah*y5;
return this.A*y4;
};
*/
function note(n,base=440,keys=12){return base*Math.pow(2,n/keys);}
var f=[],//new Filter(),
c=[0,3,7,10,14,15,17,22,26];
for(let i=0;i<c.length;i++){f.push(new Filter());}
function dsp(t){
/*var x=(t*60)%1<.5;
f.setParams(
1300+(Math.sin(t*3)*650),
4,.2
);
return f.process(x)*.5;*/
var x=0,i=0;
for(;i<c.length;i++){
f[i].setParams(
1400+(Math.sin(t*(1.3+i)*1.3)*650),
14,(((Math.sin((t+(i*TAU/c.length))*5)+1)/2)**7)*2
);
x+=f[i].process(
((t*note(c[i]+[5,10,3,8,1,6,-1][Math.floor(t*.8)%7],131.4,12.18))%1)-.5
);
}
return x/16;
}
return dsp;var sampleRate=8000,
f=t=>10*(t>>7|t|t>>6)+4*(t&t>>13|t>>6);
function mod(a,b){return(a%b+b)%b;}
function clamp(x,mn,mx){return min(max(x,mn),mx);}
function bytebeatToFloat(f,t){return((mod(f(floor(t*sampleRate)),256)/255)*2)-1;}
class biquad{
constructor(){
this.y1=0;this.x1=0;
this.y2=0;this.x2=0;
}
process(x,b0,b1,b2,a1,a2){
let o=b0*x+b1*this.x1+b2*this.x2-a1*this.y1-a2*this.y2;
if(!isFinite(o))o=x;
this.x2=this.x1;this.x1=x;
this.y2=this.y1;this.y1=o;
return o;
}
}
class compressor{
constructor(){
this.minDiv=1e-4;this.div=1;
}
process(x,speed=1){
this.div=max(this.minDiv,abs(x),this.div-(speed/sampleRate));
return x/this.div;
}
}
class convolution{
constructor(l=16){
this.l=l;
this.b=new Array(l).fill(0);
this.m=[];
for(let i=0;i<l;i++)
this.m.push(((1-(abs((i/l)-.5)*2))**51)+((Math.random()*2)-1)*.3);
this.i=0;
}
process(x,t){
let s=0;
this.b[this.i]=x;
for(let j=0;j<this.l;j++)
/*s+=this.b[j]*this.m[j];*/s+=this.b[floor(mod(this.i-j,this.l))]*this.m[j]; //the indexing is wrong
this.i=(this.i+1)%this.l;
return s;
//return this.b[this.i]-this.b[mod(this.i+1+floor((sin(t*6)+1)*2000),this.l)]; //cool wonky effect
//return this.b[mod(this.i+(floor((t*14e3)+sin(t*5)*500)%9300),this.l)];
}
}
let b=[],comp=new compressor(),conv=new convolution(1300);
for(let i=0;i<16;i++)b.push(new biquad());
return t=>{
let m=bytebeatToFloat(f,t*41.14);
for(let i=0;i<b.length;i++){
m=b[i].process(m,
sin(t/(i+1))/2,cos(t*(i+1))*0.3,sin(t+i)/5,sin(t*(1+i)-i)/4,cos(t*.2)/20);
}
m=conv.process(m,t);
return clamp(comp.process(clamp(m,-1,1),2e10)*2,-.9,.9);
};(Edited 8 minutes later.)
c=(x,a,b=Infinity)=>min(x*b,a===0?1:1-x**(a<.5?1/(2*a):2-2*a));
s=(t,p=220)=>{
let o=0;
for(let i=0;i<30;i++)
o+=c(t*(p+(sin(i*68.63)*.01)+(i-15)*.2)%1,(sin((t*2.2)+i*6.5)+1)*.5,4+(sin(t*.4)+1)*57)-.5;
return o*.1;
};
return t=>{
b=[180,140,135][((t*.3)%3)|0];
return s(t,b*.5)+s(t,b)+s(t,271)*.8+s(t,402)*.6;
};(Edited 7 minutes later.)
let notes="C,Db,D,Eb,E,F,Gb,G,Ab,A,Bb,B".split(",").reduce((o,k,i)=>(o[k]=i,o),{}),noteHz=n=>Math.pow(2,(n-49)/12)*440,read=note=>{
if(note.includes("-"))return note.split("-").map(read);
let n=40;
while(note.startsWith("_")){n-=12;note=note.replace("_","");}
while(note.startsWith("^")){n+=12;note=note.replace("^","");}
return n+notes[note];
},melody=`\
D Gb _B D
E G _A Db
Gb A _B D
E G _A Gb-E
D-_A Gb-G _B-_G Db-D
E-_A A-G Gb-_B Gb-A
_E-_G _B-Db _A-D E-^A
_Gb-_A Db-E Gb-_A Gb-E`.trim().split(/\s+/gm).map(read),tempo=160/60;
return t=>{
let at=(t*tempo)%melody.length,
e=melody[floor(at)],
swing=.66,
f=(typeof e==="number"?noteHz(e):noteHz(e[+(at%1>swing)]))-4.5; //I like to detune
return(((t*(f*(at%1<=swing?.25:.5))*1.002%1>at%1)+(t*f%1))-1)*(.3-((at%1<=swing?at%1:(at%1)-swing)**.3)*.38)*2;
};(Edited 3 minutes later.)
f=t=>{
if(t<0)return 0;
let at=(t*tempo)%melody.length,
e=melody[floor(at)],
swing=.4,
f=(typeof e==="number"?noteHz(e):noteHz(e[+(at%1>swing)]))+16.9;
return(((t*(f*(at%1<=swing?.25:.5))*1.702%1>at%1)+(t*f%1))-1)*(.3-((at%1<=swing?at%1:(at%1)-swing)**.3)*.28)*2;
};
return t=>{
t*=.87;
return f(t*2)*.4+f(t*1.5)+f(t)+f(t-.502)*.7+f(t-1.04)*.4;
};let TAU=Math.PI*2,notes="C,Db,D,Eb,E,F,Gb,G,Ab,A,Bb,B".split(",").reduce((o,k,i)=>(o[k]=i,o),{}),noteHz=n=>Math.pow(2,(n-49)/12)*440,read=note=>{
if(note.includes("-"))return note.split("-").map(read);
let n=40;
while(note.startsWith("_")){n-=12;note=note.replace("_","");}
while(note.startsWith("^")){n+=12;note=note.replace("^","");}
return n+notes[note];
},chords=`\
__C-C-Eb-G-Ab-Bb //Cm7b6
__F-_Bb-C-Eb-G-Ab-^Db //Fm11b6
__Bb-C-Db-F-Ab-Bb-^C-^Eb //Bbm11
__Eb-C-Db-Eb-F-Ab-Bb-^C //Eb13sus4`.replace(/\/\/.+$/gm,"").trim().split(/\s+/gm).map(read);/*,tempo=40/60;return t=>{
let at=(t*tempo)%chords.length,
a=chords[floor(at)],
o=0;
for(let i=0;i<a.length;i++)
o+=sin(t*noteHz(a[i])*TAU)**3;
return o*.1
};*/
let sampleRate=8000,timer={bar:0,maxCount:2**5,count:0,wait:sampleRate/13,tick:0},voices=[];
class synth{
constructor(n){
this.duration=timer.wait+(timer.wait*7*Math.random());
this.t=0;
this.channel=+(Math.random()<.5);
this.freq=noteHz(n+.3)+3*((Math.random()*2)-1);
this.tm=1/sampleRate;
this.e=1+floor(Math.random()*10);this.sign=Math.random()<.5?-1:1;
}
process(){
let{t,tm,freq,duration,e,sign}=this,time=t*tm;
let o=(sin(time*freq*TAU)**e)*sign*(1-(t/duration))*min(t*.001,1);
this.t++;
return o;
}
}
return t=>{
with(timer){
if(tick>=wait){
tick%=wait; //could be decimal
count++;
for(let i=0;i<chords[bar].length;i++)voices.push(new synth(chords[bar][i]-.1),new synth(chords[bar][i]),new synth(chords[bar][i]+.1),new synth(chords[bar][i]-12),new synth(chords[bar][i]-24));
if(count>=maxCount){ //next bar
count=0;
bar=(bar+1)%chords.length;
}
}
tick++;
}
let o=[0,0];
for(let i=0;i<voices.length;i++){
let v=voices[i];
o[v.channel]+=v.process();
if(v.t>=v.duration){
voices.splice(i,1);i--;
}
}
return[o[0]*.15,o[1]*.15];
};voices.splice(i--,1);without curly-braces but whatever. Dynamic size-changing arrays are also bad for this (due to memory management reasons), people usually use fixed-size cyclic/circular arrays for polyphonic synth voices.
Math.sin()is also slow that you should probably pre-calculate a table for that)…
(Edited 9 minutes later.)
let t=0,b=0,c=0,a=-1,d=1,l=(a,b,t)=>a+t*(b-a),bl=16e3,ba=[],bc=[0];
function f1(T){
t=t+.01+(Math.sin(t+.1)*4.01);
t+=b^(T%4);b=(t*(T%2.25))&5;
c=(t+c)/2.005;t=c-2;
t=(t%256+256)%256;
let o=(2*(t/255))-1;
a=l(-1,max(a,o),.98);d=l(1,min(d,o),.98);
return ((o-a)/max(.001,abs(d-a)))-1;
}
function f2(T){
let o=f1(T);
if(ba.length<bl){
ba.push(o);
}else{
bc=ba.reverse();ba=[];
}
return(((2*o-bc[(bl-1)-(Math.floor(T*2.9e3)%bl)]-bc[Math.floor(T*6.4e3)%bl])%2+1)%2)*.5;
}
for(let i=0;i<bl;i++)f2(i/8e3);
return f2;(Edited 3 minutes later.)
var audioCtx=new(window.AudioContext||window.webkitAudioContext)();
let semitones={C:-9,D:-7,E:-5,F:-4,G:-2,A:0,B:2};
function noteToFreq(note){
//Notes can either be integers or strings.
let semitone;
if(typeof note==="number"){semitone=note;}
else{
let match=note.match(/^([A-G])([b#]?)(-?\d+)$/i);
if(!match)throw new Error("Invalid note: "+note);
semitone=semitones[match[1].toUpperCase()];
let accidental=match[2],octave=parseInt(match[3],10);
if(accidental==="#")semitone++;
if(accidental==="b")semitone--;
semitone+=(octave-4)*12; //4 is the octave of A4
}
return 440*Math.pow(2,semitone/12);
}
function playNote(note,duration,velocity,start){
let osc=audioCtx.createOscillator(),
gain=audioCtx.createGain(),
freq=noteToFreq(note);
osc.frequency.setValueAtTime(freq,audioCtx.currentTime+start);
gain.gain.setValueAtTime(velocity,audioCtx.currentTime+start);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start(audioCtx.currentTime+start);
osc.stop(audioCtx.currentTime+start+duration);
}
function scheduleNotes(notes){
notes=notes.slice().sort((a,b)=>a.start-b.start);
let startTime=audioCtx.currentTime,
index=0,
interval=setInterval(()=>{
let currentTime=audioCtx.currentTime-startTime;
while(index<notes.length&¬es[index].start<=currentTime*1000){
let{note,duration,velocity,start}=notes[index];
playNote(note,duration,velocity,start);
index++;
}
if(index>=notes.length)
clearInterval(interval); //Stop
},50);
}
//scheduleNotes([{note:"C4",start:0,duration:10,velocity:.5},{note:"A4",start:5,duration:5,velocity:1}]);
scheduleNotes((()=>{let a=[],n=[..."CDEFGA"];for(let i=0;i<100;i++){let note=n[Math.floor(Math.random()*n.length)]+(4+Math.floor(Math.random()*3)),duration=.1+Math.random()*.2,velocity=Math.random()*.25,start=i/10;a.push({note,duration,velocity,start});}return a;})());(Edited 9 minutes later.)
function recurse(f,x,n){for(var o=x,i=0;i<n;i++)o=f(o);return o;}applyFunctionNTimes,
iterateFunction,
repeatFunction,
iterate…
(Edited 8 minutes later.)
iteratewould've been something more like this:
function iterate(f,n,...a){for(let i=0;i<n-1;i++)f(...a);return f(...a);}function iterate(f,n){for(let i=0;i<n-1;i++)f(i);return f(n-1);}(Edited 6 minutes later.)
.push()/
.pop()).
(Edited 3 minutes later.)
function recurse(f, x, n, i = 0){
if ( i < n ) {
return f(recurse(f, x, n, i + 1));
}
return x;
}(Edited 2 minutes later.)
f=n=>437*Math.pow(2,n/17),TAU=Math.PI*2,T=t/11e3,
a=[-7,-3,3,4,7,10,17,0,-14,-13,-3.5,20,21,24,31,37,33.8/*,7,20,17,10*/],a.reduce((p,c)=>p+Math.sin(f(c)*T*TAU)-((f(c)*T*.995)%1),0)*.06(Edited 2 minutes later.)
SR=32000; //sample rate
SRi=1/SR;
makeRatios=n=>{
let a=[];
for(let i=1;i<n;i++)
a.push(i/n);
return a;
};
a=[0,...[2,3,5,7/*prime numbers*/].flatMap(makeRatios),1].sort((a,b)=>a-b);
//return t=>((t*440*a[Math.floor(t*20)%a.length])%1)-.5;
st=0;
return t=>{
//let f=600*a[Math.floor(t*10)%a.length];
let f=220+220*a[Math.floor(t*10)%a.length]; //I'm pretty sure you're not supposed to multiply it like that if it's non-linear?
//f=440;
st=(st+f*SRi)%1;
//return Math.sin(t*f*Math.PI*2)*.5;
return(Math.sin(st*Math.PI*2)**5)*.75;
};(Edited 9 minutes later.)
let r=a[Math.floor(t*5)%a.length],
f=220*Math.pow(2,r);let ticks=0,start=0,
seconds=3, //until calculation
sampleRate,
oscT=0; //the time for oscillator
const TAU=Math.PI*2;
function main(t){
let freq=440;
oscT=(oscT+(freq/sampleRate))%1;
return(t%2<1?Math.sin(oscT*TAU):Math.sin(t*freq*TAU))*.5;
}
return time=>{
if(time<.1){ //Reset on playback.
ticks=0;sampleRate=undefined;
}
if(sampleRate!==undefined)
return main(time); //Play main function if there's a sample rate.
if(ticks===0)
start=time; //Set start at current time.
ticks++;
if(time-start>=seconds) //Check if specified duration has elapsed.
sampleRate=Math.max(ticks/Math.max(1e-4,time-start),1); //Divide ticks by elapsed duration to get the sample rate.
return(Math.random()-.5)*.1; //Play noise if there's no calculated sample rate.
};var PI=Math.PI,TAU=PI*2,
mod=(n,m)=>(n%m+m)%m;
class Delay{
constructor(maximumDelayInSamples=0){
this.sampleRate=44100; //Default sample rate
this.totalSize=Math.max(4,maximumDelayInSamples+1);
this.bufferData=new Float32Array(this.totalSize);
this.writePos=0;
this.readPos=0;
this.delay=0;
this.delayInt=0;
this.delayFrac=0;
this.v=new Float32Array(this.totalSize);
this.feedback=0; //Feedback factor
}
setDelay(newDelayInSamples){
const upperLimit=this.getMaximumDelayInSamples();
if(newDelayInSamples<0||newDelayInSamples>upperLimit)
throw new Error("Delay out of bounds");
this.delay=newDelayInSamples;
this.delayInt=Math.floor(this.delay);
this.delayFrac=this.delay-this.delayInt;
}
getDelay(){
return this.delay;
}
setFeedback(feedback){
this.feedback=feedback; //Set feedback factor (0 to 1)
}
prepare(numChannels,sampleRate){
this.sampleRate=sampleRate;
this.bufferData=new Float32Array(numChannels*this.totalSize);
this.writePos=new Array(numChannels).fill(0);
this.readPos=new Array(numChannels).fill(0);
this.v=new Float32Array(numChannels);
this.reset();
}
setMaximumDelayInSamples(maxDelayInSamples){
this.totalSize=Math.max(4,maxDelayInSamples+1);
this.bufferData=new Float32Array(this.totalSize);
this.reset();
}
reset(){
this.writePos.fill(0);
this.readPos.fill(0);
this.v.fill(0);
this.bufferData.fill(0);
}
pushSample(channel,sample){
const index=(this.writePos[channel]+this.totalSize-1)%this.totalSize;
this.bufferData[index]=sample;
this.writePos[channel]=index;
}
popSample(channel,delayInSamples=-1,updateReadPointer=true){
if(delayInSamples>=0)
this.setDelay(delayInSamples);
const result=this.interpolateSample(channel);
if(updateReadPointer)
this.readPos[channel]=(this.readPos[channel]+this.totalSize-1)%this.totalSize;
return result;
}
interpolateSample(channel){
const index=(this.readPos[channel]+this.delayInt)%this.totalSize,
value1=this.bufferData[index],
value2=this.bufferData[(index+1)%this.totalSize];
return value1+this.delayFrac*(value2-value1);
}
processSample(channel,inputSample){
//Get the delayed sample
const delayedSample=this.popSample(channel);
//Calculate the output sample with feedback
const outputSample=inputSample+delayedSample*this.feedback;
//Push the new sample into the delay line
this.pushSample(channel,outputSample);
return outputSample;
}
getMaximumDelayInSamples(){return this.totalSize-1;}
}
let d=new Delay(32000);
d.prepare(1,32000); //Prepare the delay line
d.setFeedback(.85); //Set the feedback factor (0 to 1)
//d.setDelay(2000);
return function dsp(time){
let o=sin(time*2e3*TAU)*(1-mod(time,.5))**40;
//d.setDelay((.05+(time%.1))*8000);
d.setDelay(500+(((1+sin(time*20))/2)*100));
o=d.processSample(0,o);
return o*.5;
};thiskeyword in JavaScript, it is the UGLIEST FUCKING SHIT I've ever seen,
with(this){…} would only ever make it worse. God, I hope there will be some kind of new syntactic sugar to get rid of thisoveruse completely. Might be better to rewrite it as a function (and so you could also avoid
newin exchange).
(Edited 9 minutes later.)
function mod(n,m){return(n%m+m)%m;}
class Delay{
constructor(maxDelay){
this.maxDelay=maxDelay;
this.buffer=new Array(maxDelay+1).fill(0);
this.writeIndex=0;
this.delay=maxDelay;
}
setDelay(delay){this.delay=Math.min(delay,this.maxDelay);}
tick(input){
this.buffer[this.writeIndex]=input;
const readIndex=mod(this.writeIndex-this.delay+this.buffer.length,this.buffer.length),
output=this.buffer[readIndex];
this.writeIndex=(this.writeIndex+1)%this.buffer.length;
return output;
}
nextOut(){return this.buffer[mod(this.writeIndex-this.delay+this.buffer.length,this.buffer.length)];}
clear(){this.buffer.fill(0);this.writeIndex=0;}
}
class OnePole{
constructor(){this.a1=0;this.b0=1;this.state=0;}
setPole(pole){
this.a1=pole;
this.b0=1-pole; //Lowpass with unity DC gain
}
tick(input){
this.state=input*this.b0+this.a1*this.state;
return this.state;
}
clear(){this.state=0;}
}
function isPrime(n){
if(n<=1)return false;if(n<=3)return true;
if(n%2===0||n%3===0)return false;
let i=5;while(i*i<=n){if(n%i===0||n%(i+2)===0)return false;i+=6;}
return true;
}
class JCRev{
constructor(T60,sampleRate=44100){
if(T60<=0)throw new Error("T60 must be positive");
this.sampleRate=sampleRate;
this.lastFrame=[0,0];
const originalLengths=[1116,1356,1422,1617,225,341,441,211,179],
scaler=sampleRate/44100;
//Adjust delay lengths to be odd primes
this.delayLengths=originalLengths.map(len=>{
let delay=Math.floor(scaler*len);
if(delay%2===0)delay++;
while(!isPrime(delay))delay+=2;
return delay;
});
//Initialize allpass delays (using indices 4,5,6 from original lengths)
this.allpassDelays=[];
for(let i=0;i<3;i++){
const len=this.delayLengths[i+4],
delay=new Delay(len);
delay.setDelay(len);
this.allpassDelays.push(delay);
}
//Initialize comb delays (indices 0-3)
this.combDelays=[];
for(let i=0;i<4;i++){
const len=this.delayLengths[i],
delay=new Delay(len);
delay.setDelay(len);
this.combDelays.push(delay);
}
//Initialize comb lowpass filters (one-pole with pole at 0.2)
this.combFilters=[];
for(let i=0;i<4;i++){
const filter=new OnePole();
filter.setPole(.2);
this.combFilters.push(filter);
}
//Decorrelation delays (indices 7 and 8)
this.outLeftDelay=new Delay(this.delayLengths[7]);
this.outLeftDelay.setDelay(this.delayLengths[7]);
this.outRightDelay=new Delay(this.delayLengths[8]);
this.outRightDelay.setDelay(this.delayLengths[8]);
this.allpassCoefficient=.7;
this.effectMix=.3;
this.combCoefficients=new Array(4).fill(0);
this.setT60(T60);
this.clear();
}
clear(){
this.allpassDelays.forEach(d=>d.clear());
this.combDelays.forEach(d=>d.clear());
this.combFilters.forEach(f=>f.clear());
this.outLeftDelay.clear();
this.outRightDelay.clear();
this.lastFrame=[0,0];
}
setT60(T60){
if(T60<=0)throw new Error("T60 must be positive");
for(let i=0;i<4;i++){
const delayTime=this.combDelays[i].delay;
this.combCoefficients[i]=Math.pow(10,(-3*delayTime)/(T60*this.sampleRate));
}
}
tick(input){
let current=input;
for(let i=0;i<3;i++){ //Process through three allpass filters in series
const delay=this.allpassDelays[i],
delayed=delay.nextOut(),
allpassOutput=-current+delayed,
delayInput=current+delayed*this.allpassCoefficient;
delay.tick(delayInput);
current=allpassOutput;
}
let combSum=0;
for(let i=0;i<4;i++){ //Process through four comb filters in parallel
const combDelay=this.combDelays[i],
combFilter=this.combFilters[i],
coeff=this.combCoefficients[i],
delayed=combDelay.nextOut(),
filtered=combFilter.tick(delayed),
feedback=filtered*coeff;
combDelay.tick(current+feedback);
combSum+=delayed;
}
//Apply decorrelation delays to left and right channels
const left=this.outLeftDelay.tick(combSum),
right=this.outRightDelay.tick(combSum);
//Mix wet/dry signals
const dryMix=1-this.effectMix,wetMix=this.effectMix;
this.lastFrame[0]=input*dryMix+left*wetMix;
this.lastFrame[1]=input*dryMix+right*wetMix;
return this.lastFrame;
}
}
let r=new JCRev(2.1,32000),tick=0,p=(x,y)=>Math.pow(Math.abs(x),y)*Math.sign(x);
function f(t){
let o=0;
for(let i=0;i<5;i++){
o+=p(sin(t*2000*(2**((i**1.75)/9))),2)*p(sin((t-8.4)*(1+i)),3000)*sin(t*(10-i)*8)*.1;
}
//if(tick%10000===0)r.clear();
tick++;
o=r.tick(o);
for(let i=0;i<o.length;i++){
o[i]=Math.tanh(o[i]);
}
return o;
}
let array=[],SR=16000,len=SR*10;
for(let i=0;i<len;i++){array.push([...f(i/SR)]);}
function lerp(a,b,x){return a+x*(b-a);}
function lerp2(a,b,x){return[a[0]+x*(b[0]-a[0]),a[1]+x*(b[1]-a[1])];}
function read(a,i){return lerp2(a[mod(floor(i),a.length)],a[mod(floor(i)+1,a.length)],(i%1+1)%1);}
let r2=new JCRev(3,32000);
return function DSP(t){
let o=[0,0];
for(let i=0;i<6;i++){
//let add=read(array,mod((t*-.5*SR*(1+i**1.4))+(sin(t*(i+2)*.4)*sin(t*(i+1))*SR*1.2),max(100,min((1-abs(sin(t*.001+i)))*array.length,array.length))));
let add=read(array,SR*sin(t*.3+6*i)*(i+1));
o[0]+=add[0]*.6;o[1]+=add[1]*.6;
}
return r2.tick((o[0]-o[1])*.4);
}function skew(x,a){return Math.min(x/a,(1-x)/(1-a));} //x:[0–1],a:[0–1]
function powCurve(x,a){return a===0?1:(a>0?Math.pow(1-x,a):Math.pow(x,-a));} //x:[0–1],a:[-∞–∞]
// 1. Sigmoid envelope (S-shaped curve with adjustable steepness)
// a=0 → linear, a>0 → steeper transition
function sigmoidEnv(x, a) {
if (a === 0) return x;
const k = a * 20; // Steepness multiplier
return 1 / (1 + Math.exp(-k * (x - 0.5)));
}
// 2. Quadratic pulse (double parabola peaking at `a`)
// Creates a sharp "n̂" shape with parabolic segments
function quadraticPulse(x, a) {
a = Math.min(Math.max(a, 1e-4), 1 - 1e-4); // Avoid division by zero
return x < a ? (x * x) / (a * a) : ((1 - x) * (1 - x)) / ((1 - a) * (1 - a));
}
// 3. Bell curve (Gaussian-like peak centered at `a`, fixed width)
// Returns values in [0,1], peaking at x=a
function bellCurve(x, a) {
const spread = 0.15; // Adjust this to change curve width
const dist = x - a;
return Math.exp(-(dist * dist) / (2 * spread * spread));
}
// 4. Smoothstep with variable steepness (generalized logistic)
// a=1 → linear, a>1 → steeper, a<1 → smoother transition
function smoothStep(x, a) {
a = Math.max(a, 0); // Ensure non-negative
x = Math.min(Math.max(x, 0), 1);
return Math.pow(x, a) / (Math.pow(x, a) + Math.pow(1 - x, a));
}
// 5. Double-exponential (custom asymmetric envelope)
// a>0: left side exponential, a<0: right side exponential
function expDouble(x, a) {
return a === 0 ? 1 :
a > 0 ? 1 - Math.pow(1 - x, a * 4 + 1) :
Math.pow(x, -a * 4 + 1);
}
let envFunctions=[skew,powCurve,sigmoidEnv,quadraticPulse,bellCurve,smoothStep,expDouble];
function makeEnv(n=5){
let layers=[];
for(let i=0;i<n;i++){
layers.push({f:Math.floor(Math.random()*envFunctions.length),a:Math.random()});
}
return x=>layers.reduce((p,c)=>envFunctions[c.f](p,c.a),x);
}
function mod(n,m){return(n%m+m)%m;}
function wavefold(x){return Math.abs(mod(x-1,4)-2)-1;}
function lerp(a,b,x){return a+x*(b-a);}
let envs=[],tick=0,last=0,first=0,duration=lerp(200,8000,Math.random());
for(let i=0;i<3;i++){envs.push(makeEnv());}
first=envs.reduce((p,c)=>c(p),tick);
if(isNaN(first))first=0;
function DSP_1(time){
let o=envs.reduce((p,c)=>c(p),tick);
tick+=1/duration;
if(tick>=1){
envs=[];
if(!isNaN(o))last=o;
for(let i=0;i<3;i++){envs.push(makeEnv());}
first=envs.reduce((p,c)=>c(p),0);if(isNaN(first))first=0;
tick-=1;
duration=lerp(200,8000,Math.random())
}
if(isNaN(o)){return Math.random();}
return wavefold(o-first);
}
function safe(x){return isNaN(x)?0:x;}
let sample=[],writeIndex=0;
for(let i=0;i<1100;i++)sample[i]=0;
return function DSP(time){
sample[floor(writeIndex)]=wavefold((sample[floor(writeIndex)]+DSP_1(time))*.9);
writeIndex=mod(writeIndex+1+safe(envs[0](time%1)*3.4),sample.length);
if(Math.random()<1e-4){
let newLen=floor(lerp(300,8000,Math.random())),oldLen=sample.length,newArray=[];
for(let i=0;i<newLen;i++){
let t=(i/newLen)*oldLen;
newArray[i]=safe(lerp(sample[floor(t)],sample[(floor(t)+1)%oldLen],t%1));
}
sample=newArray;
writeIndex=(writeIndex/oldLen)*newLen;
}
return sample[floor(writeIndex)];
}let easeFunctions=[(x,a)=>x>=a?1:0,(x,a)=>Math.pow(x,1+a),(x,a)=>Math.pow(x,1/(1+a)),(x,a)=>((Math.pow(Math.abs(2*(x-.5)),1+2*a)*Math.sign(x-.5))+1)/2,(x,a)=>((Math.pow(Math.abs(2*(x-.5)),1/(1+a))*Math.sign(x-.5))+1)/2,(x,a)=>Math.min(x/(1-a),Math.max(.5,1-((1-x)/(1-a)))),x=>.5+Math.sin(Math.PI*(x-.5))*.5,x=>(3*x*x)-(2*x*x*x),(x,a)=>a===0?x:(Math.exp(a*x)-1)/(Math.exp(a)-1),(x,a)=>x+a*x*(1-x),(x,a)=>{if(a===0)return x;a=(1/(1-a))-1;let s=a*(x-.5),mn=Math.atan(-a*.5),mx=-mn;return(Math.atan(s)-mn)/(mx-mn);},(x,a)=>a===0?x:Math.floor(x/a)*a],
mod=(n,m)=>(n%m+m)%m,oscify=(f,x,...p)=>2*f(1-2*Math.abs(.5-mod(x,1)),...p)-1,
noteHz=n=>440*Math.pow(2,n/12),TAU=Math.PI*2;
let notes=[ //(n)ote, (s)tart, (d)uration…
/*{n:0,s:0,d:.3},{n:-3,s:.2,d:.5},
{n:2,s:2,d:1},{n:5,s:2.4,d:1},
{n:2,s:4.5,d:1},{n:7,s:5.5,d:1},
{n:-15,s:0,d:2},{n:-5,s:0,d:2},
{n:-10,s:2,d:2},{n:-3,s:2,d:2},
{n:-14,s:4,d:2},{n:-3,s:4,d:2},
{n:-12,s:6,d:2},{n:-5,s:6,d:2}*/
//console.log(JSON.stringify([...notes,...(notes.map(x=>({n:x.n-2,s:x.s+4,d:x.d})))]));
{n:-20,s:0,d:4},{n:-17,s:.5,d:4},{n:-13,s:1,d:4},{n:-10,s:1.5,d:4},{n:-6,s:0,d:4},{n:-5,s:0,d:4},{n:-3,s:0,d:4},
{n:-22,s:4,d:4},{n:-19,s:4,d:4},{n:-15,s:4,d:4},{n:-12,s:4,d:4},{n:-8,s:4,d:4},{n:-7,s:4.5,d:4},{n:-5,s:5,d:4},
{n:28,s:0,d:9,v:.1},{n:14,s:0,d:9,v:.2},{n:11,s:3.5,d:2,v:.4},{n:16,s:7.5,d:.5,v:1.4}
],humanizeNote=({n,s,d,...rest})=>({n:n+(Math.random()-.5)*.4,s:s+(Math.random()-.5)*.05,d:d+Math.random()*.05,...rest});
notes=notes.flatMap(n=>[humanizeNote(n),humanizeNote(n),humanizeNote(n)]);
notes=notes.map(({...keys})=>({...keys,p:(Math.random()*2)-1}));
return function DSP(t){
let output=[0,0];
t=mod(t*1.05,8);
for(const note of notes){
let start=note.s,dur=note.d,end=start+dur;
if(t>=start&&t<end){
let localT=t-start,freq=noteHz(note.n),
attack=.01,release=1,env=Math.min(localT/attack,1,(dur-localT)/release);
//output+=Math.sin(TAU*freq*localT)*env;
//output-=oscify(easeFunctions[1],freq*localT,(sin(localT)+1)*.5)*env;
let osc=oscify(easeFunctions[mod(floor(note.n+localT*4),easeFunctions.length)],freq*localT,(sin(localT*3+note.n+t*.1)+1)*.5)*env*("v"in note?note.v:1);
output[0]-=osc*(1-note.p);output[1]-=osc*note.p;
}
}
return[Math.tanh(output[0]*.1),Math.tanh(output[1]*.1)];
};let SAMPLE_RATE=32000,TAU=Math.PI*2;
class BiquadFilter{ //musicdsp.org/files/Audio-EQ-Cookbook.txt
constructor(type="lowpass",freq=1000,Q=1){
this.type=type;
this.Fs=SAMPLE_RATE;
this.f0=freq;this.Q=Q;
this.dBGain=0;this.S=1;
this.BW=undefined;
this.a0=1;this.a1=0;this.a2=0;
this.b0=1;this.b1=0;this.b2=0;
this.x1=0;this.x2=0;
this.y1=0;this.y2=0;
this.updateCoefficients();
}
setType(type){this.type=type;this.updateCoefficients();}
setFrequency(f0){this.f0=f0;this.updateCoefficients();}
setQ(Q){this.Q=Q;this.updateCoefficients();}
setGain(dB){this.dBGain=dB;this.updateCoefficients();}
setSlope(S){this.S=S;this.updateCoefficients();}
setBandwidth(BW){this.BW=BW;this.updateCoefficients();}
updateCoefficients(){
const{type,Fs,f0,Q,dBGain,S,BW}=this;
let A=1;
if(type==="peaking"||type==="lowshelf"||type==="highshelf")A=Math.pow(10,dBGain/40);
const w0=TAU*f0/Fs,cosw0=Math.cos(w0),sinw0=Math.sin(w0);
let alpha=0;
switch(type){
case"lowpass":case"highpass":case"bandpass":case"notch":case"allpass":case"peaking":
if(BW!==undefined&&(type==="bandpass"||type==="notch")){
const numerator=(Math.log(2)/2)*BW*w0;
alpha=sinw0*Math.sinh(numerator/sinw0);
}else{
alpha=sinw0/(2*Q);
}
break;
case"lowshelf":case"highshelf":
const sqrtTerm=Math.sqrt((A+1/A)*(1/S-1)+2);
alpha=(sinw0/2)*sqrtTerm;
break;
default:throw new Error(`Unsupported filter type: ${type}`);
}
switch(type){
case"lowpass":
this.b0=(1-cosw0)/2;this.b1=1-cosw0;this.b2=this.b0;
this.a0=1+alpha;this.a1=-2*cosw0;this.a2=1-alpha;
break;
case"highpass":
this.b0=(1+cosw0)/2;this.b1=-(1+cosw0);this.b2=this.b0;
this.a0=1+alpha;this.a1=-2*cosw0;this.a2=1-alpha;
break;
case"bandpass":
this.b0=sinw0/2;this.b1=0;this.b2=-this.b0;
this.a0=1+alpha;this.a1=-2*cosw0;this.a2=1-alpha;
break;
case"notch":
this.b0=1;this.b1=-2*cosw0;this.b2=1;
this.a0=1+alpha;this.a1=-2*cosw0;this.a2=1-alpha;
break;
case"allpass":
this.b0=1-alpha;this.b1=-2*cosw0;this.b2=1+alpha;
this.a0=1+alpha;this.a1=-2*cosw0;this.a2=1-alpha;
break;
case"peaking":
this.b0=1+alpha*A;this.b1=-2*cosw0;this.b2=1-alpha*A;
this.a0=1+alpha/A;this.a1=-2*cosw0;this.a2=1-alpha/A;
break;
case"lowshelf":{
const twoSqrtAlpha=2*Math.sqrt(A)*alpha;
this.b0=A*((A+1)-(A-1)*cosw0+twoSqrtAlpha);
this.b1=2*A*((A-1)-(A+1)*cosw0);
this.b2=A*((A+1)-(A-1)*cosw0-twoSqrtAlpha);
this.a0=(A+1)+(A-1)*cosw0+twoSqrtAlpha;
this.a1=-2*((A-1)+(A+1)*cosw0);
this.a2=(A+1)+(A-1)*cosw0-twoSqrtAlpha;
break;
}
case"highshelf":{
const twoSqrtAlpha=2*Math.sqrt(A)*alpha;
this.b0=A*((A+1)+(A-1)*cosw0+twoSqrtAlpha);
this.b1=-2*A*((A-1)+(A+1)*cosw0);
this.b2=A*((A+1)+(A-1)*cosw0-twoSqrtAlpha);
this.a0=(A+1)-(A-1)*cosw0+twoSqrtAlpha;
this.a1=2*((A-1)-(A+1)*cosw0);
this.a2=(A+1)-(A-1)*cosw0-twoSqrtAlpha;
break;
}
default:throw new Error(`Unsupported filter type: ${type}`);
}
const a0=this.a0;
this.b0/=a0;this.b1/=a0;this.b2/=a0;this.a1/=a0;this.a2/=a0;
}
process(sample){
const x=sample,
y=this.b0*x+this.b1*this.x1+this.b2*this.x2-this.a1*this.y1-this.a2*this.y2;
this.x2=this.x1;this.x1=x;this.y2=this.y1;this.y1=y;
return y;
}
}
//Example usage…
function noteHz(n){return 440*2**(n/12);}
const chord=[0,4,7,11,14],filters=chord.map(n=>new BiquadFilter(Math.random()<.5?"bandpass":"lowpass",noteHz(n),100));
return function DSP(time){
let o=0;
for(let i=0,f;i<filters.length;i++){
f=filters[i];
f.setFrequency(noteHz(chord[i]+[0,3,5,-2][floor(time/2%4)]+sin(time*10)*.1)*.56)
o+=f.process((Math.random()-.5)+.4*sin(1/((time%(.2+i*.1))**1.3+.001)));
}
return Math.tanh(o*.1);
}let SEED=4,hash=[
208,34,231,213,32,248,233,56,161,78,24,140,71,48,140,254,245,255,247,247,40,
185,248,251,245,28,124,204,204,76,36,1,107,28,234,163,202,224,245,128,167,204,
9,92,217,54,239,174,173,102,193,189,190,121,100,108,167,44,43,77,180,204,8,81,
70,223,11,38,24,254,210,210,177,32,81,195,243,125,8,169,112,32,97,53,195,13,
203,9,47,104,125,117,114,124,165,203,181,235,193,206,70,180,174,0,167,181,41,
164,30,116,127,198,245,146,87,224,149,206,57,4,192,210,65,210,129,240,178,105,
228,108,245,148,140,40,35,195,38,58,65,207,215,253,65,85,208,76,62,3,237,55,89,
232,50,217,64,244,157,199,121,252,90,17,212,203,149,152,140,187,234,177,73,174,
193,100,192,143,97,53,145,135,19,103,13,90,135,151,199,91,239,247,33,39,145,
101,120,99,3,186,86,99,41,237,203,111,79,220,135,158,42,30,154,120,67,87,167,
135,176,183,191,253,115,184,21,233,58,129,233,142,39,128,211,118,137,139,255,
114,20,218,113,154,27,127,246,250,1,8,198,250,209,92,222,173,21,88,102,219
];
function mod(x,y){return(x%y+y)%y;}
function noise2(x,y){
let tmp=hash[mod(Math.floor(y)+SEED,256)];
return hash[mod(tmp+Math.floor(x),256)];
}
function linInter(x,y,s){return x+s*(y-x);}
function smoothInter(x,y,s){return linInter(x,y,s*s*(3-2*s));}
function noise2D(x,y){
let xInt=Math.floor(x),yInt=Math.floor(y),
xFrac=x-xInt,yFrac=y-yInt,
s=noise2(xInt,yInt),
t=noise2(xInt+1,yInt),
u=noise2(xInt,yInt+1),
v=noise2(xInt+1,yInt+1),
low=smoothInter(s,t,xFrac),
high=smoothInter(u,v,xFrac);
return smoothInter(low,high,yFrac);
}
function perlin2D(x,y,freq,depth){
let xa=x*freq,ya=y*freq,
amp=1,fin=0,div=0;
for(let i=0;i<depth;i++){
div+=256*amp;
fin+=noise2D(xa,ya)*amp;
amp/=2;
xa*=2;ya*=2;
}
return fin/div;
}
let TAU=Math.PI*2,sine=x=>Math.sin(TAU*x),noteHz=n=>440*2**(n/12),chord=[0,3,7,10,14,15,17,22],slots=Array(chord.length*2).fill(0),SR=16000,mix=(a,b,x)=>a+x*(b-a),readArray=(a,i)=>{let fi=Math.floor(i),li=(i%1+1)%1,al=a.length;return mix(a[mod(fi,al)],a[mod(fi+1,al)],li);};
return function DSP(time){
let o=0,s=1.2,trans=readArray([-15,-12,-10,-5,-3,-7,-8],floor(time/s)+mod(time/s,1)**24);
for(let i=0;i<chord.length;i++){
o+=sine(slots[2*i])*perlin2D(i,time,10,3)**8+sine(slots[(2*i)+1])*perlin2D(chord.length+i,time+3,10,2)**8;
slots[2*i]+=(noteHz(chord[i]+.05+trans)+perlin2D(i-10,time*.5,10,2)*10)/SR;
slots[(2*i)+1]+=(noteHz(chord[i]+-.05+trans)+perlin2D(i-15,time*.8,7,2)*10)/SR;
}
return o*.3;
};(Edited 9 minutes later.)
class DelayLine{
constructor(options={}){
const{
maxDelayInSamples=44100,numChannels=2,sampleRate=44100,interpolationType="linear"
}=options;
this.interpolationType=interpolationType;
this.sampleRate=sampleRate;
this.numChannels=numChannels;
this.maxDelay=maxDelayInSamples;
this.totalSize=Math.max(4,maxDelayInSamples+2);
//circular buffer for each channel
this.buffer=Array.from({length:numChannels},()=>new Array(this.totalSize).fill(0));
this.writePos=new Array(numChannels).fill(0);
this.readPos=new Array(numChannels).fill(0);
this.v=new Array(numChannels).fill(0); //for Thiran interpolation
this.setDelay(0); //initialize delay parameters
}
setDelay(newDelay){
const upperLimit=this.totalSize-2;
this.delay=Math.max(0,Math.min(newDelay,upperLimit));
this.delayInt=Math.floor(this.delay);
this.delayFrac=this.delay-this.delayInt;
this.updateInternalVariables();
}
updateInternalVariables(){
switch(this.interpolationType){
case"lagrange3rd":
if(this.delayFrac<2&&this.delayInt>=1){
this.delayFrac++;this.delayInt--;
}
break;
case"thiran":
if(this.delayFrac<.618&&this.delayInt>=1){
this.delayFrac++;this.delayInt--;
}
this.alpha=(1-this.delayFrac)/(1+this.delayFrac);
break;
}
}
pushSample(channel,sample){
this.buffer[channel][this.writePos[channel]]=sample;
this.writePos[channel]=(this.writePos[channel]+this.totalSize-1)%this.totalSize;
}
popSample(channel,delayInSamples=-1,updateReadPointer=true){
if(delayInSamples>=0)this.setDelay(delayInSamples);
let result;
switch(this.interpolationType){
case"none":result=this.interpolateNone(channel);break;
case"linear":result=this.interpolateLinear(channel);break;
case"lagrange3rd":result=this.interpolateLagrange3rd(channel);break;
case"thiran":result=this.interpolateThiran(channel);break;
}
if(updateReadPointer)
this.readPos[channel]=(this.readPos[channel]+this.totalSize-1)%this.totalSize;
return result;
}
//interpolation methods
interpolateNone(channel){
const index=(this.readPos[channel]+this.delayInt)%this.totalSize;
return this.buffer[channel][index];
}
interpolateLinear(channel){
const idx1=(this.readPos[channel]+this.delayInt)%this.totalSize,
idx2=(idx1+1)%this.totalSize,
y1=this.buffer[channel][idx1],y2=this.buffer[channel][idx2];
return y1+this.delayFrac*(y2-y1);
}
interpolateLagrange3rd(channel){
const idx1=(this.readPos[channel]+this.delayInt)%this.totalSize,
idx2=(idx1+1)%this.totalSize,idx3=(idx2+1)%this.totalSize,idx4=(idx3+1)%this.totalSize,
[y1,y2,y3,y4]=[
this.buffer[channel][idx1],
this.buffer[channel][idx2],
this.buffer[channel][idx3],
this.buffer[channel][idx4]
],d=this.delayFrac,d1=d-1,d2=d-2,d3=d-3;
return y1*(-d1*d2*d3/6)+d*(
y2*(d2*d3*.5)+
y3*(-d1*d3*.5)+
y4*(d1*d2/6)
);
}
interpolateThiran(channel){
const idx1=(this.readPos[channel]+this.delayInt)%this.totalSize,idx2=(idx1+1)%this.totalSize,
[y1,y2]=[this.buffer[channel][idx1],this.buffer[channel][idx2]],
output=this.delayFrac<1e-9?y1:y2+this.alpha*(y1-this.v[channel]);
this.v[channel]=output;
return output;
}
//utility methods
reset(){
this.writePos.fill(0);this.readPos.fill(0);this.v.fill(0);
this.buffer.forEach(ch=>ch.fill(0));
}
setMaxDelay(samples){
this.totalSize=Math.max(4,samples+2);
this.buffer=Array.from({length:this.numChannels},()=>new Array(this.totalSize).fill(0));
this.reset();
}
}
// === Usage example ===
let delay=new DelayLine({
maxDelayInSamples:8000,
numChannels:1,
interpolationType:"lagrange3rd"
}),last=0;
return t=>{
let o=(sin(PI*2*300*t)*(1-t%.3)**90)+(sin(PI*2*800*t)*(1-t%.45)**100)*.5;
delay.pushSample(0,o-last*.7);
delay.setDelay(1000+sin(t*2.5)*1000);
last=delay.popSample(0);
return Math.tanh(o+last);
};function shuffle(array){
for(let i=array.length-1;i>=0;i--){
const j=Math.floor(Math.random()*(i+1));
[array[i],array[j]]=[array[j],array[i]];
}
}
let mix=(a,b,x)=>a+x*(b-a),delays=[],last=[],route=[],mult=[],del=[],sampleRate=8000;
for(let i=0;i<30;i++){
delays.push(new DelayLine({
maxDelayInSamples:sampleRate,
numChannels:1,
interpolationType:"linear"
}));
del.push(mix(10,200,Math.random()**.5));
delays[i].setDelay(del[i]);
last.push(0);route.push(i);mult.push(mix(.85,.94,Math.random()));
}
shuffle(route);
let processDelays=(x,time)=>{
let o=x;
for(let i=0;i<delays.length;i++){
delays[i].pushSample(0,o-last[route[i]]*mult[i])
delays[i].setDelay(del[i]+sin(i**3.8+time*1.4)*60);
last[route[i]]=delays[i].popSample(0);
o+=last[route[i]]*.12
}
return o;
};
return t=>{
let o=(sin(PI*2*200*t)*(1-t%[.23,.3,.23,.55][(t*.7|0)%4])**90)+(Math.random()*(1-t%[.2,.5,.63,.7][(t*1.3|0)%4])**100)*.2;
o=processDelays(o,t);
return Math.tanh(o);
};window.location.hash(which would add a new entry to the history), it uses
window.location.replace()to change the URL without adding a new history entry. This method updates the URL without creating additional entries in the browser's history stack, whereas Dollchan does
window.location.hash=`#v3b64${btoa(String.fromCharCode.apply(undefined,deflateRaw(JSON.stringify(songData)))).replaceAll("=","")}`; every time you type a character which is annoying ('cause it floods your history). Besides, there's also window.history.replaceState(null,"",URL)…
let nHz=n=>459*(2**(n/12)),TAU=Math.PI*2,sine=x=>Math.sin(x*TAU),absPow=(x,y)=>Math.pow(Math.abs(x),y)*Math.sign(x),list=[[-16,0],[-4,4],[-1,5],[-5,8],[-4,10],[-9,12],[-16,14], [3,0],[7.8,1],[13.2,2],[1,5],[-1.3,10],[-2,12], [20.2,15,.25], [4,1],[6,3.5],[4,8],[3,13],[0,0],[1,4],[7,11]].flatMap(a=>[a,[a[0]+(Math.random()-.5)*.2,a[1]+Math.random()*.1,...(a[2]?[a[2]]:[])],[a[0]+(Math.random()-.5)*.3,a[1]+Math.random()*.1,...(a[2]?[a[2]]:[])]]).sort((a,b)=>a[1]-b[1]),len=16,voices=[],sampleRate=16e3,timer={time:0,add:(65/(60/4))/sampleRate,noteIdx:0};
return t=>{
timer.time+=timer.add;
if(timer.time>=len){timer.time%=len;timer.noteIdx=0;}
while(timer.noteIdx<list.length&&
list[timer.noteIdx][1]<=timer.time){
voices.push({f:nHz(list[timer.noteIdx][0]),d:10,ot:0,t:0,v:list[timer.noteIdx][2]||(.5+Math.random()*.5),c:Math.random()<.5?0:1});
timer.noteIdx++;
}
let o=[0,0];
for(let i=0;i<voices.length;i++){
let v=voices[i];
o[v.c]+=absPow(sine((v.ot+=v.f/sampleRate)*.5001),1/v.v)*sine(v.ot*.255)**2*((1-(v.t/v.d))**Math.max(7-t*.2,.8))*Math.cos(v.t*v.v*2)*v.v;
v.t+=timer.add;v.f+=v.t*.8/sampleRate;
if(v.t>=v.d){voices.splice(i,1);i--;}
}
let k=(absPow(sine(2/(.1+timer.time%2)),1+timer.time*.2)*Math.min(t*.05,1.5))+Math.random()*.1*(1-((timer.time+1)*.5%1))**20;
o[0]-=k;o[1]-=k;
return[Math.tanh(o[0]*.2),Math.tanh(o[1]*.2)];
}(Edited 4 minutes later.)
(Edited 24 seconds later.)
const TAU=Math.PI*2;
function sineWindow(x){ //x: 0 to 1
return Math.sin(TAU*(x-.25))*.5+.5;
}
function multiply(a,b){return{r:a.r*b.r-a.i*b.i,i:a.r*b.i+a.i*b.r};}
function add(a,b){return{r:a.r+b.r,i:a.i+b.i};}
function subtract(a,b){return{r:a.r-b.r,i:a.i-b.i};}
function FFT(a,invert=false){
let n=a.length;
if(n===1)return;
if(Math.log2(n)%1!==0)throw new Error("Length isn't a power of 2.");
let a0=new Array(n/2),a1=new Array(n/2);
for(let i=0;2*i<n;i++){a0[i]=a[2*i];a1[i]=a[2*i+1];}
FFT(a0,invert);FFT(a1,invert);
let ang=TAU/n*(invert?-1:1),
wn={r:Math.cos(ang),i:Math.sin(ang)},
w={r:1,i:0};
for(let i=0;i<n/2;i++){
let even=a0[i],odd=multiply(w,a1[i]),
sum=add(even,odd),difference=subtract(even,odd);
a[i]=sum;a[i+n/2]=difference;
if(invert){
a[i].r/=2;a[i].i/=2;
a[i+n/2].r/=2;a[i+n/2].i/=2;
}
w=multiply(w,wn);
}
}
let mod=(n,m)=>(n%m+m)%m,
read=(a,i)=>a[mod(Math.floor(i),a.length)],
sine=x=>Math.sin(x*TAU),
mix=(a,b,x)=>a+(b-a)*x;
class realtimeFFT{
constructor(size=11){
this.FFT_SIZE=2**size;
this.inputBuffer=[new Float32Array(this.FFT_SIZE).fill(0),new Float32Array(this.FFT_SIZE).fill(0)];
this.outputBuffer=[new Float32Array(this.FFT_SIZE).fill(0),new Float32Array(this.FFT_SIZE).fill(0)];
this.bufferIndex=[0,-(2**size)*.5];
}
process(x,t){
//record input
this.inputBuffer[0][this.bufferIndex[0]]=x;
if(this.bufferIndex[1]>=0)
this.inputBuffer[1][this.bufferIndex[1]]=x;
//windowing
let win=sineWindow,
o=this.outputBuffer[0][this.bufferIndex[0]]*win(this.bufferIndex[0]/this.FFT_SIZE)+this.outputBuffer[1][this.bufferIndex[1]]*win(this.bufferIndex[1]/this.FFT_SIZE);
for(let bI=0;bI<2;bI++){
this.bufferIndex[bI]++;
if(this.bufferIndex[bI]>=this.FFT_SIZE){
let a=[...this.inputBuffer[bI]].map(x=>({r:x,i:0}));
FFT(a,false);
let b=a.map(x=>({r:x.r,i:x.i}));
for(let i=0;i<a.length;i++){
a[i]=read(b,i*(1+sin(t*3)*.1));
}
FFT(a,true);
for(let i=0;i<a.length;i++){
this.outputBuffer[bI][i]=(a[i].r+a[i].i)*.5;//Math.sqrt(a[i].r**2+a[i].i**2);
}
this.bufferIndex[bI]=0;
}
}
return o;
}
}
let eff=new realtimeFFT(10);
return t=>{
let o=(sine(t*440)+sine(t*550)+sine(t*163))*.1;
return eff.process(o,t);
};let mod=(n,m)=>(n%m+m)%m,
read=(a,i)=>a[mod(Math.floor(i),a.length)],
mix=(a,b,x)=>a+(b-a)*x,
noteHz=n=>440*2**(n/12),
rand=(...a)=>a.length===2?mix(a[0],a[1],Math.random()):a.length===1?Math.random()*x[0]:Math.random(),
pow2=(x,y)=>Math.pow(Math.abs(x),y)*Math.sign(x);
class StringWave{
constructor(N,damp=1.001,c=.2){
this.N=N;this.heights=new Array(N);this.velocities=new Array(N);
this.pluck(rand(),pow2(rand(-1,1),.3));
this.damp=damp;this.c=c;
}
pluck(x,y){
let xi=x*this.N;
for(let i=0;i<this.N;i++){
this.heights[i]=i<xi?y*(i/xi):y*(1-(i/xi)/(this.N-xi));
this.velocities[i]=0;
}
}
update(){
const v=this.velocities,h=this.heights;
for(let i=1;i<this.N-1;i++)
v[i]=(v[i]+2*(h[i-1]+h[i+1]-2*h[i]))/this.damp;
for(let i=0;i<this.N;i++)
h[i]+=v[i]*this.c;
}
}
function normalize(array){
let mn=Math.min(...array),
mx=Math.max(...array),
o=[];
if(mn===mx)return array;
for(let i=0;i<array.length;i++)
o[i]=mix(-1,1,(array[i]-mn)/(mx-mn));
return o;
}
let SR=8000,
N=70,chord=[0,3,7,10,14,15,17,22],w=[],a=[];
for(let i=0;i<8;i++){
w[i]=new StringWave(N,1.0005,.29);
a[i]=[];
for(let j=0;j<SR*2.8;j++){
w[i].update();
a[i][j]=w[i].heights[floor(N*(.25+sin((j/SR)*3)*.2))];
}
a[i]=normalize(a[i]);
//declicking
let fade=128;
for(let j=0;j<fade;j++){
a[i][j]*=(j+1)/(fade+1);
a[i][(a[i].length-1)-j]*=(j+1)/(fade+1);
}
}
let samp=()=>{
let t=0;
return(si,sa)=>{
t+=sa/32e3;
let a0=read(a[mod(floor(si),a.length)],t),
a1=read(a[mod(floor(si)+1,a.length)],t);
return mix(a0,a1,mod(si,1));
};
},s=chord.map(n=>[samp(),samp()]);
return t=>{
let o=0;
for(let i=0;i<chord.length;i++){
//o+=read(a[mod(floor(i+t),a.length)],t*noteHz(chord[i])*N)-read(a[mod(floor(i+t*.5),a.length)],-t*noteHz(chord[i])*1.02*N);
o+=s[i][0](t+i,noteHz(chord[i]+sin(t*7+i*.3)*.05)*N)-s[i][1](t+i*2,-noteHz(chord[i]+sin(t*6.5+i*.6)*.06)*N);
}
return tanh(o*.3);
};let mod=(n,m)=>(n%m+m)%m,
oper={
"+":(a,b)=>a+b,"-":(a,b)=>a-b,
"*":(a,b)=>a*b,"/":(a,b)=>a/b,
"%":mod,"**":(a,b)=>a**b
},fun={ //.length for N of arguments
sin:Math.sin,rand:Math.random,
abs:Math.abs,floor:Math.floor
},chooseRand=a=>a[Math.floor(Math.random()*a.length)];
/*
Examples of Reverse Polish Notation (RPN):
t 220 * 1 % → (t*220)%1
rand 1 t 1 % - 5 ** * → rand()*((1-(t%1))**5)
.5 1 10 t * 2 % - abs - → .5-abs(1-((10*t)%2))
*/
function genExpr(){
const ops=Object.keys(oper),
unaries=Object.keys(fun).filter(k=>fun[k].length===1),
terminals=["t",...Object.keys(fun).filter(k=>fun[k].length===0)];
let stack=[],esd=0,maxLen=10; //eval stack depth
while(esd<1||Math.random()>.2||stack.length<3){
let options=["terminal"];
if(esd>=1)options.push("unary");
if(esd>=2)options.push("binary");
const choice=chooseRand(options);
switch(choice){
case"terminal":
if(Math.random()<.2){
stack.push(chooseRand(terminals));
}else{
stack.push(floor(random()*21)*.5-5);
}
esd++;
break;
case"unary":
stack.push(chooseRand(unaries));
break;
case"binary":
stack.push(chooseRand(ops));
esd--;
break;
}
if(stack.length>=maxLen)break;
}
//ensure minimum size and balance
while(esd!==1||stack.length<3){
if(esd<1){
stack.push(chooseRand([.5,1])); //push dummy operand
esd++;
}else if(esd>1){
stack.push("+"); //try to reduce stack
esd--;
}else{break;}
}
return stack;
}
function evalExpr(stack){
return function(tVal){
let s=[];
for(let token of stack){
try{
if(typeof token==="number"){
s.push(token);
}else if(token==="t"){
s.push(tVal);
}else if(oper.hasOwnProperty(token)){
if(s.length<2)return 0; //not enough
const b=s.pop(),a=s.pop(),
result=oper[token](a,b);
s.push(isFinite(result)?result:0);
}else if(fun.hasOwnProperty(token)){
if(fun[token].length===0){s.push(fun[token]());continue;} //e.g. rand
if(!s.length)return 0; //no args to push
const result=fun[token](s.pop()); //TODO: multiple args
s.push(isFinite(result)?result:0);
}else{return 0;}
}catch(e){
console.error("Eval error:",e,"at",token);
return 0;
}
}
const result=s.pop()||0;
return isNaN(result)||!isFinite(result)?0:result;
};
}
/*let timer={
t:0,l:.1
},expr=genExpr(),eFun=evalExpr(expr);
return(t,SR)=>{
timer.t+=1/SR;
if(timer.t>=timer.l){
timer.t%=timer.l;
expr=genExpr();eFun=evalExpr(expr);
}
return eFun(t);
};*/
let split=str=>str.split(" ").map(e=>{let n=parseFloat(e);return isNaN(n)?e:n;}),
eFun=[
evalExpr(split(".5 1 440 t * 2 % - abs -")),
evalExpr(split("rand .5 - 1 t 1 % - 5 ** *")),
...(new Array(5).fill(0).map(()=>evalExpr(genExpr())))
];
return t=>{
return eFun[floor(t/2)%eFun.length](t);
};(Edited 7 minutes later.)
url.replace(/https:\/\/([^#]+)(#.+)/,(m,p1,p2)=>p1+encodeURIComponent(p2));(Edited 1 minute later.)
var{sin,cos,floor,random,PI}=Math,TAU=Math.PI*2,mod=(n,m)=>(n%m+m)%m,
audioCtx=new(AudioContext||webkitAudioContext)(),
bufferSize=1024,sampleRate=audioCtx.sampleRate,globalTime=0,δt=1/sampleRate,
scriptNode,isPlaying=false;
function DSP(t){
//return sin(TAU*220*t);
return[sin(TAU*440*t)*sin(t),sin(TAU*440*t)*cos(t)];
}
function toggleAudio(){
if(!scriptNode){
scriptNode=audioCtx.createScriptProcessor(bufferSize,0,2); //AudioWorklet is recommended today but requires external files.
scriptNode.onaudioprocess=e=>{
let outBuffer=e.outputBuffer,signal,
outL=outBuffer.getChannelData(0),
outR=outBuffer.getChannelData(1);
for(let i=0;i<bufferSize;i++){
signal=DSP(globalTime);
if(typeof signal==="number"){
outL[i]=outR[i]=signal;
}else{
outL[i]=signal[0];outR[i]=signal[1];
}
globalTime+=δt;
}
};
scriptNode.connect(audioCtx.destination);
audioCtx.resume();isPlaying=true;
}else if(isPlaying){
scriptNode.disconnect(audioCtx.destination);isPlaying=false;
}else{
scriptNode.connect(audioCtx.destination);audioCtx.resume();isPlaying=true;
}
}
toggleAudio(); //Play, but call again to pause.