1 line
32 KiB
JavaScript
1 line
32 KiB
JavaScript
class VTUtils{static random(t,e){let a=Math.random();if(void 0===t)return a;if(void 0===e)return t instanceof Array?t[Math.floor(a*t.length)]:a*t;if(t>e){let a=t;t=e,e=a}return a*(e-t)+t}static randomInt(t,e){return Math.floor(VTUtils.random(t,e))}static normalize(t,e,a){return(t-a)/(e-a)}static distance(t,e,a,i){let s=t-a,n=e-i;return Math.sqrt(s*s+n*n)}static map(t,e,a,i,s,n){let l=(t-e)/(a-e)*(s-i)+i;return n?i<s?this.constrain(l,i,s):this.constrain(l,s,i):l}static constrain(t,e,a){return Math.max(Math.min(t,a),e)}static hsvToRgb(t,e,a){let i,s,n,l=Math.floor(6*t),r=6*t-l,o=a*(1-e),d=a*(1-r*e),c=a*(1-(1-r)*e);switch(l%6){case 0:i=a,s=c,n=o;break;case 1:i=d,s=a,n=o;break;case 2:i=o,s=a,n=c;break;case 3:i=o,s=d,n=a;break;case 4:i=c,s=o,n=a;break;case 5:i=a,s=o,n=d}return{r:i,g:s,b:n}}static peakRGB(t){return{r:t,g:1-t,b:0}}}class VTVector{constructor(t,e,a){this.x=t||0,this.y=e||0,this.z=a||0}static createRandom(t,e,a){return t=t||1,e=e||1,a=a||0,new VTVector(VTUtils.random(-t,t),VTUtils.random(-e,e),VTUtils.random(-a,a))}mult(t){this.x*=t,this.y*=t,this.z*=t}set(t){this.x=t.x,this.y=t.y,this.z=t.z}add(t){this.x=this.x+t.x,this.y=this.y+t.y,this.z=this.z+t.z}addXYZ(t,e,a){this.x+=t,this.y+=e,this.z+=a}setXYZ(t,e,a){this.x=t||0,this.y=e||0,this.z=a||0}clone(){return new VTVector(this.x,this.y,this.z)}}function $(t,e){return(e=e||document).querySelector(t)}function $$(t,e){return(e=e||document).querySelectorAll(t)}function b64toBlob(t,e){const a=atob(t),i=new Array(a.length);for(let t=0;t<a.length;t++)i[t]=a.charCodeAt(t);const s=new Uint8Array(i);return new Blob([s],{type:e})}function create(t,e){let a=document.createElement(t);return e&&(a.innerHTML=e),a}function append(t,e){for(let a of e)t.appendChild(a)}function hexToRgb(t){t=t.replace("#","");let e=parseInt(t,16);return[(e>>16&255)/255,(e>>8&255)/255,(255&e)/255]}Node.prototype.addDelegatedEventListener=function(t,e,a){this.addEventListener(t,t=>{let i=t.target;if(i.matches(e))a(t,i);else{let s=i.closest(e);if(s)try{a(t,s)}catch(t){NotificationHandler.createNotification("FATAL ERROR WITHIN HANDLER!","error",1e3)}}})},Node.prototype.hasClass=function(t){let e=t.split(","),a=null;for(let t of e){if(!1===a)break;a=this.classList.contains(t.trim())}return!0===a},Node.prototype.addClass=function(t){let e=t.split(",");for(let t of e)this.classList.add(t.trim());return this},Node.prototype.removeClass=function(t){let e=t.split(",");for(let t of e)this.classList.remove(t.trim());return this},Node.prototype.toggleClass=function(t,e){let a=t.split(",");for(let t of a)this.classList.toggle(t.trim(),e)},Node.prototype.switchClass=function(t,e,a){let i=this.classList;a?(i.remove(t),i.add(e)):(i.remove(e),i.add(t))},Node.prototype.toggleCheck=function(t,e){let a=this.classList,i=t.split(",");for(let t of i){let i=t.trim();e?a.add(i):a.remove(i)}},File.prototype.toBase64=function(t){const e=new FileReader;e.onloadend=t,e.readAsDataURL(this)};class TDUtils{static lastMatrix={m:null};static multiply(t,e){let a=e[0],i=e[1],s=e[2],n=e[3],l=e[4],r=e[5],o=e[6],d=e[7],c=e[8],h=e[9],u=e[10],p=e[11],g=e[12],f=e[13],m=e[14],v=e[15],y=t[0],C=t[1],w=t[2],T=t[3],b=t[4],S=t[5],x=t[6],D=t[7],U=t[8],H=t[9],A=t[10],L=t[11],R=t[12],E=t[13],F=t[14],I=t[15];return[a*y+i*b+s*U+n*R,a*C+i*S+s*H+n*E,a*w+i*x+s*A+n*F,a*T+i*D+s*L+n*I,l*y+r*b+o*U+d*R,l*C+r*S+o*H+d*E,l*w+r*x+o*A+d*F,l*T+r*D+o*L+d*I,c*y+h*b+u*U+p*R,c*C+h*S+u*H+p*E,c*w+h*x+u*A+p*F,c*T+h*D+u*L+p*I,g*y+f*b+m*U+v*R,g*C+f*S+m*H+v*E,g*w+f*x+m*A+v*F,g*T+f*D+m*L+v*I]}static translate(t,e,a,i,s){s=s||new Float32Array(16);let n=t[0],l=t[1],r=t[2],o=t[3],d=t[4],c=t[5],h=t[6],u=t[7],p=t[8],g=t[9],f=t[10],m=t[11],v=t[12],y=t[13],C=t[14],w=t[15];return s[0]=n,s[1]=l,s[2]=r,s[3]=o,s[4]=d,s[5]=c,s[6]=h,s[7]=u,s[8]=p,s[9]=g,s[10]=f,s[11]=m,s[12]=n*e+d*a+p*i+v,s[13]=l*e+c*a+g*i+y,s[14]=r*e+h*a+f*i+C,s[15]=o*e+u*a+m*i+w,s}static xRotation(t){t=TDUtils.degToRad(t);let e=Math.cos(t),a=Math.sin(t);return[1,0,0,0,0,e,a,0,0,-a,e,0,0,0,0,1]}static yRotation(t){t=TDUtils.degToRad(t);let e=Math.cos(t),a=Math.sin(t);return[e,0,-a,0,0,1,0,0,a,0,e,0,0,0,0,1]}static zRotation(t){t=TDUtils.degToRad(t);let e=Math.cos(t),a=Math.sin(t);return[e,a,0,0,-a,e,0,0,0,0,1,0,0,0,0,1]}static degToRad(t){return t*Math.PI/180}static scale(t,e,a,i){return(i=i||new Float32Array(16))[0]=t,i[5]=e,i[10]=a,i}static lookAt(t,e,a,i){i=i||new Float32Array(16);let s=TDUtils.normalize(TDUtils.subtractVectors(t,e)),n=TDUtils.normalize(TDUtils.cross(a,s)),l=TDUtils.normalize(TDUtils.cross(s,n));return i[0]=n[0],i[1]=n[1],i[2]=n[2],i[4]=l[0],i[5]=l[1],i[6]=l[2],i[8]=s[0],i[9]=s[1],i[10]=s[2],i[12]=t[0],i[13]=t[1],i[14]=t[2],i[15]=1,i}static cross(t,e,a){return(a=a||new Float32Array(3))[0]=t[1]*e[2]-t[2]*e[1],a[1]=t[2]*e[0]-t[0]*e[2],a[2]=t[0]*e[1]-t[1]*e[0],a}static normalize(t,e){e=e||new Float32Array(3);let a=Math.sqrt(t[0]*t[0]+t[1]*t[1]+t[2]*t[2]);return a>1e-5&&(e[0]=t[0]/a,e[1]=t[1]/a,e[2]=t[2]/a),e}static subtractVectors(t,e,a){return(a=a||new Float32Array(3))[0]=t[0]-e[0],a[1]=t[1]-e[1],a[2]=t[2]-e[2],a}static perspective(t,e,a,i,s){s=s||new Float32Array(16);let n=Math.tan(.5*Math.PI-.5*t),l=1/(a-i);return s[0]=n/e,s[5]=n,s[10]=(a+i)*l,s[11]=-1,s[14]=a*i*l*2,s}static inverse(t,e){e=e||new Float32Array(16);let a=t[0],i=t[1],s=t[2],n=t[3],l=t[4],r=t[5],o=t[6],d=t[7],c=t[8],h=t[9],u=t[10],p=t[11],g=t[12],f=t[13],m=t[14],v=t[15],y=u*v,C=m*p,w=o*v,T=m*d,b=o*p,S=u*d,x=s*v,D=m*n,U=s*p,H=u*n,A=s*d,L=o*n,R=c*f,E=g*h,F=l*f,I=g*r,M=l*h,N=c*r,k=a*f,P=g*i,V=a*h,$=c*i,B=a*r,O=l*i,z=y*r+T*h+b*f-(C*r+w*h+S*f),_=C*i+x*h+H*f-(y*i+D*h+U*f),G=w*i+D*r+A*f-(T*i+x*r+L*f),j=S*i+U*r+L*h-(b*i+H*r+A*h),W=1/(a*z+l*_+c*G+g*j);return e[0]=W*z,e[1]=W*_,e[2]=W*G,e[3]=W*j,e[4]=W*(C*l+w*c+S*g-(y*l+T*c+b*g)),e[5]=W*(y*a+D*c+U*g-(C*a+x*c+H*g)),e[6]=W*(T*a+x*l+L*g-(w*a+D*l+A*g)),e[7]=W*(b*a+H*l+A*c-(S*a+U*l+L*c)),e[8]=W*(R*d+I*p+M*v-(E*d+F*p+N*v)),e[9]=W*(E*n+k*p+$*v-(R*n+P*p+V*v)),e[10]=W*(F*n+P*d+B*v-(I*n+k*d+O*v)),e[11]=W*(N*n+V*d+O*p-(M*n+$*d+B*p)),e[12]=W*(F*u+N*m+E*o-(M*m+R*o+I*u)),e[13]=W*(V*m+R*s+P*u-(k*u+$*m+E*s)),e[14]=W*(k*o+O*m+I*s-(B*m+F*s+P*o)),e[15]=W*(B*u+M*s+$*o-(V*o+O*u+N*s)),e}static aspectView(t){return[1*t,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]}static getMatrix(t,e,a,i,s,n){let l=this.lastMatrix,r=TDUtils;if(!(r.isSame("fov",t)&&r.isSame("aspect",e)&&r.isSame("near",a)&&r.isSame("far",i)&&r.isSame("cam",s)&&r.isSame("radius",n))){let r=TDUtils.perspective(TDUtils.degToRad(t),e,a,i),o=TDUtils.yRotation(TDUtils.degToRad(s));o=TDUtils.translate(o,0,0,1.5*n);let d=TDUtils.inverse(o);r=TDUtils.multiply(r,d),l.m=r}return l.m}static isSame(t,e){let a=this.lastMatrix;return a[t]===e||(a[t]=e,!1)}static updateRotate(t,e){let a=vConf.get(t,e)+vConf.get(t+"-inc",0);a>360?a-=360:a<-360&&(a+=360),vConf.set(t,a)}}class Template{constructor(){this.tpl={}}async loadTemplate(t){let e=this;this.tpl[t]||await fetch(templateDir+t+".tpl").then(t=>t.text()).then(a=>{e.tpl[t]=a})}async loadArray(t){for(let e of t)await this.loadTemplate(e)}parseTemplate(t,e){if(!this.tpl[t])return"";let a,i=this.tpl[t];for(;null!==(a=templateEx.exec(i));){a.index===templateEx.lastIndex&&templateEx.lastIndex++;let t=a[0],s=e[a[1]];null==s&&(s=""),i=i.replace(t,s)}return i}parseFromAPI(t,e,a){fetch(t).then(t=>t.json()).then(t=>{a(this.parseTemplate(e,t))}).catch(console.error)}}const templateEx=/\$(.*?)\$/gm,templateDir="out/tpl/";class ShaderHandler{constructor(t){this.gl=t,this.shaderNames=[],this.shaders={},this.programs={}}setGL(t){this.gl=t}async loadShader(t,e){this.shaderNames.push(t),await this.load(t,e+t+".vert",this.gl.VERTEX_SHADER),await this.load(t,e+t+".frag",this.gl.FRAGMENT_SHADER)}async load(t,e,a){let i=t+"_"+a;if(!this.shaders[i]){let t=await fetch(e),s=this.createShader(await t.text(),a);s&&(this.shaders[i]=s)}return!!this.shaders[i]}getShader(t,e){let a=t+"_"+e;return this.shaders[a]}getAllShaders(){return this.shaderNames}async createProgramForEach(t){t=t||this.shaderNames;for(let e=0;e<t.length;e++){let a=t[e];if(!await shaderHandler.createProgram(a,[a]))return!1}return!0}createShader(t,e){let a=this.gl,i=a.createShader(e);return a.shaderSource(i,t),a.compileShader(i),a.getShaderParameter(i,a.COMPILE_STATUS)?i:(console.error(a.getShaderInfoLog(i)),a.deleteShader(i),null)}createProgram(t,e){let a=this.gl,i=a.createProgram();for(let t=0;t<e.length;t++)a.attachShader(i,this.getShader(e[t],a.VERTEX_SHADER)),a.attachShader(i,this.getShader(e[t],a.FRAGMENT_SHADER));return a.linkProgram(i),a.getProgramParameter(i,a.LINK_STATUS)?(this.programs[t]=i,i):(console.log(a.getProgramInfoLog(i)),a.deleteProgram(i),null)}getProgram(t){return this.programs[t]}use(t){let e=this.programs[t];return this.gl.useProgram(e),e}async loadArray(t,e){let a=this;for(const i of t)await a.loadShader(i,e);await a.createProgramForEach(t)}}const AudioContext=window.AudioContext||window.webkitAudioContext;class AudioHandler{async init(){this.isStarted=!1,this.audioFile=new Audio,this.actx=new AudioContext,this.analyser=this.actx.createAnalyser(),this.analyser.fftSize=4096,this.lastSong=null,await this.connectAll()}async connectAll(){this.source=this.actx.createMediaElementSource(this.audioFile),this.source.connect(this.analyser),this.analyser.connect(this.actx.destination),this.audioFile.addEventListener("ended",player.nextSong.bind(player))}async start(){""!==this.audioFile.src&&(this.isStarted||(this.isStarted=!0,await this.actx.resume()))}async stop(){this.isStarted&&(this.isStarted=!1,await this.actx.suspend())}fftSize(t){this.analyser.fftSize=t}smoothing(t){this.analyser.smoothingTimeConstant=t}loadSong(t){if(!t)return NotificationHandler.createNotification("Sorry!<br> Currently no Song is uploaded!","error",2e3),!1;let e=this,a=t.file;e.lastSong&&URL.revokeObjectURL(e.lastSong),e.lastSong=this.audioFile.src=URL.createObjectURL(a),this.isStarted||this.start().catch(alert),this.audioFile.play().then(e=>{pConf.get("showPlaying","true")&&NotificationHandler.createNotification("<span class='now-playing'>Now Playing:</span>"+t.getAudioName(),"info",pConf.get("showPlayingTime",1e3)),window.dispatchEvent(new CustomEvent("playSong"))}).catch(t=>{NotificationHandler.createNotification(t.message,"error",1e3),player.nextSong()})}getIntArray(t){let e=new Uint8Array(t);return this.analyser.getByteFrequencyData(e),e}getFloatArray(){let t=new Float32Array(this.analyser.fftSize);return this.analyser.getFloatTimeDomainData(t),t}}class AudioPlayerFile{constructor(t,e){this.file=t,this.name=this.getName(),this.id3=null,this.index=e}getName(){let t=this.file.name.split(".");return t.pop(),t=t.join("."),t}getID3Tag(t){return t||null===this.id3?(eventHandler.sendData("getData",{file:this.file,name:this.name,index:this.index,force:!0===t}),{title:this.name,artist:"VA"}):this.id3}getAudioName(){let t=this.getID3Tag();return template.parseTemplate("audio-information",t)}}class PlayerConfigHandler{async init(){await template.loadArray(["config/nav","config/content","config/visualitem"]),this.last="base",$(".settings-icon").addEventListener("click",this.open.bind(this)),$("modal-content").addDelegatedEventListener("click",".config-nav .item",this.navHandler.bind(this))}open(){if(void 0===this.content){let t=template.parseTemplate("config/nav",{});t+=template.parseTemplate("config/content",{content:""}),this.content=t}gui.modal.renderModal("Settings",this.content,"by VersusTuneZ"),this.handleById(),gui.modal.showModal()}navHandler(t,e){this.last=e.dataset.id,this.handleById()}handleById(){let t=this.last;new VisualConfig("visual"===t,"base"===t);let e=$(".config-nav .item.active"),a=$('.config-nav .item[data-id="'+t+'"]');e&&e.removeClass("active"),a&&a.addClass("active")}}class VisualConfig{static visualTemplates={};constructor(t,e){this.content=$("modal-content .config-content"),t?this.renderVisualConfig(visual.c):e?this.renderBase():this.renderVisuals()}renderVisuals(){let t=Object.keys(visual.visuals),e='<section class="visuals">';for(let a=0;a<t.length;a++)e+=template.parseTemplate("config/visualitem",{title:visual.visuals[t[a]].name,id:t[a],active:t[a]===visual.c?"active":""});e+="</div>",this.content.innerHTML=e}async renderBase(){let t=await this.loadVisualConfig("base"),e=create("section");e.addClass("base"),e.innerHTML=GUIHelper.fromJSON(t,pConf),this.content.innerHTML=e.outerHTML}async renderVisualConfig(t){let e=await this.loadVisualConfig(t,vConf),a=create("section");a.addClass("visual"),a.innerHTML=GUIHelper.fromJSON(e,vConf),a.innerHTML+=GUIHelper.createButton({action:"resetVConf",name:"Reset Visual Config"}),a.innerHTML+=GUIHelper.createButton({action:"makeModalTransparent",name:"toggle Modal Opacity"}),this.content.innerHTML=a.outerHTML}async loadVisualConfig(t){let e=VisualConfig.visualTemplates;return e[t]||(e[t]=await fetch("/out/gui/"+t+".json").then(t=>t.json())),e[t]}}class Player{async init(){this.playlist=new Playlist}nextSong(){let t=this.playlist.getNext();audioHandler.loadSong(t)}prevSong(){let t=this.playlist.getPrevious();audioHandler.loadSong(t)}playStop(){if(!audioHandler.lastSong){let t=this.playlist.getCurrent();return void audioHandler.loadSong(t)}let t=audioHandler.audioFile;t.paused?t.play():t.pause(),window.dispatchEvent(new CustomEvent("playSong"))}playByID(t){this.playlist.index=t;let e=this.playlist.getCurrent();audioHandler.loadSong(e)}}const PAGINATIONLIMIT=50;class Playlist{constructor(){this.list=[],this.shuffled=[],this.index=0,this.page=0,this.isShuffle=!1,$("body").addDelegatedEventListener("change",'input[type="file"]',this.changeFiles.bind(this)),$("body").addDelegatedEventListener("click",".pagination .item",this.handlePagination.bind(this)),eventHandler.addEvent("id3-request",this.handle.bind(this)),eventHandler.addEvent("id3-request-force",this.forceID3.bind(this))}shuffle(){let t=this.list.length;if(t<3)this.shuffled=[0,1,2];else for(let e=0;e<t;e++){let a=VTUtils.randomInt(0,t-1);this.swap(e,a)}}swap(t,e){this.shuffled[t]=e,this.shuffled[e]=t}getNext(){let t=this.list,e=t.length-1,a=this.index+1;return a>e&&(a=0),this.index=a,t[this.getRealIndex()]}getPrevious(){let t=this.list,e=t.length-1,a=this.index-1;return a<0&&(a=e),this.index=a,t[this.getRealIndex()]}getCurrent(){return this.list[this.getRealIndex()]}setPlaylist(t){this.index=0,this.forceData=void 0,this.list=t,this.shuffle()}handlePagination(t,e){e.hasClass("inactive")||(e.hasClass("next-site")?this.renderPagination(this.page+1):this.renderPagination(this.page-1))}renderPagination(t){void 0===t&&(t=this.page);let e=this.list.length,a=Math.ceil(e/50)-1;t<0&&(t=0),t>a&&(t=a);let i=50*t,s=i+50,n="";if(this.page=t,s>=e&&(s=e),e>0){let t=this.list;for(let e=i;e<s;e++){let a={index:e.toString(),nr:e+1,title:t[this.getRealIndex(e)].getAudioName(),active:audioHandler.audioFile.paused||e!==this.index?"":"active"};n+=template.parseTemplate("playlist-item",a)}}else n="<h1>No Songs uploaded!</h1>";let l=a>1&&t<a;gui.modal.renderModal("Playlist",template.parseTemplate("playlist",{content:n}),template.parseTemplate("playlist-footer",{prevActive:t>0?"active":"inactive",nextActive:l?"active":"inactive",page:t+1+" / "+parseInt(a+1)}))}changeFiles(t,e){if("upload-dir"!==e.id)return;let a=[],i=0;for(let t of e.files)if(t&&-1!==t.type.indexOf("audio")&&null===t.name.match(".m3u")){let e=new AudioPlayerFile(t,i++);a.push(e)}this.setPlaylist(a),a.length>0?(NotificationHandler.createNotification("Songs added successfully!<br> Songs: "+a.length,"success",3e3),this.renderPagination(0)):NotificationHandler.createNotification("File Upload failed!","error",3e3)}getRealIndex(t){return void 0===t&&(t=this.index),this.isShuffle?this.shuffled[t]:t}handle(t){let e=t.index;"waiting"!==t.status&&(this.list[e].id3=t,this.timeout&&window.clearTimeout(this.timeout),this.timeout=setTimeout(this.renderPagination.bind(this),100))}forceID3(t){let e=this;e.forceData||(e.forceData={},e.forceNotification=NotificationHandler.createNotification("TagReader -> 0 / "+e.list.length,"info",-1));let a=t.index;if("waiting"===t.status)return;e.list[a].id3=t,e.forceData[a]=!0;let i=Object.keys(e.forceData).length;this.forceNotification.updateMessageOnly("TagReader -> "+i+" / "+e.list.length),i===e.list.length&&e.forceNotification.remove()}}class GUI{async init(){this.data={},this.modal=new Modal,await template.loadArray(["playlist-item","playlist","playlist-footer","audio-information","inputs/color","inputs/input","inputs/slider","inputs/switch","inputs/select","inputs/option"]),this.initDropZone()}initDropZone(){"drag dragstart dragend dragover dragenter dragleave drop".split(" ").forEach(t=>{window.addEventListener(t,async t=>{t.preventDefault(),t.stopPropagation(),"drop"===t.type&&(t.dataTransfer.files.length>0?(t.dataTransfer.id="upload-dir",player.playlist.changeFiles(t,t.dataTransfer)):alert("Sorry you need to upload files!"))})})}}class GUIHelper{static fromJSON(t,e){let a=[];for(let i of t)switch(i.type){case"slider":a.push(GUIHelper.createSliders(i,e));break;case"color":a.push(GUIHelper.createColorPicker(i,e));break;case"checkbox":a.push(GUIHelper.createCheckbox(i,e));break;case"input":a.push(GUIHelper.createInputField(i,e));break;case"select":a.push(GUIHelper.createSelect(i,e));break;case"button":a.push(GUIHelper.createButton(i,e));break;default:console.error("Unknown Type: "+i.type)}return a.join(" ")}static createSliders(t,e){let a="";if("object"==typeof t.name)for(let i=0;i<t.name.length;i++){let s={};Object.assign(s,t),s.showName=t.name[i],s.name=GUIHelper.richName(t,t.props[i]),a+=GUIHelper.createSlider(s,e)}else a=GUIHelper.createSlider(t,e);return a}static createSlider(t,e){let a={};return Object.assign(a,t),a.value=e.get(a.name,a.value),template.parseTemplate("inputs/slider",a)}static createColorPicker(t,e){let a={};return Object.assign(a,t),a.value=e.get(a.name,a.value),template.parseTemplate("inputs/color",a)}static createCheckbox(t,e){let a={};return Object.assign(a,t),a.value=e.get(a.name,a.value)?"checked":"",template.parseTemplate("inputs/switch",a)}static createInputField(t,e){let a={};return Object.assign(a,t),a.value=e.get(a.name,a.value),template.parseTemplate("inputs/input",a)}static createSelect(t,e){let a={};Object.assign(a,t),a.value=e.get(a.name,a.value);let i="";for(let t=0;t<a.options.length;t++)i+=template.parseTemplate("inputs/option",{value:a.options[t]});return a.options=i,a.event="visualConf",a.conf=e.type,template.parseTemplate("inputs/select",a)}static richName(t,e){return""!==t.group?t.group+"-"+e:e}static createButton(t,e){return`<div class='button spaced' data-action="${t.action}">${t.name}</div>`}}class Modal{constructor(){this.currentModal="",this.modal=$("#modal"),this.parent=this.modal.parentNode,this.modal.addDelegatedEventListener("click","header .close",this.closeModal.bind(this))}resetModal(){this.renderModal("","","")}renderModal(t,e,a){$("#modal").removeClass("lightMode"),this.currentModal=t,this.renderHeader(t),this.renderContent(e),this.renderFooter(a)}renderHeader(t){$("header .headline",this.modal).innerHTML=t}renderContent(t){$("modal-content",this.modal).innerHTML=t}renderFooter(t){$("modal-footer .inner",this.modal).innerHTML=t||"by VersusTuneZ"}closeModal(){this.parent.addClass("hide")}isCurrent(t){return t===this.currentModal}showModal(){this.parent.removeClass("hide")}}class Visual{constructor(){this.data=[],this.dataArray=[],this.name="Default"}updateData(){}updateFFT(t){}draw(){}setup(){}}class VisualDrawer{constructor(){this.visuals={wave:new Wave,wave2d:new Wave2D},this.lastMainColor={base:"#-1",color:[0,0,0]},this.lastSecondColor={base:"#-1",color:[0,0,0]}}init(){this.switch(pConf.get("visual","wave2d")),this.updateLoop()}switch(t){null!=this.visuals[t]&&(this.c=t,vConf.loadConfigByName(this.c),this.visuals[this.c].setup(),pConf.set("visual",this.c),pConf.save())}updateLoop(){let t=this.visuals[this.c],e=shaderHandler.use(this.c);this.updateSeekbar(),this.prepare(e),t.updateData(),t.draw(e),requestAnimationFrame(this.updateLoop.bind(this))}updateSeekbar(){cInfo.width=window.innerWidth,cInfo.height=window.innerHeight;let t=audioHandler.audioFile;if(ctx.clearRect(0,0,cInfo.width,cInfo.height),!t.paused&&pConf.get("showSeekbar",!0)){let e=t.duration,a=t.currentTime/e*cInfo.width;ctx.fillStyle=pConf.get("seekColor","#fff"),ctx.fillRect(0,c.height-10,a,c.height)}}prepare(t){c.width=window.innerWidth,c.height=window.innerHeight,gl.viewport(0,0,gl.canvas.width,gl.canvas.height),gl.clearColor(0,0,0,parseFloat(pConf.get("alphaValue",0))),gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT),gl.enable(gl.DEPTH_TEST),gl.depthFunc(gl.LEQUAL),gl.enable(gl.CULL_FACE),this.setColor(t)}setColor(t){let e=gl.getUniformLocation(t,"u_baseColor"),a=gl.getUniformLocation(t,"u_maxColor"),i=this.lastMainColor,s=this.lastSecondColor;this.updateColor("lastMainColor","baseColor"),this.updateColor("lastSecondColor","gradientToColor"),gl.uniform3fv(e,i.color),gl.uniform3fv(a,s.color)}updateColor(t,e){let a=this[t],i=vConf.get(e,"#ffffff");i!==a.base&&(a.color=hexToRgb(i),a.base=i)}}class ImageUploader{async init(){this.image=pConf.get("bgURL",""),this.color=pConf.get("bgColour","#000000"),this.alpha=pConf.get("alphaValue",.5),this.getRealImage(),this.applyValues(),$("#modal").addDelegatedEventListener("change",'#image-upload input:not([type="color"])',this.changeHandler.bind(this)),$("#modal").addDelegatedEventListener("input","#image-upload input#color",this.changeHandler.bind(this))}async renderModal(){await template.loadTemplate("image"),gui.modal.resetModal(),gui.modal.renderModal("Background-Image",template.parseTemplate("image",{value:this.image,bgValue:this.color,alphaValue:this.alpha}),""),gui.modal.showModal()}changeHandler(t,e){"color"===e.id?this.color=e.value:"alphaValue"===e.id?this.alpha=e.value:(pConf.set("bgMode",e.id),"image"===e.id?(e.files[0].toBase64((t,e)=>{e?alert("Error converting image!"):(pConf.set("bgURL",t.currentTarget.result),pConf.save())}),this.image=URL.createObjectURL(e.files[0])):(this.image=e.value,pConf.set("bgURL",this.image))),pConf.set("bgColour",this.color),pConf.set("alphaValue",this.alpha),this.applyValues(),pConf.save()}applyValues(){let t=$("body");t.style.backgroundImage="url("+this.image+")",t.style.backgroundColor=this.color}getRealImage(){let t=pConf.get("bgMode"),e=pConf.get("bgURL","");if("image"===t){if(""!==e&&e.startsWith("data:image")){let t=e.split(";"),a=t.shift(),i=t.join(";").replace("base64,","");this.image=URL.createObjectURL(b64toBlob(i,a))}}else this.image=e}}const imageUploader=new ImageUploader;class NotificationHandler{static instance=new NotificationHandler;constructor(){this.outer=$(".notification"),this.notifications=[]}async init(){await template.loadTemplate("notification")}static createNotification(t,e,a){a=parseInt(a||"3000");let i=NotificationHandler.instance,s=new Notification(t,e,a);return i.notifications.push(s),s.show(),s}}class Notification{constructor(t,e,a){this.outer=NotificationHandler.instance.outer,this.message=t,this.type=e,this.time=a,this.isRemoved=!1}async show(){let t=this,e=-1===t.time;t.item=create("div"),t.item.addClass("notification-item, "+t.type),e&&(t.type+=" endless"),t.updateContent(t.message),this.outer.appendChild(t.item),e||setTimeout(this.remove.bind(this),t.time)}async remove(){if(this.isRemoved)return;this.isRemoved=!0,this.outer.removeChild(this.item);let t=NotificationHandler.instance.notifications,e=t.indexOf(this);t.splice(e,1)}updateContent(t){let e={message:t,time:-1===this.time?1e3:this.time+1,type:this.type};this.item.innerHTML=template.parseTemplate("notification",e)}updateMessageOnly(t){let e=$(".message",this.item);e&&(e.innerHTML=t)}}class Config{static allConfigs={};constructor(t){this.config={},this.name="",this.type=t,Config.allConfigs[t]=this}loadConfigByName(t){this.save(),this.name="config-"+t;let e=localStorage.getItem(this.name);e&&(this.config=JSON.parse(e))}save(){""!==this.name&&localStorage.setItem(this.name,JSON.stringify(this.config))}set(t,e){this.config[t]=e}remove(t){delete this.config[t]}get(t,e){let a=this.config[t];return null==a&&(this.config[t]=e,a=e),a}reset(){NotificationHandler.createNotification("CONFIG REQUEST SUCCESS FOR "+this.type,"success",2e3),this.config={},this.save()}}class Sphere extends Visual{constructor(){super(),this.name="Sphere"}draw(){}setup(){}}class Wave extends Visual{constructor(){super(),this.name="3D Wave"}updateData(){let t=audioHandler.getFloatArray(),e=2/t.length,a=-1,i=0;for(let s=0;s<t.length;s++)this.data[i]=a,this.data[i+1]=t[s],this.data[i+2]=0,this.data[i+3]=a,this.data[i+6]=a,this.data[i+8]=t[s+1]||0,i+=9,a+=e}draw(t){this.prepare(t);let e=this.position,a=gl.createBuffer();this.rotate(t),gl.bindBuffer(gl.ARRAY_BUFFER,a),gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(this.data),gl.DYNAMIC_DRAW);let i=gl.createVertexArray();gl.bindVertexArray(i),gl.enableVertexAttribArray(e),gl.vertexAttribPointer(e,3,gl.FLOAT,!0,0,0),gl.drawArrays(vConf.get("waveForm",gl.TRIANGLES),0,this.data.length/3),this.afterDraw()}rotate(t){let e=[1/(c.width/c.height),0,0,0,0,.6,0,0,0,0,1,0,0,0,0,1];e=TDUtils.multiply(e,TDUtils.xRotation(vConf.get("rotation-x",10))),e=TDUtils.multiply(e,TDUtils.yRotation(vConf.get("rotation-y",50))),e=TDUtils.multiply(e,TDUtils.zRotation(vConf.get("rotation-z",-30)));let a=gl.getUniformLocation(t,"u_matrix");gl.uniformMatrix4fv(a,!1,e)}setup(){this.updateFFT(vConf.get("fftSize",4096)),vConf.get("rotation-z",-30),vConf.get("rotation-y",50),vConf.get("rotation-x",10)}updateFFT(t){audioHandler.fftSize(t),this.data=new Float32Array(9*t)}prepare(t){this.position=gl.getAttribLocation(t,"a_position"),this.color=gl.getUniformLocation(t,"u_color");let e=gl.getUniformLocation(t,"u_lightPos");gl.uniform3fv(e,vConf.get("light",[0,5,-56]))}afterDraw(){TDUtils.updateRotate("rotation-x",10),TDUtils.updateRotate("rotation-y",50),TDUtils.updateRotate("rotation-z",-30),vConf.save()}}class Wave2D extends Visual{constructor(){super(),this.name="2D Wave"}updateData(){let t=audioHandler.getFloatArray(),e=2/t.length,a=-1,i=0;for(let s=0;s<t.length;s++)this.data[i]=a,this.data[i+1]=t[s],this.data[i+2]=t[s],i+=3,a+=e}draw(t){this.prepare(t);let e=this.position,a=gl.createBuffer();this.rotate(t),gl.bindBuffer(gl.ARRAY_BUFFER,a),gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(this.data),gl.DYNAMIC_DRAW);let i=gl.createVertexArray();gl.bindVertexArray(i),gl.enableVertexAttribArray(e),gl.vertexAttribPointer(e,3,gl.FLOAT,!0,0,0),gl.drawArrays(vConf.get("waveForm",gl.LINE_STRIP),0,this.data.length/3),this.afterDraw()}rotate(t){let e=[1,0,0,0,0,.6,0,0,0,0,1,0,0,0,0,1];e=TDUtils.multiply(e,TDUtils.xRotation(vConf.get("rotation-x",0))),e=TDUtils.multiply(e,TDUtils.yRotation(vConf.get("rotation-y",0))),e=TDUtils.multiply(e,TDUtils.zRotation(vConf.get("rotation-z",0)));let a=gl.getUniformLocation(t,"u_matrix");gl.uniformMatrix4fv(a,!1,e)}setup(){this.updateFFT(vConf.get("fftSize",16384))}updateFFT(t){audioHandler.fftSize(t),this.data=new Float32Array(3*t)}prepare(t){this.position=gl.getAttribLocation(t,"a_position"),this.color=gl.getUniformLocation(t,"u_color");let e=gl.getUniformLocation(t,"u_lightPos");gl.uniform3fv(e,vConf.get("light",[0,5,-56]))}afterDraw(){TDUtils.updateRotate("rotation-x",0),TDUtils.updateRotate("rotation-y",0),TDUtils.updateRotate("rotation-z",0),vConf.save()}}class Water extends Visual{constructor(){super(),this.name="Water"}draw(){}setup(){audioHandler.fftSize(256)}}class EventHandler{constructor(){this.events={}}addEvent(t,e){this.events[t]=e}sendData(t,e){worker.postMessage({cmd:t,data:e})}handleEvent(t){let e=t.data;e.cmd&&this.events[e.cmd]&&this.events[e.cmd](e.data)}}async function initHandler(){let t=$("body");$(".playlist.menu-icon").addEventListener("click",t=>{player.playlist.renderPagination(player.playlist.page),gui.modal.showModal()}),t.addDelegatedEventListener("click",".playlist-item",(t,e)=>{let a=e.dataset.index;player.playByID(parseInt(a)),togglePlayButton("pause")}),t.addDelegatedEventListener("click",".controls button",(t,e)=>{switch(e.id){case"previous":player.prevSong();break;case"next":player.nextSong();break;case"play":player.playStop();break;case"shuffle":player.playlist.isShuffle=!player.playlist.isShuffle,toggleShuffle()}togglePlayButton(audioHandler.audioFile.paused?"play":"pause")}),window.addEventListener("playSong",setActiveOnPlaylist),$(".upload-image").addEventListener("click",imageUploader.renderModal.bind(imageUploader)),t.addDelegatedEventListener("click",".readAll",t=>{let e=player.playlist.list;for(let t=0;t<e.length;t++)e[t].getID3Tag(!0)}),t.addDelegatedEventListener("input",'.input-range input[type="range"]',(t,e)=>{$(".current",e.parentNode).innerText=e.value}),t.addDelegatedEventListener("input",'input[type="color"]',(t,e)=>{$(".colorBlob",e.parentNode).style.backgroundColor=e.value}),t.addDelegatedEventListener("click",".visual-item",(t,e)=>{visual.switch(e.dataset.id||"wave"),$("modal-content .visuals .active").removeClass("active"),e.addClass("active")}),t.addDelegatedEventListener("input","section.base input",(t,e)=>{"checkbox"===e.type?pConf.set(e.name,e.checked):setValue(e.name,e.value,pConf,e.dataset.type),pConf.save()}),t.addDelegatedEventListener("input","section.visual input",(t,e)=>{"checkbox"===e.type?vConf.set(e.name,e.checked):setValue(e.name,e.value,vConf,e.dataset.type),vConf.save()}),t.addDelegatedEventListener("click",".button[data-action]",(t,e)=>{switch(e.dataset.action){case"resetVConf":vConf.reset(),setTimeout(t=>{playerConf.handleById()},30);break;case"makeModalTransparent":$("#modal").toggleClass("lightMode")}})}function setValue(t,e,a,i){switch(i){case"float":e=parseFloat(e);break;case"int":e=parseInt(e)}a.set(t,e)}function setActiveOnPlaylist(t){let e=$('.playlist-item[data-index="'+player.playlist.index+'"]'),a=$(".playlist-item.active");a&&a.removeClass("active"),e&&e.addClass("active")}function toggleShuffle(){let t=player.playlist.isShuffle;$("#shuffle").toggleCheck("active",t);let e=t?"enabled":"disabled";NotificationHandler.createNotification("Shuffle: "+e,"info",500)}function togglePlayButton(t){$$("#play .icon").forEach(e=>{e.dataset.name===t?e.removeClass("hide"):e.addClass("hide")})}!function(){const t=$("body");t.addDelegatedEventListener("click","custom-select .label",(t,e)=>{let a=e.parentNode,i=$$("custom-option",a),s=$("custom-options",a);if(a.hasClass("open"))s.style.maxHeight="",a.removeClass("open");else{let t=0;i.forEach((function(e){t+=e.offsetHeight})),s.style.maxHeight=t+"px",a.addClass("open")}}),t.addDelegatedEventListener("click","custom-select custom-option",(t,e)=>{let a=e.closest("custom-select"),i=$("input",a);$$("custom-option.active").forEach(t=>{t.removeClass("active")}),e.addClass("active"),i&&(i.value=e.dataset.value||e.innerText,$(".label",a).innerText=e.innerText,a.removeClass("open"),e.parentNode.style.maxHeight="",window.dispatchEvent(new CustomEvent("selectChanged",{detail:{select:a,event:a.dataset.event,value:i.value,name:i.name}})))}),window.addEventListener("selectChanged",t=>{"visualConf"===t.detail.event&&(t.preventDefault(),t.stopPropagation(),function(t){try{let e=t.value,a=Config.allConfigs[t.select.dataset.conf];"fftSize"===t.name&&(e=parseInt(t.value),visual.visuals[visual.c].updateFFT(e)),a.set(t.name,e),a.save()}catch(t){console.error(t)}}(t.detail))})}();class Startup{constructor(){this.modules={startup:!1,"id3-ready":!1}}moduleLoaded(t){this.modules[t]=!0,this.allModulesLoaded()}allModulesLoaded(){for(let t in this.modules)if(!this.modules[t])return!1;return window.dispatchEvent(new CustomEvent("startupFin")),!0}}const shaderHandler=new ShaderHandler(null),audioHandler=new AudioHandler,gui=new GUI,visual=new VisualDrawer,template=new Template,player=new Player,vConf=new Config("visual"),pConf=new Config("player"),worker=new Worker("/out/js/worker.min.js"),startup=new Startup,eventHandler=new EventHandler,playerConf=new PlayerConfigHandler;let c,gl,cInfo,ctx;async function startUP(){if(pConf.loadConfigByName("default"),c=$("#c"),gl=c.getContext("webgl2"),cInfo=$("#cInfo"),ctx=cInfo.getContext("2d"),!gl)return alert("SORRY THE BROWSER DOESN'T SUPPORT WEBGL2"),!1;shaderHandler.setGL(gl),await shaderHandler.loadArray(["wave","sphere","water","wave2d"],"shaders/"),await NotificationHandler.instance.init(),await audioHandler.init(),await player.init(),await visual.init(),await gui.init(),await imageUploader.init(),await playerConf.init(),await initHandler()}worker.addEventListener("message",t=>{"startup"!==t.data.status?eventHandler.handleEvent(t):startup.moduleLoaded(t.data.cmd)}),window.addEventListener("startupFin",t=>{setTimeout(t=>{$(".loading-screen").remove()},100)}),startUP().then(t=>{startup.moduleLoaded("startup")}); |