//Chris Xiong 2015-2025 //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,_d) { this.x=NSUI.canvas.width/2;this.y=NSUI.canvas.height/2; this.vx=_vx;this.vy=_vy; this.d = _d ? 1 - _d : 0.995; this.color=_c>6?6:_c;this.active=true; } update() { const canvas=NSUI.canvas; this.x += this.vx; this.y += this.vy; if (this.vx > 5 && this.vy > 5) { this.vx *= this.d; this.vy *= this.d; } if(this.x<-30||this.x>canvas.width+30||this.y<-30||this.y>canvas.height+30 || Number.isNaN(this.x) || Number.isNaN(this.y)) this.active=false; } draw(cctx) { cctx.drawImage(NSInk.inkimg[this.color],this.x+5*window.devicePixelRatio,this.y+5*window.devicePixelRatio); } } class TrendTracker { constructor(_window_size, _default_value) { this.window_size = _window_size; this.data = new Array(_window_size).fill(_default_value); this.meanx = (this.window_size - 1) / 2; } push(v) { this.data.shift(); this.data.push(v); this.update_slope(); } update_slope() { const meanx = this.meanx; const meany = this.data.reduce((s, v) => s + v / this.window_size, 0); this.slope = this.data.reduce((s, v, i) => s + (i - meanx) * (v - meany), 0) / this.data.reduce((s, _, i) => s + (i - meanx) * (i - meanx), 0); this.intercept = meany - this.slope * meanx; this.meanv = meany; } } 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, _61spbinw:5, _61spaccel:.3617274873, _61spdamp:0.1617174873, _61spvelcap:2*6.17274873, _61spgvel:.06617274873, _61spelas:.2617274873, _61spf:42.617274873, _61spelasth:.1617274873, _61spcaps:new Float32Array(29), _61spcapsv:new Float32Array(29), _61spft:performance.now(), frms:0, lastfpsupd:null, fps:0, 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); } } }, distr_log_bins:function(data, nbins, shift) { let ret = new Float32Array(nbins); let binfreq = 0; const cctx = NSVisualization.cctx; //cctx.fillStyle=window.getComputedStyle(document.body).getPropertyValue("--principal-fg"); const cw = NSUI.canvas.width; for (let i = 0; i < nbins; ++i) { const lbin = Math.floor(binfreq); binfreq = Math.pow(2, (i + shift) * NSVisualization.nbinsp / (nbins - 1 + shift)) - 1; const rbin = Math.max(Math.floor(binfreq), lbin + 1); //cctx.fillText(lbin, i / nbins * cw + 8, 60); //cctx.fillText(rbin, i / nbins * cw + 8, 90); const clamp0 = (x) => Math.max(0, x); ret[i] = Math.pow(data. slice(lbin, rbin + 1). reduce( (s, c) => s + clamp0((c - NSAudio.anlznode.minDecibels) / (NSAudio.anlznode.maxDecibels - NSAudio.anlznode.minDecibels)) / (rbin - lbin + 1) , 0), 2);//iS tHiS dBv^2? //cctx.fillText(ret[i].toFixed(2), i / nbins * cw + 8, 120); } return ret; }, 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 _61spfdomv = NSVisualization.distr_log_bins(freqdomv, this._61spbins, this._61spbinw); const ft=performance.now() - this._61spft; this._61spft=performance.now(); const rft=ft/1000*24; for(let i=0;i1000) { this._61spcaps[i]=0; this._61spcapsv[i]=0; continue; } let vel=this._61spgvel*this._61spcapsv[i]; //cctx.fillStyle = this._61spcapsv[i] > 0 ? "#f00" : "#0f0"; //cctx.fillText(Math.abs(this._61spcapsv[i]).toFixed(1), i / this._61spbins * canvas.width + 8, 120); if (Number.isNaN(this._61spcaps[i]) || !Number.isFinite(this._61spcaps[i])) { this._61spcaps[i] = _61spfdomv[i]; this._61spcapsv[i] = 0; } if (this._61spcaps[i] - (vel < 0.5 ? vel * rft : 0) <= _61spfdomv[i]) { if (!(this._61spcaps[i] < 1e-2 && this._61spcapsv[i] < 1e-2 && _61spfdomv[i] < 1e-2)) { 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._61spcaps[i] = _61spfdomv[i]; this._61spcapsv[i] = 0; } } 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]); this._61spcaps[i] = Math.max(this._61spcaps[i], _61spfdomv[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() { const self=NSVisualization; const cctx=self.cctx; ++self.frms; if(Date.now()-self.lastfpsupd>500) { if(self.lastfpsupd) self.fps=1000*self.frms/(Date.now()-self.lastfpsupd),self.frms=0; self.lastfpsupd=Date.now(); } const ts=Date.now(); 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; default: return; } cctx.fillStyle=window.getComputedStyle(document.body).getPropertyValue("--principal-fg"); cctx.font="2em 'CMU Typewriter Text w', 'CMU Typewriter Text', 'TeX Gyre Cursor', 'FreeMono', 'Courier New', Courier, monospace"; const fpst=`FPS: ${self.fps.toFixed(1)}, update time ${Date.now()-ts} ms`; cctx.fillText(fpst,0,24); 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, debug_render:false, erms_tracker:new TrendTracker(8, 0), freq_tracker:[], 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(); this.freq_tracker[i] = new TrendTracker(8, 0); } for(let i=0;i s += a * a / NSAudio.anlznode.frequencyBinCount, 0)); const erms = Math.pow(Math.pow(10, rms / 10), .5); this.erms_tracker.push(erms); const evals = NSVisualization.distr_log_bins(freqarr, 8, 2); evals[6] += evals[7]; const clamped_lerp = function(v, imin, imax, omin, omax) { if (v <= imin) return omin; if (v >= imax) return omax; const f = (v - imin) / (imax - imin); return f * (omax - omin) + omin; }; let base_emission_modifier = 1; if (this.erms_tracker.slope < -0.015) base_emission_modifier = clamped_lerp(this.erms_tracker.slope, -0.03, -0.015, 0.3, 1); else if (this.erms_tracker.slope > 0.01) base_emission_modifier = clamped_lerp(this.erms_tracker.slope, 0.01, 0.1, 1, 5); const base_emission_factor = Math.max(1, Math.pow(erms * 8, 2)); const base_velocity_factor = clamped_lerp(erms, 0.05, 0.4, 12, 80); const base_veldamp_factor = clamped_lerp(erms, 0.1, 0.4, 0.005, 0.001); const freq_emission_factors = []; const freq_emission_modifiers = []; const freq_velocity_shift = []; const freq_velocity_variances = []; const actual_emission = []; for(let i = 0; i < 7; ++i) { this.freq_tracker[i].push(evals[i]); let r = evals[i] * 255; freq_emission_modifiers[i] = 1; if (this.freq_tracker[i].slope < -0.01) freq_emission_modifiers[i] = Math.pow(clamped_lerp(this.freq_tracker[i].slope, -0.03, -0.01, 0, 1), 2); else if (this.freq_tracker[i].slope > 0.005) freq_emission_modifiers[i] = clamped_lerp(this.freq_tracker[i].slope, 0.005, 0.05, 1, 5); freq_emission_factors[i] = Math.pow(evals[i] * (i > 1 ? (i > 4 ? 20 : 10) : 7), 2) / 10; freq_velocity_shift[i] = Math.pow(clamped_lerp(this.freq_tracker[i].slope, 0.005, 0.03, 4, 8), 1.5); freq_velocity_variances[i] = Math.min(base_velocity_factor * 0.5, Math.pow(evals[i] * 10, 0.5)); actual_emission[i] = 0; if (r - this.ifcaps[i] > 5) { let color=i; actual_emission[i] = base_emission_factor * base_emission_modifier * freq_emission_factors[i] * freq_emission_modifiers[i]; actual_emission[i] = Math.min(Math.max(Math.floor(actual_emission[i]), 1), 24); for(let k = 0; k < actual_emission[i]; ++k) { const rad = Math.random() * Math.PI * 2; const vel = (base_velocity_factor + freq_velocity_shift[i]) * 0.25 + (Math.random() * 2 - 1) * freq_velocity_variances[i]; this.createInk(vel * Math.cos(rad), vel * Math.sin(rad), color, base_veldamp_factor); } } if (r > this.ifcaps[i]) this.ifcaps[i] = r; else this.ifcaps[i] -= 3; } if (this.debug_render) { const debug_render_bars = function(data, n, max, bipolar, style) { const cw = NSUI.canvas.width; const ch = NSUI.canvas.height; for (let i = 0; i < n; ++i) { cctx.fillStyle = Array.isArray(style) ? style[i] : style; const h = Math.min(Math.max(-data[i] / max, -1), 1); cctx.fillRect(i * (cw / n), bipolar ? ch / 2 : ch, cw / n * 0.96, h * ch * 0.6); } } debug_render_bars(evals, 7, 1, false, NSInk.ic1); const fsl = []; for (let i = 0; i < 7; ++i) fsl.push(this.freq_tracker[i].slope); debug_render_bars(fsl, 7, 0.1, true, NSInk.ic2); cctx.fillStyle = "rgba(70,130,180)"; cctx.fillRect(0, 0, erms * NSUI.canvas.width, 10); cctx.fillRect(NSUI.canvas.width / 2, 10, 10 * this.erms_tracker.slope * NSUI.canvas.width / 2, 10); cctx.fillStyle=window.getComputedStyle(document.body).getPropertyValue("--principal-fg"); cctx.font="2em 'CMU Typewriter Text w', 'CMU Typewriter Text', 'TeX Gyre Cursor', 'FreeMono', 'Courier New', Courier, monospace"; cctx.fillText(`${erms.toFixed(4).padStart(7)} ${this.erms_tracker.slope.toFixed(6).padStart(9)}`,0,90); cctx.fillText(`${base_emission_factor.toFixed(3).padStart(6)} ${base_emission_modifier.toFixed(3).padStart(6)}`,0,120); for (let i = 0; i < 7; ++i) { cctx.fillStyle = NSInk.ic1[i]; cctx.fillText(evals[i].toFixed(2), i / 7 * NSUI.canvas.width + 8, 180); cctx.fillText(this.freq_tracker[i].slope.toFixed(4).padStart(7), i / 7 * NSUI.canvas.width + 8, 210); } } }catch(e){ cctx.clearRect(0,0,canvas.width,canvas.height); console.error(e); } 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);