//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:{'vorbis':{'mime':'audio/ogg; codecs=vorbis','disp':'ogg 224 kbps','ext':'ogg'},'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{ const ra=shuffle?a.sord:a.ord; const rb=shuffle?b.sord:b.ord; return ra10)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') return NSAudio.switchAudioTrack(); 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{ for(let i=0;i{ tarr[rp.plistord]=rp; if(--cnt<=0)a(null); },()=>{b(null);}); } }); await p; for(let i=0;i=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{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"; }, switchAudioTrack:function() { const a = NSUI.audio; if (a.audioTracks.length != 2) return false; const t = a.currentTime; const e = a.audioTracks[0].enabled; [a.audioTracks[0].enabled, a.audioTracks[1].enabled] = [!e, e]; a.currentTime = t; return true; } }; 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;i0)--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;i0)--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;iMath.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.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;i500) { 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;i7) { 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;kthis.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{ sh.elem('overlaytext').innerHTML+="Done!
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{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...
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); } document.addEventListener("DOMContentLoaded", init);