//Chris Xiong 2015-2021
//License: Expat
//WARNING: This file contains profanity (thrown as exceptions).
let NSInk=null;
let NSAudio=null;
let NSVisualization=null;
let NSPlayer=null;
let NSUI=null;
const sh={
elem:function(e)
{return document.getElementById(e);},
newelem:function(e)
{return document.createElement(e);},
getcookie:function(key)
{return document.cookie.replace(new RegExp('(?:(?:^|.*;\\s*)'+key+'\\s*\\=\\s*([^;]*).*$)|^.*$'),'$1');},
setcookie:function(key,value)
{return document.cookie=`${key}=${value};max-age=31536000`;},
};
class Ink
{
constructor(_vx,_vy,_c)
{
this.x=NSUI.canvas.width/2;this.y=NSUI.canvas.height/2;
this.vx=_vx;this.vy=_vy;
this.color=_c>6?6:_c;this.active=true;
}
update()
{
const canvas=NSUI.canvas;
this.x+=this.vx;this.y+=this.vy;
this.vx*=0.995;this.vy*=0.995;
if(this.x<-10||this.x>canvas.width+10||this.y<-10||this.y>canvas.height+10)
this.active=false;
}
draw(cctx)
{
cctx.drawImage(NSInk.inkimg[this.color],this.x+5*window.devicePixelRatio,this.y+5*window.devicePixelRatio);
}
}
NSPlayer={
plistname:null,
tracks:null,
current:null,
shuffle:0,
repeat:0,
served_formats:{'ogg':{'mime':'audio/ogg; codecs=vorbis','disp':'ogg 224 kbps'},'flac':{'mime':'audio/flac','disp':'flac'},'opus':{'mime':'audio/ogg; codecs=opus','disp':'opus 96 kbps'},'m4a':{'mime':'audio/aac','disp':'aac 192kbps'}},
get_preferred_or_default_format:function()
{
if (sh.getcookie('preferredformat') in this.served_formats)
return sh.getcookie('preferredformat');
for (let fmt in this.served_formats)
if (sh.elem('audio').canPlayType(this.served_formats[fmt].mime)!='')
return fmt;
},
load_playlist:async function(pln,ord)
{
let r=null;
const resp=await fetch(new Request(`/libs/music/player.d/playlists/${pln}.playlist?${new Date().getTime()}`));
if(!resp.ok)throw "shit";
r=await resp.text();
let rarr=r.split('\n');
let tarr=[];
for(let i=1;i<rarr.length;++i)
{
let t=rarr[i].trim();
if(!t.length)continue;
let titem={};
titem.title=t;
titem.ord=i-1;
tarr.push(titem);
}
return ({plistname:pln,playlist:tarr,plistord:ord});
},
play:function(id)
{
if(!this.plistname)return;
window.history.replaceState('','The Stupid Online Player',
`#${encodeURIComponent(this.plistname)}/${encodeURIComponent(this.tracks[id].title)}`);
NSUI.iplaypause.style.backgroundPosition=`${NSUI.bpauserect}`;
this.current=id;
NSUI.lbnowplaying.innerHTML="Now Playing: ";
const a=sh.newelem("a");
a.innerHTML=this.tracks[id].title;
a.href=`javascript:NSUI.showNotes("${this.tracks[id].title}")`;
NSUI.lbnowplaying.appendChild(a);
if(navigator.mediaSession)
navigator.mediaSession.metadata=new MediaMetadata({title:this.tracks[id].title,album:this.plistname});
NSUI.set_highlighted(this.plistname,this.tracks[id].title);
const fmt=NSPlayer.get_preferred_or_default_format();
NSUI.audio.src=`//filestorage.chrisoft.org/music/${fmt}/${this.tracks[id].title}.${fmt}`;
NSUI.audio.load();
return NSUI.audio.play();
},
next:function()
{
this.current=(this.current+1)%this.tracks.length;
if(this.current==0&&this.shuffle)
NSUI.switch_playlist(this.plistname);
this.play(this.current);
},
prev:function()
{
this.current=(this.current-1+this.tracks.length)%this.tracks.length;
this.play(this.current);
},
switch_playlist:function(plistname,plist)
{
this.plistname=plistname;
this.tracks=plist;
},
sort_playlist:function(shuffle,plist)
{
const tarr=plist?plist:this.tracks;
if(!tarr)return;
if(shuffle)
for(let i=0;i<tarr.length;++i)
tarr[i].sord=Math.random()*tarr.length;
tarr.sort(
(a,b)=>{
const ra=shuffle?a.sord:a.ord;
const rb=shuffle?b.sord:b.ord;
return ra<rb?-1:1;
}
);
if(plist)plist=tarr;else this.tracks=tarr;
}
};
NSUI={
playlist:null,
audio:null,
canvas:null,
iplaypause:null,
irepeat:null,
ishuffle:null,
lbnowplaying:null,
playlists:null,
ulplaylists:null,
swplaylist:null,
selectedplist:null,
vissel:null,
pbnext:null,
pbprev:null,
ctrlcontainer:null,
am3u8:null,
swformat:null,
bplayrect:"0 -48px",
bpauserect:"-24px -48px",
brallrect:"-24px -24px",
brnonrect:"-48px -24px",
bronerect:"0 0",
bsoffrect:"-24px 0",
bshonrect:"-48px 0",
plistshown:true,
setup_ui:function()
{
window.devicePixelRatio=window.devicePixelRatio?window.devicePixelRatio:1;
window.onresize=function()
{
if(window.innerWidth<768)
setupevents();
else unsetevents();
};
window.onresize();
this.playlist=sh.elem('playlist');
this.swplaylist=sh.elem('plistsw');
this.audio=sh.elem('audio');
this.canvas=sh.elem('cvs');
this.iplaypause=sh.elem('imgplaypause');
this.irepeat=sh.elem('imgrepeat');
this.ishuffle=sh.elem('imgshuffle');
this.lbnowplaying=sh.elem('nowplaying');
this.ulplaylists=sh.elem('plists');
this.vissel=sh.elem('visualizationsel');
this.pbnext=sh.elem('pbnext');
this.pbprev=sh.elem('pbprev');
this.ctrlcontainer=sh.elem('ctrlcontainer');
this.am3u8=sh.elem('am3u8');
this.swformat=sh.elem('formatsw');
if(!(sh.getcookie('preferredformat') in NSPlayer.served_formats))
sh.setcookie('preferredformat','');
const fmt=NSPlayer.get_preferred_or_default_format();
const cantplay=(NSUI.audio.canPlayType(NSPlayer.served_formats[fmt].mime)=='')?' !':'';
this.swformat.innerHTML=`[${NSPlayer.served_formats[fmt].disp}${cantplay}]`;
NSUI.canvas.width=NSUI.canvas.clientWidth*window.devicePixelRatio;
NSUI.canvas.height=NSUI.canvas.clientHeight*window.devicePixelRatio;
NSUI.vissel.onchange=function(){
if(this.value!='none'&&this.oldvalue=='none')requestAnimationFrame(NSVisualization.updateVisualization);
else NSUI.canvas.getContext('2d').clearRect(0,0,NSUI.canvas.width,NSUI.canvas.height);
sh.setcookie('playervisualization',this.value);
this.oldvalue=this.value;
};
sh.elem('shufflesw').onclick=function(){
NSUI.shuffle_switch(NSPlayer.shuffle=1-NSPlayer.shuffle);
NSUI.ishuffle.style.backgroundPosition=`${NSPlayer.shuffle?NSUI.bshonrect:NSUI.bsoffrect}`;
sh.setcookie('playershuffle',NSPlayer.shuffle);
};
sh.elem('repeatsw').onclick=function(){
NSPlayer.repeat=1-NSPlayer.repeat;
NSUI.audio.loop=NSPlayer.repeat?true:false;
NSUI.irepeat.style.backgroundPosition=`${NSPlayer.repeat?NSUI.bronerect:NSUI.brallrect}`;
sh.setcookie('playerrepeat',NSPlayer.repeat);
};
sh.elem('formatsw').onclick=function(){
const cfmt=NSPlayer.get_preferred_or_default_format();
fmts=Object.keys(NSPlayer.served_formats);
const nfmt=fmts[(fmts.indexOf(cfmt)+1)%fmts.length];
sh.setcookie('preferredformat',nfmt);
const cantplay=(NSUI.audio.canPlayType(NSPlayer.served_formats[nfmt].mime)=='')?' !':'';
NSUI.swformat.innerHTML=`[${NSPlayer.served_formats[nfmt].disp}${cantplay}]`;
if(NSUI.selectedplist)
NSUI.switch_playlist(NSUI.selectedplist);
};
sh.elem('plistsw').onclick=NSUI.togglePlist;
sh.elem('tsliderbase').onclick=
sh.elem('tsliderbase').onmousemove=function(e)
{
if(e.type=='click'||(e.type=='mousemove'&&e.buttons==1))
{
const sr=this.getBoundingClientRect();
const p=(e.clientX-sr.left)/sr.width;
NSUI.audio.currentTime=NSUI.audio.duration*p;
}
};
const playpausef=function()
{
if(NSUI.audio.src=='')
{
if(NSUI.selectedplist)
{
NSUI.switch_playlist(NSUI.selectedplist);
NSUI.switch_track(0);
}
return;
}
if(NSUI.audio.paused)
{
NSUI.audio.play();
NSUI.iplaypause.style.backgroundPosition=`${NSUI.bpauserect}`;
}
else
{
NSUI.audio.pause();
NSUI.iplaypause.style.backgroundPosition=`${NSUI.bplayrect}`;
}
};
NSUI.iplaypause.onclick=playpausef;
NSUI.pbprev.onclick=function()
{
if(NSUI.audio.currentTime>10)NSUI.audio.currentTime=0;
else NSPlayer.prev();
};
NSUI.pbnext.onclick=NSPlayer.next.bind(NSPlayer);
if(navigator.mediaSession)
{
navigator.mediaSession.setActionHandler('previoustrack',NSUI.pbprev.onclick);
navigator.mediaSession.setActionHandler('nexttrack',NSUI.pbnext.onclick);
}
window.onkeydown=function(e)
{
if(e.key==' ')
{
playpausef();
return false;
}
if(e.key=='c')
if(NSUI.audio.audioTracks.length==2)
{
const t=NSUI.audio.currentTime;
NSUI.audio.audioTracks[0].enabled^=1;
NSUI.audio.audioTracks[1].enabled^=1;
NSUI.audio.currentTime=t;
}
return true;
};
},
load_playlists:async function()
{
const moi=this;
let r=null;
const resp=await fetch(new Request(`/libs/music/player.d/playlists/playlists?${new Date().getTime()}`));
if(!resp.ok)throw "shit";
r=await resp.text();
let rarr=r.split('\n');
let tarr=[];
let cnt=0;
for(let i=0;i<rarr.length;++i)
if(rarr[i].trim().length)++cnt;
const p=new Promise((a,b)=>{
for(let i=0;i<rarr.length;++i)
{
let t=rarr[i].trim();
if(!t.length)continue;
NSPlayer.load_playlist(t,i).then((rp)=>{
tarr[rp.plistord]=rp;
if(--cnt<=0)a(null);
},()=>{b(null);});
}
});
await p;
for(let i=0;i<tarr.length;++i)
{
const e=sh.newelem('li');
const ea=sh.newelem('a');
ea.innerHTML=tarr[i].plistname;
ea.classList.add('listitem');
ea.href='javascript:void(0);';
ea.pid=i;
ea.onclick=function(e){
e.preventDefault();
for(let i=0;i<NSUI.ulplaylists.childNodes.length;++i)
{
const ta=NSUI.ulplaylists.childNodes[i].firstChild;
if(ta.pid==this.pid)
{
if(ta.classList.contains('highlighted'))
NSUI.showNotes(this.innerHTML);
ta.classList.add('highlighted');ta.classList.add('active');
}
else{ta.classList.remove('highlighted');ta.classList.remove('active');}
}
NSUI.present_playlist.bind(NSUI,this.pid)();
if(NSPlayer.current)
NSUI.set_highlighted(NSPlayer.plistname,NSPlayer.tracks[NSPlayer.current].title);
};
e.appendChild(ea);
NSUI.ulplaylists.appendChild(e);
}
moi.playlists=tarr;
},
present_playlist:function(id)
{
while(this.playlist.firstChild)
this.playlist.removeChild(this.playlist.firstChild);
const list=this.playlists[id].playlist;
for(let i=0;i<list.length;++i)
{
const l=sh.newelem('li');
const a=sh.newelem('a');
a.innerHTML=list[i].title;
const fmt=NSPlayer.get_preferred_or_default_format();
a.href=`//filestorage.chrisoft.org/music/${fmt}/${list[i].title}.${fmt}`;
a.ord=i;
a.onclick=function(e){e.preventDefault();NSUI.switch_track.bind(NSUI,this.ord)();};
l.appendChild(a);
this.playlist.appendChild(l);
}
const d=sh.newelem('div');d.style.height=`${NSUI.ctrlcontainer.getBoundingClientRect().height+16}px`;
this.playlist.appendChild(d);
this.selectedplist=this.playlists[id].plistname;
this.am3u8.href=`https://chrisoft.org/libs/music/player.d/cgi-bin/m3u8.cgi?plist=${this.playlists[id].plistname}&type=${NSPlayer.get_preferred_or_default_format()}`;
},
switch_playlist:function(pl,setactive)
{
let cid=0;
for(;cid<this.playlists.length;++cid)
if(this.playlists[cid].plistname==pl)break;
if(cid>=this.playlists.length)return;
if(setactive)
{
NSUI.ulplaylists.childNodes[cid].firstChild.classList.add('highlighted');
NSUI.ulplaylists.childNodes[cid].firstChild.classList.add('active');
}
//VERY stupid design, but it _should_ work.
NSPlayer.switch_playlist(pl,this.playlists[cid].playlist);
NSPlayer.sort_playlist(NSPlayer.shuffle);
this.playlists[cid].playlist=NSPlayer.tracks;
this.present_playlist(cid);
this.selectedplist=pl;
},
switch_track:function(id)
{
if(NSPlayer.plistname!=this.selectedplist)
this.switch_playlist(this.selectedplist);
return NSPlayer.play(id);
},
set_highlighted:function(pl,t)
{
if(pl!=this.selectedplist)return;
let cid=0;
for(;cid<this.playlists.length;++cid)
if(this.playlists[cid].plistname==pl)break;
const clist=this.playlists[cid].playlist;
for(let i=0;i<clist.length;++i)
if(clist[i].title==t)
this.playlist.childNodes[i].firstChild.classList.add('highlighted');
else
this.playlist.childNodes[i].firstChild.classList.remove('highlighted');
},
shuffle_switch:function(shuffle)
{
for(let i=0;i<this.playlists.length;++i)
{
NSPlayer.sort_playlist(shuffle,this.playlists[i].playlist);
if(this.playlists[i].plistname==this.selectedplist)this.present_playlist(i);
}
NSPlayer.sort_playlist(shuffle);
},
togglePlist:function()
{
NSUI.plistshown=!NSUI.plistshown;
NSUI.swplaylist.innerHTML=`[${NSUI.plistshown?'Hide':'Show'} Playlist]`;
NSUI.playlist.style.opacity=NSUI.plistshown?'1':'0';
NSUI.playlist.style.pointerEvents=NSUI.plistshown?'auto':'none';
},
formatTime:function(t)
{
if(isNaN(t))return '-:--';
let m=Math.floor(t/60),s=Math.round(t-Math.floor(t/60)*60);
if(s<10)return `${m}:0${s}`;
else if(s==60)return `${m+1}:00`;
else return `${m}:${s}`;
},
bufferedUpdate:function()
{
let r=0;
for(let i=0;i<NSUI.audio.buffered.length;++i)
r=r<NSUI.audio.buffered.end(i)?NSUI.audio.buffered.end(i):r;
document.getElementById('cbuff').style.width=r/NSUI.audio.duration*100+'%';
},
timeUpdate:function()
{
document.getElementById('timenow').innerHTML=NSUI.formatTime(NSUI.audio.currentTime);
document.getElementById('timeleft').innerHTML=`-${NSUI.formatTime(NSUI.audio.duration-NSUI.audio.currentTime)}`;
document.getElementById('ctime').style.width=NSUI.audio.currentTime/NSUI.audio.duration*100+'%';
NSUI.bufferedUpdate();
},
showNotes:async function(title)
{
const nd=sh.elem("notes");
const nt=sh.elem("ntext");
nt.innerHTML="Loading..."
nd.style.display="block";
setTimeout(()=>{nd.style.opacity=1.;},5);
const url=`//filestorage.chrisoft.org/music/notes/${title}.note`;
try{
const resp=await fetch(new Request(url));
if(!resp.ok)throw "shit";
r=await resp.text();
nt.innerHTML=r;
}catch(e){nt.innerHTML="This particular track doesn't seem to have a note.";}
},
hideNotes:function(title)
{
const nd=sh.elem("notes");
nd.style.opacity=0.;
setTimeout(()=>{nd.style.display="none";},500);
},
showHelp:function()
{
const hd=sh.elem("helpoverlay");
hd.style.display="block";
setTimeout(()=>{hd.style.opacity=1.;},5);
},
hideHelp:function()
{
const hd=sh.elem("helpoverlay");
hd.style.opacity=0.;
setTimeout(()=>{hd.style.display="none";},500);
}
};
NSAudio={
audioctx:null,
srcnode:null,
anlznode:null,
audioInit:function()
{
window.AudioContext=window.AudioContext||window.webkitAudioContext||window.mozAudioContext||window.msAudioContext;
if(!window.AudioContext)alert('This page requires Web Audio API to work...');
if(this.audioctx===null)this.audioctx=new AudioContext;
if(this.anlznode===null)this.anlznode=this.audioctx.createAnalyser();
this.anlznode.fftSize=2*NSVisualization.nbins;
NSUI.audio.volume=1;
},
connect:function()
{
if(this.srcnode===null)
this.srcnode=this.audioctx.createMediaElementSource(NSUI.audio);
this.srcnode.connect(this.anlznode);
this.anlznode.connect(this.audioctx.destination);
if(this.audioctx.state!="running")throw "resume required";
},
};
NSVisualization={
nbinsp:10,
nbins:Math.pow(2,10),
spectgrmw:1200,
spectrw:0.7,
spectrm:0.08125,
caps:[],
cctx:null,
spectgrmp:0,
_61spbins:29,
_61spfintv:Math.pow(1024,1./29),
_61spbinw:9,
_61spaccel:.617274873,
_61spdamp:Math.pow(.617274873,6.17274873),
_61spvelcap:2*6.17274873,
_61spgvel:.025,
_61spelas:.617274873,
_61spf:6.17274873*6.17274873,
_61spelasth:.017274873,
_61spcaps:new Float32Array(29),
_61spcapsv:new Float32Array(29),
_61spft:new Date(),
init:function()
{
window.requestAnimationFrame=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.msRequestAnimationFrame;
this.cctx=NSUI.canvas.getContext('2d');
this.spectrw=NSUI.canvas.width/this.nbins;
this.spectrm=this.spectrw/10;this.spectrw*=0.9;
for(let i=0;i<this.nbins;++i)this.caps[i]=0;
},
update_spectrum:function()
{
const cctx=this.cctx,canvas=NSUI.canvas;
cctx.clearRect(0,0,canvas.width,canvas.height);
try{
let freqdomv=new Uint8Array(NSAudio.anlznode.frequencyBinCount);
NSAudio.anlznode.getByteFrequencyData(freqdomv);
for(let i=0;i<this.nbins;++i)
{
cctx.fillStyle='hsl('+i*360.0/this.nbins+',100%,50%)';
cctx.fillRect(i*(this.spectrw+this.spectrm),canvas.height-(canvas.height*freqdomv[i]/255.),this.spectrw,canvas.height*freqdomv[i]/255.);
if(this.caps[i]<freqdomv[i])this.caps[i]=freqdomv[i];else if(this.caps[i]>0)--this.caps[i];
cctx.fillStyle='hsl('+i*360.0/this.nbins+',100%,25%)';
cctx.fillRect(i*(this.spectrw+this.spectrm),canvas.height-(canvas.height*this.caps[i]/255.)-1,this.spectrw,1);
}
}catch(e){
for(let i=0;i<this.nbins;++i)
{
if(this.caps[i]>0)--this.caps[i];
cctx.fillStyle='hsl('+i*360.0/this.nbins+',100%,25%)';
cctx.fillRect(i*(this.spectrw+this.spectrm),canvas.height-this.caps[i]-1,this.spectrw,1);
}
}
},
update_61_spectrum:function()
{
const cctx=this.cctx,canvas=NSUI.canvas;
cctx.clearRect(0,0,canvas.width,canvas.height);
try{
let freqdomv=new Float32Array(NSAudio.anlznode.frequencyBinCount);
NSAudio.anlznode.getFloatFrequencyData(freqdomv);
let binl=Math.pow(2,(this._61spbinw-1)*this.nbinsp/(this._61spbins-1+this._61spbinw))-1;
let _61spfdomv=new Float32Array(this._61spbins);
const ft=new Date()-this._61spft;
this._61spft=new Date();
const rft=ft/1000*24;
for(let i=0;i<this._61spbins;++i)
{
let binr=Math.pow(2,(i+this._61spbinw)*this.nbinsp/(this._61spbins-1+this._61spbinw))-1;
if(Math.floor(binr)<=Math.floor(binl))binr=binl+1;
let binli=Math.floor(binl);
let binri=Math.floor(binr);
const clamp=(x)=>Math.max(0,Math.min(1,x));
_61spfdomv[i]=Math.pow(freqdomv.
slice(binli,binri+1).
reduce(
(s,c)=>s+
clamp(
(c-NSAudio.anlznode.minDecibels)
/(NSAudio.anlznode.maxDecibels-NSAudio.anlznode.minDecibels)
)/(binri-binli+1)
,0),2);//iS tHiS dBv^2?
binl=binr;
if(ft>1000)
{
this._61spcaps[i]=0;
this._61spcapsv[i]=0;
continue;
}
let vel=this._61spgvel*this._61spcapsv[i];
if(this._61spcaps[i]-vel*rft<=_61spfdomv[i])
{
let elf=0;
if(vel>this._61spgvel*this._61spvelcap)
{
vel=this._61spgvel*this._61spvelcap;//cap this so that Newton could rest in peace.
this._61spcapsv[i]=this._61spvelcap;
}
if(vel>rft*this._61spelasth)
elf=this._61spcapsv[i]*this._61spelas;
this._61spcapsv[i]=(this._61spcaps[i]-vel*rft-_61spfdomv[i])*this._61spf-elf;
this._61spcaps[i]=_61spfdomv[i];
}
else
{
this._61spcapsv[i]+=rft*(this._61spaccel-this._61spdamp*Math.sign(this._61spcapsv[i])*Math.pow(this._61spcapsv[i],2));
vel=this._61spgvel*this._61spcapsv[i];
this._61spcaps[i]-=Math.min(rft*vel,this._61spcaps[i]);
}
cctx.fillStyle=`rgba(70,130,180,${0.36+0.64*_61spfdomv[i]})`;
cctx.fillRect(i*(canvas.width/this._61spbins),canvas.height-(0.617274873*canvas.height*_61spfdomv[i]),canvas.width/this._61spbins*0.96,0.617274873*canvas.height*_61spfdomv[i]);
cctx.fillStyle='rgb(110,139,161)';
cctx.fillRect(i*(canvas.width/this._61spbins),canvas.height-(0.617274873*canvas.height*this._61spcaps[i])-4,canvas.width/this._61spbins*0.96,4);
}
}catch(e){}
},
update_scope:function()
{
const cctx=this.cctx,canvas=NSUI.canvas;
cctx.clearRect(0,0,canvas.width,canvas.height);
try{
let timedomv=new Uint8Array(NSAudio.anlznode.frequencyBinCount);
NSAudio.anlznode.getByteTimeDomainData(timedomv);
cctx.lineWidth=window.devicePixelRatio;
cctx.strokeStyle='#000';
if(isDarkTheme)cctx.strokeStyle='#FFF';
cctx.beginPath();
for(let i=0,x=0;i<this.nbins;++i)
{
if(i==0)cctx.moveTo(x,timedomv[i]/128.*canvas.height/2);
else cctx.lineTo(x,timedomv[i]/128.*canvas.height/2);
x+=canvas.width*1./this.nbins;
}
cctx.stroke();
}catch(e){
cctx.beginPath();
cctx.moveTo(0,canvas.height/2);
cctx.lineTo(canvas.width,canvas.height/2);
cctx.stroke();
}
},
update_spectrogram:function()
{
const cctx=this.cctx,canvas=NSUI.canvas;
try{
let freqdomv=new Uint8Array(NSAudio.anlznode.frequencyBinCount);
NSAudio.anlznode.getByteFrequencyData(freqdomv);
cctx.clearRect(this.spectgrmp/this.spectgrmw*canvas.width,0,canvas.width/this.spectgrmw,canvas.height);
for(let i=0;i<this.nbins;++i)
{
let color=(isDarkTheme?'rgba(255,255,255,':'rgba(0,0,0,')+freqdomv[i]/256.+')';
cctx.fillStyle=color;
cctx.fillRect(this.spectgrmp/this.spectgrmw*canvas.width,(this.nbins-i)/this.nbins*canvas.height,canvas.width/this.spectgrmw,canvas.height/this.nbins);
}
if(!NSUI.audio.paused)++this.spectgrmp;
if(this.spectgrmp>=this.spectgrmw)this.spectgrmp=0;
}catch(e){
this.spectgrmp=0;
cctx.clearRect(0,0,canvas.width,canvas.height);
}
},
updateVisualization:function()
{
switch(NSUI.vissel.value)
{
case 'spectrum':
NSVisualization.update_spectrum();
break;
case 'spspectrum':
NSVisualization.update_61_spectrum();
break;
case 'scope':
NSVisualization.update_scope();
break;
case 'spectrogram':
NSVisualization.update_spectrogram();
break;
case 'inkfountain':
NSInk.update();
break;
}
if(NSUI.vissel.value!='none')
requestAnimationFrame(NSVisualization.updateVisualization);
}
};
NSInk={
ic1:['#FF3333','#FF8800','#FFFF00','#CCFF00','#33CCFF','#0000FF','#9966FF'],
ic2:['rgba(204,51,51,0.6)','rgba(187,85,0,0.6)','rgba(255,204,0,0.6)','rgba(153,204,0,0.6)','rgba(51,153,255,0.6)','rgba(0,0,102,0.6)','rgba(153,51,204,0.6)'],
inks:[],
inkimg:[],
ifcaps:[],
nbinsif:128,
inkPrepare:function()
{
for(let i=0;i<7;++i)
{
this.inkimg[i]=document.createElement('canvas');
this.inkimg[i].width=this.inkimg[i].height=10*window.devicePixelRatio;
const cctx=this.inkimg[i].getContext('2d');
cctx.fillStyle=this.ic1[i];
cctx.beginPath();
cctx.arc(5*window.devicePixelRatio,5*window.devicePixelRatio,3*window.devicePixelRatio,0,2*Math.PI);
cctx.fill();
cctx.fillStyle=this.ic2[i];
cctx.beginPath();
cctx.arc(5*window.devicePixelRatio,5*window.devicePixelRatio,5*window.devicePixelRatio,0,2*Math.PI);
cctx.fill();
}
for(let i=0;i<NSVisualization.nbins;++i)this.ifcaps[i]=0;
this.nbinsif=NSVisualization.nbins/8;
},
createInk:function(_vx,_vy,_c1,_c2)
{
let f=false;
for(let i=0;i<this.inks.length;++i)
{
if(!this.inks[i].active)
{this.inks[i]=new Ink(_vx,_vy,_c1,_c2);f=true;break;}
}
if(!f&&this.inks.length<512)this.inks.push(new Ink(_vx,_vy,_c1,_c2));
},
frms:0,lastfpsupd:null,fps:0,
update:function()
{
const canvas=NSUI.canvas;
const cctx=canvas.getContext('2d'),ts=Date.now();
cctx.clearRect(0,0,canvas.width,canvas.height);
++this.frms;if(Date.now()-this.lastfpsupd>500)
{
if(this.lastfpsupd)
this.fps=1000*this.frms/(Date.now()-this.lastfpsupd),this.frms=0;
this.lastfpsupd=Date.now();
}
try{
let freqarr=new Uint8Array(NSAudio.anlznode.frequencyBinCount);
NSAudio.anlznode.getByteFrequencyData(freqarr);
for(let i=0;i<this.nbinsif;++i)
{
let r=0;
for(let j=0;j<8;++j)r+=freqarr[i*8+j];
r/=8.;
if(r-this.ifcaps[i]>7)
{
let color=Math.floor(i*8.0/this.nbinsif),rad=Math.random()*Math.PI*2;
let ndrops=(128-i)/128.*3+1;ndrops*=(r/128.);ndrops=Math.floor(ndrops);if(ndrops<1)ndrops=1;
for(let k=0;k<ndrops;++k)
{
this.createInk(((r-this.ifcaps[i]-7)/32*24+12)*Math.cos(rad),((r-this.ifcaps[i]-7)/32*24+12)*Math.sin(rad),color);
rad=Math.random()*Math.PI*2;
}
}
if(r>this.ifcaps[i])this.ifcaps[i]=r;else this.ifcaps[i]-=2;
}
}catch(e){
cctx.clearRect(0,0,canvas.width,canvas.height);
}
const tu=Date.now();
let aa=0;
for(let i=0;i<this.inks.length;++i)
if(this.inks[i].active){this.inks[i].update();this.inks[i].draw(cctx);++aa;}
cctx.fillStyle='#000';
cctx.font="1em 'CMU Typewriter Text w', 'CMU Typewriter Text', 'TeX Gyre Cursor', 'FreeMono', 'Courier New', Courier, monospace";
cctx.fillText('Active droplets '+aa+', '+this.fps.toFixed(2)+' FPS',0,10);
cctx.fillText('Update time '+(tu-ts)+'ms, render time '+(Date.now()-tu)+'ms',0,24);
}
};
function init()
{
if(!window.devicePixelRatio)window.devicePixelRatio=1;
loadTheme();
NSUI.setup_ui();
NSUI.load_playlists()
.then(()=>{
sh.elem('overlaytext').innerHTML+="Done!<br>Click or tap anywhere to start."
sh.elem('overlay').onclick=function(){
let initerr=0;
try{
NSAudio.audioInit();NSAudio.connect();
}catch(e){initerr=1;console.log(e);if(NSAudio.audioctx!==null)NSAudio.audioctx.resume();}
if(window.location.hash.length)
{
let p=window.location.hash.substr(1).split('/');
for(let i=0;i<p.length;++i)p[i]=decodeURIComponent(p[i]);
NSUI.switch_playlist(p[0],true);
let id=0;
for(;id<NSPlayer.tracks.length&&NSPlayer.tracks[id].title!=p[1];++id);
if(id<NSPlayer.tracks.length)
{
const p=NSUI.switch_track(id);
if(p!=undefined)
p.then(_=>{sh.elem('overlay').style.display='none';}).catch(e=>{});
else
//this browser is from an era before this autoplay policy mess,
//assume it succeeded
sh.elem('overlay').style.display='none';
}
}
else if(!initerr)sh.elem('overlay').style.display='none';
}
sh.elem('overlay').onclick();
},
()=>{
sh.elem('overlaytext').innerHTML+="Failed...<br>Maybe try refreshing the page?"
});
NSVisualization.init();NSInk.inkPrepare();
NSUI.audio.ontimeupdate=NSUI.timeUpdate;
NSUI.audio.onended=NSPlayer.next.bind(NSPlayer);
NSUI.audio.onplay=NSUI.audio.ondurationchange=function(){
if(!NSUI.audio.audioTracks)return;
if(NSUI.audio.audioTracks.length==2)
sh.elem('mt').style.display='inline';
else
sh.elem('mt').style.display='none';
};
switch(sh.getcookie('playershuffle'))
{
case '1':NSPlayer.shuffle=1;break;
default:
case '0':NSPlayer.shuffle=0;break;
}
switch(sh.getcookie('playerrepeat'))
{
case '1':
NSPlayer.repeat=1;
NSUI.audio.loop=true;
break;
default:
case '0':
NSPlayer.repeat=0;
NSUI.audio.loop=false;
break;
}
NSUI.ishuffle.style.backgroundPosition=`${NSPlayer.shuffle?NSUI.bshonrect:NSUI.bsoffrect}`;
NSUI.irepeat.style.backgroundPosition=`${NSPlayer.repeat?NSUI.bronerect:NSUI.brallrect}`;
if(sh.getcookie('playervisualization').length!==0)
NSUI.vissel.value=sh.getcookie('playervisualization');
requestAnimationFrame(NSVisualization.updateVisualization);
}