diff --git a/build/task/js.js b/build/task/js.js index c612353..388edc8 100644 --- a/build/task/js.js +++ b/build/task/js.js @@ -14,12 +14,15 @@ const visuals = [ const config = { src: [ basePath + 'utils.js', + basePath + 'gl/glUtils.js', basePath + 'template.js', - basePath + 'handler.js', + basePath + 'gl/handler.js', basePath + 'audio.js', basePath + 'player.js', basePath + 'gui.js', basePath + 'visual.js', + basePath + 'imageUploader.js', + basePath + 'notification.js', basePath + 'config.js', ...visuals, basePath + 'eventHandler.js', diff --git a/build/task/spriteBuilder.js b/build/task/spriteBuilder.js index d19382e..edd45f9 100644 --- a/build/task/spriteBuilder.js +++ b/build/task/spriteBuilder.js @@ -42,7 +42,7 @@ function buildIconSprites() { fal.faCogs, fal.faFolderUpload, fal.faListMusic, - fal.faFileAudio, + fal.faFileImage, ], vt: [] }; diff --git a/index.html b/index.html index 434111b..5f1208e 100644 --- a/index.html +++ b/index.html @@ -17,13 +17,10 @@ - +
+
diff --git a/out/gui/wave.json b/out/gui/wave.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/out/gui/wave.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/out/icon-sprite.svg b/out/icon-sprite.svg index 6f4f495..165e217 100644 --- a/out/icon-sprite.svg +++ b/out/icon-sprite.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/out/js/scripts.js b/out/js/scripts.js index a973d10..1a4fbe3 100644 --- a/out/js/scripts.js +++ b/out/js/scripts.js @@ -51,13 +51,12 @@ class VTUtils { } static hsvToRgb(h, s, v) { - var r, g, b; - - var i = Math.floor(h * 6); - var f = h * 6 - i; - var p = v * (1 - s); - var q = v * (1 - f * s); - var t = v * (1 - (1 - f) * s); + let r, g, b, + i = Math.floor(h * 6), + f = h * 6 - i, + p = v * (1 - s), + q = v * (1 - f * s), + t = v * (1 - (1 - f) * s); switch (i % 6) { case 0: @@ -142,57 +141,226 @@ class VTVector { } } +function $(sel, s) { + s = s || document; + return s.querySelector(sel); +} + +function $$(sel, s) { + s = s || document; + return s.querySelectorAll(sel); +} + +Node.prototype.addDelegatedEventListener = function (type, aim, cb) { + this.addEventListener(type, (event) => { + let target = event.target; + if (target.matches(aim)) { + cb(event, target); + } else { + let parent = target.closest(aim); + if (parent) { + cb(event, parent); + } + } + }) +}; + +Node.prototype.hasClass = function (className) { + let items = className.split(','), + has = null; + for (let item of items) { + if (has === false) { + break; + } + has = this.classList.contains(item.trim()); + } + return has === true; +} +Node.prototype.addClass = function (className) { + let items = className.split(','); + for (let item of items) { + this.classList.add(item.trim()); + } + return this; +} +Node.prototype.removeClass = function (className) { + let items = className.split(','); + for (let item of items) { + this.classList.remove(item.trim()); + } + return this; +} +Node.prototype.toggleClass = function (className, force) { + let items = className.split(','); + for (let item of items) { + this.classList.toggle(item.trim(), force); + } +} +Node.prototype.switchClass = function (clOne, clTwo, twoOne) { + let cl = this.classList; + if (twoOne) { + cl.remove(clOne); + cl.add(clTwo) + } else { + cl.remove(clTwo) + cl.add(clOne) + } +} +Node.prototype.toggleCheck = function (className, force) { + let cl = this.classList; + let items = className.split(','); + for (let item of items) { + let clOne = item.trim(); + if (force) { + cl.add(clOne); + } else { + cl.remove(clOne) + } + } +} + +File.prototype.toBase64 = function (cb) { + const reader = new FileReader(); + reader.onloadend = cb; + reader.readAsDataURL(this); +} + +function b64toBlob(b64Data, type) { + const byteCharacters = atob(b64Data); + const byteNumbers = new Array(byteCharacters.length); + for (let i = 0; i < byteCharacters.length; i++) { + byteNumbers[i] = byteCharacters.charCodeAt(i); + } + const byteArray = new Uint8Array(byteNumbers); + return new Blob([byteArray], {type: type}); +} + +function create(name, content) { + let d = document.createElement(name); + if (content) { + d.innerHTML = content; + } + return d; +} + +function append(to, array) { + for (let item of array) { + to.appendChild(item); + } +} + +function loadFromJSONToVisualData(useKeys) { + fetch('/audio-vis/out/showCase.json').then((res) => { + return res.json() + }).then(e => { + let floatArray; + if (useKeys) { + let keys = Object.keys(e); + floatArray = new Float32Array(keys.length); + for (let i = 0; i < keys.length; i++) { + floatArray[i] = e[keys[i]]; + } + } else { + floatArray = new Float32Array(e.length); + for (let i = 0; i < e.length; i++) { + floatArray[i] = e[i]; + } + } + visual.visuals[visual.c].data = floatArray; + }) +} +// most of the functions are from https://webglfundamentals.org/webgl/resources/m4.js! but i doesnt want to use them all and make some adjustment to them! class TDUtils { static multiply(a, b) { - let b00 = b[0 * 4 + 0]; - let b01 = b[0 * 4 + 1]; - let b02 = b[0 * 4 + 2]; - let b03 = b[0 * 4 + 3]; - let b10 = b[1 * 4 + 0]; - let b11 = b[1 * 4 + 1]; - let b12 = b[1 * 4 + 2]; - let b13 = b[1 * 4 + 3]; - let b20 = b[2 * 4 + 0]; - let b21 = b[2 * 4 + 1]; - let b22 = b[2 * 4 + 2]; - let b23 = b[2 * 4 + 3]; - let b30 = b[3 * 4 + 0]; - let b31 = b[3 * 4 + 1]; - let b32 = b[3 * 4 + 2]; - let b33 = b[3 * 4 + 3]; - let a00 = a[0 * 4 + 0]; - let a01 = a[0 * 4 + 1]; - let a02 = a[0 * 4 + 2]; - let a03 = a[0 * 4 + 3]; - let a10 = a[1 * 4 + 0]; - let a11 = a[1 * 4 + 1]; - let a12 = a[1 * 4 + 2]; - let a13 = a[1 * 4 + 3]; - let a20 = a[2 * 4 + 0]; - let a21 = a[2 * 4 + 1]; - let a22 = a[2 * 4 + 2]; - let a23 = a[2 * 4 + 3]; - let a30 = a[3 * 4 + 0]; - let a31 = a[3 * 4 + 1]; - let a32 = a[3 * 4 + 2]; - let a33 = a[3 * 4 + 3]; - let dst = []; - dst[0] = b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30; - dst[1] = b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31; - dst[2] = b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32; - dst[3] = b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33; - dst[4] = b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30; - dst[5] = b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31; - dst[6] = b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32; - dst[7] = b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33; - dst[8] = b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30; - dst[9] = b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31; - dst[10] = b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32; - dst[11] = b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33; - dst[12] = b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30; - dst[13] = b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31; - dst[14] = b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32; - dst[15] = b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33; + let b00 = b[0]; + let b01 = b[1]; + let b02 = b[2]; + let b03 = b[3]; + let b10 = b[4]; + let b11 = b[5]; + let b12 = b[6]; + let b13 = b[7]; + let b20 = b[8]; + let b21 = b[9]; + let b22 = b[10]; + let b23 = b[11]; + let b30 = b[12]; + let b31 = b[13]; + let b32 = b[14]; + let b33 = b[15]; + let a00 = a[0]; + let a01 = a[1]; + let a02 = a[2]; + let a03 = a[3]; + let a10 = a[4]; + let a11 = a[5]; + let a12 = a[6]; + let a13 = a[7]; + let a20 = a[8]; + let a21 = a[9]; + let a22 = a[10]; + let a23 = a[11]; + let a30 = a[12]; + let a31 = a[13]; + let a32 = a[14]; + let a33 = a[15]; + return [ + b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30, + b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31, + b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32, + b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33, + b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30, + b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31, + b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32, + b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33, + b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30, + b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31, + b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32, + b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33, + b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30, + b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31, + b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32, + b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33 + ]; + } + + static translate(m, tx, ty, tz, dst) { + dst = dst || new Float32Array(16); + + let m00 = m[0], + m01 = m[1], + m02 = m[2], + m03 = m[3], + m10 = m[4], + m11 = m[5], + m12 = m[6], + m13 = m[7], + m20 = m[8], + m21 = m[9], + m22 = m[10], + m23 = m[11], + m30 = m[12], + m31 = m[13], + m32 = m[14], + m33 = m[15]; + dst[0] = m00; + dst[1] = m01; + dst[2] = m02; + dst[3] = m03; + dst[4] = m10; + dst[5] = m11; + dst[6] = m12; + dst[7] = m13; + dst[8] = m20; + dst[9] = m21; + dst[10] = m22; + dst[11] = m23; + + dst[12] = m00 * tx + m10 * ty + m20 * tz + m30; + dst[13] = m01 * tx + m11 * ty + m21 * tz + m31; + dst[14] = m02 * tx + m12 * ty + m22 * tz + m32; + dst[15] = m03 * tx + m13 * ty + m23 * tz + m33; + return dst; } @@ -235,52 +403,203 @@ class TDUtils { static degToRad(d) { return d * Math.PI / 180; } -} -function $(sel, s) { - return $$(sel, s)[0]; -} - -function $$(sel, s) { - s = s || document; - return s.querySelectorAll(sel); -} - -Node.prototype.addDelegatedEventListener = function (type, aim, cb) { - this.addEventListener(type, (event) => { - let target = event.target; - if (target.matches(aim)) { - cb(event, target); - } else { - let parent = target.closest(aim); - if (parent) { - cb(event, parent); - } - } - }) -}; - -Node.prototype.hasClass = function (className) { - return this.classList.contains(className); -} -Node.prototype.addClass = function (className) { - return this.classList.add(className); -} -Node.prototype.removeClass = function (className) { - return this.classList.remove(className); -} - -function create(name, content) { - let d = document.createElement(name); - if (content) { - d.innerHTML = content; + static scale(sx, sy, sz, dst) { + dst = dst || new Float32Array(16); + dst[0] = sx; + dst[5] = sy; + dst[10] = sz; + return dst; } - return d; -} -function append(to, array) { - for (let item in array) { - to.appendChild(array[item]); + static lookAt(cameraPosition, target, up, dst) { + dst = dst || new Float32Array(16); + let zAxis = TDUtils.normalize( + TDUtils.subtractVectors(cameraPosition, target)); + let xAxis = TDUtils.normalize(TDUtils.cross(up, zAxis)); + let yAxis = TDUtils.normalize(TDUtils.cross(zAxis, xAxis)); + + dst[0] = xAxis[0]; + dst[1] = xAxis[1]; + dst[2] = xAxis[2]; + dst[4] = yAxis[0]; + dst[5] = yAxis[1]; + dst[6] = yAxis[2]; + dst[8] = zAxis[0]; + dst[9] = zAxis[1]; + dst[10] = zAxis[2]; + dst[12] = cameraPosition[0]; + dst[13] = cameraPosition[1]; + dst[14] = cameraPosition[2]; + dst[15] = 1; + + return dst; + } + + static cross(a, b, dst) { + dst = dst || new Float32Array(3); + dst[0] = a[1] * b[2] - a[2] * b[1]; + dst[1] = a[2] * b[0] - a[0] * b[2]; + dst[2] = a[0] * b[1] - a[1] * b[0]; + return dst; + } + + static normalize(v, dst) { + dst = dst || new Float32Array(3); + let length = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); + if (length > 0.00001) { + dst[0] = v[0] / length; + dst[1] = v[1] / length; + dst[2] = v[2] / length; + } + return dst; + } + + static subtractVectors(a, b, dst) { + dst = dst || new Float32Array(3); + dst[0] = a[0] - b[0]; + dst[1] = a[1] - b[1]; + dst[2] = a[2] - b[2]; + return dst; + } + + static perspective(fieldOfViewInRadians, aspect, near, far, dst) { + dst = dst || new Float32Array(16); + let f = Math.tan(Math.PI * 0.5 - 0.5 * fieldOfViewInRadians), + rangeInv = 1.0 / (near - far); + + dst[0] = f / aspect; + dst[5] = f; + dst[10] = (near + far) * rangeInv; + dst[11] = -1; + dst[14] = near * far * rangeInv * 2; + return dst; + } + + static inverse(m, dst) { + dst = dst || new Float32Array(16); + let m00 = m[0], + m01 = m[1], + m02 = m[2], + m03 = m[3], + m10 = m[4], + m11 = m[5], + m12 = m[6], + m13 = m[7], + m20 = m[8], + m21 = m[9], + m22 = m[10], + m23 = m[11], + m30 = m[12], + m31 = m[13], + m32 = m[14], + m33 = m[15], + tmp_0 = m22 * m33, + tmp_1 = m32 * m23, + tmp_2 = m12 * m33, + tmp_3 = m32 * m13, + tmp_4 = m12 * m23, + tmp_5 = m22 * m13, + tmp_6 = m02 * m33, + tmp_7 = m32 * m03, + tmp_8 = m02 * m23, + tmp_9 = m22 * m03, + tmp_10 = m02 * m13, + tmp_11 = m12 * m03, + tmp_12 = m20 * m31, + tmp_13 = m30 * m21, + tmp_14 = m10 * m31, + tmp_15 = m30 * m11, + tmp_16 = m10 * m21, + tmp_17 = m20 * m11, + tmp_18 = m00 * m31, + tmp_19 = m30 * m01, + tmp_20 = m00 * m21, + tmp_21 = m20 * m01, + tmp_22 = m00 * m11, + tmp_23 = m10 * m01, + + t0 = (tmp_0 * m11 + tmp_3 * m21 + tmp_4 * m31) - + (tmp_1 * m11 + tmp_2 * m21 + tmp_5 * m31), + t1 = (tmp_1 * m01 + tmp_6 * m21 + tmp_9 * m31) - + (tmp_0 * m01 + tmp_7 * m21 + tmp_8 * m31), + t2 = (tmp_2 * m01 + tmp_7 * m11 + tmp_10 * m31) - + (tmp_3 * m01 + tmp_6 * m11 + tmp_11 * m31), + t3 = (tmp_5 * m01 + tmp_8 * m11 + tmp_11 * m21) - + (tmp_4 * m01 + tmp_9 * m11 + tmp_10 * m21), + + d = 1.0 / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3); + + dst[0] = d * t0; + dst[1] = d * t1; + dst[2] = d * t2; + dst[3] = d * t3; + dst[4] = d * ((tmp_1 * m10 + tmp_2 * m20 + tmp_5 * m30) - + (tmp_0 * m10 + tmp_3 * m20 + tmp_4 * m30)); + dst[5] = d * ((tmp_0 * m00 + tmp_7 * m20 + tmp_8 * m30) - + (tmp_1 * m00 + tmp_6 * m20 + tmp_9 * m30)); + dst[6] = d * ((tmp_3 * m00 + tmp_6 * m10 + tmp_11 * m30) - + (tmp_2 * m00 + tmp_7 * m10 + tmp_10 * m30)); + dst[7] = d * ((tmp_4 * m00 + tmp_9 * m10 + tmp_10 * m20) - + (tmp_5 * m00 + tmp_8 * m10 + tmp_11 * m20)); + dst[8] = d * ((tmp_12 * m13 + tmp_15 * m23 + tmp_16 * m33) - + (tmp_13 * m13 + tmp_14 * m23 + tmp_17 * m33)); + dst[9] = d * ((tmp_13 * m03 + tmp_18 * m23 + tmp_21 * m33) - + (tmp_12 * m03 + tmp_19 * m23 + tmp_20 * m33)); + dst[10] = d * ((tmp_14 * m03 + tmp_19 * m13 + tmp_22 * m33) - + (tmp_15 * m03 + tmp_18 * m13 + tmp_23 * m33)); + dst[11] = d * ((tmp_17 * m03 + tmp_20 * m13 + tmp_23 * m23) - + (tmp_16 * m03 + tmp_21 * m13 + tmp_22 * m23)); + dst[12] = d * ((tmp_14 * m22 + tmp_17 * m32 + tmp_13 * m12) - + (tmp_16 * m32 + tmp_12 * m12 + tmp_15 * m22)); + dst[13] = d * ((tmp_20 * m32 + tmp_12 * m02 + tmp_19 * m22) - + (tmp_18 * m22 + tmp_21 * m32 + tmp_13 * m02)); + dst[14] = d * ((tmp_18 * m12 + tmp_23 * m32 + tmp_15 * m02) - + (tmp_22 * m32 + tmp_14 * m02 + tmp_19 * m12)); + dst[15] = d * ((tmp_22 * m22 + tmp_16 * m02 + tmp_21 * m12) - + (tmp_20 * m12 + tmp_23 * m22 + tmp_17 * m02)); + + return dst; + } + + static aspectView(aspect) { + return [ + 1 * aspect, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ] + } + + static lastMatrix = {m: null}; + + static getMatrix(fov, aspect, near, far, camAngle, radius) { + let lMat = this.lastMatrix, + u = TDUtils; + if (!u.isSame('fov', fov) + || !u.isSame('aspect', aspect) + || !u.isSame('near', near) + || !u.isSame('far', far) + || !u.isSame('cam', camAngle) + || !u.isSame('radius', radius) + ) { + let matrix = TDUtils.perspective(TDUtils.degToRad(fov), aspect, near, far), + cameraMatrix = TDUtils.yRotation(TDUtils.degToRad(camAngle)); + cameraMatrix = TDUtils.translate(cameraMatrix, 0, 0, radius * 1.5); + let viewMatrix = TDUtils.inverse(cameraMatrix); + matrix = TDUtils.multiply(matrix, viewMatrix) + lMat.m = matrix; + } + return lMat.m; + } + + static isSame(key, value) { + let lMat = this.lastMatrix; + if (lMat[key] !== value) { + lMat[key] = value; + return false; + } + return true; } } class Template { @@ -435,8 +754,8 @@ class AudioHandler { let self = this; self.isStarted = false; self.audioFile = new Audio(); - self.actx = new AudioContext() - self.analyser = self.actx.createAnalyser() + self.actx = new AudioContext(); + self.analyser = self.actx.createAnalyser(); self.analyser.fftSize = 4096; self.lastSong = null; await self.connectAll(); @@ -482,9 +801,13 @@ class AudioHandler { } self.lastSong = this.audioFile.src = URL.createObjectURL(src); if (!this.isStarted) { - this.start(); + this.start().catch(alert); } - this.audioFile.play(); + this.audioFile.play().then(e => { + window.dispatchEvent(new CustomEvent('playSong')); + }).catch(e => { + player.nextSong(); + }); } getIntArray(steps) { @@ -494,7 +817,7 @@ class AudioHandler { } getFloatArray() { - let dataArray = new Float32Array(this.analyser.frequencyBinCount); + let dataArray = new Float32Array(this.analyser.fftSize); this.analyser.getFloatTimeDomainData(dataArray); return dataArray; } @@ -518,6 +841,7 @@ class Player { if (!audioHandler.lastSong) { let next = this.playlist.getCurrent(); audioHandler.loadSong(next.file); + return; } let audioFile = audioHandler.audioFile; if (audioFile.paused) { @@ -525,10 +849,12 @@ class Player { } else { audioFile.pause(); } + window.dispatchEvent(new CustomEvent('playSong')); } playByID(number) { - let next = this.playlist.getFile(number); + this.playlist.index = number; + let next = this.playlist.getCurrent(); audioHandler.loadSong(next.file); } } @@ -591,11 +917,6 @@ class Playlist { return items[this.index]; } - getFile(index) { - let items = this.isShuffle ? this.shuffled : this.list; - return items[index]; - } - // on new upload... this has to be an array! setPlaylist(files) { this.index = 0; @@ -634,9 +955,10 @@ class Playlist { let items = this.isShuffle ? this.shuffled : this.list; for (let i = s; i < e; i++) { let obj = { - index: i, + index: i.toString(), nr: i + 1, - title: items[i].name + title: items[i].name, + active: !audioHandler.audioFile.paused && i === this.index ? 'active' : '' } data += template.parseTemplate("playlist-item", obj); } @@ -659,6 +981,10 @@ class Playlist { //playlist handler for file input! changeFiles(e, el) { + + if (el.id !== 'upload-dir') { + return; + } let files = []; let i = 0; for (let file of el.files) { @@ -703,16 +1029,6 @@ class GUI { } } - renderModal(content, title) { - let modal = $('#modal'), - p = modal.parentNode, - h = $('header .headline', modal), - con = $('modal-content', modal); - h.innerHTML = title; - con.innerHTML = content; - p.classList.remove('hide'); - } - initDropZone() { let items = 'drag dragstart dragend dragover dragenter dragleave drop'.split(' '); items.forEach(el => { @@ -721,6 +1037,7 @@ class GUI { e.stopPropagation(); if (e.type === 'drop') { if (e.dataTransfer.files.length > 0) { + e.dataTransfer.id = 'upload-dir'; player.playlist.changeFiles(e, e.dataTransfer); } else { alert("Sorry you need to upload files!"); @@ -749,7 +1066,6 @@ class Modal { this.renderHeader(title); this.renderContent(content); this.renderFooter(footer); - this.showModal(); } renderHeader(header) { @@ -803,17 +1119,17 @@ class VisualDrawer { "wave": new Wave(), "water": new Water() } - this.c = "wave"; } init() { - this.visuals[this.c].setup(); + this.switch('wave'); this.updateLoop(); } switch(visual) { if (this.visuals[visual] != null) { this.c = visual; + vConf.loadConfigByName(this.c); this.visuals[this.c].setup(); } } @@ -823,9 +1139,104 @@ class VisualDrawer { let pro = shaderHandler.use(self.c); let vis = self.visuals[self.c]; vis.updateData(); + this.prepare(); vis.draw(pro); requestAnimationFrame(self.updateLoop.bind(self)) } + + prepare() { + 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); + } +} +class ImageUploader { + async init() { + this.image = pConf.get("bgURL", ""); + this.color = pConf.get("bgColour", "#000000"); + this.alpha = pConf.get("alphaValue", 0.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(e, el) { + if (el.id === 'color') { + this.color = el.value; + } else if (el.id === "alphaValue") { + this.alpha = el.value; + } else { + pConf.set('bgMode', el.id); + if (el.id === 'image') { + el.files[0].toBase64((e, b) => { + if (b) { + alert("Error converting image!"); + return; + } + pConf.set('bgURL', e.currentTarget.result); + pConf.save(); + }) + this.image = URL.createObjectURL(el.files[0]); + } else { + this.image = el.value; + pConf.set('bgURL', this.image); + } + } + pConf.set('bgColour', this.color); + pConf.set('alphaValue', this.alpha); + this.applyValues(); + pConf.save(); + } + + applyValues() { + let body = $('body'); + body.style.backgroundImage = 'url(' + this.image + ')'; + body.style.backgroundColor = this.color; + let blob = $('#colorBlob'); + if (blob) { + blob.style.backgroundColor = this.color; + } + } + + getRealImage() { + let mode = pConf.get('bgMode'), + value = pConf.get("bgURL", ""); + if (mode === 'image') { + if (value !== '' && value.startsWith('data:image')) { + let split = value.split(";"), + type = split.shift(), + message = split.join(";").replace("base64,", ""); + this.image = URL.createObjectURL(b64toBlob(message, type)); + } + } else { + this.image = value; + } + } +} + +const imageUploader = new ImageUploader(); +class Notification { + constructor() { + + } } class Config { constructor() { @@ -834,26 +1245,29 @@ class Config { } loadConfigByName(name) { - this.saveConfig(); + this.save(); this.name = 'config-' + name; - this.config = JSON.parse(this.name); + let item = localStorage.getItem(this.name); + if (item) { + this.config = JSON.parse(item); + } } - saveConfig() { + save() { if (this.name !== '') { localStorage.setItem(this.name, JSON.stringify(this.config)); } } - addItem(name, value) { + set(name, value) { this.config[name] = value; } - removeItem(name) { + remove(name) { delete this.config[name]; } - getItem(name, def) { + get(name, def) { let value = this.config[name]; if (value === undefined || value === null) { this.config[name] = def; @@ -872,22 +1286,32 @@ class Sphere extends Visual { // 3D Audio-Waves -> maybe also 2D? class Wave extends Visual { updateData() { - this.data = []; + //only for debug! remove pls + if (window.stopUpdate) { + return; + } let data = audioHandler.getFloatArray(); let add = 2 / data.length, x = -1; + let outerLoop = 0; for (let i = 0; i < data.length; i++) { - this.data.push(x, data[i], data[i]); + //first + this.data[outerLoop] = x; + this.data[outerLoop + 1] = data[i]; + this.data[outerLoop + 2] = 0; + //second + this.data[outerLoop + 3] = x; + //third + this.data[outerLoop + 6] = x; + this.data[outerLoop + 8] = data[i + 1] || 0; + outerLoop += 9; x += add; } } draw(program) { - c.width = window.innerWidth; - c.height = window.innerHeight; this.prepare(program); let position = this.position, - color = this.color, positionBuffer = gl.createBuffer(); this.rotate(program); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); @@ -896,37 +1320,39 @@ class Wave extends Visual { gl.bindVertexArray(vao); gl.enableVertexAttribArray(position); gl.vertexAttribPointer(position, 3, gl.FLOAT, true, 0, 0); - gl.clearColor(0, 0, 0, 1); - gl.enable(gl.DEPTH_TEST); - gl.depthFunc(gl.LEQUAL); - gl.clearDepth(2.0) - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); - gl.drawArrays(gl.LINE_STRIP || gl.POINTS, 0, this.data.length / 3); + gl.drawArrays(vConf.get("waveForm", gl.TRIANGLES), 0, this.data.length / 3); } rotate(program) { - let aspect = c.height / c.width, + let aspect = c.width / c.height, matrix = [ - 1 / aspect, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - ] - matrix = TDUtils.multiply(matrix, TDUtils.xRotation(config.getItem("xRotate", 0))); - matrix = TDUtils.multiply(matrix, TDUtils.yRotation(config.getItem("yRotate", 0))); - matrix = TDUtils.multiply(matrix, TDUtils.zRotation(config.getItem("zRotate", 0))); + 1 / aspect, 0, 0, 0, + 0, 0.6, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ] + matrix = TDUtils.multiply(matrix, TDUtils.xRotation(vConf.get("xRotate", 0))); + matrix = TDUtils.multiply(matrix, TDUtils.yRotation(vConf.get("yRotate", 0))); + matrix = TDUtils.multiply(matrix, TDUtils.zRotation(vConf.get("zRotate", 0))); let rotate = gl.getUniformLocation(program, "u_matrix"); gl.uniformMatrix4fv(rotate, false, matrix); } setup() { audioHandler.fftSize(16384) + this.data = new Float32Array(16384 * 9); + vConf.get("zRotate", TDUtils.degToRad(-30)); + vConf.get("yRotate", TDUtils.degToRad(50)); + vConf.get("xRotate", TDUtils.degToRad(10)); } prepare(program) { this.position = gl.getAttribLocation(program, "a_position"); this.color = gl.getUniformLocation(program, "u_color"); + let lightPos = gl.getUniformLocation(program, "u_lightPos"), + matrix = gl.getUniformLocation(program, "u_matrix"); + gl.uniform3fv(lightPos, vConf.get("light", [0, 5, -56])); + //gl.uniformMatrix4fv(matrix, false, TDUtils.getMatrix(90, c.width / c.height, 1, 2000, 200, 200)); } } //animate Water the way like the Audio is Coming... 256FFT-Size max! @@ -943,6 +1369,7 @@ async function initHandler() { let body = $('body'); $('.playlist.menu-icon').addEventListener('click', e => { player.playlist.renderPagination(player.playlist.page); + gui.modal.showModal(); }); body.addDelegatedEventListener('click', '.playlist-item', (e, el) => { @@ -962,16 +1389,37 @@ async function initHandler() { case 'play': player.playStop(); break; + case 'shuffle': + player.playlist.isShuffle = !player.playlist.isShuffle; + toggleShuffle(); + break; } togglePlayButton(audioHandler.audioFile.paused ? 'play' : 'pause'); }); + window.addEventListener('playSong', setActiveOnPlaylist); + $('.upload-image').addEventListener('click', imageUploader.renderModal.bind(imageUploader)); } +function setActiveOnPlaylist(e) { + let item = $('.playlist-item[data-index="' + player.playlist.index + '"]'), + active = $('.playlist-item.active'); + if (active) { + active.removeClass('active'); + } + if (item) { + item.addClass('active'); + } +} + +function toggleShuffle() { + let active = player.playlist.isShuffle; + $('#shuffle').toggleCheck('active', active); +} function togglePlayButton(status) { let icons = $$('#play .icon'); icons.forEach(el => { - if(el.dataset.name === status) { + if (el.dataset.name === status) { el.removeClass('hide'); } else { el.addClass('hide'); @@ -984,12 +1432,14 @@ const shaderHandler = new ShaderHandler(null), visual = new VisualDrawer(), template = new Template(), player = new Player(), - config = new Config(); + vConf = new Config(), + pConf = new Config(); let c = null, gl = null; async function startUP() { + pConf.loadConfigByName('default'); c = document.body.querySelector('#c'), gl = c.getContext("webgl2"); if (!gl) { @@ -1002,6 +1452,7 @@ async function startUP() { await player.init(); await visual.init(); await gui.init(); + await imageUploader.init(); await initHandler(); } diff --git a/out/js/scripts.min.js b/out/js/scripts.min.js index d1f8c6c..4892939 100644 --- a/out/js/scripts.min.js +++ b/out/js/scripts.min.js @@ -1 +1 @@ -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,r=e-i;return Math.sqrt(s*s+r*r)}static map(t,e,a,i,s,r){let l=(t-e)/(a-e)*(s-i)+i;return r?i{let i=t.target;if(i.matches(e))a(t,i);else{let s=i.closest(e);s&&a(t,s)}})},Node.prototype.hasClass=function(t){return this.classList.contains(t)},Node.prototype.addClass=function(t){return this.classList.add(t)},Node.prototype.removeClass=function(t){return this.classList.remove(t)};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];i=i.replace(t,e[a[1]]||"")}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;ee&&(a=0),this.index=a,t[a]}getPrevious(){let t=this.isShuffle?this.shuffled:this.list,e=t.length-1,a=this.index-1;return a<0&&(a=e),this.index=a,t[a]}getCurrent(){return(this.isShuffle?this.shuffled:this.list)[this.index]}getFile(t){return(this.isShuffle?this.shuffled:this.list)[t]}setPlaylist(t){this.index=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){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,r="";if(this.page=t,s>=e&&(s=e),e>0){let t=this.isShuffle?this.shuffled:this.list;for(let e=i;e1&&t0?"active":"inactive",nextActive:l?"active":"inactive",page:t+1+" / "+parseInt(a+1)}))}changeFiles(t,e){let a=[],i=0;for(let t of e.files)if(t&&-1!==t.type.indexOf("audio")&&null===t.name.match(".m3u")){let e=t.name.split(".");e.pop(),e=e.join("."),a.push({file:t,name:e,index:i++})}this.setPlaylist(a),a.length>0?this.renderPagination(0):alert("No Valid AudioFiles found!")}}class GUI{async init(){this.data={},this.modal=new Modal,await this.loadForVis(),await template.loadArray(["playlist-item","playlist","playlist-footer"]),this.initDropZone()}async loadForVis(){let t=visual.c;null==this.data[t]&&(this.data[t]=await fetch("out/gui/"+t+".json").then(t=>t.json()))}renderModal(t,e){let a=$("#modal"),i=a.parentNode,s=$("header .headline",a),r=$("modal-content",a);s.innerHTML=e,r.innerHTML=t,i.classList.remove("hide")}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?player.playlist.changeFiles(t,t.dataTransfer):alert("Sorry you need to upload files!"))})})}}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){this.currentModal=t,this.renderHeader(t),this.renderContent(e),this.renderFooter(a),this.showModal()}renderHeader(t){$("header .headline",this.modal).innerHTML=t}renderContent(t){$("modal-content",this.modal).innerHTML=t}renderFooter(t){$("modal-footer",this.modal).innerHTML=t}closeModal(){this.parent.addClass("hide")}isCurrent(t){return t===this.currentModal}showModal(){this.parent.removeClass("hide")}}class Visual{constructor(){this.data=[],this.dataArray=[]}updateData(){}draw(){}setup(){}}class VisualDrawer{constructor(){this.visuals={sphere:new Sphere,wave:new Wave,water:new Water},this.c="wave"}init(){this.visuals[this.c].setup(),this.updateLoop()}switch(t){null!=this.visuals[t]&&(this.c=t,this.visuals[this.c].setup())}updateLoop(){let t=shaderHandler.use(this.c),e=this.visuals[this.c];e.updateData(),e.draw(t),requestAnimationFrame(this.updateLoop.bind(this))}}class Config{constructor(){this.config={},this.name=""}loadConfigByName(t){this.saveConfig(),this.name="config-"+t,this.config=JSON.parse(this.name)}saveConfig(){""!==this.name&&localStorage.setItem(this.name,JSON.stringify(this.config))}addItem(t,e){this.config[t]=e}removeItem(t){delete this.config[t]}getItem(t,e){let a=this.config[t];return null==a&&(this.config[t]=e,a=e),a}}class Sphere extends Visual{draw(){}setup(){}}class Wave extends Visual{updateData(){this.data=[];let t=audioHandler.getFloatArray(),e=2/t.length,a=-1;for(let i=0;i{player.playlist.renderPagination(player.playlist.page)}),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()}togglePlayButton(audioHandler.audioFile.paused?"play":"pause")})}function togglePlayButton(t){$$("#play .icon").forEach(e=>{e.dataset.name===t?e.removeClass("hide"):e.addClass("hide")})}const shaderHandler=new ShaderHandler(null),audioHandler=new AudioHandler,gui=new GUI,visual=new VisualDrawer,template=new Template,player=new Player,config=new Config;let c=null,gl=null;async function startUP(){if(c=document.body.querySelector("#c"),gl=c.getContext("webgl2"),!gl)return alert("SORRY THE BROWSER DOESN'T SUPPORT WEBGL2"),!1;shaderHandler.setGL(gl),await shaderHandler.loadArray(["wave","sphere","water"],"shaders/"),await audioHandler.init(),await player.init(),await visual.init(),await gui.init(),await initHandler()}startUP().then(t=>{setTimeout(t=>{$(".loading-screen").remove()},100)}); \ No newline at end of file +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,l=e-i;return Math.sqrt(s*s+l*l)}static map(t,e,a,i,s,l){let r=(t-e)/(a-e)*(s-i)+i;return l?it.json()).then(e=>{let a;if(t){let t=Object.keys(e);a=new Float32Array(t.length);for(let i=0;i{let i=t.target;if(i.matches(e))a(t,i);else{let s=i.closest(e);s&&a(t,s)}})},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 multiply(t,e){let a=e[0],i=e[1],s=e[2],l=e[3],r=e[4],n=e[5],o=e[6],d=e[7],h=e[8],c=e[9],u=e[10],g=e[11],p=e[12],f=e[13],m=e[14],y=e[15],w=t[0],v=t[1],S=t[2],T=t[3],x=t[4],A=t[5],C=t[6],U=t[7],b=t[8],R=t[9],L=t[10],D=t[11],F=t[12],E=t[13],M=t[14],P=t[15];return[a*w+i*x+s*b+l*F,a*v+i*A+s*R+l*E,a*S+i*C+s*L+l*M,a*T+i*U+s*D+l*P,r*w+n*x+o*b+d*F,r*v+n*A+o*R+d*E,r*S+n*C+o*L+d*M,r*T+n*U+o*D+d*P,h*w+c*x+u*b+g*F,h*v+c*A+u*R+g*E,h*S+c*C+u*L+g*M,h*T+c*U+u*D+g*P,p*w+f*x+m*b+y*F,p*v+f*A+m*R+y*E,p*S+f*C+m*L+y*M,p*T+f*U+m*D+y*P]}static translate(t,e,a,i,s){s=s||new Float32Array(16);let l=t[0],r=t[1],n=t[2],o=t[3],d=t[4],h=t[5],c=t[6],u=t[7],g=t[8],p=t[9],f=t[10],m=t[11],y=t[12],w=t[13],v=t[14],S=t[15];return s[0]=l,s[1]=r,s[2]=n,s[3]=o,s[4]=d,s[5]=h,s[6]=c,s[7]=u,s[8]=g,s[9]=p,s[10]=f,s[11]=m,s[12]=l*e+d*a+g*i+y,s[13]=r*e+h*a+p*i+w,s[14]=n*e+c*a+f*i+v,s[15]=o*e+u*a+m*i+S,s}static xRotation(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){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){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)),l=TDUtils.normalize(TDUtils.cross(a,s)),r=TDUtils.normalize(TDUtils.cross(s,l));return i[0]=l[0],i[1]=l[1],i[2]=l[2],i[4]=r[0],i[5]=r[1],i[6]=r[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 l=Math.tan(.5*Math.PI-.5*t),r=1/(a-i);return s[0]=l/e,s[5]=l,s[10]=(a+i)*r,s[11]=-1,s[14]=a*i*r*2,s}static inverse(t,e){e=e||new Float32Array(16);let a=t[0],i=t[1],s=t[2],l=t[3],r=t[4],n=t[5],o=t[6],d=t[7],h=t[8],c=t[9],u=t[10],g=t[11],p=t[12],f=t[13],m=t[14],y=t[15],w=u*y,v=m*g,S=o*y,T=m*d,x=o*g,A=u*d,C=s*y,U=m*l,b=s*g,R=u*l,L=s*d,D=o*l,F=h*f,E=p*c,M=r*f,P=p*n,H=r*c,V=h*n,I=a*f,N=p*i,k=a*c,B=h*i,z=a*n,$=r*i,_=w*n+T*c+x*f-(v*n+S*c+A*f),O=v*i+C*c+R*f-(w*i+U*c+b*f),j=S*i+U*n+L*f-(T*i+C*n+D*f),G=A*i+b*n+D*c-(x*i+R*n+L*c),W=1/(a*_+r*O+h*j+p*G);return e[0]=W*_,e[1]=W*O,e[2]=W*j,e[3]=W*G,e[4]=W*(v*r+S*h+A*p-(w*r+T*h+x*p)),e[5]=W*(w*a+U*h+b*p-(v*a+C*h+R*p)),e[6]=W*(T*a+C*r+D*p-(S*a+U*r+L*p)),e[7]=W*(x*a+R*r+L*h-(A*a+b*r+D*h)),e[8]=W*(F*d+P*g+H*y-(E*d+M*g+V*y)),e[9]=W*(E*l+I*g+B*y-(F*l+N*g+k*y)),e[10]=W*(M*l+N*d+z*y-(P*l+I*d+$*y)),e[11]=W*(V*l+k*d+$*g-(H*l+B*d+z*g)),e[12]=W*(M*u+V*m+E*o-(H*m+F*o+P*u)),e[13]=W*(k*m+F*s+N*u-(I*u+B*m+E*s)),e[14]=W*(I*o+$*m+P*s-(z*m+M*s+N*o)),e[15]=W*(z*u+H*s+B*o-(k*o+$*u+V*s)),e}static aspectView(t){return[1*t,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]}static lastMatrix={m:null};static getMatrix(t,e,a,i,s,l){let r=this.lastMatrix,n=TDUtils;if(!(n.isSame("fov",t)&&n.isSame("aspect",e)&&n.isSame("near",a)&&n.isSame("far",i)&&n.isSame("cam",s)&&n.isSame("radius",l))){let n=TDUtils.perspective(TDUtils.degToRad(t),e,a,i),o=TDUtils.yRotation(TDUtils.degToRad(s));o=TDUtils.translate(o,0,0,1.5*l);let d=TDUtils.inverse(o);n=TDUtils.multiply(n,d),r.m=n}return r.m}static isSame(t,e){let a=this.lastMatrix;return a[t]===e||(a[t]=e,!1)}}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];i=i.replace(t,e[a[1]]||"")}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{window.dispatchEvent(new CustomEvent("playSong"))}).catch(t=>{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 Player{async init(){this.playlist=new Playlist}nextSong(){let t=this.playlist.getNext();audioHandler.loadSong(t.file)}prevSong(){let t=this.playlist.getPrevious();audioHandler.loadSong(t.file)}playStop(){if(!audioHandler.lastSong){let t=this.playlist.getCurrent();return void audioHandler.loadSong(t.file)}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.file)}}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))}shuffle(){let t=this.list.length;t<3&&(this.shuffled=this.list);for(let e=0;ee&&(a=0),this.index=a,t[a]}getPrevious(){let t=this.isShuffle?this.shuffled:this.list,e=t.length-1,a=this.index-1;return a<0&&(a=e),this.index=a,t[a]}getCurrent(){return(this.isShuffle?this.shuffled:this.list)[this.index]}setPlaylist(t){this.index=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){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,l="";if(this.page=t,s>=e&&(s=e),e>0){let t=this.isShuffle?this.shuffled:this.list;for(let e=i;e1&&t0?"active":"inactive",nextActive:r?"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=t.name.split(".");e.pop(),e=e.join("."),a.push({file:t,name:e,index:i++})}this.setPlaylist(a),a.length>0?this.renderPagination(0):alert("No Valid AudioFiles found!")}}class GUI{async init(){this.data={},this.modal=new Modal,await this.loadForVis(),await template.loadArray(["playlist-item","playlist","playlist-footer"]),this.initDropZone()}async loadForVis(){let t=visual.c;null==this.data[t]&&(this.data[t]=await fetch("out/gui/"+t+".json").then(t=>t.json()))}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 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){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",this.modal).innerHTML=t}closeModal(){this.parent.addClass("hide")}isCurrent(t){return t===this.currentModal}showModal(){this.parent.removeClass("hide")}}class Visual{constructor(){this.data=[],this.dataArray=[]}updateData(){}draw(){}setup(){}}class VisualDrawer{constructor(){this.visuals={sphere:new Sphere,wave:new Wave,water:new Water}}init(){this.switch("wave"),this.updateLoop()}switch(t){null!=this.visuals[t]&&(this.c=t,vConf.loadConfigByName(this.c),this.visuals[this.c].setup())}updateLoop(){let t=shaderHandler.use(this.c),e=this.visuals[this.c];e.updateData(),this.prepare(),e.draw(t),requestAnimationFrame(this.updateLoop.bind(this))}prepare(){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)}}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;let e=$("#colorBlob");e&&(e.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 Notification{constructor(){}}class Config{constructor(){this.config={},this.name=""}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}}class Sphere extends Visual{draw(){}setup(){}}class Wave extends Visual{updateData(){if(window.stopUpdate)return;let t=audioHandler.getFloatArray(),e=2/t.length,a=-1,i=0;for(let s=0;s{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))}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)}function togglePlayButton(t){$$("#play .icon").forEach(e=>{e.dataset.name===t?e.removeClass("hide"):e.addClass("hide")})}const shaderHandler=new ShaderHandler(null),audioHandler=new AudioHandler,gui=new GUI,visual=new VisualDrawer,template=new Template,player=new Player,vConf=new Config,pConf=new Config;let c=null,gl=null;async function startUP(){if(pConf.loadConfigByName("default"),c=document.body.querySelector("#c"),gl=c.getContext("webgl2"),!gl)return alert("SORRY THE BROWSER DOESN'T SUPPORT WEBGL2"),!1;shaderHandler.setGL(gl),await shaderHandler.loadArray(["wave","sphere","water"],"shaders/"),await audioHandler.init(),await player.init(),await visual.init(),await gui.init(),await imageUploader.init(),await initHandler()}startUP().then(t=>{setTimeout(t=>{$(".loading-screen").remove()},100)}); \ No newline at end of file diff --git a/out/theme/style.css b/out/theme/style.css index 6071afb..0aec36b 100644 --- a/out/theme/style.css +++ b/out/theme/style.css @@ -1 +1 @@ -*{box-sizing:border-box}:focus{outline:0}body,html{padding:0;margin:0;overflow:hidden;font-size:16px;font-family:sans-serif;background-color:#000}div{position:fixed;color:#fff;padding:1em}.hide{display:none!important}.icon{width:1em;height:1em;vertical-align:middle;font-size:1em;shape-rendering:geometricPrecision;transition:transform .5s cubic-bezier(.22,.61,.36,1);stroke-width:5px;text-align:center;display:block}#c{width:100%;height:100%}group{display:block;padding-bottom:10px;user-select:none}group-label{display:block;border-bottom:1px solid #fff;font-size:21px;font-weight:500;user-select:none}group-input{display:flex;align-items:center;margin-top:5px;user-select:none}group-input label{padding-right:10px;user-select:none;width:150px}group-input input{flex-grow:1;user-select:none;max-width:150px}group-input button{border:1px solid #dcdcdc;background-color:transparent;color:#fff;margin-left:5px}.closed{transform:translateX(-350px);transition:all .5s}.top-menu-left{display:flex}.top-menu-left div{position:relative}.loading-screen{z-index:100;background-color:#000;width:100vw;height:100vh;display:flex;align-items:center;justify-content:center;flex-direction:column}.loading-screen span{font-family:monospace;font-size:4vw;z-index:2}.loading-screen loader{position:absolute;top:calc(6vw + 10px);left:0;right:0;bottom:0;margin:auto;display:block;width:30vw;height:6px;transform:scaleX(0);transform-origin:left;background-color:#006ea8;animation:loadingBar 2s infinite}.loading-screen loader.delay{background-color:rgba(0,110,168,.24);filter:blur(1px);animation-delay:.05s}@keyframes loadingBar{0%,100%{transform:scaleX(0) scaleY(0)}50%{transform:scaleX(1) scaleY(1);transform-origin:left}100%,51%{transform-origin:right}}.grey-screen{position:fixed;top:0;left:0;background-color:rgba(0,0,0,.5);width:100vw;height:100vh;display:flex;justify-content:center;align-items:center}.grey-screen.hide{display:none!important}#modal{max-width:860px;width:90%;min-height:200px;background-color:#333;padding:unset;box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23)}#modal header{height:50px;font-size:30px;line-height:50px;padding-left:10px;overflow:hidden;background-color:#212121;display:flex}#modal header .headline{flex-grow:1}#modal header .close{margin-right:10px;font-size:24px;cursor:pointer}#modal header .close:hover{color:#ff3232}#modal modal-content{display:block;max-height:calc(100vh - 200px);overflow:auto}#modal modal-footer{display:block}input[type=range]{width:100%}input[type=range]:focus{outline:0}input[type=range]:focus::-webkit-slider-runnable-track{background:#545454}input[type=range]:focus::-ms-fill-lower{background:#424242}input[type=range]:focus::-ms-fill-upper{background:#545454}input[type=range]::-webkit-slider-runnable-track{width:100%;height:25.6px;cursor:pointer;box-shadow:1px 1px 1px #000,0 0 1px #0d0d0d;background:#424242;border-radius:0;border:0 solid #010101}input[type=range]::-webkit-slider-thumb{height:25px;width:15px;border-radius:0;cursor:pointer;margin-top:.3px}switch input{position:absolute;appearance:none;opacity:0}switch input:checked+label:after{transform:translateX(20px)}switch label{display:block;border-radius:10px;width:40px;height:20px;background-color:#dcdcdc;position:relative;cursor:pointer;padding:0}switch label:after{content:'';background-color:#ff3232;position:absolute;top:2px;left:2px;height:16px;width:16px;border-radius:10px;transition:.5s}input[type=file]{position:fixed;left:-100000vw;height:1px;width:1px}.controls{right:0;display:flex}.controls button,.menu-icon{background-color:rgba(33,33,33,.6);border:none;font-size:1.4em;border-top:4px solid #089bec;padding:1.5rem;cursor:pointer;color:#fff;transition:.5s}.controls button.active,.menu-icon.active{border-color:#ff066a}.controls button:hover,.menu-icon:hover{background-color:rgba(21,21,21,.7);border-color:#aaef22}playlist{display:flex;flex-direction:column}playlist div{padding:unset;position:unset}playlist .pagination{display:flex;justify-content:flex-end;font-size:1.5em;padding:5px}playlist .pagination .item{cursor:pointer;user-select:none;border-radius:5px;margin:0 3px}playlist .pagination .item.inactive{color:#aaa;pointer-events:none;cursor:not-allowed}playlist .pagination .item:hover{color:#006ea8}playlist .playlist-item{display:flex;padding:5px;border-bottom:1px solid #dcdcdc;cursor:pointer}playlist .playlist-item-title{margin-left:10px;padding:5px;display:flex;align-items:center}playlist .playlist-item-number{padding:5px 10px 5px 5px;border-right:1px solid #ff3232;width:50px}playlist .playlist-item:hover{background-color:rgba(0,0,0,.4)} \ No newline at end of file +@charset "UTF-8";*{box-sizing:border-box}:focus{outline:0}body,html{padding:0;margin:0;overflow:hidden;font-size:16px;font-family:sans-serif;background-color:#303030;background-repeat:no-repeat;background-position:center;background-size:cover}div{position:fixed;color:#fff;padding:1em}.hide{display:none!important}.icon{width:1em;height:1em;vertical-align:middle;font-size:1em;shape-rendering:geometricPrecision;transition:transform .5s cubic-bezier(.22,.61,.36,1);stroke-width:5px;text-align:center;display:block}::-webkit-scrollbar{width:3px;height:3px}::-webkit-scrollbar-button{width:15px;height:15px}::-webkit-scrollbar-thumb{background:#e1e1e1;border:0 none #fff;border-radius:100px}::-webkit-scrollbar-thumb:hover{background:#fff}::-webkit-scrollbar-thumb:active{background:#3949ab}::-webkit-scrollbar-track{background:#666;border:0 none #fff;border-radius:46px}::-webkit-scrollbar-track:hover{background:#666}::-webkit-scrollbar-track:active{background:#666}::-webkit-scrollbar-corner{background:0 0}#c{width:100%;height:100%}group{display:block;padding-bottom:10px;user-select:none}group-label{display:block;border-bottom:1px solid #fff;font-size:21px;font-weight:500;user-select:none}group-input{display:flex;align-items:center;margin-top:5px;user-select:none}group-input label{padding-right:10px;user-select:none;width:150px}group-input input{flex-grow:1;user-select:none;max-width:150px}group-input button{border:1px solid #dcdcdc;background-color:transparent;color:#fff;margin-left:5px}.closed{transform:translateX(-350px);transition:all .5s}.top-menu-left{display:flex}.top-menu-left div{position:relative}.loading-screen{z-index:100;background-color:#000;width:100vw;height:100vh;display:flex;align-items:center;justify-content:center;flex-direction:column}.loading-screen span{font-family:monospace;font-size:4vw;z-index:2}.loading-screen loader{position:absolute;top:calc(6vw + 10px);left:0;right:0;bottom:0;margin:auto;display:block;width:30vw;height:6px;transform:scaleX(0);transform-origin:left;background-color:#3949ab;animation:loadingBar 2s infinite}.loading-screen loader.delay{background-color:rgba(0,110,168,.24);filter:blur(1px);animation-delay:.05s}@keyframes loadingBar{0%,100%{transform:scaleX(0) scaleY(0)}50%{transform:scaleX(1) scaleY(1);transform-origin:left}100%,51%{transform-origin:right}}.grey-screen{position:fixed;top:0;left:0;background-color:rgba(0,0,0,.5);width:100vw;height:100vh;display:flex;justify-content:center;align-items:center}.grey-screen.hide{display:none!important}#image-upload form{margin-top:20px;display:flex;flex-direction:column;align-items:center}.range{-webkit-appearance:none;width:100%;margin:5px 0}.range.center{margin-left:auto;margin-right:auto}.range.right{margin-left:10%}.range:focus{outline:0}.range::-webkit-slider-runnable-track{width:100%;height:5px;cursor:pointer;background:rgba(0,0,0,.12);border-radius:15px;border:none}.range::-webkit-slider-thumb{border:0 solid rgba(0,0,30,0);height:15px;width:15px;border-radius:15px;background:#ff0089;cursor:pointer;-webkit-appearance:none;margin-top:-5px}.range:focus::-webkit-slider-runnable-track{background:rgba(89,89,89,.12)}.range::-moz-range-track{width:100%;height:5px;cursor:pointer;background:rgba(0,0,0,.12);border-radius:15px;border:none}.range::-moz-range-thumb{border:0 solid rgba(0,0,30,0);height:15px;width:15px;border-radius:15px;background:#ff0089;cursor:pointer}.range::-ms-track{width:100%;height:5px;cursor:pointer;background:0 0;border-color:transparent;color:transparent}.range::-ms-fill-lower{background:rgba(0,0,0,.12);border:none;border-radius:30px}.range::-ms-fill-upper{background:rgba(0,0,0,.12);border:none;border-radius:30px}.range::-ms-thumb{border:0 solid rgba(0,0,30,0);height:15px;width:15px;border-radius:15px;background:#ff0089;cursor:pointer}.range:focus::-ms-fill-lower{background:rgba(0,0,0,.12)}.range:focus::-ms-fill-upper{background:rgba(89,89,89,.12)}.input{width:90%;position:relative;display:inline-block;margin-top:1rem;background-color:transparent}input:focus+.input:after{width:100%}.input input:not([type=range]){border:none;border-bottom:2px solid #dcdcdc;position:relative;width:100%;background-color:transparent;padding:5px;color:#fff}.input input:not([type=range]):focus{outline:0}.input.center{margin-left:auto;margin-right:auto}.input.right{margin-left:10%}.input-label{display:none}.floating-label .input-label{display:block;position:absolute;top:0;transition:.4s cubic-bezier(.25,.8,.25,1);pointer-events:none;border-bottom:1px solid transparent}.floating-label input:focus~.input-label,.floating-label input:valid~.input-label{transform:translateY(-.72rem);color:#ff0089;font-size:.7rem}.floating-label input:valid~.input-label{transform:translateY(-.72rem);color:#3949ab;font-size:.7rem}.focus{content:'';width:0;background-color:#ff0a8e;position:absolute;bottom:0;left:0;right:0;margin:auto;height:2px;transition:.4s cubic-bezier(.8,.4,.25,1)}input:focus~.focus{width:100%}*{box-sizing:border-box}switch input{position:absolute;appearance:none;opacity:0}switch input:checked+label:after{transform:translateX(20px)}switch label{display:block;border-radius:10px;width:40px;height:20px;background-color:#dcdcdc;position:relative;cursor:pointer;padding:0}switch label:after{content:'';background-color:#ff3232;position:absolute;top:2px;left:2px;height:16px;width:16px;border-radius:10px;transition:.5s}input[type=file]{position:fixed;left:-100000vw;height:1px;width:1px}input[type=color]{opacity:0}.color-picker{position:relative}.color-picker input{position:absolute!important;top:0}#colorBlob{display:block;width:100%;height:20px;margin:5px 0}.button{border:1px solid #3949ab;padding:.5em 1em;cursor:pointer;transition:.5s}.button:hover{border-color:#ff0089}.controls{right:0;display:flex}.controls button,.menu-icon{background-color:rgba(33,33,33,.6);border:none;font-size:1.4em;border-top:4px solid #3949ab;padding:1.5rem;cursor:pointer;color:#fff;transition:.5s}.controls button.active,.menu-icon.active{border-color:#ff0089}.controls button:hover,.menu-icon:hover{background-color:rgba(21,21,21,.7);border-color:#5ff507}playlist{display:flex;flex-direction:column}playlist div{padding:unset;position:unset}playlist .pagination{display:flex;justify-content:flex-end;font-size:1.5em;padding:5px}playlist .pagination .current{font-size:.9em}playlist .pagination .item{cursor:pointer;user-select:none;border-radius:5px;margin:0 3px;display:flex;align-items:center}playlist .pagination .item.inactive{color:#aaa;pointer-events:none;cursor:not-allowed}playlist .pagination .item:hover{color:#3949ab}playlist .playlist-item{display:flex;padding:5px;box-shadow:0 -1px 0 0 rgba(0,0,0,.08);cursor:pointer;transition:.5s}playlist .playlist-item.active{background-color:rgba(0,0,0,.2)}playlist .playlist-item.active .playlist-item-title:before{content:'🔊 ';padding-right:5px}playlist .playlist-item-title{margin-left:10px;padding:5px;display:flex;align-items:center}playlist .playlist-item-number{padding:5px 10px 5px 5px;width:50px}playlist .playlist-item:hover{background-color:rgba(0,0,0,.4)}#modal{max-width:860px;width:90%;background-color:#303030;padding:unset;box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23);border-radius:15px;overflow:hidden}#modal div{position:unset}#modal header{height:50px;font-size:30px;line-height:50px;padding-left:10px;overflow:hidden;background-color:#212121;display:flex;box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23)}#modal header .headline{flex-grow:1}#modal header .close{margin-right:10px;font-size:24px;cursor:pointer}#modal header .close:hover{color:#3949ab}#modal modal-content{display:block;max-height:calc(100vh - 200px);overflow:auto}#modal modal-footer{display:block;box-shadow:0 -3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23)}.notification{right:0;bottom:0;display:flex;width:320px} \ No newline at end of file diff --git a/out/tpl/image.tpl b/out/tpl/image.tpl new file mode 100644 index 0000000..bf02e73 --- /dev/null +++ b/out/tpl/image.tpl @@ -0,0 +1,27 @@ +
+ + + +
+ + +

You only want to change the color? do it!

+ + + +
+
\ No newline at end of file diff --git a/out/tpl/playlist-item.tpl b/out/tpl/playlist-item.tpl index 5e988d0..a6f0512 100644 --- a/out/tpl/playlist-item.tpl +++ b/out/tpl/playlist-item.tpl @@ -1,4 +1,4 @@ - +
$nr$
$title$
\ No newline at end of file diff --git a/raw/javascript/app.js b/raw/javascript/app.js index d1d3b6d..3f564c8 100644 --- a/raw/javascript/app.js +++ b/raw/javascript/app.js @@ -4,12 +4,14 @@ const shaderHandler = new ShaderHandler(null), visual = new VisualDrawer(), template = new Template(), player = new Player(), - config = new Config(); + vConf = new Config(), + pConf = new Config(); let c = null, gl = null; async function startUP() { + pConf.loadConfigByName('default'); c = document.body.querySelector('#c'), gl = c.getContext("webgl2"); if (!gl) { @@ -22,6 +24,7 @@ async function startUP() { await player.init(); await visual.init(); await gui.init(); + await imageUploader.init(); await initHandler(); } diff --git a/raw/javascript/audio.js b/raw/javascript/audio.js index 0d34bf3..a0c7ae8 100644 --- a/raw/javascript/audio.js +++ b/raw/javascript/audio.js @@ -5,8 +5,8 @@ class AudioHandler { let self = this; self.isStarted = false; self.audioFile = new Audio(); - self.actx = new AudioContext() - self.analyser = self.actx.createAnalyser() + self.actx = new AudioContext(); + self.analyser = self.actx.createAnalyser(); self.analyser.fftSize = 4096; self.lastSong = null; await self.connectAll(); @@ -52,9 +52,13 @@ class AudioHandler { } self.lastSong = this.audioFile.src = URL.createObjectURL(src); if (!this.isStarted) { - this.start(); + this.start().catch(alert); } - this.audioFile.play(); + this.audioFile.play().then(e => { + window.dispatchEvent(new CustomEvent('playSong')); + }).catch(e => { + player.nextSong(); + }); } getIntArray(steps) { @@ -64,7 +68,7 @@ class AudioHandler { } getFloatArray() { - let dataArray = new Float32Array(this.analyser.frequencyBinCount); + let dataArray = new Float32Array(this.analyser.fftSize); this.analyser.getFloatTimeDomainData(dataArray); return dataArray; } diff --git a/raw/javascript/config.js b/raw/javascript/config.js index 07ec7ad..0ddffad 100644 --- a/raw/javascript/config.js +++ b/raw/javascript/config.js @@ -5,26 +5,29 @@ class Config { } loadConfigByName(name) { - this.saveConfig(); + this.save(); this.name = 'config-' + name; - this.config = JSON.parse(this.name); + let item = localStorage.getItem(this.name); + if (item) { + this.config = JSON.parse(item); + } } - saveConfig() { + save() { if (this.name !== '') { localStorage.setItem(this.name, JSON.stringify(this.config)); } } - addItem(name, value) { + set(name, value) { this.config[name] = value; } - removeItem(name) { + remove(name) { delete this.config[name]; } - getItem(name, def) { + get(name, def) { let value = this.config[name]; if (value === undefined || value === null) { this.config[name] = def; diff --git a/raw/javascript/eventHandler.js b/raw/javascript/eventHandler.js index 41fdfd6..d68bf18 100644 --- a/raw/javascript/eventHandler.js +++ b/raw/javascript/eventHandler.js @@ -2,6 +2,7 @@ async function initHandler() { let body = $('body'); $('.playlist.menu-icon').addEventListener('click', e => { player.playlist.renderPagination(player.playlist.page); + gui.modal.showModal(); }); body.addDelegatedEventListener('click', '.playlist-item', (e, el) => { @@ -21,16 +22,37 @@ async function initHandler() { case 'play': player.playStop(); break; + case 'shuffle': + player.playlist.isShuffle = !player.playlist.isShuffle; + toggleShuffle(); + break; } togglePlayButton(audioHandler.audioFile.paused ? 'play' : 'pause'); }); + window.addEventListener('playSong', setActiveOnPlaylist); + $('.upload-image').addEventListener('click', imageUploader.renderModal.bind(imageUploader)); } +function setActiveOnPlaylist(e) { + let item = $('.playlist-item[data-index="' + player.playlist.index + '"]'), + active = $('.playlist-item.active'); + if (active) { + active.removeClass('active'); + } + if (item) { + item.addClass('active'); + } +} + +function toggleShuffle() { + let active = player.playlist.isShuffle; + $('#shuffle').toggleCheck('active', active); +} function togglePlayButton(status) { let icons = $$('#play .icon'); icons.forEach(el => { - if(el.dataset.name === status) { + if (el.dataset.name === status) { el.removeClass('hide'); } else { el.addClass('hide'); diff --git a/raw/javascript/gl/glUtils.js b/raw/javascript/gl/glUtils.js new file mode 100644 index 0000000..2c2e304 --- /dev/null +++ b/raw/javascript/gl/glUtils.js @@ -0,0 +1,333 @@ +// most of the functions are from https://webglfundamentals.org/webgl/resources/m4.js! but i doesnt want to use them all and make some adjustment to them! +class TDUtils { + static multiply(a, b) { + let b00 = b[0]; + let b01 = b[1]; + let b02 = b[2]; + let b03 = b[3]; + let b10 = b[4]; + let b11 = b[5]; + let b12 = b[6]; + let b13 = b[7]; + let b20 = b[8]; + let b21 = b[9]; + let b22 = b[10]; + let b23 = b[11]; + let b30 = b[12]; + let b31 = b[13]; + let b32 = b[14]; + let b33 = b[15]; + let a00 = a[0]; + let a01 = a[1]; + let a02 = a[2]; + let a03 = a[3]; + let a10 = a[4]; + let a11 = a[5]; + let a12 = a[6]; + let a13 = a[7]; + let a20 = a[8]; + let a21 = a[9]; + let a22 = a[10]; + let a23 = a[11]; + let a30 = a[12]; + let a31 = a[13]; + let a32 = a[14]; + let a33 = a[15]; + return [ + b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30, + b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31, + b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32, + b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33, + b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30, + b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31, + b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32, + b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33, + b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30, + b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31, + b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32, + b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33, + b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30, + b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31, + b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32, + b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33 + ]; + } + + static translate(m, tx, ty, tz, dst) { + dst = dst || new Float32Array(16); + + let m00 = m[0], + m01 = m[1], + m02 = m[2], + m03 = m[3], + m10 = m[4], + m11 = m[5], + m12 = m[6], + m13 = m[7], + m20 = m[8], + m21 = m[9], + m22 = m[10], + m23 = m[11], + m30 = m[12], + m31 = m[13], + m32 = m[14], + m33 = m[15]; + dst[0] = m00; + dst[1] = m01; + dst[2] = m02; + dst[3] = m03; + dst[4] = m10; + dst[5] = m11; + dst[6] = m12; + dst[7] = m13; + dst[8] = m20; + dst[9] = m21; + dst[10] = m22; + dst[11] = m23; + + dst[12] = m00 * tx + m10 * ty + m20 * tz + m30; + dst[13] = m01 * tx + m11 * ty + m21 * tz + m31; + dst[14] = m02 * tx + m12 * ty + m22 * tz + m32; + dst[15] = m03 * tx + m13 * ty + m23 * tz + m33; + + return dst; + } + + static xRotation(angleInRadians) { + let c = Math.cos(angleInRadians); + let s = Math.sin(angleInRadians); + + return [ + 1, 0, 0, 0, + 0, c, s, 0, + 0, -s, c, 0, + 0, 0, 0, 1, + ]; + } + + static yRotation(angleInRadians) { + let c = Math.cos(angleInRadians); + let s = Math.sin(angleInRadians); + + return [ + c, 0, -s, 0, + 0, 1, 0, 0, + s, 0, c, 0, + 0, 0, 0, 1, + ]; + } + + static zRotation(angleInRadians) { + let c = Math.cos(angleInRadians); + let s = Math.sin(angleInRadians); + + return [ + c, s, 0, 0, + -s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, + ]; + } + + static degToRad(d) { + return d * Math.PI / 180; + } + + static scale(sx, sy, sz, dst) { + dst = dst || new Float32Array(16); + dst[0] = sx; + dst[5] = sy; + dst[10] = sz; + return dst; + } + + static lookAt(cameraPosition, target, up, dst) { + dst = dst || new Float32Array(16); + let zAxis = TDUtils.normalize( + TDUtils.subtractVectors(cameraPosition, target)); + let xAxis = TDUtils.normalize(TDUtils.cross(up, zAxis)); + let yAxis = TDUtils.normalize(TDUtils.cross(zAxis, xAxis)); + + dst[0] = xAxis[0]; + dst[1] = xAxis[1]; + dst[2] = xAxis[2]; + dst[4] = yAxis[0]; + dst[5] = yAxis[1]; + dst[6] = yAxis[2]; + dst[8] = zAxis[0]; + dst[9] = zAxis[1]; + dst[10] = zAxis[2]; + dst[12] = cameraPosition[0]; + dst[13] = cameraPosition[1]; + dst[14] = cameraPosition[2]; + dst[15] = 1; + + return dst; + } + + static cross(a, b, dst) { + dst = dst || new Float32Array(3); + dst[0] = a[1] * b[2] - a[2] * b[1]; + dst[1] = a[2] * b[0] - a[0] * b[2]; + dst[2] = a[0] * b[1] - a[1] * b[0]; + return dst; + } + + static normalize(v, dst) { + dst = dst || new Float32Array(3); + let length = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); + if (length > 0.00001) { + dst[0] = v[0] / length; + dst[1] = v[1] / length; + dst[2] = v[2] / length; + } + return dst; + } + + static subtractVectors(a, b, dst) { + dst = dst || new Float32Array(3); + dst[0] = a[0] - b[0]; + dst[1] = a[1] - b[1]; + dst[2] = a[2] - b[2]; + return dst; + } + + static perspective(fieldOfViewInRadians, aspect, near, far, dst) { + dst = dst || new Float32Array(16); + let f = Math.tan(Math.PI * 0.5 - 0.5 * fieldOfViewInRadians), + rangeInv = 1.0 / (near - far); + + dst[0] = f / aspect; + dst[5] = f; + dst[10] = (near + far) * rangeInv; + dst[11] = -1; + dst[14] = near * far * rangeInv * 2; + return dst; + } + + static inverse(m, dst) { + dst = dst || new Float32Array(16); + let m00 = m[0], + m01 = m[1], + m02 = m[2], + m03 = m[3], + m10 = m[4], + m11 = m[5], + m12 = m[6], + m13 = m[7], + m20 = m[8], + m21 = m[9], + m22 = m[10], + m23 = m[11], + m30 = m[12], + m31 = m[13], + m32 = m[14], + m33 = m[15], + tmp_0 = m22 * m33, + tmp_1 = m32 * m23, + tmp_2 = m12 * m33, + tmp_3 = m32 * m13, + tmp_4 = m12 * m23, + tmp_5 = m22 * m13, + tmp_6 = m02 * m33, + tmp_7 = m32 * m03, + tmp_8 = m02 * m23, + tmp_9 = m22 * m03, + tmp_10 = m02 * m13, + tmp_11 = m12 * m03, + tmp_12 = m20 * m31, + tmp_13 = m30 * m21, + tmp_14 = m10 * m31, + tmp_15 = m30 * m11, + tmp_16 = m10 * m21, + tmp_17 = m20 * m11, + tmp_18 = m00 * m31, + tmp_19 = m30 * m01, + tmp_20 = m00 * m21, + tmp_21 = m20 * m01, + tmp_22 = m00 * m11, + tmp_23 = m10 * m01, + + t0 = (tmp_0 * m11 + tmp_3 * m21 + tmp_4 * m31) - + (tmp_1 * m11 + tmp_2 * m21 + tmp_5 * m31), + t1 = (tmp_1 * m01 + tmp_6 * m21 + tmp_9 * m31) - + (tmp_0 * m01 + tmp_7 * m21 + tmp_8 * m31), + t2 = (tmp_2 * m01 + tmp_7 * m11 + tmp_10 * m31) - + (tmp_3 * m01 + tmp_6 * m11 + tmp_11 * m31), + t3 = (tmp_5 * m01 + tmp_8 * m11 + tmp_11 * m21) - + (tmp_4 * m01 + tmp_9 * m11 + tmp_10 * m21), + + d = 1.0 / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3); + + dst[0] = d * t0; + dst[1] = d * t1; + dst[2] = d * t2; + dst[3] = d * t3; + dst[4] = d * ((tmp_1 * m10 + tmp_2 * m20 + tmp_5 * m30) - + (tmp_0 * m10 + tmp_3 * m20 + tmp_4 * m30)); + dst[5] = d * ((tmp_0 * m00 + tmp_7 * m20 + tmp_8 * m30) - + (tmp_1 * m00 + tmp_6 * m20 + tmp_9 * m30)); + dst[6] = d * ((tmp_3 * m00 + tmp_6 * m10 + tmp_11 * m30) - + (tmp_2 * m00 + tmp_7 * m10 + tmp_10 * m30)); + dst[7] = d * ((tmp_4 * m00 + tmp_9 * m10 + tmp_10 * m20) - + (tmp_5 * m00 + tmp_8 * m10 + tmp_11 * m20)); + dst[8] = d * ((tmp_12 * m13 + tmp_15 * m23 + tmp_16 * m33) - + (tmp_13 * m13 + tmp_14 * m23 + tmp_17 * m33)); + dst[9] = d * ((tmp_13 * m03 + tmp_18 * m23 + tmp_21 * m33) - + (tmp_12 * m03 + tmp_19 * m23 + tmp_20 * m33)); + dst[10] = d * ((tmp_14 * m03 + tmp_19 * m13 + tmp_22 * m33) - + (tmp_15 * m03 + tmp_18 * m13 + tmp_23 * m33)); + dst[11] = d * ((tmp_17 * m03 + tmp_20 * m13 + tmp_23 * m23) - + (tmp_16 * m03 + tmp_21 * m13 + tmp_22 * m23)); + dst[12] = d * ((tmp_14 * m22 + tmp_17 * m32 + tmp_13 * m12) - + (tmp_16 * m32 + tmp_12 * m12 + tmp_15 * m22)); + dst[13] = d * ((tmp_20 * m32 + tmp_12 * m02 + tmp_19 * m22) - + (tmp_18 * m22 + tmp_21 * m32 + tmp_13 * m02)); + dst[14] = d * ((tmp_18 * m12 + tmp_23 * m32 + tmp_15 * m02) - + (tmp_22 * m32 + tmp_14 * m02 + tmp_19 * m12)); + dst[15] = d * ((tmp_22 * m22 + tmp_16 * m02 + tmp_21 * m12) - + (tmp_20 * m12 + tmp_23 * m22 + tmp_17 * m02)); + + return dst; + } + + static aspectView(aspect) { + return [ + 1 * aspect, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ] + } + + static lastMatrix = {m: null}; + + static getMatrix(fov, aspect, near, far, camAngle, radius) { + let lMat = this.lastMatrix, + u = TDUtils; + if (!u.isSame('fov', fov) + || !u.isSame('aspect', aspect) + || !u.isSame('near', near) + || !u.isSame('far', far) + || !u.isSame('cam', camAngle) + || !u.isSame('radius', radius) + ) { + let matrix = TDUtils.perspective(TDUtils.degToRad(fov), aspect, near, far), + cameraMatrix = TDUtils.yRotation(TDUtils.degToRad(camAngle)); + cameraMatrix = TDUtils.translate(cameraMatrix, 0, 0, radius * 1.5); + let viewMatrix = TDUtils.inverse(cameraMatrix); + matrix = TDUtils.multiply(matrix, viewMatrix) + lMat.m = matrix; + } + return lMat.m; + } + + static isSame(key, value) { + let lMat = this.lastMatrix; + if (lMat[key] !== value) { + lMat[key] = value; + return false; + } + return true; + } +} \ No newline at end of file diff --git a/raw/javascript/handler.js b/raw/javascript/gl/handler.js similarity index 100% rename from raw/javascript/handler.js rename to raw/javascript/gl/handler.js diff --git a/raw/javascript/gui.js b/raw/javascript/gui.js index b24922c..d400bf4 100644 --- a/raw/javascript/gui.js +++ b/raw/javascript/gui.js @@ -20,16 +20,6 @@ class GUI { } } - renderModal(content, title) { - let modal = $('#modal'), - p = modal.parentNode, - h = $('header .headline', modal), - con = $('modal-content', modal); - h.innerHTML = title; - con.innerHTML = content; - p.classList.remove('hide'); - } - initDropZone() { let items = 'drag dragstart dragend dragover dragenter dragleave drop'.split(' '); items.forEach(el => { @@ -38,6 +28,7 @@ class GUI { e.stopPropagation(); if (e.type === 'drop') { if (e.dataTransfer.files.length > 0) { + e.dataTransfer.id = 'upload-dir'; player.playlist.changeFiles(e, e.dataTransfer); } else { alert("Sorry you need to upload files!"); @@ -66,7 +57,6 @@ class Modal { this.renderHeader(title); this.renderContent(content); this.renderFooter(footer); - this.showModal(); } renderHeader(header) { diff --git a/raw/javascript/imageUploader.js b/raw/javascript/imageUploader.js new file mode 100644 index 0000000..bf25d8a --- /dev/null +++ b/raw/javascript/imageUploader.js @@ -0,0 +1,78 @@ +class ImageUploader { + async init() { + this.image = pConf.get("bgURL", ""); + this.color = pConf.get("bgColour", "#000000"); + this.alpha = pConf.get("alphaValue", 0.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(e, el) { + if (el.id === 'color') { + this.color = el.value; + } else if (el.id === "alphaValue") { + this.alpha = el.value; + } else { + pConf.set('bgMode', el.id); + if (el.id === 'image') { + el.files[0].toBase64((e, b) => { + if (b) { + alert("Error converting image!"); + return; + } + pConf.set('bgURL', e.currentTarget.result); + pConf.save(); + }) + this.image = URL.createObjectURL(el.files[0]); + } else { + this.image = el.value; + pConf.set('bgURL', this.image); + } + } + pConf.set('bgColour', this.color); + pConf.set('alphaValue', this.alpha); + this.applyValues(); + pConf.save(); + } + + applyValues() { + let body = $('body'); + body.style.backgroundImage = 'url(' + this.image + ')'; + body.style.backgroundColor = this.color; + let blob = $('#colorBlob'); + if (blob) { + blob.style.backgroundColor = this.color; + } + } + + getRealImage() { + let mode = pConf.get('bgMode'), + value = pConf.get("bgURL", ""); + if (mode === 'image') { + if (value !== '' && value.startsWith('data:image')) { + let split = value.split(";"), + type = split.shift(), + message = split.join(";").replace("base64,", ""); + this.image = URL.createObjectURL(b64toBlob(message, type)); + } + } else { + this.image = value; + } + } +} + +const imageUploader = new ImageUploader(); \ No newline at end of file diff --git a/raw/javascript/notification.js b/raw/javascript/notification.js new file mode 100644 index 0000000..09e53be --- /dev/null +++ b/raw/javascript/notification.js @@ -0,0 +1,5 @@ +class Notification { + constructor() { + + } +} \ No newline at end of file diff --git a/raw/javascript/player.js b/raw/javascript/player.js index 6085afc..9332899 100644 --- a/raw/javascript/player.js +++ b/raw/javascript/player.js @@ -17,6 +17,7 @@ class Player { if (!audioHandler.lastSong) { let next = this.playlist.getCurrent(); audioHandler.loadSong(next.file); + return; } let audioFile = audioHandler.audioFile; if (audioFile.paused) { @@ -24,10 +25,12 @@ class Player { } else { audioFile.pause(); } + window.dispatchEvent(new CustomEvent('playSong')); } playByID(number) { - let next = this.playlist.getFile(number); + this.playlist.index = number; + let next = this.playlist.getCurrent(); audioHandler.loadSong(next.file); } } @@ -90,11 +93,6 @@ class Playlist { return items[this.index]; } - getFile(index) { - let items = this.isShuffle ? this.shuffled : this.list; - return items[index]; - } - // on new upload... this has to be an array! setPlaylist(files) { this.index = 0; @@ -133,9 +131,10 @@ class Playlist { let items = this.isShuffle ? this.shuffled : this.list; for (let i = s; i < e; i++) { let obj = { - index: i, + index: i.toString(), nr: i + 1, - title: items[i].name + title: items[i].name, + active: !audioHandler.audioFile.paused && i === this.index ? 'active' : '' } data += template.parseTemplate("playlist-item", obj); } @@ -158,6 +157,10 @@ class Playlist { //playlist handler for file input! changeFiles(e, el) { + + if (el.id !== 'upload-dir') { + return; + } let files = []; let i = 0; for (let file of el.files) { diff --git a/raw/javascript/utils.js b/raw/javascript/utils.js index e426e72..5f4f58f 100644 --- a/raw/javascript/utils.js +++ b/raw/javascript/utils.js @@ -51,13 +51,12 @@ class VTUtils { } static hsvToRgb(h, s, v) { - var r, g, b; - - var i = Math.floor(h * 6); - var f = h * 6 - i; - var p = v * (1 - s); - var q = v * (1 - f * s); - var t = v * (1 - (1 - f) * s); + let r, g, b, + i = Math.floor(h * 6), + f = h * 6 - i, + p = v * (1 - s), + q = v * (1 - f * s), + t = v * (1 - (1 - f) * s); switch (i % 6) { case 0: @@ -142,103 +141,9 @@ class VTVector { } } -class TDUtils { - static multiply(a, b) { - let b00 = b[0 * 4 + 0]; - let b01 = b[0 * 4 + 1]; - let b02 = b[0 * 4 + 2]; - let b03 = b[0 * 4 + 3]; - let b10 = b[1 * 4 + 0]; - let b11 = b[1 * 4 + 1]; - let b12 = b[1 * 4 + 2]; - let b13 = b[1 * 4 + 3]; - let b20 = b[2 * 4 + 0]; - let b21 = b[2 * 4 + 1]; - let b22 = b[2 * 4 + 2]; - let b23 = b[2 * 4 + 3]; - let b30 = b[3 * 4 + 0]; - let b31 = b[3 * 4 + 1]; - let b32 = b[3 * 4 + 2]; - let b33 = b[3 * 4 + 3]; - let a00 = a[0 * 4 + 0]; - let a01 = a[0 * 4 + 1]; - let a02 = a[0 * 4 + 2]; - let a03 = a[0 * 4 + 3]; - let a10 = a[1 * 4 + 0]; - let a11 = a[1 * 4 + 1]; - let a12 = a[1 * 4 + 2]; - let a13 = a[1 * 4 + 3]; - let a20 = a[2 * 4 + 0]; - let a21 = a[2 * 4 + 1]; - let a22 = a[2 * 4 + 2]; - let a23 = a[2 * 4 + 3]; - let a30 = a[3 * 4 + 0]; - let a31 = a[3 * 4 + 1]; - let a32 = a[3 * 4 + 2]; - let a33 = a[3 * 4 + 3]; - let dst = []; - dst[0] = b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30; - dst[1] = b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31; - dst[2] = b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32; - dst[3] = b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33; - dst[4] = b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30; - dst[5] = b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31; - dst[6] = b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32; - dst[7] = b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33; - dst[8] = b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30; - dst[9] = b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31; - dst[10] = b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32; - dst[11] = b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33; - dst[12] = b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30; - dst[13] = b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31; - dst[14] = b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32; - dst[15] = b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33; - return dst; - } - - static xRotation(angleInRadians) { - let c = Math.cos(angleInRadians); - let s = Math.sin(angleInRadians); - - return [ - 1, 0, 0, 0, - 0, c, s, 0, - 0, -s, c, 0, - 0, 0, 0, 1, - ]; - } - - static yRotation(angleInRadians) { - let c = Math.cos(angleInRadians); - let s = Math.sin(angleInRadians); - - return [ - c, 0, -s, 0, - 0, 1, 0, 0, - s, 0, c, 0, - 0, 0, 0, 1, - ]; - } - - static zRotation(angleInRadians) { - let c = Math.cos(angleInRadians); - let s = Math.sin(angleInRadians); - - return [ - c, s, 0, 0, - -s, c, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1, - ]; - } - - static degToRad(d) { - return d * Math.PI / 180; - } -} - function $(sel, s) { - return $$(sel, s)[0]; + s = s || document; + return s.querySelector(sel); } function $$(sel, s) { @@ -261,13 +166,73 @@ Node.prototype.addDelegatedEventListener = function (type, aim, cb) { }; Node.prototype.hasClass = function (className) { - return this.classList.contains(className); + let items = className.split(','), + has = null; + for (let item of items) { + if (has === false) { + break; + } + has = this.classList.contains(item.trim()); + } + return has === true; } Node.prototype.addClass = function (className) { - return this.classList.add(className); + let items = className.split(','); + for (let item of items) { + this.classList.add(item.trim()); + } + return this; } Node.prototype.removeClass = function (className) { - return this.classList.remove(className); + let items = className.split(','); + for (let item of items) { + this.classList.remove(item.trim()); + } + return this; +} +Node.prototype.toggleClass = function (className, force) { + let items = className.split(','); + for (let item of items) { + this.classList.toggle(item.trim(), force); + } +} +Node.prototype.switchClass = function (clOne, clTwo, twoOne) { + let cl = this.classList; + if (twoOne) { + cl.remove(clOne); + cl.add(clTwo) + } else { + cl.remove(clTwo) + cl.add(clOne) + } +} +Node.prototype.toggleCheck = function (className, force) { + let cl = this.classList; + let items = className.split(','); + for (let item of items) { + let clOne = item.trim(); + if (force) { + cl.add(clOne); + } else { + cl.remove(clOne) + } + } +} + +File.prototype.toBase64 = function (cb) { + const reader = new FileReader(); + reader.onloadend = cb; + reader.readAsDataURL(this); +} + +function b64toBlob(b64Data, type) { + const byteCharacters = atob(b64Data); + const byteNumbers = new Array(byteCharacters.length); + for (let i = 0; i < byteCharacters.length; i++) { + byteNumbers[i] = byteCharacters.charCodeAt(i); + } + const byteArray = new Uint8Array(byteNumbers); + return new Blob([byteArray], {type: type}); } function create(name, content) { @@ -279,7 +244,28 @@ function create(name, content) { } function append(to, array) { - for (let item in array) { - to.appendChild(array[item]); + for (let item of array) { + to.appendChild(item); } +} + +function loadFromJSONToVisualData(useKeys) { + fetch('/audio-vis/out/showCase.json').then((res) => { + return res.json() + }).then(e => { + let floatArray; + if (useKeys) { + let keys = Object.keys(e); + floatArray = new Float32Array(keys.length); + for (let i = 0; i < keys.length; i++) { + floatArray[i] = e[keys[i]]; + } + } else { + floatArray = new Float32Array(e.length); + for (let i = 0; i < e.length; i++) { + floatArray[i] = e[i]; + } + } + visual.visuals[visual.c].data = floatArray; + }) } \ No newline at end of file diff --git a/raw/javascript/visual.js b/raw/javascript/visual.js index 1df87b0..14c0356 100644 --- a/raw/javascript/visual.js +++ b/raw/javascript/visual.js @@ -22,17 +22,17 @@ class VisualDrawer { "wave": new Wave(), "water": new Water() } - this.c = "wave"; } init() { - this.visuals[this.c].setup(); + this.switch('wave'); this.updateLoop(); } switch(visual) { if (this.visuals[visual] != null) { this.c = visual; + vConf.loadConfigByName(this.c); this.visuals[this.c].setup(); } } @@ -42,7 +42,19 @@ class VisualDrawer { let pro = shaderHandler.use(self.c); let vis = self.visuals[self.c]; vis.updateData(); + this.prepare(); vis.draw(pro); requestAnimationFrame(self.updateLoop.bind(self)) } + + prepare() { + 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); + } } \ No newline at end of file diff --git a/raw/javascript/visuals/wave.js b/raw/javascript/visuals/wave.js index 7b583b5..4552e78 100644 --- a/raw/javascript/visuals/wave.js +++ b/raw/javascript/visuals/wave.js @@ -1,22 +1,32 @@ // 3D Audio-Waves -> maybe also 2D? class Wave extends Visual { updateData() { - this.data = []; + //only for debug! remove pls + if (window.stopUpdate) { + return; + } let data = audioHandler.getFloatArray(); let add = 2 / data.length, x = -1; + let outerLoop = 0; for (let i = 0; i < data.length; i++) { - this.data.push(x, data[i], data[i]); + //first + this.data[outerLoop] = x; + this.data[outerLoop + 1] = data[i]; + this.data[outerLoop + 2] = 0; + //second + this.data[outerLoop + 3] = x; + //third + this.data[outerLoop + 6] = x; + this.data[outerLoop + 8] = data[i + 1] || 0; + outerLoop += 9; x += add; } } draw(program) { - c.width = window.innerWidth; - c.height = window.innerHeight; this.prepare(program); let position = this.position, - color = this.color, positionBuffer = gl.createBuffer(); this.rotate(program); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); @@ -25,36 +35,38 @@ class Wave extends Visual { gl.bindVertexArray(vao); gl.enableVertexAttribArray(position); gl.vertexAttribPointer(position, 3, gl.FLOAT, true, 0, 0); - gl.clearColor(0, 0, 0, 1); - gl.enable(gl.DEPTH_TEST); - gl.depthFunc(gl.LEQUAL); - gl.clearDepth(2.0) - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); - gl.drawArrays(gl.LINE_STRIP || gl.POINTS, 0, this.data.length / 3); + gl.drawArrays(vConf.get("waveForm", gl.TRIANGLES), 0, this.data.length / 3); } rotate(program) { - let aspect = c.height / c.width, + let aspect = c.width / c.height, matrix = [ - 1 / aspect, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - ] - matrix = TDUtils.multiply(matrix, TDUtils.xRotation(config.getItem("xRotate", 0))); - matrix = TDUtils.multiply(matrix, TDUtils.yRotation(config.getItem("yRotate", 0))); - matrix = TDUtils.multiply(matrix, TDUtils.zRotation(config.getItem("zRotate", 0))); + 1 / aspect, 0, 0, 0, + 0, 0.6, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ] + matrix = TDUtils.multiply(matrix, TDUtils.xRotation(vConf.get("xRotate", 0))); + matrix = TDUtils.multiply(matrix, TDUtils.yRotation(vConf.get("yRotate", 0))); + matrix = TDUtils.multiply(matrix, TDUtils.zRotation(vConf.get("zRotate", 0))); let rotate = gl.getUniformLocation(program, "u_matrix"); gl.uniformMatrix4fv(rotate, false, matrix); } setup() { audioHandler.fftSize(16384) + this.data = new Float32Array(16384 * 9); + vConf.get("zRotate", TDUtils.degToRad(-30)); + vConf.get("yRotate", TDUtils.degToRad(50)); + vConf.get("xRotate", TDUtils.degToRad(10)); } prepare(program) { this.position = gl.getAttribLocation(program, "a_position"); this.color = gl.getUniformLocation(program, "u_color"); + let lightPos = gl.getUniformLocation(program, "u_lightPos"), + matrix = gl.getUniformLocation(program, "u_matrix"); + gl.uniform3fv(lightPos, vConf.get("light", [0, 5, -56])); + //gl.uniformMatrix4fv(matrix, false, TDUtils.getMatrix(90, c.width / c.height, 1, 2000, 200, 200)); } } \ No newline at end of file diff --git a/raw/scss/_controls.scss b/raw/scss/_controls.scss index b1f7dc2..fcd9a19 100644 --- a/raw/scss/_controls.scss +++ b/raw/scss/_controls.scss @@ -7,18 +7,18 @@ background-color: rgba(33, 33, 33, .6); border: none; font-size: 1.4em; - border-top: 4px solid #089bec; + border-top: 4px solid $primary; padding: 1.5rem; cursor: pointer; color: #fff; transition: .5s; &.active { - border-color: #ff066a; + border-color: $second; } &:hover { background-color: rgba(21, 21, 21, .7); - border-color: #aaef22; + border-color: $active; } } \ No newline at end of file diff --git a/raw/scss/_gui.scss b/raw/scss/_gui.scss index f62aea7..c335b35 100644 --- a/raw/scss/_gui.scss +++ b/raw/scss/_gui.scss @@ -84,7 +84,7 @@ group-input { height: 6px; transform: scaleX(0); transform-origin: left; - background-color: #006ea8; + background-color: $primary; animation: loadingBar 2s infinite; &.delay { @@ -124,45 +124,9 @@ group-input { } } -#modal { - max-width: 860px; - width: 90%; - min-height: 200px; - background-color: #333; - padding: unset; - box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); - - header { - height: 50px; - font-size: 30px; - line-height: 50px; - padding-left: 10px; - overflow: hidden; - background-color: #212121; - display: flex; - - .headline { - flex-grow: 1; - } - - .close { - margin-right: 10px; - font-size: 24px; - cursor: pointer; - - &:hover { - color: #ff3232; - } - } - } - - modal-content { - display: block; - max-height: calc(100vh - 200px); - overflow: auto; - } - - modal-footer { - display: block; - } +#image-upload form { + margin-top: 20px; + display: flex; + flex-direction: column; + align-items: center; } \ No newline at end of file diff --git a/raw/scss/_input.scss b/raw/scss/_input.scss index 5bfe3bb..fe41f33 100644 --- a/raw/scss/_input.scss +++ b/raw/scss/_input.scss @@ -1,39 +1,182 @@ -input[type=range] { +.range { + -webkit-appearance: none; width: 100%; - - &:focus { - outline: none; - } - - &:focus::-webkit-slider-runnable-track { - background: #545454; - } - - &:focus::-ms-fill-lower { - background: #424242; - } - - &:focus::-ms-fill-upper { - background: #545454; - } + margin: 5px 0; } -input[type=range]::-webkit-slider-runnable-track { +.range.center { + margin-left: auto; + margin-right: auto; +} + +.range.right { + margin-left: 10%; +} + +.range:focus { + outline: none; +} + +.range::-webkit-slider-runnable-track { width: 100%; - height: 25.6px; + height: 5px; cursor: pointer; - box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; - background: #424242; - border-radius: 0; - border: 0 solid #010101; + background: rgba(0, 0, 0, 0.12); + border-radius: 15px; + border: none; } -input[type=range]::-webkit-slider-thumb { - height: 25px; +.range::-webkit-slider-thumb { + border: 0 solid rgba(0, 0, 30, 0); + height: 15px; width: 15px; - border-radius: 0; + border-radius: 15px; + background: #ff0089; cursor: pointer; - margin-top: 0.3px; + -webkit-appearance: none; + margin-top: -5px; +} + +.range:focus::-webkit-slider-runnable-track { + background: rgba(89, 89, 89, 0.12); +} + +.range::-moz-range-track { + width: 100%; + height: 5px; + cursor: pointer; + background: rgba(0, 0, 0, 0.12); + border-radius: 15px; + border: none; +} + +.range::-moz-range-thumb { + border: 0 solid rgba(0, 0, 30, 0); + height: 15px; + width: 15px; + border-radius: 15px; + background: #ff0089; + cursor: pointer; +} + +.range::-ms-track { + width: 100%; + height: 5px; + cursor: pointer; + background: transparent; + border-color: transparent; + color: transparent; +} + +.range::-ms-fill-lower { + background: rgba(0, 0, 0, 0.12); + border: none; + border-radius: 30px; +} + +.range::-ms-fill-upper { + background: rgba(0, 0, 0, 0.12); + border: none; + border-radius: 30px; +} + +.range::-ms-thumb { + border: 0 solid rgba(0, 0, 30, 0); + height: 15px; + width: 15px; + border-radius: 15px; + background: #ff0089; + cursor: pointer; +} + +.range:focus::-ms-fill-lower { + background: rgba(0, 0, 0, 0.12); +} + +.range:focus::-ms-fill-upper { + background: rgba(89, 89, 89, 0.12); +} + +.input { + width: 90%; + position: relative; + display: inline-block; + margin-top: 1rem; + background-color: transparent; +} + +input:focus + .input:after { + width: 100%; +} + +.input input:not([type=range]) { + border: none; + border-bottom: 2px solid #dcdcdc; + position: relative; + width: 100%; + background-color: transparent; + padding: 5px; + color: #fff; +} + +.input input:not([type=range]):focus { + outline: none; +} + +.input.center { + margin-left: auto; + margin-right: auto; +} + +.input.right { + margin-left: 10%; +} + +.input-label { + display: none; +} + +.floating-label .input-label { + display: block; + position: absolute; + top: 0; + transition: 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); + pointer-events: none; + border-bottom: 1px solid transparent; +} + +.floating-label input:focus ~ .input-label, +.floating-label input:valid ~ .input-label { + transform: translateY(-0.72rem); + color: #ff0089; + font-size: .7rem; +} + +.floating-label input:valid ~ .input-label { + transform: translateY(-0.72rem); + color: #3949ab; + font-size: .7rem; +} + +.focus { + content: ''; + width: 0; + background-color: #ff0a8e; + position: absolute; + bottom: 0; + left: 0; + right: 0; + margin: auto; + height: 2px; + transition: 0.4s cubic-bezier(0.8, 0.4, 0.25, 1); +} + +input:focus ~ .focus { + width: 100%; +} + +* { + box-sizing: border-box; } switch { @@ -76,4 +219,35 @@ input[type="file"] { left: -100000vw; height: 1px; width: 1px; +} + +input[type="color"] { + opacity: 0; +} + +.color-picker { + position: relative; + + input { + position: absolute !important; + top: 0; + } +} + +#colorBlob { + display: block; + width: 100%; + height: 20px; + margin: 5px 0; +} + +.button { + border: 1px solid $primary; + padding: 0.5em 1em; + cursor: pointer; + transition: .5s; + + &:hover { + border-color: $second; + } } \ No newline at end of file diff --git a/raw/scss/_modal.scss b/raw/scss/_modal.scss new file mode 100644 index 0000000..3c45c4d --- /dev/null +++ b/raw/scss/_modal.scss @@ -0,0 +1,49 @@ +#modal { + max-width: 860px; + width: 90%; + background-color: $bg; + padding: unset; + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); + border-radius: 15px; + overflow: hidden; + + div { + position: unset; + } + + header { + height: 50px; + font-size: 30px; + line-height: 50px; + padding-left: 10px; + overflow: hidden; + background-color: $darker; + display: flex; + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); + + .headline { + flex-grow: 1; + } + + .close { + margin-right: 10px; + font-size: 24px; + cursor: pointer; + + &:hover { + color: $primary; + } + } + } + + modal-content { + display: block; + max-height: calc(100vh - 200px); + overflow: auto; + } + + modal-footer { + display: block; + box-shadow: 0 -3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); + } +} \ No newline at end of file diff --git a/raw/scss/_notification.scss b/raw/scss/_notification.scss new file mode 100644 index 0000000..f7ad355 --- /dev/null +++ b/raw/scss/_notification.scss @@ -0,0 +1,6 @@ +.notification { + right: 0; + bottom: 0; + display: flex; + width: 320px; +} \ No newline at end of file diff --git a/raw/scss/_playlist.scss b/raw/scss/_playlist.scss index 215fb2a..bd171c8 100644 --- a/raw/scss/_playlist.scss +++ b/raw/scss/_playlist.scss @@ -13,11 +13,17 @@ playlist { font-size: 1.5em; padding: 5px; + .current { + font-size: .9em; + } + .item { cursor: pointer; user-select: none; border-radius: 5px; margin: 0 3px; + display: flex; + align-items: center; &.inactive { color: #aaa; @@ -26,7 +32,7 @@ playlist { } &:hover { - color: #006ea8; + color: $primary; } } } @@ -34,8 +40,20 @@ playlist { .playlist-item { display: flex; padding: 5px; - border-bottom: 1px solid #dcdcdc; + box-shadow: 0px -1px 0px 0px rgba(0, 0, 0, .08); cursor: pointer; + transition: .5s; + + &.active { + background-color: rgba(0, 0, 0, .2); + + .playlist-item-title { + &:before { + content: '🔊 '; + padding-right: 5px; + } + } + } &-title { margin-left: 10px; @@ -46,7 +64,6 @@ playlist { &-number { padding: 5px 10px 5px 5px; - border-right: 1px solid #ff3232; width: 50px; } diff --git a/raw/scss/_scrollbar.scss b/raw/scss/_scrollbar.scss new file mode 100644 index 0000000..716abd9 --- /dev/null +++ b/raw/scss/_scrollbar.scss @@ -0,0 +1,42 @@ + +::-webkit-scrollbar { + width: 3px; + height: 3px; +} + +::-webkit-scrollbar-button { + width: 15px; + height: 15px; +} + +::-webkit-scrollbar-thumb { + background: #e1e1e1; + border: 0 none #ffffff; + border-radius: 100px; +} + +::-webkit-scrollbar-thumb:hover { + background: #ffffff; +} + +::-webkit-scrollbar-thumb:active { + background: $primary; +} + +::-webkit-scrollbar-track { + background: #666666; + border: 0 none #ffffff; + border-radius: 46px; +} + +::-webkit-scrollbar-track:hover { + background: #666666; +} + +::-webkit-scrollbar-track:active { + background: #666666; +} + +::-webkit-scrollbar-corner { + background: transparent; +} \ No newline at end of file diff --git a/raw/scss/_variables.scss b/raw/scss/_variables.scss new file mode 100644 index 0000000..f55368c --- /dev/null +++ b/raw/scss/_variables.scss @@ -0,0 +1,6 @@ +$bg: #303030; +$darker: #212121; + +$primary: #3949ab; +$second: #ff0089; +$active: #5ff507; \ No newline at end of file diff --git a/raw/scss/style.scss b/raw/scss/style.scss index e1817a8..c8de357 100644 --- a/raw/scss/style.scss +++ b/raw/scss/style.scss @@ -1,3 +1,6 @@ +@import "variables"; + + * { box-sizing: border-box; } @@ -12,7 +15,10 @@ html, body { overflow: hidden; font-size: 16px; font-family: sans-serif; - background-color: #000000; + background-color: $bg; + background-repeat: no-repeat; + background-position: center; + background-size: cover; } div { @@ -38,7 +44,10 @@ div { } +@import "scrollbar"; @import "gui"; @import "input"; @import "controls"; -@import "playlist"; \ No newline at end of file +@import "playlist"; +@import "modal"; +@import "notification"; \ No newline at end of file diff --git a/shaders/wave.frag b/shaders/wave.frag index 03b4508..04a1395 100644 --- a/shaders/wave.frag +++ b/shaders/wave.frag @@ -4,15 +4,23 @@ // to pick one. mediump is a good default. It means "medium precision" precision mediump float; -in vec3 pos; +in vec4 pos; +in vec3 v_surfaceToLight; uniform vec4 u_color; out vec4 outColor; void main() { - vec3 color = pos.xyz; - color.z = color.z + 1.0; - color.z = color.z / 2.0; - color.z = color.z * 255.0; + vec4 fragNormal = normalize(pos); + float u_light = 0.3; + float light = max(dot(fragNormal.xyz, normalize(v_surfaceToLight).xyz), u_light); + vec3 baseColor = vec3(0, 0, 1); + vec3 maxColor = vec3(1, 0, 0); + float y = pos.z; + if (y < 0.0) { + y = y * -1.0; + } + vec3 color = mix(baseColor, maxColor, y); outColor = vec4(color, 1.0); + outColor.rgb *= light; } \ No newline at end of file diff --git a/shaders/wave.vert b/shaders/wave.vert index 863c1a3..bafb157 100644 --- a/shaders/wave.vert +++ b/shaders/wave.vert @@ -2,13 +2,13 @@ in vec3 a_position; uniform mat4 u_matrix; +uniform vec3 u_lightPos; -out vec3 pos; +out vec4 pos; +out vec3 v_surfaceToLight; void main() { - // convert the position from pixels to 0.0 to 1.0 - vec4 scale = vec4(a_position, 1) * u_matrix; - scale.y = scale.y * 0.85; - gl_Position = scale; - pos = a_position; + pos = u_matrix * vec4(a_position, 1); + gl_Position = pos; + v_surfaceToLight = u_lightPos - pos.xyz; } \ No newline at end of file