From d1ae2059f75519018b7410dd0f2d94925ca6bac5 Mon Sep 17 00:00:00 2001 From: VersusTune Date: Tue, 7 Apr 2020 21:44:46 +0200 Subject: [PATCH 1/7] WIP --- .gitignore | 1 + build/gulpfile.js | 20 ++ build/package.json | 36 +++ build/task/js.js | 37 +++ build/task/jsonMinifier.js | 26 ++ build/task/scss.js | 17 ++ build/task/spriteBuilder.js | 104 ++++++++ index.html | 71 +++-- js/handler.js | 9 + js/index.js | 63 +++-- js/old.js | 88 ------- js/sphere.js | 48 ++-- js/utils.js | 39 ++- js/{gui.js => visualGUI.js} | 119 +++++---- out/gui/.gitkeep | 0 out/gui/sphere.json | 1 + out/icon-sprite.svg | 1 + out/js/scripts.js | 431 +++++++++++++++++++++++++++++++ out/js/scripts.min.js | 1 + out/theme/style.css | 1 + raw/gui/sphere.json | 134 ++++++++++ raw/icons/.gitkeep | 0 raw/javascript/app.js | 24 ++ raw/javascript/audio.js | 5 + raw/javascript/gui.js | 5 + raw/javascript/handler.js | 95 +++++++ raw/javascript/player.js | 5 + raw/javascript/utils.js | 275 ++++++++++++++++++++ raw/javascript/visual.js | 15 ++ raw/javascript/visuals/sphere.js | 7 + raw/scss/_controls.scss | 24 ++ raw/scss/_gui.scss | 108 ++++++++ raw/scss/_input.scss | 79 ++++++ raw/scss/style.scss | 43 +++ shaders/sphere.frag | 8 +- shaders/sphere.vert | 8 +- shaders/{test.frag => wave.frag} | 0 shaders/{test.vert => wave.vert} | 0 style.css | 215 --------------- 39 files changed, 1735 insertions(+), 428 deletions(-) create mode 100644 build/gulpfile.js create mode 100644 build/package.json create mode 100644 build/task/js.js create mode 100644 build/task/jsonMinifier.js create mode 100644 build/task/scss.js create mode 100644 build/task/spriteBuilder.js delete mode 100644 js/old.js rename js/{gui.js => visualGUI.js} (72%) create mode 100644 out/gui/.gitkeep create mode 100644 out/gui/sphere.json create mode 100644 out/icon-sprite.svg create mode 100644 out/js/scripts.js create mode 100644 out/js/scripts.min.js create mode 100644 out/theme/style.css create mode 100644 raw/gui/sphere.json create mode 100644 raw/icons/.gitkeep create mode 100644 raw/javascript/app.js create mode 100644 raw/javascript/audio.js create mode 100644 raw/javascript/gui.js create mode 100644 raw/javascript/handler.js create mode 100644 raw/javascript/player.js create mode 100644 raw/javascript/utils.js create mode 100644 raw/javascript/visual.js create mode 100644 raw/javascript/visuals/sphere.js create mode 100644 raw/scss/_controls.scss create mode 100644 raw/scss/_gui.scss create mode 100644 raw/scss/_input.scss create mode 100644 raw/scss/style.scss rename shaders/{test.frag => wave.frag} (100%) rename shaders/{test.vert => wave.vert} (100%) delete mode 100644 style.css diff --git a/.gitignore b/.gitignore index 0f2e5aa..27f553a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /webGLTest.iml /.idea/ +/build/node_modules/ \ No newline at end of file diff --git a/build/gulpfile.js b/build/gulpfile.js new file mode 100644 index 0000000..c1d6674 --- /dev/null +++ b/build/gulpfile.js @@ -0,0 +1,20 @@ +const gulp = require('gulp'), + spriteBuild = require('./task/spriteBuilder').buildIconSprites, + scss = require('./task/scss').buildCSS, + js = require('./task/js').build, + gui = require('./task/jsonMinifier').build; + +gulp.task('scss', scss); +gulp.task('js', js); +gulp.task('sprite', spriteBuild); +gulp.task('gui', gui); + +gulp.task('watchMe', () => { + gulp.watch('./../raw/javascript/**/*.js', gulp.series('js')); + gulp.watch('./../raw/scss/**/*.scss', gulp.series('scss')); + gulp.watch('./../raw/gui/**/*.json', gulp.series('gui')); +}); + +gulp.task('default', gulp.parallel('js', 'scss', 'sprite', 'gui')); + +gulp.task('watch', gulp.parallel('js', 'scss', 'sprite', 'gui', 'watchMe')); diff --git a/build/package.json b/build/package.json new file mode 100644 index 0000000..b7d731d --- /dev/null +++ b/build/package.json @@ -0,0 +1,36 @@ +{ + "name": "vtwebbuild", + "version": "1.0.0", + "description": "Buildprocess for VTWeb like babble, sass and other things", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://gitlab.com/versustune/vtweb.git" + }, + "author": "Maurice Grönwoldt", + "license": "ISC", + "bugs": { + "url": "https://gitlab.com/versustune/vtweb/issues" + }, + "homepage": "https://gitlab.com/versustune/vtweb#readme", + "dependencies": { + "@fortawesome/fontawesome-pro": "^5.5.0", + "@fortawesome/pro-light-svg-icons": "^5.5.0", + "@fortawesome/pro-regular-svg-icons": "^5.5.0", + "@fortawesome/pro-solid-svg-icons": "^5.5.0", + "fontawesome-svg-sprite-generator": "^1.0.0", + "gulp": "^4.0.0", + "gulp-clean-css": "^4.0.0", + "gulp-concat": "^2.6.1", + "gulp-rename": "^1.4.0", + "gulp-sass": "^4.0.2", + "gulp-terser": "^1.1.7", + "merge-stream": "^1.0.1", + "node-fs-extra": "^0.8.2", + "require-dir": "^1.0.0", + "svg-sprite": "^1.4.0" + } +} diff --git a/build/task/js.js b/build/task/js.js new file mode 100644 index 0000000..7b7741b --- /dev/null +++ b/build/task/js.js @@ -0,0 +1,37 @@ +const terser = require('gulp-terser'), + concat = require('gulp-concat'), + rename = require('gulp-rename'), + gulp = require('gulp'); + +const basePath = __dirname + '/../../raw/javascript/'; +const visualPath = basePath + 'visuals/'; +const visuals = [ + visualPath + 'sphere.js', + //visualPath + 'wave.js', + //visualPath + 'water.js', + //visualPath + 'experimental.js', +] +const config = { + src: [ + basePath + 'utils.js', + basePath + 'handler.js', + basePath + 'audio.js', + basePath + 'player.js', + basePath + 'gui.js', + basePath + 'visual.js', + ...visuals, + basePath + 'app.js' + ], + dest: __dirname + '/../../out/js' +}; + +function build() { + return gulp.src(config.src) + .pipe(concat('scripts.js')) + .pipe(gulp.dest(config.dest)) + .pipe(rename('scripts.min.js')) + .pipe(terser()) + .pipe(gulp.dest(config.dest)); +} + +module.exports.build = build; diff --git a/build/task/jsonMinifier.js b/build/task/jsonMinifier.js new file mode 100644 index 0000000..ef6795c --- /dev/null +++ b/build/task/jsonMinifier.js @@ -0,0 +1,26 @@ +const gulp = require('gulp'), + fs = require('fs') + + +const basePath = __dirname + '/../../raw/gui/'; + +const config = { + src: basePath + '*.json', + dest: __dirname + '/../../out/gui/' +} + +function build() { + fs.readdirSync(basePath).forEach(file => { + try { + if (fs.lstatSync(basePath + file).isDirectory()) + return + let content = fs.readFileSync(basePath + file).toString() + fs.writeFileSync(config.dest + file, JSON.stringify(JSON.parse(content))) + } catch (e) { + console.error(e) + } + }) + return gulp.src(config.src) +} + +module.exports.build = build; \ No newline at end of file diff --git a/build/task/scss.js b/build/task/scss.js new file mode 100644 index 0000000..a52931b --- /dev/null +++ b/build/task/scss.js @@ -0,0 +1,17 @@ +const sass = require('gulp-sass'), + clean = require('gulp-clean-css'), + gulp = require('gulp'); + +const config = { + src: __dirname + '/../../raw/scss/**/*.scss', + dest: __dirname + '/../../out/theme' +}; + +function buildCSS() { + return gulp.src(config.src) + .pipe(sass().on('error', sass.logError)) + .pipe(clean()) + .pipe(gulp.dest(config.dest)); +} + +module.exports.buildCSS = buildCSS; diff --git a/build/task/spriteBuilder.js b/build/task/spriteBuilder.js new file mode 100644 index 0000000..29f5e33 --- /dev/null +++ b/build/task/spriteBuilder.js @@ -0,0 +1,104 @@ +const faSvgSprite = require('fontawesome-svg-sprite-generator'), + SVGSpriter = require('svg-sprite'), + path = require('path'), + fs = require('fs'), + fal = require('@fortawesome/pro-light-svg-icons'), + far = require('@fortawesome/pro-regular-svg-icons'), + fas = require('@fortawesome/pro-solid-svg-icons'), + fsextra = require('node-fs-extra'), + gulp = require('gulp'); + +let toMergeData = []; + +function buildIconSprites() { + let config = { + src: __dirname + '/../../raw/icons', + dest: __dirname + '/../../out/icon-sprite.svg', + spriter: { + shape: { + id: { + generator: function (name) { + return "vt-" + name.replace('.svg', ''); + } + } + }, + "svg": { + "xmlDeclaration": false, + "doctypeDeclaration": false + }, + "mode": { + "symbol": true + } + } + }; + + let icons = { + fa: [ + fal.faPlay, + fal.faPause, + fal.faCaretRight, + fal.faCaretLeft, + fal.faRandom, + fal.faCogs, + fal.faFolderUpload, + fal.faListMusic, + ], + vt: [] + }; + + builder.init(config, icons); + builder.generateFontAwesome(); + builder.generateFromIcons(); + return gulp.src(config.src); +} + +let builder = { + init: function (config, icons) { + this.config = config; + this.icons = icons; + }, + + generateFontAwesome: function () { + let sprite = faSvgSprite.generate(this.icons.fa, {license: '', xmlDeclaration: false}); + toMergeData.push(sprite.svg); + }, + + generateFromIcons: function () { + builder.merge(); + }, + + merge: function () { + if (toMergeData.length === 0) { + return; + } + let data = ''; + for (let item of toMergeData) { + data += item.replace('', '').replace(/(]*)/g, ''); + } + data += ''; + + let dirname = path.dirname(builder.config.dest); + if (!fs.existsSync(dirname)) { + fsextra.mkdirpSync(dirname); + } + fs.writeFileSync(builder.config.dest, data); + }, + + readFiles: function (dir, spriter, icons) { + let files = fs.readdirSync(dir); + for (let file of files) { + let split = file.split('.'); + let suffix = split[split.length - 1]; + if (suffix === 'svg' && icons.indexOf('vt-' + split[0]) !== -1) { + let filePath = path.resolve(dir, file); + spriter.add( + filePath, + file, + fs.readFileSync(filePath, {encoding: 'utf-8'}) + ); + } + } + } +}; + +module.exports.buildIconSprites = buildIconSprites; diff --git a/index.html b/index.html index 4e1c80a..eb50ef4 100644 --- a/index.html +++ b/index.html @@ -3,32 +3,55 @@ WEBGL Test - + - -
- - Rotation - - - Transform - - - Color - - - World - - - Draw - +
+ + + Loading
-
- - - - - +
+ + + +
+
+ + + +
+ + diff --git a/js/handler.js b/js/handler.js index b68f701..884a547 100644 --- a/js/handler.js +++ b/js/handler.js @@ -80,4 +80,13 @@ class Shaders { getProgram(name) { return this.programs[name]; } + + async loadArray(list, path) { + let self = this; + for (const e of list) { + await self.loadShader(e, path) + } + await self.createProgramForEach(list) + return true; + } } \ No newline at end of file diff --git a/js/index.js b/js/index.js index 2be60e4..95b4392 100644 --- a/js/index.js +++ b/js/index.js @@ -1,22 +1,35 @@ -let shaderHandler, gl, c, actx, analyser, peak; -let positionData = []; -let positionSize = 8192 * 2 * 2; +let shaderHandler, gl, c, actx, analyser, peak, isInit = false; -function createAudioContextStream(stream) { +function createAudioContextStream(audio) { let AudioContext = window.AudioContext || window.webkitAudioContext; if (actx) { actx.close(); } actx = new AudioContext(); analyser = actx.createAnalyser(); + let MEDIA_ELEMENT_NODES = new WeakMap(); let Source; analyser.fftSize = 4096; analyser.maxDecibels = 0; analyser.smoothingTimeConstant = .4; - Source = actx.createMediaStreamSource(stream); + if (audio) { + if (MEDIA_ELEMENT_NODES.has(audio)) { + Source = MEDIA_ELEMENT_NODES.get(audio); + } else { + Source = actx.createMediaElementSource(audio); + MEDIA_ELEMENT_NODES.set(audio, Source); + } - Source.connect(analyser); + Source.connect(analyser); + analyser.connect(actx.destination); + audio.oncanplay = () => { + actx.resume(); + }; + audio.onended = function () { + actx.pause(); + }; + } return null; } @@ -28,26 +41,38 @@ async function init() { return false; } shaderHandler = new Shaders(gl); - await shaderHandler.loadShader("test", "shaders/"); + await shaderHandler.loadShader("wave", "shaders/"); //sphere shader await shaderHandler.loadShader("sphere", "shaders/", gl.VERTEX_SHADER); - return shaderHandler.createProgramForEach(["test", "sphere"]); + return shaderHandler.createProgramForEach(["wave", "sphere"]); } -(function () { - navigator.mediaDevices.getUserMedia({audio: true, video: false}) - .then(createAudioContextStream).then(e => { - generateRotationSliders(); - generateColorSliders(); - generateWorldSliders(); - generateDrawSliders(); - generateTranslateSliders(); +function createView() { + if (!isInit) { + createAudioContextStream(audioFile); init().then(b => { if (b) { - sphereObject.drawMode = gl.POINTS; + loadConfig(); draw(); } }) - }); -})(); \ No newline at end of file + isInit = true; + } +} + +function initGUI() { + generateRotationSliders(); + generateColorSliders(); + generateWorldSliders(); + generateDrawSliders(); + generateTranslateSliders(); +} + +(function () { + if (document.readyState === "complete" || document.readyState === "interactive") { + initGUI() + } else { + document.addEventListener('DOMContentLoaded', initGUI); + } +})() \ No newline at end of file diff --git a/js/old.js b/js/old.js deleted file mode 100644 index ac3b912..0000000 --- a/js/old.js +++ /dev/null @@ -1,88 +0,0 @@ -function readData() { - let items = analyser.fftSize; - let dataArray = new Float32Array(items); - analyser.getFloatTimeDomainData(dataArray); - let space = 255 / (items + 2); - let pos = [0, 127.5]; - let x = space; - peak = 0; - for (let i = 0; i < items; i++) { - let data = (dataArray[i] * 125) + 127.5 - if (Math.abs(data) > peak) { - peak = data; - } - pos.push(x); - pos.push(data); - x += space; - } - pos.push(255, 127.5); - positions = pos; -} - -let positions = [ - Math.random(), Math.random(), - Math.random(), Math.random(), - Math.random(), Math.random(), -]; - -/*function draw() { - readData(); - let program = shaderHandler.getProgram("test"); - let positionAttributeLocation = gl.getAttribLocation(program, "a_position"); - var colorLocation = gl.getUniformLocation(program, "u_color"); - let positionBuffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); - gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.DYNAMIC_DRAW); - let vao = gl.createVertexArray(); - gl.bindVertexArray(vao); - gl.enableVertexAttribArray(positionAttributeLocation); - gl.vertexAttribPointer( - positionAttributeLocation, 2, gl.FLOAT, false, 0, 0); - gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); - - gl.clearColor(0, 0, 0, 1); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - gl.useProgram(program); - gl.uniform4f(colorLocation, peak / 255, (255 - peak) / 255, .2, 1); - gl.drawArrays(gl.LINE_STRIP, 0, positions.length / 2); - - let positionBuffer2 = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer2); - gl.uniform4f(colorLocation, 0, 0, 1, .3); - gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.DYNAMIC_DRAW); - gl.drawArrays(gl.POINTS, 0, positions.length / 2); - - requestAnimationFrame(draw); -}*/ - -function setupSphere() { - sphereData = []; - let data = readData(), - radData = data[0]; - let sin = Math.sin, - cos = Math.cos, - total = sphereObject.total || 50, - cTotal = (total + 1) * (total + 1), - r = sphereObject.radius || 1000, - rx = r / c.width, - ry = r / c.height, - counter = 0; - for (let i = 1; i <= total; i++) { - let lat = VTUtils.map(i, 0, total, 0, Math.PI, false); - for (let j = 1; j <= total; j++) { - let lon = VTUtils.map(j, 0, total, 0, Math.TWO_PI, false); - let map = VTUtils.map(counter, 0, cTotal, 0, radData.length - 1, false); - map = Math.floor(map); - let realRX = rx + radData[map]; - let realRY = ry + radData[map]; - let x = rx * sin(lon) * cos(lat); - let y = rx * sin(lon) * sin(lat); - let z = ry * cos(lon); - sphereData.push(x); - sphereData.push(y); - sphereData.push(z); - counter++; - } - } - return [VTUtils.hsvToRgb(data[1], 1, 1), data[1]]; -} diff --git a/js/sphere.js b/js/sphere.js index b83985e..a27d272 100644 --- a/js/sphere.js +++ b/js/sphere.js @@ -7,20 +7,21 @@ let sphereObject = { rotation: [0, 0, 0], //radians rotateInc: [0.0, 0.0, 0.0], //degreesInc rotateByBeat: true, - translate: [1, 1, 1], + translate: [0, 0, 0], total: 50, radius: 500, color: {r: 0, g: 0, b: 0}, colorByBeat: true, - drawMode: 0, + drawMode: 5, sphereMode: 0, lightPos: [0, 0, 0], pointSize: 2, steps: 512, - dirtyMode: false + dirtyMode: false, + light: 0.3 } -function readData() { +function readDataBar() { let items = sphereObject.steps; let dataArray = new Uint8Array(items); analyser.getByteFrequencyData(dataArray); @@ -36,6 +37,7 @@ function readData() { let sphereDataVectors = [], lastTotal = 0; +// avoid garbage collection each run function prepareData() { let total = sphereObject.total; if (lastTotal !== total) { @@ -52,7 +54,7 @@ function prepareData() { function setupSphere() { sphereData = []; - let data = readData(), + let data = readDataBar(), radData = data[0], map = VTUtils.map, total = sphereObject.total, @@ -94,14 +96,21 @@ function setupSphere() { function getAddRad(counter, data, total) { let mapping, rAdd, map = VTUtils.map; + let h = total / 2; if (sphereObject.sphereMode === 3) { - let h = total / 2; if (counter > h) { mapping = map(counter, h, total, data.length - 1, 0); } else { mapping = map(counter, 0, h, 0, data.length - 1); } rAdd = data[Math.round(mapping)] || 0; + } else if (sphereObject.sphereMode === 4) { + if (counter > h) { + mapping = map(counter, h, total, 0, data.length - 1); + } else { + mapping = map(counter, 0, h, data.length - 1, 0); + } + rAdd = data[Math.round(mapping)] || 0; } else { mapping = map(counter, 0, total, 0, data.length - 1); rAdd = data[Math.round(mapping)] || 0; @@ -109,18 +118,16 @@ function getAddRad(counter, data, total) { return rAdd; } -let position, world, color, rotate, light, lightPos, lightAngle = 90; +let position, color, rotate, light, lightPos; function prepare(program) { position = gl.getAttribLocation(program, "a_position"); - world = gl.getUniformLocation(program, 'u_world'); color = gl.getUniformLocation(program, "u_color"); light = gl.getUniformLocation(program, "u_light"); rotate = gl.getUniformLocation(program, "u_matrix"); lightPos = gl.getUniformLocation(program, "u_lightPos"); let pointSize = gl.getUniformLocation(program, "u_pointSize"); - gl.uniformMatrix4fv(world, false, TDUtils.yRotation(TDUtils.degToRad(lightAngle))); - gl.uniform3fv(light, [0.95, 0.62, 0.094]); + gl.uniform3fv(light, [sphereObject.light, 0, 0]); gl.uniform3fv(lightPos, sphereObject.lightPos); gl.uniform1f(pointSize, sphereObject.pointSize); } @@ -135,16 +142,15 @@ function draw() { gl.useProgram(program); prepare(program); let matrix = [ - sphereObject.translate[0] / aspect, 0, 0, 0, - 0, sphereObject.translate[1], 0, 0, - 0, 0, sphereObject.translate[2], 0, - 0, 0, 0, 1 + 1 / aspect, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + sphereObject.translate[0], sphereObject.translate[1], sphereObject.translate[2], 1 ] matrix = TDUtils.multiply(matrix, TDUtils.xRotation(sphereObject.rotation[0])); matrix = TDUtils.multiply(matrix, TDUtils.yRotation(sphereObject.rotation[1])); matrix = TDUtils.multiply(matrix, TDUtils.zRotation(sphereObject.rotation[2])); gl.uniformMatrix4fv(rotate, false, matrix); - let positionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(sphereData), gl.DYNAMIC_DRAW); @@ -152,7 +158,6 @@ function draw() { 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); @@ -186,12 +191,6 @@ function sphereMode(lat, lon, i, counter, rx, ry) { sin = Math.sin, cos = Math.cos; switch (sphereObject.sphereMode) { - case 0: - case 3: - x = rx * sin(lat) * cos(lon); - y = ry * sin(lat) * sin(lon); - z = ry * cos(lat); - break; case 1: x = rx * sin(lon) * cos(lat); y = ry * sin(lon) * sin(lat); @@ -202,6 +201,11 @@ function sphereMode(lat, lon, i, counter, rx, ry) { y = ry * sin(lat) * sin(lat); z = ry * cos(lon); break; + default: + x = rx * sin(lat) * cos(lon); + y = ry * sin(lat) * sin(lon); + z = ry * cos(lat); + break; } return {x: x, y: y, z: z}; } \ No newline at end of file diff --git a/js/utils.js b/js/utils.js index 68e1886..1b56266 100644 --- a/js/utils.js +++ b/js/utils.js @@ -131,7 +131,7 @@ class VTVector { this.z += z; } - setXYZ(x,y,z) { + setXYZ(x, y, z) { this.x = x || 0; this.y = y || 0; this.z = z || 0; @@ -236,3 +236,40 @@ class TDUtils { 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); + } + } + }) +}; + +function create(name, content) { + let d = document.createElement(name); + if (content) { + d.innerHTML = content; + } + return d; +} + +function append(to, array) { + for (let item in array) { + to.appendChild(array[item]); + } +} \ No newline at end of file diff --git a/js/gui.js b/js/visualGUI.js similarity index 72% rename from js/gui.js rename to js/visualGUI.js index a16a30e..a98be8b 100644 --- a/js/gui.js +++ b/js/visualGUI.js @@ -1,40 +1,3 @@ -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); - } - } - }) -}; - -function create(name, content) { - let d = document.createElement(name); - if (content) { - d.innerHTML = content; - } - return d; -} - -function append(to, array) { - for (let item in array) { - to.appendChild(array[item]); - } -} - function generateRotationSliders() { let group = $('#rotate'); generateSlider(["X", "Y", "Z", "X-Inc", "Y-Inc", "Z-Inc"], 0, 360, 0, group, "rotate"); @@ -43,7 +6,7 @@ function generateRotationSliders() { function generateTranslateSliders() { let group = $('#translate'); - generateSlider(["X", "Y", "Z"], -1, 1, 1, group, "translate"); + generateSlider(["X", "Y", "Z"], -1, 1, 0, group, "translate"); } function generateColorSliders() { @@ -54,20 +17,20 @@ function generateColorSliders() { function generateWorldSliders() { let group = $('#world'); - generateSlider(["LightPos-X", "LightPos-Y", "LightPos-Z"], -1, 1, 1, group, "light"); - generateSlider(["LightAngle"], 0, 360, 90, group, "light"); + generateSlider(["LightPos-X", "LightPos-Y", "LightPos-Z"], -100, 100, 0, group, "light"); + generateSlider(["Light"], 0, 1, 0.3, group, "light"); } function generateDrawSliders() { let group = $('#draw'), g = "drawMode" generateSlider(["DrawMode"], 0, 6, 0, group, g); - generateSlider(["Form"], 0, 3, 0, group, g); + generateSlider(["Form"], 0, 4, 0, group, g); generateSlider(["Radius"], 20, 1500, 500, group, g); - generateSlider(["Total"], 0, 200, 50, group, g); + generateSlider(["Total"], 20, 250, 50, group, g); generateSlider(["PointSize"], 1, 10, 2, group, g, .2); - generateSlider(["Smoothing"], 0, 1, 0.8, group, g, .05); - generateSlider(["Steps"], 512, analyser.fftSize / 2, 512, group, g, 256); + generateSlider(["Smoothing"], 0, 100, 80, group, g, 1); + generateSlider(["Steps"], 256, 2048, 512, group, g, 16); generateCheckBox(["Dirty"], false, group, g) } @@ -102,11 +65,10 @@ function setColors() { function setWorld() { let group = $('#world'); - sphereObject.lightPos[0] = getValue($('#LightPos-X', group)) * 0.2; - sphereObject.lightPos[1] = getValue($('#LightPos-Y', group)) * 0.2; - sphereObject.lightPos[2] = getValue($('#LightPos-Z', group)) * 0.2; - - lightAngle = getValue($('#LightAngle', group)) + sphereObject.lightPos[0] = getValue($('#LightPos-X', group)); + sphereObject.lightPos[1] = getValue($('#LightPos-Y', group)); + sphereObject.lightPos[2] = getValue($('#LightPos-Z', group)); + sphereObject.light = getValue($('#Light', group)); } function setDraw() { @@ -118,7 +80,7 @@ function setDraw() { sphereObject.pointSize = getValue($('#PointSize', group)); sphereObject.steps = getValue($('#Steps', group)); sphereObject.dirtyMode = $('#drawModeDirty', group).checked; - analyser.smoothingTimeConstant = getValue($('#Smoothing', group)); + analyser.smoothingTimeConstant = getValue($('#Smoothing', group)) / 100; } function setTranslate() { @@ -183,6 +145,7 @@ function changeHandler(el) { } else if (d === "drawMode") { setDraw(); } + saveConfig(); } document.body.addDelegatedEventListener('input', 'group-input input', (ev, el) => { @@ -205,4 +168,60 @@ function getValue(slider) { $('.settings-icon').addEventListener('click', function () { $('.off-can').classList.toggle("closed"); + $('.settings-icon').classList.toggle("open"); +}) + +window.addEventListener('keyup', e => { + if (e.key === 'F11') { + c.requestFullscreen(); + return; + } + if (e.key === 'Escape') { + if (document.fullscreenElement) { + document.exitFullscreen().then(console.log); + } + } + if (e.key === 'p') { + audioFile.play(); + } + if (e.key === 's') { + audioFile.pause(); + } +}); + +function saveConfig() { + localStorage.setItem('config-sphere', JSON.stringify(sphereObject)); +} + +function loadConfig() { + let item = localStorage.getItem('config-sphere'); + if (item && item !== "") { + sphereObject = JSON.parse(item); + } +} + +let uploadField = $('#upload'); +let audioFile = new Audio(); +let lastSong = null; +uploadField.addEventListener('change', e => { + let file = uploadField.files[0]; + if (file && file.type.indexOf('audio') !== -1 && file.name.match(".m3u") === null && file.name.match(".wma") === null) { + if (lastSong) { + URL.revokeObjectURL(lastSong); + } + audioFile.src = URL.createObjectURL(file); + document.title = file.name; + lastSong = audioFile.src; + createView(); + } +}) + +$('#play').addEventListener('click', e => { + if (audioFile.src !== "") { + c.requestFullscreen().then(r => { + setTimeout(x => { + audioFile.play(); + }, 1000) + }); + } }) \ No newline at end of file diff --git a/out/gui/.gitkeep b/out/gui/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/out/gui/sphere.json b/out/gui/sphere.json new file mode 100644 index 0000000..6e39aa1 --- /dev/null +++ b/out/gui/sphere.json @@ -0,0 +1 @@ +[{"group":"rotation","name":["X","Y","Z"],"props":[0,1,2],"type":"slider","max":360,"min":0},{"group":"rotation","name":["X-Inc","Y-Inc","Z-Inc"],"props":[0,1,2],"type":"slider","max":1,"min":0,"stepSize":0.01},{"group":"rotation","name":"by-Beat","type":"checkbox","value":true,"tooltip":"Rotate the Sphere based by Beat
Low-Freq = Y, Mid-Freq = Z, High-Freq = X"},{"group":"color","name":["R","G","B"],"props":["r","g","b"],"type":"slider","max":255,"min":0},{"group":"light","name":["X","Y","Z"],"props":[0,1,2],"type":"slider","max":1,"min":-1,"value":0,"stepSize":0.1},{"group":"light","name":"Light","type":"slider","max":1,"min":0,"stepSize":0.05},{"group":"color","name":"by-Beat","type":"checkbox","value":true,"tooltip":"Draw The Color Strength based on the Low-Freq"},{"group":"draw","name":"DrawMode","type":"slider","max":6,"min":0},{"group":"draw","name":"Form","type":"slider","max":4,"min":0},{"group":"draw","name":"Radius","type":"slider","max":1500,"min":20},{"group":"draw","name":"Total","type":"slider","max":200,"min":20},{"group":"draw","name":"PointSize","type":"slider","max":10,"min":1,"stepSize":0.2},{"group":"draw","name":"Dirty","type":"checkbox","value":false,"tooltip":"Full Draw or with less Points"}] \ No newline at end of file diff --git a/out/icon-sprite.svg b/out/icon-sprite.svg new file mode 100644 index 0000000..f13e5ca --- /dev/null +++ b/out/icon-sprite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/out/js/scripts.js b/out/js/scripts.js new file mode 100644 index 0000000..dac8955 --- /dev/null +++ b/out/js/scripts.js @@ -0,0 +1,431 @@ +class VTUtils { + static random(min, max) { + let rand = Math.random(); + if (typeof min === 'undefined') { + return rand; + } else if (typeof max === 'undefined') { + if (min instanceof Array) { + return min[Math.floor(rand * min.length)]; + } else { + return rand * min; + } + } else { + if (min > max) { + let tmp = min; + min = max; + max = tmp; + } + return rand * (max - min) + min; + } + }; + + static randomInt(min, max) { + return Math.floor(VTUtils.random(min, max)); + } + + static normalize(val, max, min) { + return (val - min) / (max - min); + }; + + static distance(x, y, x2, y2) { + let a = x - x2; + let b = y - y2; + + return Math.sqrt(a * a + b * b); + } + + static map(n, start1, stop1, start2, stop2, withinBounds) { + let newVal = (n - start1) / (stop1 - start1) * (stop2 - start2) + start2; + if (!withinBounds) { + return newVal; + } + if (start2 < stop2) { + return this.constrain(newVal, start2, stop2); + } else { + return this.constrain(newVal, stop2, start2); + } + }; + + static constrain(n, low, high) { + return Math.max(Math.min(n, high), low); + } + + 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); + + switch (i % 6) { + case 0: + r = v, g = t, b = p; + break; + case 1: + r = q, g = v, b = p; + break; + case 2: + r = p, g = v, b = t; + break; + case 3: + r = p, g = q, b = v; + break; + case 4: + r = t, g = p, b = v; + break; + case 5: + r = v, g = p, b = q; + break; + } + + return {r: r, g: g, b: b}; + } + + static peakRGB(peak) { + return { + r: peak, + g: 1 - peak, + b: 0 + }; + } +} + +class VTVector { + constructor(x, y, z) { + this.x = x || 0; + this.y = y || 0; + this.z = z || 0; + } + + //helper + static createRandom(x, y, z) { + x = x || 1; + y = y || 1; + z = z || 0; + return new VTVector(VTUtils.random(-x, x), VTUtils.random(-y, y), VTUtils.random(-z, z)); + } + + mult(times) { + this.x *= times; + this.y *= times; + this.z *= times; + } + + set(vector) { + this.x = vector.x; + this.y = vector.y; + this.z = vector.z; + } + + add(vector) { + this.x = this.x + vector.x; + this.y = this.y + vector.y; + this.z = this.z + vector.z; + } + + addXYZ(x, y, z) { + this.x += x; + this.y += y; + this.z += z; + } + + setXYZ(x, y, z) { + this.x = x || 0; + this.y = y || 0; + this.z = z || 0; + } + + clone() { + return new VTVector(this.x, this.y, this.z); + } +} + +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]; +} + +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); + } + } + }) +}; + +function create(name, content) { + let d = document.createElement(name); + if (content) { + d.innerHTML = content; + } + return d; +} + +function append(to, array) { + for (let item in array) { + to.appendChild(array[item]); + } +} +class ShaderHandler { + constructor(gl) { + this.gl = gl; + this.shaderNames = []; + this.shaders = {}; + this.programs = {}; + } + + setGL(gl) { + this.gl = gl; + } + + async loadShader(name, path) { + this.shaderNames.push(name); + await this.load(name, path + name + ".vert", this.gl.VERTEX_SHADER); + await this.load(name, path + name + ".frag", this.gl.FRAGMENT_SHADER); + } + + async load(name, url, type) { + let realName = name + "_" + type; + if (!this.shaders[realName]) { + let data = await fetch(url); + let shader = this.createShader(await data.text(), type); + if (shader) { + this.shaders[realName] = shader; + } + } + return !!this.shaders[realName]; + } + + getShader(name, type) { + let realName = name + "_" + type; + return this.shaders[realName]; + } + + getAllShaders() { + return this.shaderNames; + } + + async createProgramForEach(arr) { + arr = arr || this.shaderNames; + for (let i = 0; i < arr.length; i++) { + let shader = arr[i]; + let v = await shaderHandler.createProgram(shader, [shader]) + if (!v) { + return false; + } + } + return true; + } + + createShader(source, type) { + let gl = this.gl; + let shader = gl.createShader(type); + gl.shaderSource(shader, source); + gl.compileShader(shader); + if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + return shader; + } + console.error(gl.getShaderInfoLog(shader)); + gl.deleteShader(shader); + return null; + } + + createProgram(name, shaders) { + let gl = this.gl; + let pro = gl.createProgram(); + for (let i = 0; i < shaders.length; i++) { + gl.attachShader(pro, this.getShader(shaders[i], gl.VERTEX_SHADER)); + gl.attachShader(pro, this.getShader(shaders[i], gl.FRAGMENT_SHADER)); + } + gl.linkProgram(pro); + var success = gl.getProgramParameter(pro, gl.LINK_STATUS); + if (success) { + this.programs[name] = pro; + return pro; + } + + console.log(gl.getProgramInfoLog(pro)); + gl.deleteProgram(pro); + return null; + } + + getProgram(name) { + return this.programs[name]; + } + + async loadArray(list, path) { + let self = this; + for (const e of list) { + await self.loadShader(e, path) + } + await self.createProgramForEach(list) + } +} +class AudioHandler { + async init() { + this.audioFile = new Audio(); + } +} +class Player { + async init() { + + } +} +class GUI { + init() { + + } +} +class Visual { + constructor() { + this.data = []; //for drawing + this.dataArray = []; + } + draw() { + } + + setup() { + } +} + +class VisualDrawer { + +} +class Sphere extends Visual { + draw() { + } + + setup() { + } +} +const shaderHandler = new ShaderHandler(null), + audioHandler = new AudioHandler(), + gui = new GUI(), + player = new Player(); + +async function startUP() { + let c = document.body.querySelector('#c'), + gl = c.getContext("webgl2"); + if (!gl) { + alert("SORRY THE BROWSER DOESN'T SUPPORT WEBGL2"); + return false; + } + shaderHandler.setGL(gl) + await shaderHandler.loadArray(["wave", "sphere"], 'shaders/'); + await audioHandler.init(); + await player.init(); + gui.init(); +} + +startUP().then(r => { + setTimeout(e => { + $('.loading-screen').remove(); + }, 100) +}); \ No newline at end of file diff --git a/out/js/scripts.min.js b/out/js/scripts.min.js new file mode 100644 index 0000000..ae2c780 --- /dev/null +++ b/out/js/scripts.min.js @@ -0,0 +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,r){let s=t-a,i=e-r;return Math.sqrt(s*s+i*i)}static map(t,e,a,r,s,i){let n=(t-e)/(a-e)*(s-r)+r;return i?r{let r=t.target;if(r.matches(e))a(t,r);else{let s=r.closest(e);s&&a(t,s)}})};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 r=t+"_"+a;if(!this.shaders[r]){let t=await fetch(e),s=this.createShader(await t.text(),a);s&&(this.shaders[r]=s)}return!!this.shaders[r]}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{setTimeout(t=>{$(".loading-screen").remove()},100)}); \ No newline at end of file diff --git a/out/theme/style.css b/out/theme/style.css new file mode 100644 index 0000000..118e5e6 --- /dev/null +++ b/out/theme/style.css @@ -0,0 +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}}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} \ No newline at end of file diff --git a/raw/gui/sphere.json b/raw/gui/sphere.json new file mode 100644 index 0000000..2f8936b --- /dev/null +++ b/raw/gui/sphere.json @@ -0,0 +1,134 @@ +[ + { + "group": "rotation", + "name": [ + "X", + "Y", + "Z" + ], + "props": [ + 0, + 1, + 2 + ], + "type": "slider", + "max": 360, + "min": 0 + }, + { + "group": "rotation", + "name": [ + "X-Inc", + "Y-Inc", + "Z-Inc" + ], + "props": [ + 0, + 1, + 2 + ], + "type": "slider", + "max": 1, + "min": 0, + "stepSize": 0.01 + }, + { + "group": "rotation", + "name": "by-Beat", + "type": "checkbox", + "value": true, + "tooltip": "Rotate the Sphere based by Beat
Low-Freq = Y, Mid-Freq = Z, High-Freq = X" + }, + { + "group": "color", + "name": [ + "R", + "G", + "B" + ], + "props": [ + "r", + "g", + "b" + ], + "type": "slider", + "max": 255, + "min": 0 + }, + { + "group": "light", + "name": [ + "X", + "Y", + "Z" + ], + "props": [ + 0, + 1, + 2 + ], + "type": "slider", + "max": 1, + "min": -1, + "value": 0, + "stepSize": 0.1 + }, + { + "group": "light", + "name": "Light", + "type": "slider", + "max": 1, + "min": 0, + "stepSize": 0.05 + }, + { + "group": "color", + "name": "by-Beat", + "type": "checkbox", + "value": true, + "tooltip": "Draw The Color Strength based on the Low-Freq" + }, + { + "group": "draw", + "name": "DrawMode", + "type": "slider", + "max": 6, + "min": 0 + }, + { + "group": "draw", + "name": "Form", + "type": "slider", + "max": 4, + "min": 0 + }, + { + "group": "draw", + "name": "Radius", + "type": "slider", + "max": 1500, + "min": 20 + }, + { + "group": "draw", + "name": "Total", + "type": "slider", + "max": 200, + "min": 20 + }, + { + "group": "draw", + "name": "PointSize", + "type": "slider", + "max": 10, + "min": 1, + "stepSize": 0.2 + }, + { + "group": "draw", + "name": "Dirty", + "type": "checkbox", + "value": false, + "tooltip": "Full Draw or with less Points" + } +] \ No newline at end of file diff --git a/raw/icons/.gitkeep b/raw/icons/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/raw/javascript/app.js b/raw/javascript/app.js new file mode 100644 index 0000000..de5e54f --- /dev/null +++ b/raw/javascript/app.js @@ -0,0 +1,24 @@ +const shaderHandler = new ShaderHandler(null), + audioHandler = new AudioHandler(), + gui = new GUI(), + player = new Player(); + +async function startUP() { + let c = document.body.querySelector('#c'), + gl = c.getContext("webgl2"); + if (!gl) { + alert("SORRY THE BROWSER DOESN'T SUPPORT WEBGL2"); + return false; + } + shaderHandler.setGL(gl) + await shaderHandler.loadArray(["wave", "sphere"], 'shaders/'); + await audioHandler.init(); + await player.init(); + gui.init(); +} + +startUP().then(r => { + setTimeout(e => { + $('.loading-screen').remove(); + }, 100) +}); \ No newline at end of file diff --git a/raw/javascript/audio.js b/raw/javascript/audio.js new file mode 100644 index 0000000..f8aa9dc --- /dev/null +++ b/raw/javascript/audio.js @@ -0,0 +1,5 @@ +class AudioHandler { + async init() { + this.audioFile = new Audio(); + } +} \ No newline at end of file diff --git a/raw/javascript/gui.js b/raw/javascript/gui.js new file mode 100644 index 0000000..7228c33 --- /dev/null +++ b/raw/javascript/gui.js @@ -0,0 +1,5 @@ +class GUI { + init() { + + } +} \ No newline at end of file diff --git a/raw/javascript/handler.js b/raw/javascript/handler.js new file mode 100644 index 0000000..a676610 --- /dev/null +++ b/raw/javascript/handler.js @@ -0,0 +1,95 @@ +class ShaderHandler { + constructor(gl) { + this.gl = gl; + this.shaderNames = []; + this.shaders = {}; + this.programs = {}; + } + + setGL(gl) { + this.gl = gl; + } + + async loadShader(name, path) { + this.shaderNames.push(name); + await this.load(name, path + name + ".vert", this.gl.VERTEX_SHADER); + await this.load(name, path + name + ".frag", this.gl.FRAGMENT_SHADER); + } + + async load(name, url, type) { + let realName = name + "_" + type; + if (!this.shaders[realName]) { + let data = await fetch(url); + let shader = this.createShader(await data.text(), type); + if (shader) { + this.shaders[realName] = shader; + } + } + return !!this.shaders[realName]; + } + + getShader(name, type) { + let realName = name + "_" + type; + return this.shaders[realName]; + } + + getAllShaders() { + return this.shaderNames; + } + + async createProgramForEach(arr) { + arr = arr || this.shaderNames; + for (let i = 0; i < arr.length; i++) { + let shader = arr[i]; + let v = await shaderHandler.createProgram(shader, [shader]) + if (!v) { + return false; + } + } + return true; + } + + createShader(source, type) { + let gl = this.gl; + let shader = gl.createShader(type); + gl.shaderSource(shader, source); + gl.compileShader(shader); + if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + return shader; + } + console.error(gl.getShaderInfoLog(shader)); + gl.deleteShader(shader); + return null; + } + + createProgram(name, shaders) { + let gl = this.gl; + let pro = gl.createProgram(); + for (let i = 0; i < shaders.length; i++) { + gl.attachShader(pro, this.getShader(shaders[i], gl.VERTEX_SHADER)); + gl.attachShader(pro, this.getShader(shaders[i], gl.FRAGMENT_SHADER)); + } + gl.linkProgram(pro); + var success = gl.getProgramParameter(pro, gl.LINK_STATUS); + if (success) { + this.programs[name] = pro; + return pro; + } + + console.log(gl.getProgramInfoLog(pro)); + gl.deleteProgram(pro); + return null; + } + + getProgram(name) { + return this.programs[name]; + } + + async loadArray(list, path) { + let self = this; + for (const e of list) { + await self.loadShader(e, path) + } + await self.createProgramForEach(list) + } +} \ No newline at end of file diff --git a/raw/javascript/player.js b/raw/javascript/player.js new file mode 100644 index 0000000..ebe312f --- /dev/null +++ b/raw/javascript/player.js @@ -0,0 +1,5 @@ +class Player { + async init() { + + } +} \ No newline at end of file diff --git a/raw/javascript/utils.js b/raw/javascript/utils.js new file mode 100644 index 0000000..1b56266 --- /dev/null +++ b/raw/javascript/utils.js @@ -0,0 +1,275 @@ +class VTUtils { + static random(min, max) { + let rand = Math.random(); + if (typeof min === 'undefined') { + return rand; + } else if (typeof max === 'undefined') { + if (min instanceof Array) { + return min[Math.floor(rand * min.length)]; + } else { + return rand * min; + } + } else { + if (min > max) { + let tmp = min; + min = max; + max = tmp; + } + return rand * (max - min) + min; + } + }; + + static randomInt(min, max) { + return Math.floor(VTUtils.random(min, max)); + } + + static normalize(val, max, min) { + return (val - min) / (max - min); + }; + + static distance(x, y, x2, y2) { + let a = x - x2; + let b = y - y2; + + return Math.sqrt(a * a + b * b); + } + + static map(n, start1, stop1, start2, stop2, withinBounds) { + let newVal = (n - start1) / (stop1 - start1) * (stop2 - start2) + start2; + if (!withinBounds) { + return newVal; + } + if (start2 < stop2) { + return this.constrain(newVal, start2, stop2); + } else { + return this.constrain(newVal, stop2, start2); + } + }; + + static constrain(n, low, high) { + return Math.max(Math.min(n, high), low); + } + + 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); + + switch (i % 6) { + case 0: + r = v, g = t, b = p; + break; + case 1: + r = q, g = v, b = p; + break; + case 2: + r = p, g = v, b = t; + break; + case 3: + r = p, g = q, b = v; + break; + case 4: + r = t, g = p, b = v; + break; + case 5: + r = v, g = p, b = q; + break; + } + + return {r: r, g: g, b: b}; + } + + static peakRGB(peak) { + return { + r: peak, + g: 1 - peak, + b: 0 + }; + } +} + +class VTVector { + constructor(x, y, z) { + this.x = x || 0; + this.y = y || 0; + this.z = z || 0; + } + + //helper + static createRandom(x, y, z) { + x = x || 1; + y = y || 1; + z = z || 0; + return new VTVector(VTUtils.random(-x, x), VTUtils.random(-y, y), VTUtils.random(-z, z)); + } + + mult(times) { + this.x *= times; + this.y *= times; + this.z *= times; + } + + set(vector) { + this.x = vector.x; + this.y = vector.y; + this.z = vector.z; + } + + add(vector) { + this.x = this.x + vector.x; + this.y = this.y + vector.y; + this.z = this.z + vector.z; + } + + addXYZ(x, y, z) { + this.x += x; + this.y += y; + this.z += z; + } + + setXYZ(x, y, z) { + this.x = x || 0; + this.y = y || 0; + this.z = z || 0; + } + + clone() { + return new VTVector(this.x, this.y, this.z); + } +} + +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]; +} + +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); + } + } + }) +}; + +function create(name, content) { + let d = document.createElement(name); + if (content) { + d.innerHTML = content; + } + return d; +} + +function append(to, array) { + for (let item in array) { + to.appendChild(array[item]); + } +} \ No newline at end of file diff --git a/raw/javascript/visual.js b/raw/javascript/visual.js new file mode 100644 index 0000000..5f8dbd7 --- /dev/null +++ b/raw/javascript/visual.js @@ -0,0 +1,15 @@ +class Visual { + constructor() { + this.data = []; //for drawing + this.dataArray = []; + } + draw() { + } + + setup() { + } +} + +class VisualDrawer { + +} \ No newline at end of file diff --git a/raw/javascript/visuals/sphere.js b/raw/javascript/visuals/sphere.js new file mode 100644 index 0000000..73c8b2c --- /dev/null +++ b/raw/javascript/visuals/sphere.js @@ -0,0 +1,7 @@ +class Sphere extends Visual { + draw() { + } + + setup() { + } +} \ No newline at end of file diff --git a/raw/scss/_controls.scss b/raw/scss/_controls.scss new file mode 100644 index 0000000..b1f7dc2 --- /dev/null +++ b/raw/scss/_controls.scss @@ -0,0 +1,24 @@ +.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; + + &.active { + border-color: #ff066a; + } + + &:hover { + background-color: rgba(21, 21, 21, .7); + border-color: #aaef22; + } +} \ No newline at end of file diff --git a/raw/scss/_gui.scss b/raw/scss/_gui.scss new file mode 100644 index 0000000..01338cf --- /dev/null +++ b/raw/scss/_gui.scss @@ -0,0 +1,108 @@ +#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; + + label { + padding-right: 10px; + user-select: none; + width: 150px; + } + + input { + flex-grow: 1; + user-select: none; + max-width: 150px; + } + + 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; + 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; + + span { + font-family: monospace; + font-size: 4vw; + z-index: 2; + } + + 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; + + &.delay { + background-color: rgba(0, 110, 168, 0.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; + } + 51%, 100% { + transform-origin: right; + } +} \ No newline at end of file diff --git a/raw/scss/_input.scss b/raw/scss/_input.scss new file mode 100644 index 0000000..5bfe3bb --- /dev/null +++ b/raw/scss/_input.scss @@ -0,0 +1,79 @@ +input[type=range] { + width: 100%; + + &:focus { + outline: none; + } + + &:focus::-webkit-slider-runnable-track { + background: #545454; + } + + &:focus::-ms-fill-lower { + background: #424242; + } + + &: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: 0.3px; +} + +switch { + input { + position: absolute; + appearance: none; + opacity: 0; + + &:checked + label:after { + transform: translateX(20px); + } + } + + label { + display: block; + border-radius: 10px; + width: 40px; + height: 20px; + background-color: #dcdcdc; + position: relative; + cursor: pointer; + padding: 0; + + &: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; +} \ No newline at end of file diff --git a/raw/scss/style.scss b/raw/scss/style.scss new file mode 100644 index 0000000..5f04f9f --- /dev/null +++ b/raw/scss/style.scss @@ -0,0 +1,43 @@ +* { + box-sizing: border-box; +} + +*:focus { + outline: none; +} + +html, body { + padding: 0; + margin: 0; + overflow: hidden; + font-size: 16px; + font-family: sans-serif; + background-color: #000000; +} + +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; +} + + +@import "gui"; +@import "input"; +@import "controls"; \ No newline at end of file diff --git a/shaders/sphere.frag b/shaders/sphere.frag index 57589f2..987b45c 100644 --- a/shaders/sphere.frag +++ b/shaders/sphere.frag @@ -3,17 +3,17 @@ // fragment shaders don't have a default precision so we need // to pick one. mediump is a good default. It means "medium precision" precision highp float; - +in vec4 fragNormal; in vec3 v_surfaceToLight; uniform vec4 u_color; +uniform vec3 u_light; uniform vec3 u_lightPos; out vec4 outColor; void main() { - vec3 surfaceToLightDirection = normalize(v_surfaceToLight); + float light = max(dot(fragNormal.xyz, normalize(v_surfaceToLight).xyz), u_light.x); outColor = u_color; - float light = 1.0; - //outColor.rgb *= surfaceToLightDirection; + outColor.rgb *= light; } \ No newline at end of file diff --git a/shaders/sphere.vert b/shaders/sphere.vert index 45fc06f..2ed16f7 100644 --- a/shaders/sphere.vert +++ b/shaders/sphere.vert @@ -1,12 +1,11 @@ #version 300 es in vec4 a_position; -uniform mat4 u_world; uniform mat4 u_matrix; uniform vec3 u_lightPos; -uniform vec3 u_light; uniform float u_pointSize; +out vec4 fragNormal; out vec3 v_surfaceToLight; void main() { @@ -14,8 +13,7 @@ void main() { vec4 pos = a_position * u_matrix; gl_Position = pos; gl_PointSize = u_pointSize; - - vec3 surfaceWorldPosition = (u_world * pos).xyz; - + fragNormal = normalize(pos); + vec3 surfaceWorldPosition = (u_matrix * pos).xyz; v_surfaceToLight = u_lightPos - surfaceWorldPosition; } \ No newline at end of file diff --git a/shaders/test.frag b/shaders/wave.frag similarity index 100% rename from shaders/test.frag rename to shaders/wave.frag diff --git a/shaders/test.vert b/shaders/wave.vert similarity index 100% rename from shaders/test.vert rename to shaders/wave.vert diff --git a/style.css b/style.css deleted file mode 100644 index ccea8c6..0000000 --- a/style.css +++ /dev/null @@ -1,215 +0,0 @@ -html, body { - padding: 0; - margin: 0; - overflow: hidden; - font-size: 16px; - font-family: sans-serif; -} - -div { - position: fixed; - color: #fff; - padding: 1em; -} - -#c { - width: 100%; - height: 100%; -} - -.settings-icon { - right: 0; - bottom: 0; - font-size: 1.5em; - cursor: pointer; -} - -.settings-icon:hover { - color: #0199ff; -} - -.off-can { - top: 0; - left: 0; - width: 300px; - background-color: rgba(33, 33, 33, 0.6); - height: 100vh; - transition: all .5s; - user-select: none; -} - -group { - display: block; - padding-bottom: 10px; - user-select: none; -} - -group-label { - display: block; - border-bottom: 1px solid #ffffff; - 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; -} - -input[type=range] { - -webkit-appearance: none; - width: 100%; -} - -input[type=range]:focus { - outline: none; -} - -input[type=range]::-webkit-slider-runnable-track { - width: 100%; - height: 25.6px; - cursor: pointer; - box-shadow: 1px 1px 1px #000000, 0 0 1px #0d0d0d; - background: #424242; - border-radius: 0; - border: 0 solid #010101; -} - -input[type=range]::-webkit-slider-thumb { - box-shadow: 0 0 0 #470000, 0 0 0 #610000; - border: 0 solid #ff0000; - height: 25px; - width: 15px; - border-radius: 0; - background: #a8c64e; - cursor: pointer; - -webkit-appearance: none; - margin-top: 0.3px; -} - -input[type=range]:focus::-webkit-slider-runnable-track { - background: #545454; -} - -input[type=range]::-moz-range-track { - width: 100%; - height: 25.6px; - cursor: pointer; - box-shadow: 1px 1px 1px #000000, 0 0 1px #0d0d0d; - background: #424242; - border-radius: 0; - border: 0 solid #010101; -} - -input[type=range]::-moz-range-thumb { - box-shadow: 0 0 0 #470000, 0 0 0 #610000; - border: 0 solid #ff0000; - height: 25px; - width: 15px; - border-radius: 0; - background: #a8c64e; - cursor: pointer; -} - -input[type=range]::-ms-track { - width: 100%; - height: 25.6px; - cursor: pointer; - background: transparent; - border-color: transparent; - color: transparent; -} - -input[type=range]::-ms-fill-lower { - background: #303030; - border: 0 solid #010101; - border-radius: 0; - box-shadow: 1px 1px 1px #000000, 0 0 1px #0d0d0d; -} - -input[type=range]::-ms-fill-upper { - background: #424242; - border: 0 solid #010101; - border-radius: 0; - box-shadow: 1px 1px 1px #000000, 0 0 1px #0d0d0d; -} - -input[type=range]::-ms-thumb { - box-shadow: 0 0 0 #470000, 0 0 0 #610000; - border: 0 solid #ff0000; - width: 15px; - border-radius: 0; - background: #a8c64e; - cursor: pointer; - height: 25px; -} - -input[type=range]:focus::-ms-fill-lower { - background: #424242; -} - -input[type=range]:focus::-ms-fill-upper { - background: #545454; -} - -switch input { - position: absolute; - appearance: none; - opacity: 0; -} - -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; -} - -switch input:checked + label:after { - transform: translateX(20px); -} - From 300b6c41064efa84f56dad8a5147ae09e95eb75e Mon Sep 17 00:00:00 2001 From: versustunez Date: Sat, 1 Aug 2020 21:51:54 +0200 Subject: [PATCH 2/7] WIP --- build/task/js.js | 7 +- build/task/spriteBuilder.js | 1 + index.html | 35 +- out/icon-sprite.svg | 2 +- out/js/scripts.js | 593 +++++++++++++++++++++++++++++++- out/js/scripts.min.js | 2 +- out/theme/style.css | 2 +- out/tpl/playlist-footer.tpl | 17 + out/tpl/playlist-item.tpl | 4 + out/tpl/playlist.tpl | 5 + raw/gui/wave.json | 1 + raw/javascript/app.js | 16 +- raw/javascript/audio.js | 68 +++- raw/javascript/config.js | 35 ++ raw/javascript/eventHandler.js | 39 +++ raw/javascript/gui.js | 95 ++++- raw/javascript/handler.js | 6 + raw/javascript/player.js | 177 ++++++++++ raw/javascript/template.js | 44 +++ raw/javascript/utils.js | 10 + raw/javascript/visual.js | 33 ++ raw/javascript/visuals/water.js | 10 + raw/javascript/visuals/wave.js | 60 ++++ raw/scss/_gui.scss | 60 ++++ raw/scss/_playlist.scss | 57 +++ raw/scss/style.scss | 3 +- shaders/water.frag | 13 + shaders/water.vert | 11 + shaders/wave.frag | 7 +- shaders/wave.vert | 15 +- 30 files changed, 1399 insertions(+), 29 deletions(-) create mode 100644 out/tpl/playlist-footer.tpl create mode 100644 out/tpl/playlist-item.tpl create mode 100644 out/tpl/playlist.tpl create mode 100644 raw/gui/wave.json create mode 100644 raw/javascript/config.js create mode 100644 raw/javascript/eventHandler.js create mode 100644 raw/javascript/template.js create mode 100644 raw/javascript/visuals/water.js create mode 100644 raw/javascript/visuals/wave.js create mode 100644 raw/scss/_playlist.scss create mode 100644 shaders/water.frag create mode 100644 shaders/water.vert diff --git a/build/task/js.js b/build/task/js.js index 7b7741b..c612353 100644 --- a/build/task/js.js +++ b/build/task/js.js @@ -7,19 +7,22 @@ const basePath = __dirname + '/../../raw/javascript/'; const visualPath = basePath + 'visuals/'; const visuals = [ visualPath + 'sphere.js', - //visualPath + 'wave.js', - //visualPath + 'water.js', + visualPath + 'wave.js', + visualPath + 'water.js', //visualPath + 'experimental.js', ] const config = { src: [ basePath + 'utils.js', + basePath + 'template.js', basePath + 'handler.js', basePath + 'audio.js', basePath + 'player.js', basePath + 'gui.js', basePath + 'visual.js', + basePath + 'config.js', ...visuals, + basePath + 'eventHandler.js', basePath + 'app.js' ], dest: __dirname + '/../../out/js' diff --git a/build/task/spriteBuilder.js b/build/task/spriteBuilder.js index 29f5e33..d19382e 100644 --- a/build/task/spriteBuilder.js +++ b/build/task/spriteBuilder.js @@ -42,6 +42,7 @@ function buildIconSprites() { fal.faCogs, fal.faFolderUpload, fal.faListMusic, + fal.faFileAudio, ], vt: [] }; diff --git a/index.html b/index.html index eb50ef4..434111b 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - WEBGL Test + VS3D-Vis @@ -19,11 +19,19 @@
+ +
+ +
diff --git a/out/icon-sprite.svg b/out/icon-sprite.svg index f13e5ca..6f4f495 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 dac8955..a973d10 100644 --- a/out/js/scripts.js +++ b/out/js/scripts.js @@ -260,6 +260,16 @@ Node.prototype.addDelegatedEventListener = function (type, aim, cb) { }) }; +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) { @@ -273,6 +283,50 @@ function append(to, array) { to.appendChild(array[item]); } } +class Template { + constructor() { + this.tpl = {}; + } + + async loadTemplate(name) { + let self = this; + if (!this.tpl[name]) { + await fetch(templateDir + name + ".tpl").then((r) => r.text()).then(c => { + self.tpl[name] = c; + }) + } + } + + async loadArray(names) { + for (let name of names) { + await this.loadTemplate(name); + } + } + + parseTemplate(name, data) { + if (!this.tpl[name]) { + return "" + } + let m, d = this.tpl[name]; + while ((m = templateEx.exec(d)) !== null) { + if (m.index === templateEx.lastIndex) { + templateEx.lastIndex++; + } + let key = m[0]; + d = d.replace(key, data[m[1]] || "") + } + return d; + } + + parseFromAPI(url, name, cb) { + fetch(url).then((r) => r.json()).then(d => { + cb(this.parseTemplate(name, d)) + }).catch(console.error) + } +} + +const templateEx = /\$(.*?)\$/gm; +const templateDir = "out/tpl/" class ShaderHandler { constructor(gl) { this.gl = gl; @@ -360,6 +414,12 @@ class ShaderHandler { return this.programs[name]; } + use(name) { + let pro = this.programs[name]; + this.gl.useProgram(pro); + return pro; + } + async loadArray(list, path) { let self = this; for (const e of list) { @@ -368,19 +428,355 @@ class ShaderHandler { await self.createProgramForEach(list) } } +const AudioContext = window.AudioContext || window.webkitAudioContext; + class AudioHandler { async init() { - this.audioFile = new Audio(); + let self = this; + self.isStarted = false; + self.audioFile = new Audio(); + self.actx = new AudioContext() + self.analyser = self.actx.createAnalyser() + self.analyser.fftSize = 4096; + self.lastSong = null; + await self.connectAll(); + } + + async connectAll() { + let self = this; + self.source = self.actx.createMediaElementSource(self.audioFile); + self.source.connect(self.analyser); + self.analyser.connect(self.actx.destination); + self.audioFile.addEventListener('ended', player.nextSong.bind(player)); + } + + async start() { + if (this.audioFile.src === '') { + return; + } + if (!this.isStarted) { + this.isStarted = true; + await this.actx.resume(); + } + } + + async stop() { + if (this.isStarted) { + this.isStarted = false; + await this.actx.suspend(); + } + } + + fftSize(size) { + this.analyser.fftSize = size; + } + + smoothing(float) { + this.analyser.smoothingTimeConstant = float; + } + + loadSong(src) { + let self = this; + if (self.lastSong) { + URL.revokeObjectURL(self.lastSong); + } + self.lastSong = this.audioFile.src = URL.createObjectURL(src); + if (!this.isStarted) { + this.start(); + } + this.audioFile.play(); + } + + getIntArray(steps) { + let dataArray = new Uint8Array(steps); + this.analyser.getByteFrequencyData(dataArray); + return dataArray; + } + + getFloatArray() { + let dataArray = new Float32Array(this.analyser.frequencyBinCount); + this.analyser.getFloatTimeDomainData(dataArray); + return dataArray; } } class Player { async init() { + this.playlist = new Playlist(); + } + nextSong() { + let next = this.playlist.getNext(); + audioHandler.loadSong(next.file); + } + + prevSong() { + let next = this.playlist.getPrevious(); + audioHandler.loadSong(next.file); + } + + playStop() { + if (!audioHandler.lastSong) { + let next = this.playlist.getCurrent(); + audioHandler.loadSong(next.file); + } + let audioFile = audioHandler.audioFile; + if (audioFile.paused) { + audioFile.play(); + } else { + audioFile.pause(); + } + } + + playByID(number) { + let next = this.playlist.getFile(number); + audioHandler.loadSong(next.file); + } +} + +const PAGINATIONLIMIT = 50; + +class Playlist { + constructor() { + this.list = []; + this.shuffled = []; + this.index = 0; + this.page = 0; + this.isShuffle = false; + $('body').addDelegatedEventListener('change', 'input[type="file"]', this.changeFiles.bind(this)); + $('body').addDelegatedEventListener('click', '.pagination .item', this.handlePagination.bind(this)); + } + + shuffle() { + // only shuffle if more then 2 elements are in + let len = this.list.length; + if (len < 3) { + this.shuffled = this.list; + } + // the current-list need to be shuffled... + for (let i = 0; i < len; i++) { + let random = VTUtils.randomInt(0, len - 1); + this.swap(i, random); + } + } + + swap(a, b) { + this.shuffled[a] = this.list[b]; + this.shuffled[b] = this.list[a]; + } + + getNext() { + let items = this.isShuffle ? this.shuffled : this.list, + len = items.length - 1, + next = this.index + 1; + if (next > len) { + next = 0; + } + this.index = next; + return items[next]; + } + + getPrevious() { + let items = this.isShuffle ? this.shuffled : this.list, + len = items.length - 1, + next = this.index - 1; + if (next < 0) { + next = len; + } + this.index = next; + return items[next]; + } + + getCurrent() { + let items = this.isShuffle ? this.shuffled : this.list; + 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; + this.list = files; + this.shuffle(); + } + + handlePagination(event, el) { + if (el.hasClass('inactive')) { + return; + } + if (el.hasClass('next-site')) { + this.renderPagination(this.page + 1); + } else { + this.renderPagination(this.page - 1); + } + } + + renderPagination(page) { + let length = this.list.length, + maxSite = Math.ceil(length / PAGINATIONLIMIT) - 1; + if (page < 0) { + page = 0; + } + if (page > maxSite) { + page = maxSite; + } + let s = page * PAGINATIONLIMIT, + e = s + PAGINATIONLIMIT, + data = ""; + this.page = page; + if (e >= length) { + e = length; + } + if (length > 0) { + let items = this.isShuffle ? this.shuffled : this.list; + for (let i = s; i < e; i++) { + let obj = { + index: i, + nr: i + 1, + title: items[i].name + } + data += template.parseTemplate("playlist-item", obj); + } + } else { + data = "

No Songs uploaded!

"; + } + let hasNext = maxSite > 1 && page < maxSite; + gui.modal.renderModal( + "Playlist", + template.parseTemplate("playlist", { + content: data, + }), + template.parseTemplate('playlist-footer', { + prevActive: page > 0 ? 'active' : 'inactive', + nextActive: hasNext ? 'active' : 'inactive', + page: (page + 1) + ' / ' + parseInt(maxSite + 1), + }) + ); + } + + //playlist handler for file input! + changeFiles(e, el) { + let files = []; + let i = 0; + for (let file of el.files) { + if (file && file.type.indexOf('audio') !== -1 && file.name.match(".m3u") === null) { + let name = file.name.split("."); + name.pop(); + name = name.join("."); + files.push({ + file: file, + name: name, + index: i++ + }); + } + } + this.setPlaylist(files); + if (files.length > 0) { + this.renderPagination(0); + } else { + alert("No Valid AudioFiles found!"); + } } } class GUI { - init() { + async init() { + this.data = {}; + this.modal = new Modal(); + // load first vis window! + await this.loadForVis(); + await template.loadArray([ + 'playlist-item', + 'playlist', + 'playlist-footer' + ]); + this.initDropZone(); + } + async loadForVis() { + let c = visual.c, + d = this.data[c]; + if (d == null) { + this.data[c] = await fetch("out/gui/" + c + ".json").then((r) => r.json()); + } + } + + 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 => { + window.addEventListener(el, async e => { + e.preventDefault(); + e.stopPropagation(); + if (e.type === 'drop') { + if (e.dataTransfer.files.length > 0) { + player.playlist.changeFiles(e, e.dataTransfer); + } else { + alert("Sorry you need to upload files!"); + } + } + }); + }); + }; +} + +class Modal { + constructor() { + let self = this; + self.currentModal = ''; + self.modal = $('#modal'); + self.parent = self.modal.parentNode; + self.modal.addDelegatedEventListener('click', 'header .close', this.closeModal.bind(this)); + } + + resetModal() { + this.renderModal('', '', ''); + } + + renderModal(title, content, footer) { + this.currentModal = title; + this.renderHeader(title); + this.renderContent(content); + this.renderFooter(footer); + this.showModal(); + } + + renderHeader(header) { + let h = $('header .headline', this.modal); + h.innerHTML = header; + } + + renderContent(content) { + let con = $('modal-content', this.modal); + con.innerHTML = content; + } + + renderFooter(footer) { + let con = $('modal-footer', this.modal); + con.innerHTML = footer; + } + + closeModal() { + this.parent.addClass("hide") + } + + isCurrent(title) { + return title === this.currentModal; + } + + showModal() { + this.parent.removeClass("hide") } } class Visual { @@ -388,6 +784,11 @@ class Visual { this.data = []; //for drawing this.dataArray = []; } + + updateData() { + + } + draw() { } @@ -396,7 +797,70 @@ class Visual { } 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(visual) { + if (this.visuals[visual] != null) { + this.c = visual; + this.visuals[this.c].setup(); + } + } + + updateLoop() { + let self = this; + let pro = shaderHandler.use(self.c); + let vis = self.visuals[self.c]; + vis.updateData(); + vis.draw(pro); + requestAnimationFrame(self.updateLoop.bind(self)) + } +} +class Config { + constructor() { + this.config = {}; + this.name = '' + } + + loadConfigByName(name) { + this.saveConfig(); + this.name = 'config-' + name; + this.config = JSON.parse(this.name); + } + + saveConfig() { + if (this.name !== '') { + localStorage.setItem(this.name, JSON.stringify(this.config)); + } + } + + addItem(name, value) { + this.config[name] = value; + } + + removeItem(name) { + delete this.config[name]; + } + + getItem(name, def) { + let value = this.config[name]; + if (value === undefined || value === null) { + this.config[name] = def; + value = def; + } + return value; + } } class Sphere extends Visual { draw() { @@ -405,23 +869,140 @@ class Sphere extends Visual { setup() { } } +// 3D Audio-Waves -> maybe also 2D? +class Wave extends Visual { + updateData() { + this.data = []; + let data = audioHandler.getFloatArray(); + let add = 2 / data.length, + x = -1; + for (let i = 0; i < data.length; i++) { + this.data.push(x, data[i], data[i]); + 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); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.data), gl.DYNAMIC_DRAW); + let vao = gl.createVertexArray(); + 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); + } + + rotate(program) { + let aspect = c.height / c.width, + 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))); + let rotate = gl.getUniformLocation(program, "u_matrix"); + gl.uniformMatrix4fv(rotate, false, matrix); + } + + setup() { + audioHandler.fftSize(16384) + } + + prepare(program) { + this.position = gl.getAttribLocation(program, "a_position"); + this.color = gl.getUniformLocation(program, "u_color"); + } +} +//animate Water the way like the Audio is Coming... 256FFT-Size max! +class Water extends Visual { + draw() { + } + + setup() { + audioHandler.fftSize(256) + } + +} +async function initHandler() { + let body = $('body'); + $('.playlist.menu-icon').addEventListener('click', e => { + player.playlist.renderPagination(player.playlist.page); + }); + + body.addDelegatedEventListener('click', '.playlist-item', (e, el) => { + let number = el.dataset.index; + player.playByID(parseInt(number)); + togglePlayButton('pause'); + }); + + body.addDelegatedEventListener('click', '.controls button', (e, el) => { + switch (el.id) { + case 'previous': + player.prevSong(); + break; + case 'next': + player.nextSong() + break; + case 'play': + player.playStop(); + break; + } + togglePlayButton(audioHandler.audioFile.paused ? 'play' : 'pause'); + }); +} + + +function togglePlayButton(status) { + let icons = $$('#play .icon'); + icons.forEach(el => { + if(el.dataset.name === status) { + el.removeClass('hide'); + } else { + el.addClass('hide'); + } + }) +} const shaderHandler = new ShaderHandler(null), audioHandler = new AudioHandler(), gui = new GUI(), - player = new Player(); + visual = new VisualDrawer(), + template = new Template(), + player = new Player(), + config = new Config(); + +let c = null, + gl = null; async function startUP() { - let c = document.body.querySelector('#c'), + c = document.body.querySelector('#c'), gl = c.getContext("webgl2"); if (!gl) { alert("SORRY THE BROWSER DOESN'T SUPPORT WEBGL2"); return false; } shaderHandler.setGL(gl) - await shaderHandler.loadArray(["wave", "sphere"], 'shaders/'); + await shaderHandler.loadArray(["wave", "sphere", "water"], 'shaders/'); await audioHandler.init(); await player.init(); - gui.init(); + await visual.init(); + await gui.init(); + await initHandler(); } startUP().then(r => { diff --git a/out/js/scripts.min.js b/out/js/scripts.min.js index ae2c780..d1f8c6c 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,r){let s=t-a,i=e-r;return Math.sqrt(s*s+i*i)}static map(t,e,a,r,s,i){let n=(t-e)/(a-e)*(s-r)+r;return i?r{let r=t.target;if(r.matches(e))a(t,r);else{let s=r.closest(e);s&&a(t,s)}})};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 r=t+"_"+a;if(!this.shaders[r]){let t=await fetch(e),s=this.createShader(await t.text(),a);s&&(this.shaders[r]=s)}return!!this.shaders[r]}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{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,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 diff --git a/out/theme/style.css b/out/theme/style.css index 118e5e6..6071afb 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}}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} \ No newline at end of file +*{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 diff --git a/out/tpl/playlist-footer.tpl b/out/tpl/playlist-footer.tpl new file mode 100644 index 0000000..bf2dac5 --- /dev/null +++ b/out/tpl/playlist-footer.tpl @@ -0,0 +1,17 @@ + + + \ No newline at end of file diff --git a/out/tpl/playlist-item.tpl b/out/tpl/playlist-item.tpl new file mode 100644 index 0000000..5e988d0 --- /dev/null +++ b/out/tpl/playlist-item.tpl @@ -0,0 +1,4 @@ + +
$nr$
+
$title$
+
\ No newline at end of file diff --git a/out/tpl/playlist.tpl b/out/tpl/playlist.tpl new file mode 100644 index 0000000..3ad6108 --- /dev/null +++ b/out/tpl/playlist.tpl @@ -0,0 +1,5 @@ + +
+ $content$ +
+
\ No newline at end of file diff --git a/raw/gui/wave.json b/raw/gui/wave.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/raw/gui/wave.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/raw/javascript/app.js b/raw/javascript/app.js index de5e54f..d1d3b6d 100644 --- a/raw/javascript/app.js +++ b/raw/javascript/app.js @@ -1,20 +1,28 @@ const shaderHandler = new ShaderHandler(null), audioHandler = new AudioHandler(), gui = new GUI(), - player = new Player(); + visual = new VisualDrawer(), + template = new Template(), + player = new Player(), + config = new Config(); + +let c = null, + gl = null; async function startUP() { - let c = document.body.querySelector('#c'), + c = document.body.querySelector('#c'), gl = c.getContext("webgl2"); if (!gl) { alert("SORRY THE BROWSER DOESN'T SUPPORT WEBGL2"); return false; } shaderHandler.setGL(gl) - await shaderHandler.loadArray(["wave", "sphere"], 'shaders/'); + await shaderHandler.loadArray(["wave", "sphere", "water"], 'shaders/'); await audioHandler.init(); await player.init(); - gui.init(); + await visual.init(); + await gui.init(); + await initHandler(); } startUP().then(r => { diff --git a/raw/javascript/audio.js b/raw/javascript/audio.js index f8aa9dc..0d34bf3 100644 --- a/raw/javascript/audio.js +++ b/raw/javascript/audio.js @@ -1,5 +1,71 @@ +const AudioContext = window.AudioContext || window.webkitAudioContext; + class AudioHandler { async init() { - this.audioFile = new Audio(); + let self = this; + self.isStarted = false; + self.audioFile = new Audio(); + self.actx = new AudioContext() + self.analyser = self.actx.createAnalyser() + self.analyser.fftSize = 4096; + self.lastSong = null; + await self.connectAll(); + } + + async connectAll() { + let self = this; + self.source = self.actx.createMediaElementSource(self.audioFile); + self.source.connect(self.analyser); + self.analyser.connect(self.actx.destination); + self.audioFile.addEventListener('ended', player.nextSong.bind(player)); + } + + async start() { + if (this.audioFile.src === '') { + return; + } + if (!this.isStarted) { + this.isStarted = true; + await this.actx.resume(); + } + } + + async stop() { + if (this.isStarted) { + this.isStarted = false; + await this.actx.suspend(); + } + } + + fftSize(size) { + this.analyser.fftSize = size; + } + + smoothing(float) { + this.analyser.smoothingTimeConstant = float; + } + + loadSong(src) { + let self = this; + if (self.lastSong) { + URL.revokeObjectURL(self.lastSong); + } + self.lastSong = this.audioFile.src = URL.createObjectURL(src); + if (!this.isStarted) { + this.start(); + } + this.audioFile.play(); + } + + getIntArray(steps) { + let dataArray = new Uint8Array(steps); + this.analyser.getByteFrequencyData(dataArray); + return dataArray; + } + + getFloatArray() { + let dataArray = new Float32Array(this.analyser.frequencyBinCount); + this.analyser.getFloatTimeDomainData(dataArray); + return dataArray; } } \ No newline at end of file diff --git a/raw/javascript/config.js b/raw/javascript/config.js new file mode 100644 index 0000000..07ec7ad --- /dev/null +++ b/raw/javascript/config.js @@ -0,0 +1,35 @@ +class Config { + constructor() { + this.config = {}; + this.name = '' + } + + loadConfigByName(name) { + this.saveConfig(); + this.name = 'config-' + name; + this.config = JSON.parse(this.name); + } + + saveConfig() { + if (this.name !== '') { + localStorage.setItem(this.name, JSON.stringify(this.config)); + } + } + + addItem(name, value) { + this.config[name] = value; + } + + removeItem(name) { + delete this.config[name]; + } + + getItem(name, def) { + let value = this.config[name]; + if (value === undefined || value === null) { + this.config[name] = def; + value = def; + } + return value; + } +} \ No newline at end of file diff --git a/raw/javascript/eventHandler.js b/raw/javascript/eventHandler.js new file mode 100644 index 0000000..41fdfd6 --- /dev/null +++ b/raw/javascript/eventHandler.js @@ -0,0 +1,39 @@ +async function initHandler() { + let body = $('body'); + $('.playlist.menu-icon').addEventListener('click', e => { + player.playlist.renderPagination(player.playlist.page); + }); + + body.addDelegatedEventListener('click', '.playlist-item', (e, el) => { + let number = el.dataset.index; + player.playByID(parseInt(number)); + togglePlayButton('pause'); + }); + + body.addDelegatedEventListener('click', '.controls button', (e, el) => { + switch (el.id) { + case 'previous': + player.prevSong(); + break; + case 'next': + player.nextSong() + break; + case 'play': + player.playStop(); + break; + } + togglePlayButton(audioHandler.audioFile.paused ? 'play' : 'pause'); + }); +} + + +function togglePlayButton(status) { + let icons = $$('#play .icon'); + icons.forEach(el => { + if(el.dataset.name === status) { + el.removeClass('hide'); + } else { + el.addClass('hide'); + } + }) +} \ No newline at end of file diff --git a/raw/javascript/gui.js b/raw/javascript/gui.js index 7228c33..b24922c 100644 --- a/raw/javascript/gui.js +++ b/raw/javascript/gui.js @@ -1,5 +1,98 @@ class GUI { - init() { + async init() { + this.data = {}; + this.modal = new Modal(); + // load first vis window! + await this.loadForVis(); + await template.loadArray([ + 'playlist-item', + 'playlist', + 'playlist-footer' + ]); + this.initDropZone(); + } + async loadForVis() { + let c = visual.c, + d = this.data[c]; + if (d == null) { + this.data[c] = await fetch("out/gui/" + c + ".json").then((r) => r.json()); + } + } + + 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 => { + window.addEventListener(el, async e => { + e.preventDefault(); + e.stopPropagation(); + if (e.type === 'drop') { + if (e.dataTransfer.files.length > 0) { + player.playlist.changeFiles(e, e.dataTransfer); + } else { + alert("Sorry you need to upload files!"); + } + } + }); + }); + }; +} + +class Modal { + constructor() { + let self = this; + self.currentModal = ''; + self.modal = $('#modal'); + self.parent = self.modal.parentNode; + self.modal.addDelegatedEventListener('click', 'header .close', this.closeModal.bind(this)); + } + + resetModal() { + this.renderModal('', '', ''); + } + + renderModal(title, content, footer) { + this.currentModal = title; + this.renderHeader(title); + this.renderContent(content); + this.renderFooter(footer); + this.showModal(); + } + + renderHeader(header) { + let h = $('header .headline', this.modal); + h.innerHTML = header; + } + + renderContent(content) { + let con = $('modal-content', this.modal); + con.innerHTML = content; + } + + renderFooter(footer) { + let con = $('modal-footer', this.modal); + con.innerHTML = footer; + } + + closeModal() { + this.parent.addClass("hide") + } + + isCurrent(title) { + return title === this.currentModal; + } + + showModal() { + this.parent.removeClass("hide") } } \ No newline at end of file diff --git a/raw/javascript/handler.js b/raw/javascript/handler.js index a676610..5a6f1a6 100644 --- a/raw/javascript/handler.js +++ b/raw/javascript/handler.js @@ -85,6 +85,12 @@ class ShaderHandler { return this.programs[name]; } + use(name) { + let pro = this.programs[name]; + this.gl.useProgram(pro); + return pro; + } + async loadArray(list, path) { let self = this; for (const e of list) { diff --git a/raw/javascript/player.js b/raw/javascript/player.js index ebe312f..6085afc 100644 --- a/raw/javascript/player.js +++ b/raw/javascript/player.js @@ -1,5 +1,182 @@ class Player { async init() { + this.playlist = new Playlist(); + } + nextSong() { + let next = this.playlist.getNext(); + audioHandler.loadSong(next.file); + } + + prevSong() { + let next = this.playlist.getPrevious(); + audioHandler.loadSong(next.file); + } + + playStop() { + if (!audioHandler.lastSong) { + let next = this.playlist.getCurrent(); + audioHandler.loadSong(next.file); + } + let audioFile = audioHandler.audioFile; + if (audioFile.paused) { + audioFile.play(); + } else { + audioFile.pause(); + } + } + + playByID(number) { + let next = this.playlist.getFile(number); + audioHandler.loadSong(next.file); + } +} + +const PAGINATIONLIMIT = 50; + +class Playlist { + constructor() { + this.list = []; + this.shuffled = []; + this.index = 0; + this.page = 0; + this.isShuffle = false; + $('body').addDelegatedEventListener('change', 'input[type="file"]', this.changeFiles.bind(this)); + $('body').addDelegatedEventListener('click', '.pagination .item', this.handlePagination.bind(this)); + } + + shuffle() { + // only shuffle if more then 2 elements are in + let len = this.list.length; + if (len < 3) { + this.shuffled = this.list; + } + // the current-list need to be shuffled... + for (let i = 0; i < len; i++) { + let random = VTUtils.randomInt(0, len - 1); + this.swap(i, random); + } + } + + swap(a, b) { + this.shuffled[a] = this.list[b]; + this.shuffled[b] = this.list[a]; + } + + getNext() { + let items = this.isShuffle ? this.shuffled : this.list, + len = items.length - 1, + next = this.index + 1; + if (next > len) { + next = 0; + } + this.index = next; + return items[next]; + } + + getPrevious() { + let items = this.isShuffle ? this.shuffled : this.list, + len = items.length - 1, + next = this.index - 1; + if (next < 0) { + next = len; + } + this.index = next; + return items[next]; + } + + getCurrent() { + let items = this.isShuffle ? this.shuffled : this.list; + 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; + this.list = files; + this.shuffle(); + } + + handlePagination(event, el) { + if (el.hasClass('inactive')) { + return; + } + if (el.hasClass('next-site')) { + this.renderPagination(this.page + 1); + } else { + this.renderPagination(this.page - 1); + } + } + + renderPagination(page) { + let length = this.list.length, + maxSite = Math.ceil(length / PAGINATIONLIMIT) - 1; + if (page < 0) { + page = 0; + } + if (page > maxSite) { + page = maxSite; + } + let s = page * PAGINATIONLIMIT, + e = s + PAGINATIONLIMIT, + data = ""; + this.page = page; + if (e >= length) { + e = length; + } + if (length > 0) { + let items = this.isShuffle ? this.shuffled : this.list; + for (let i = s; i < e; i++) { + let obj = { + index: i, + nr: i + 1, + title: items[i].name + } + data += template.parseTemplate("playlist-item", obj); + } + } else { + data = "

No Songs uploaded!

"; + } + let hasNext = maxSite > 1 && page < maxSite; + gui.modal.renderModal( + "Playlist", + template.parseTemplate("playlist", { + content: data, + }), + template.parseTemplate('playlist-footer', { + prevActive: page > 0 ? 'active' : 'inactive', + nextActive: hasNext ? 'active' : 'inactive', + page: (page + 1) + ' / ' + parseInt(maxSite + 1), + }) + ); + } + + //playlist handler for file input! + changeFiles(e, el) { + let files = []; + let i = 0; + for (let file of el.files) { + if (file && file.type.indexOf('audio') !== -1 && file.name.match(".m3u") === null) { + let name = file.name.split("."); + name.pop(); + name = name.join("."); + files.push({ + file: file, + name: name, + index: i++ + }); + } + } + this.setPlaylist(files); + if (files.length > 0) { + this.renderPagination(0); + } else { + alert("No Valid AudioFiles found!"); + } } } \ No newline at end of file diff --git a/raw/javascript/template.js b/raw/javascript/template.js new file mode 100644 index 0000000..06ac580 --- /dev/null +++ b/raw/javascript/template.js @@ -0,0 +1,44 @@ +class Template { + constructor() { + this.tpl = {}; + } + + async loadTemplate(name) { + let self = this; + if (!this.tpl[name]) { + await fetch(templateDir + name + ".tpl").then((r) => r.text()).then(c => { + self.tpl[name] = c; + }) + } + } + + async loadArray(names) { + for (let name of names) { + await this.loadTemplate(name); + } + } + + parseTemplate(name, data) { + if (!this.tpl[name]) { + return "" + } + let m, d = this.tpl[name]; + while ((m = templateEx.exec(d)) !== null) { + if (m.index === templateEx.lastIndex) { + templateEx.lastIndex++; + } + let key = m[0]; + d = d.replace(key, data[m[1]] || "") + } + return d; + } + + parseFromAPI(url, name, cb) { + fetch(url).then((r) => r.json()).then(d => { + cb(this.parseTemplate(name, d)) + }).catch(console.error) + } +} + +const templateEx = /\$(.*?)\$/gm; +const templateDir = "out/tpl/" \ No newline at end of file diff --git a/raw/javascript/utils.js b/raw/javascript/utils.js index 1b56266..e426e72 100644 --- a/raw/javascript/utils.js +++ b/raw/javascript/utils.js @@ -260,6 +260,16 @@ Node.prototype.addDelegatedEventListener = function (type, aim, cb) { }) }; +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) { diff --git a/raw/javascript/visual.js b/raw/javascript/visual.js index 5f8dbd7..1df87b0 100644 --- a/raw/javascript/visual.js +++ b/raw/javascript/visual.js @@ -3,6 +3,11 @@ class Visual { this.data = []; //for drawing this.dataArray = []; } + + updateData() { + + } + draw() { } @@ -11,5 +16,33 @@ class Visual { } 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(visual) { + if (this.visuals[visual] != null) { + this.c = visual; + this.visuals[this.c].setup(); + } + } + + updateLoop() { + let self = this; + let pro = shaderHandler.use(self.c); + let vis = self.visuals[self.c]; + vis.updateData(); + vis.draw(pro); + requestAnimationFrame(self.updateLoop.bind(self)) + } } \ No newline at end of file diff --git a/raw/javascript/visuals/water.js b/raw/javascript/visuals/water.js new file mode 100644 index 0000000..848f19c --- /dev/null +++ b/raw/javascript/visuals/water.js @@ -0,0 +1,10 @@ +//animate Water the way like the Audio is Coming... 256FFT-Size max! +class Water extends Visual { + draw() { + } + + setup() { + audioHandler.fftSize(256) + } + +} \ No newline at end of file diff --git a/raw/javascript/visuals/wave.js b/raw/javascript/visuals/wave.js new file mode 100644 index 0000000..7b583b5 --- /dev/null +++ b/raw/javascript/visuals/wave.js @@ -0,0 +1,60 @@ +// 3D Audio-Waves -> maybe also 2D? +class Wave extends Visual { + updateData() { + this.data = []; + let data = audioHandler.getFloatArray(); + let add = 2 / data.length, + x = -1; + for (let i = 0; i < data.length; i++) { + this.data.push(x, data[i], data[i]); + 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); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.data), gl.DYNAMIC_DRAW); + let vao = gl.createVertexArray(); + 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); + } + + rotate(program) { + let aspect = c.height / c.width, + 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))); + let rotate = gl.getUniformLocation(program, "u_matrix"); + gl.uniformMatrix4fv(rotate, false, matrix); + } + + setup() { + audioHandler.fftSize(16384) + } + + prepare(program) { + this.position = gl.getAttribLocation(program, "a_position"); + this.color = gl.getUniformLocation(program, "u_color"); + } +} \ No newline at end of file diff --git a/raw/scss/_gui.scss b/raw/scss/_gui.scss index 01338cf..f62aea7 100644 --- a/raw/scss/_gui.scss +++ b/raw/scss/_gui.scss @@ -50,6 +50,7 @@ group-input { .top-menu-left { display: flex; + div { position: relative; } @@ -105,4 +106,63 @@ group-input { 51%, 100% { 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; + + &.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, 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; + } } \ No newline at end of file diff --git a/raw/scss/_playlist.scss b/raw/scss/_playlist.scss new file mode 100644 index 0000000..215fb2a --- /dev/null +++ b/raw/scss/_playlist.scss @@ -0,0 +1,57 @@ +playlist { + display: flex; + flex-direction: column; + + div { + padding: unset; + position: unset; + } + + .pagination { + display: flex; + justify-content: flex-end; + font-size: 1.5em; + padding: 5px; + + .item { + cursor: pointer; + user-select: none; + border-radius: 5px; + margin: 0 3px; + + &.inactive { + color: #aaa; + pointer-events: none; + cursor: not-allowed; + } + + &:hover { + color: #006ea8; + } + } + } + + .playlist-item { + display: flex; + padding: 5px; + border-bottom: 1px solid #dcdcdc; + cursor: pointer; + + &-title { + margin-left: 10px; + padding: 5px; + display: flex; + align-items: center; + } + + &-number { + padding: 5px 10px 5px 5px; + border-right: 1px solid #ff3232; + width: 50px; + } + + &:hover { + background-color: rgba(0, 0, 0, .4); + } + } +} \ No newline at end of file diff --git a/raw/scss/style.scss b/raw/scss/style.scss index 5f04f9f..e1817a8 100644 --- a/raw/scss/style.scss +++ b/raw/scss/style.scss @@ -40,4 +40,5 @@ div { @import "gui"; @import "input"; -@import "controls"; \ No newline at end of file +@import "controls"; +@import "playlist"; \ No newline at end of file diff --git a/shaders/water.frag b/shaders/water.frag new file mode 100644 index 0000000..0bd75c4 --- /dev/null +++ b/shaders/water.frag @@ -0,0 +1,13 @@ +#version 300 es + +// fragment shaders don't have a default precision so we need +// to pick one. mediump is a good default. It means "medium precision" +precision mediump float; + +uniform vec4 u_color; + +out vec4 outColor; + +void main() { + outColor = vec4(255,255,255,0); +} \ No newline at end of file diff --git a/shaders/water.vert b/shaders/water.vert new file mode 100644 index 0000000..ddf3857 --- /dev/null +++ b/shaders/water.vert @@ -0,0 +1,11 @@ +#version 300 es + +in vec2 a_position; +void main() { + // convert the position from pixels to 0.0 to 1.0 + vec2 scale = a_position / vec2(255.0, 255.0); + vec2 remap = scale * 2.0; + vec2 space = remap - 1.0; + space.y = space.y * 0.85; + gl_Position = vec4(space, 0,1); +} \ No newline at end of file diff --git a/shaders/wave.frag b/shaders/wave.frag index 088b9b9..03b4508 100644 --- a/shaders/wave.frag +++ b/shaders/wave.frag @@ -4,10 +4,15 @@ // to pick one. mediump is a good default. It means "medium precision" precision mediump float; +in vec3 pos; uniform vec4 u_color; out vec4 outColor; void main() { - outColor = u_color; + vec3 color = pos.xyz; + color.z = color.z + 1.0; + color.z = color.z / 2.0; + color.z = color.z * 255.0; + outColor = vec4(color, 1.0); } \ No newline at end of file diff --git a/shaders/wave.vert b/shaders/wave.vert index ddf3857..863c1a3 100644 --- a/shaders/wave.vert +++ b/shaders/wave.vert @@ -1,11 +1,14 @@ #version 300 es -in vec2 a_position; +in vec3 a_position; +uniform mat4 u_matrix; + +out vec3 pos; + void main() { // convert the position from pixels to 0.0 to 1.0 - vec2 scale = a_position / vec2(255.0, 255.0); - vec2 remap = scale * 2.0; - vec2 space = remap - 1.0; - space.y = space.y * 0.85; - gl_Position = vec4(space, 0,1); + vec4 scale = vec4(a_position, 1) * u_matrix; + scale.y = scale.y * 0.85; + gl_Position = scale; + pos = a_position; } \ No newline at end of file From 9d5259767ce43358d67a88d4c07db784979e98fb Mon Sep 17 00:00:00 2001 From: versustunez Date: Wed, 5 Aug 2020 11:24:59 +0200 Subject: [PATCH 3/7] - added imageUploader - fixed loading songs - cleanup utils - added some helper class - cleanup preparing of WEBGL2 - added 3D wave - added light-support - added configs - added gui-events for playing, shuffling and playlist --- build/task/js.js | 5 +- build/task/spriteBuilder.js | 2 +- index.html | 19 +- out/gui/wave.json | 1 + out/icon-sprite.svg | 2 +- out/js/scripts.js | 759 +++++++++++++++++++++++------ out/js/scripts.min.js | 2 +- out/theme/style.css | 2 +- out/tpl/image.tpl | 27 + out/tpl/playlist-item.tpl | 2 +- raw/javascript/app.js | 5 +- raw/javascript/audio.js | 14 +- raw/javascript/config.js | 15 +- raw/javascript/eventHandler.js | 24 +- raw/javascript/gl/glUtils.js | 333 +++++++++++++ raw/javascript/{ => gl}/handler.js | 0 raw/javascript/gui.js | 12 +- raw/javascript/imageUploader.js | 78 +++ raw/javascript/notification.js | 5 + raw/javascript/player.js | 19 +- raw/javascript/utils.js | 202 ++++---- raw/javascript/visual.js | 16 +- raw/javascript/visuals/wave.js | 54 +- raw/scss/_controls.scss | 6 +- raw/scss/_gui.scss | 48 +- raw/scss/_input.scss | 228 ++++++++- raw/scss/_modal.scss | 49 ++ raw/scss/_notification.scss | 6 + raw/scss/_playlist.scss | 23 +- raw/scss/_scrollbar.scss | 42 ++ raw/scss/_variables.scss | 6 + raw/scss/style.scss | 13 +- shaders/wave.frag | 18 +- shaders/wave.vert | 12 +- 34 files changed, 1631 insertions(+), 418 deletions(-) create mode 100644 out/gui/wave.json create mode 100644 out/tpl/image.tpl create mode 100644 raw/javascript/gl/glUtils.js rename raw/javascript/{ => gl}/handler.js (100%) create mode 100644 raw/javascript/imageUploader.js create mode 100644 raw/javascript/notification.js create mode 100644 raw/scss/_modal.scss create mode 100644 raw/scss/_notification.scss create mode 100644 raw/scss/_scrollbar.scss create mode 100644 raw/scss/_variables.scss 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 From 25fcefcb50e085abe6fab4d762bd274ce4ad395c Mon Sep 17 00:00:00 2001 From: versustunez Date: Thu, 6 Aug 2020 23:44:37 +0200 Subject: [PATCH 4/7] WIP --- build/gulpfile.js | 9 +- build/task/js.js | 5 + build/task/worker.js | 26 + index.html | 99 +-- js/utils.js | 21 +- out/gui/base.json | 1 + out/gui/sphere.json | 2 +- out/gui/wave.json | 2 +- out/gui/wave2d.json | 1 + out/js/jsmediatags.min.js | 85 +++ out/js/scripts.js | 953 +++++++++++++++++++++++--- out/js/scripts.min.js | 2 +- out/js/worker.js | 217 ++++++ out/js/worker.min.js | 1 + out/theme/style.css | 2 +- out/tpl/audio-information.tpl | 4 + out/tpl/config/content.tpl | 3 + out/tpl/config/nav.tpl | 5 + out/tpl/config/visualitem.tpl | 8 + out/tpl/image.tpl | 18 +- out/tpl/inputs/color.tpl | 6 + out/tpl/inputs/input.tpl | 5 + out/tpl/inputs/option.tpl | 3 + out/tpl/inputs/select.tpl | 8 + out/tpl/inputs/slider.tpl | 7 + out/tpl/inputs/switch.tpl | 5 + out/tpl/notification.tpl | 4 + out/tpl/playlist-footer.tpl | 3 + raw/gui/base.json | 39 ++ raw/gui/sphere.json | 3 +- raw/gui/wave.json | 73 +- raw/gui/wave2d.json | 73 ++ raw/javascript/FileHandler.js | 37 + raw/javascript/app.js | 39 +- raw/javascript/audio.js | 13 +- raw/javascript/config.js | 12 +- raw/javascript/eventHandler.js | 96 +++ raw/javascript/gl/glUtils.js | 35 +- raw/javascript/gui.js | 126 +++- raw/javascript/imageUploader.js | 4 - raw/javascript/notification.js | 76 +- raw/javascript/player.js | 90 ++- raw/javascript/playerConfigHandler.js | 106 +++ raw/javascript/select.js | 64 ++ raw/javascript/startup.js | 23 + raw/javascript/template.js | 6 +- raw/javascript/utils.js | 34 +- raw/javascript/visual.js | 66 +- raw/javascript/visuals/sphere.js | 5 + raw/javascript/visuals/water.js | 5 + raw/javascript/visuals/wave.js | 41 +- raw/javascript/visuals/wave2d.js | 73 ++ raw/scss/_config.scss | 54 ++ raw/scss/_gui.scss | 44 +- raw/scss/_input.scss | 136 +++- raw/scss/_modal.scss | 10 + raw/scss/_notification.scss | 117 +++- raw/scss/_playlist.scss | 2 + raw/scss/_variables.scss | 12 +- raw/scss/style.scss | 5 +- raw/worker/app.js | 4 + raw/worker/database.js | 97 +++ raw/worker/eventHandler.js | 30 + raw/worker/id3.js | 86 +++ shaders/wave.frag | 4 +- shaders/wave.vert | 6 + shaders/wave2d.frag | 19 + shaders/wave2d.vert | 19 + 68 files changed, 2982 insertions(+), 307 deletions(-) create mode 100644 build/task/worker.js create mode 100644 out/gui/base.json create mode 100644 out/gui/wave2d.json create mode 100755 out/js/jsmediatags.min.js create mode 100644 out/js/worker.js create mode 100644 out/js/worker.min.js create mode 100644 out/tpl/audio-information.tpl create mode 100644 out/tpl/config/content.tpl create mode 100644 out/tpl/config/nav.tpl create mode 100644 out/tpl/config/visualitem.tpl create mode 100644 out/tpl/inputs/color.tpl create mode 100644 out/tpl/inputs/input.tpl create mode 100644 out/tpl/inputs/option.tpl create mode 100644 out/tpl/inputs/select.tpl create mode 100644 out/tpl/inputs/slider.tpl create mode 100644 out/tpl/inputs/switch.tpl create mode 100644 out/tpl/notification.tpl create mode 100644 raw/gui/base.json create mode 100644 raw/gui/wave2d.json create mode 100644 raw/javascript/FileHandler.js create mode 100644 raw/javascript/playerConfigHandler.js create mode 100644 raw/javascript/select.js create mode 100644 raw/javascript/startup.js create mode 100644 raw/javascript/visuals/wave2d.js create mode 100644 raw/scss/_config.scss create mode 100644 raw/worker/app.js create mode 100644 raw/worker/database.js create mode 100644 raw/worker/eventHandler.js create mode 100644 raw/worker/id3.js create mode 100644 shaders/wave2d.frag create mode 100644 shaders/wave2d.vert diff --git a/build/gulpfile.js b/build/gulpfile.js index c1d6674..36d8cf9 100644 --- a/build/gulpfile.js +++ b/build/gulpfile.js @@ -2,19 +2,22 @@ const gulp = require('gulp'), spriteBuild = require('./task/spriteBuilder').buildIconSprites, scss = require('./task/scss').buildCSS, js = require('./task/js').build, - gui = require('./task/jsonMinifier').build; + gui = require('./task/jsonMinifier').build, + worker = require('./task/worker').build; gulp.task('scss', scss); gulp.task('js', js); gulp.task('sprite', spriteBuild); gulp.task('gui', gui); +gulp.task('workerJS', worker); gulp.task('watchMe', () => { gulp.watch('./../raw/javascript/**/*.js', gulp.series('js')); + gulp.watch('./../raw/worker/**/*.js', gulp.series('workerJS')); gulp.watch('./../raw/scss/**/*.scss', gulp.series('scss')); gulp.watch('./../raw/gui/**/*.json', gulp.series('gui')); }); -gulp.task('default', gulp.parallel('js', 'scss', 'sprite', 'gui')); +gulp.task('default', gulp.parallel('js', 'scss', 'sprite', 'gui', 'workerJS')); -gulp.task('watch', gulp.parallel('js', 'scss', 'sprite', 'gui', 'watchMe')); +gulp.task('watch', gulp.parallel('js', 'scss', 'sprite', 'gui', 'workerJS', 'watchMe')); diff --git a/build/task/js.js b/build/task/js.js index 388edc8..a295e68 100644 --- a/build/task/js.js +++ b/build/task/js.js @@ -8,6 +8,7 @@ const visualPath = basePath + 'visuals/'; const visuals = [ visualPath + 'sphere.js', visualPath + 'wave.js', + visualPath + 'wave2d.js', visualPath + 'water.js', //visualPath + 'experimental.js', ] @@ -18,6 +19,8 @@ const config = { basePath + 'template.js', basePath + 'gl/handler.js', basePath + 'audio.js', + basePath + 'FileHandler.js', + basePath + 'playerConfigHandler.js', basePath + 'player.js', basePath + 'gui.js', basePath + 'visual.js', @@ -26,6 +29,8 @@ const config = { basePath + 'config.js', ...visuals, basePath + 'eventHandler.js', + basePath + 'select.js', + basePath + 'startup.js', basePath + 'app.js' ], dest: __dirname + '/../../out/js' diff --git a/build/task/worker.js b/build/task/worker.js new file mode 100644 index 0000000..cbd2ba9 --- /dev/null +++ b/build/task/worker.js @@ -0,0 +1,26 @@ +const terser = require('gulp-terser'), + concat = require('gulp-concat'), + rename = require('gulp-rename'), + gulp = require('gulp'); + +const basePath = __dirname + '/../../raw/worker/'; +const config = { + src: [ + basePath + 'eventHandler.js', + basePath + 'database.js', + basePath + 'id3.js', + basePath + 'app.js', + ], + dest: __dirname + '/../../out/js' +}; + +function build() { + return gulp.src(config.src) + .pipe(concat('worker.js')) + .pipe(gulp.dest(config.dest)) + .pipe(rename('worker.min.js')) + .pipe(terser()) + .pipe(gulp.dest(config.dest)); +} + +module.exports.build = build; diff --git a/index.html b/index.html index 5f1208e..3e9cf34 100644 --- a/index.html +++ b/index.html @@ -11,57 +11,60 @@ Loading -
- - - `}}class Modal{constructor(){this.currentModal="",this.modal=$("#modal"),this.parent=this.modal.parentNode,this.modal.addDelegatedEventListener("click","header .close",this.closeModal.bind(this))}resetModal(){this.renderModal("","","")}renderModal(t,e,a){$("#modal").removeClass("lightMode"),this.currentModal=t,this.renderHeader(t),this.renderContent(e),this.renderFooter(a)}renderHeader(t){$("header .headline",this.modal).innerHTML=t}renderContent(t){$("modal-content",this.modal).innerHTML=t}renderFooter(t){$("modal-footer .inner",this.modal).innerHTML=t||"by VersusTuneZ"}closeModal(){this.parent.addClass("hide")}isCurrent(t){return t===this.currentModal}showModal(){this.parent.removeClass("hide")}}class Visual{constructor(){this.data=[],this.dataArray=[],this.name="Default"}updateData(){}updateFFT(t){}draw(){}setup(){}}class VisualDrawer{constructor(){this.visuals={wave:new Wave,wave2d:new Wave2D},this.lastMainColor={base:"#-1",color:[0,0,0]},this.lastSecondColor={base:"#-1",color:[0,0,0]}}init(){this.switch(pConf.get("visual","wave2d")),this.updateLoop()}switch(t){null!=this.visuals[t]&&(this.c=t,vConf.loadConfigByName(this.c),this.visuals[this.c].setup(),pConf.set("visual",this.c),pConf.save())}updateLoop(){let t=this.visuals[this.c],e=shaderHandler.use(this.c);this.updateSeekbar(),this.prepare(e),t.updateData(),t.draw(e),requestAnimationFrame(this.updateLoop.bind(this))}updateSeekbar(){cInfo.width=window.innerWidth,cInfo.height=window.innerHeight;let t=audioHandler.audioFile;if(ctx.clearRect(0,0,cInfo.width,cInfo.height),!t.paused&&pConf.get("showSeekbar",!0)){let e=t.duration,a=t.currentTime/e*cInfo.width;ctx.fillStyle=pConf.get("seekColor","#fff"),ctx.fillRect(0,c.height-10,a,c.height)}}prepare(t){c.width=window.innerWidth,c.height=window.innerHeight,gl.viewport(0,0,gl.canvas.width,gl.canvas.height),gl.clearColor(0,0,0,parseFloat(pConf.get("alphaValue",0))),gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT),gl.enable(gl.DEPTH_TEST),gl.depthFunc(gl.LEQUAL),gl.enable(gl.CULL_FACE),this.setColor(t)}setColor(t){let e=gl.getUniformLocation(t,"u_baseColor"),a=gl.getUniformLocation(t,"u_maxColor"),i=this.lastMainColor,s=this.lastSecondColor;this.updateColor("lastMainColor","baseColor"),this.updateColor("lastSecondColor","gradientToColor"),gl.uniform3fv(e,i.color),gl.uniform3fv(a,s.color)}updateColor(t,e){let a=this[t],i=vConf.get(e,"#ffffff");i!==a.base&&(a.color=hexToRgb(i),a.base=i)}}class ImageUploader{async init(){this.image=pConf.get("bgURL",""),this.color=pConf.get("bgColour","#000000"),this.alpha=pConf.get("alphaValue",.5),this.getRealImage(),this.applyValues(),$("#modal").addDelegatedEventListener("change",'#image-upload input:not([type="color"])',this.changeHandler.bind(this)),$("#modal").addDelegatedEventListener("input","#image-upload input#color",this.changeHandler.bind(this))}async renderModal(){await template.loadTemplate("image"),gui.modal.resetModal(),gui.modal.renderModal("Background-Image",template.parseTemplate("image",{value:this.image,bgValue:this.color,alphaValue:this.alpha}),""),gui.modal.showModal()}changeHandler(t,e){"color"===e.id?this.color=e.value:"alphaValue"===e.id?this.alpha=e.value:(pConf.set("bgMode",e.id),"image"===e.id?(e.files[0].toBase64((t,e)=>{e?alert("Error converting image!"):(pConf.set("bgURL",t.currentTarget.result),pConf.save())}),this.image=URL.createObjectURL(e.files[0])):(this.image=e.value,pConf.set("bgURL",this.image))),pConf.set("bgColour",this.color),pConf.set("alphaValue",this.alpha),this.applyValues(),pConf.save()}applyValues(){let t=$("body");t.style.backgroundImage="url("+this.image+")",t.style.backgroundColor=this.color}getRealImage(){let t=pConf.get("bgMode"),e=pConf.get("bgURL","");if("image"===t){if(""!==e&&e.startsWith("data:image")){let t=e.split(";"),a=t.shift(),i=t.join(";").replace("base64,","");this.image=URL.createObjectURL(b64toBlob(i,a))}}else this.image=e}}const imageUploader=new ImageUploader;class NotificationHandler{static instance=new NotificationHandler;constructor(){this.outer=$(".notification"),this.notifications=[]}async init(){await template.loadTemplate("notification")}static createNotification(t,e,a){a=parseInt(a||"3000");let i=NotificationHandler.instance,s=new Notification(t,e,a);return i.notifications.push(s),s.show(),s}}class Notification{constructor(t,e,a){this.outer=NotificationHandler.instance.outer,this.message=t,this.type=e,this.time=a,this.isRemoved=!1}async show(){let t=this,e=-1===t.time;t.item=create("div"),t.item.addClass("notification-item, "+t.type),e&&(t.type+=" endless"),t.updateContent(t.message),this.outer.appendChild(t.item),e||setTimeout(this.remove.bind(this),t.time)}async remove(){if(this.isRemoved)return;this.isRemoved=!0,this.outer.removeChild(this.item);let t=NotificationHandler.instance.notifications,e=t.indexOf(this);t.splice(e,1)}updateContent(t){let e={message:t,time:-1===this.time?1e3:this.time+1,type:this.type};this.item.innerHTML=template.parseTemplate("notification",e)}updateMessageOnly(t){let e=$(".message",this.item);e&&(e.innerHTML=t)}}class Config{static allConfigs={};constructor(t){this.config={},this.name="",this.type=t,Config.allConfigs[t]=this}loadConfigByName(t){this.save(),this.name="config-"+t;let e=localStorage.getItem(this.name);e&&(this.config=JSON.parse(e))}save(){""!==this.name&&localStorage.setItem(this.name,JSON.stringify(this.config))}set(t,e){this.config[t]=e}remove(t){delete this.config[t]}get(t,e){let a=this.config[t];return null==a&&(this.config[t]=e,a=e),a}reset(){NotificationHandler.createNotification("CONFIG REQUEST SUCCESS FOR "+this.type,"success",2e3),this.config={},this.save()}}class Sphere extends Visual{constructor(){super(),this.name="Sphere"}draw(){}setup(){}}class Wave extends Visual{constructor(){super(),this.name="3D Wave"}updateData(){let t=audioHandler.getFloatArray(),e=2/t.length,a=-1,i=0;for(let s=0;s{player.playlist.renderPagination(player.playlist.page),gui.modal.showModal()}),t.addDelegatedEventListener("click",".playlist-item",(t,e)=>{let a=e.dataset.index;player.playByID(parseInt(a)),togglePlayButton("pause")}),t.addDelegatedEventListener("click",".controls button",(t,e)=>{switch(e.id){case"previous":player.prevSong();break;case"next":player.nextSong();break;case"play":player.playStop();break;case"shuffle":player.playlist.isShuffle=!player.playlist.isShuffle,toggleShuffle()}togglePlayButton(audioHandler.audioFile.paused?"play":"pause")}),window.addEventListener("playSong",setActiveOnPlaylist),$(".upload-image").addEventListener("click",imageUploader.renderModal.bind(imageUploader)),t.addDelegatedEventListener("click",".readAll",t=>{let e=player.playlist.list;for(let t=0;t{$(".current",e.parentNode).innerText=e.value}),t.addDelegatedEventListener("input",'input[type="color"]',(t,e)=>{$(".colorBlob",e.parentNode).style.backgroundColor=e.value}),t.addDelegatedEventListener("click",".visual-item",(t,e)=>{visual.switch(e.dataset.id||"wave"),$("modal-content .visuals .active").removeClass("active"),e.addClass("active")}),t.addDelegatedEventListener("input","section.base input",(t,e)=>{"checkbox"===e.type?pConf.set(e.name,e.checked):setValue(e.name,e.value,pConf,e.dataset.type),pConf.save()}),t.addDelegatedEventListener("input","section.visual input",(t,e)=>{"checkbox"===e.type?vConf.set(e.name,e.checked):setValue(e.name,e.value,vConf,e.dataset.type),vConf.save()}),t.addDelegatedEventListener("click",".button[data-action]",(t,e)=>{switch(e.dataset.action){case"resetVConf":vConf.reset(),setTimeout(t=>{playerConf.handleById()},30);break;case"makeModalTransparent":$("#modal").toggleClass("lightMode")}})}function setValue(t,e,a,i){switch(i){case"float":e=parseFloat(e);break;case"int":e=parseInt(e)}a.set(t,e)}function setActiveOnPlaylist(t){let e=$('.playlist-item[data-index="'+player.playlist.index+'"]'),a=$(".playlist-item.active");a&&a.removeClass("active"),e&&e.addClass("active")}function toggleShuffle(){let t=player.playlist.isShuffle;$("#shuffle").toggleCheck("active",t);let e=t?"enabled":"disabled";NotificationHandler.createNotification("Shuffle: "+e,"info",500)}function togglePlayButton(t){$$("#play .icon").forEach(e=>{e.dataset.name===t?e.removeClass("hide"):e.addClass("hide")})}!function(){const t=$("body");t.addDelegatedEventListener("click","custom-select .label",(t,e)=>{let a=e.parentNode,i=$$("custom-option",a),s=$("custom-options",a);if(a.hasClass("open"))s.style.maxHeight="",a.removeClass("open");else{let t=0;i.forEach((function(e){t+=e.offsetHeight})),s.style.maxHeight=t+"px",a.addClass("open")}}),t.addDelegatedEventListener("click","custom-select custom-option",(t,e)=>{let a=e.closest("custom-select"),i=$("input",a);$$("custom-option.active").forEach(t=>{t.removeClass("active")}),e.addClass("active"),i&&(i.value=e.dataset.value||e.innerText,$(".label",a).innerText=e.innerText,a.removeClass("open"),e.parentNode.style.maxHeight="",window.dispatchEvent(new CustomEvent("selectChanged",{detail:{select:a,event:a.dataset.event,value:i.value,name:i.name}})))}),window.addEventListener("selectChanged",t=>{"visualConf"===t.detail.event&&(t.preventDefault(),t.stopPropagation(),function(t){try{let e=t.value,a=Config.allConfigs[t.select.dataset.conf];"fftSize"===t.name&&(e=parseInt(t.value),visual.visuals[visual.c].updateFFT(e)),a.set(t.name,e),a.save()}catch(t){console.error(t)}}(t.detail))})}();class Startup{constructor(){this.modules={startup:!1,"id3-ready":!1}}moduleLoaded(t){this.modules[t]=!0,this.allModulesLoaded()}allModulesLoaded(){for(let t in this.modules)if(!this.modules[t])return!1;return window.dispatchEvent(new CustomEvent("startupFin")),!0}}const shaderHandler=new ShaderHandler(null),audioHandler=new AudioHandler,gui=new GUI,visual=new VisualDrawer,template=new Template,player=new Player,vConf=new Config("visual"),pConf=new Config("player"),worker=new Worker("/out/js/worker.min.js"),startup=new Startup,eventHandler=new EventHandler,playerConf=new PlayerConfigHandler;let c,gl,cInfo,ctx;async function startUP(){if(pConf.loadConfigByName("default"),c=$("#c"),gl=c.getContext("webgl2"),cInfo=$("#cInfo"),ctx=cInfo.getContext("2d"),!gl)return alert("SORRY THE BROWSER DOESN'T SUPPORT WEBGL2"),!1;shaderHandler.setGL(gl),await shaderHandler.loadArray(["wave","sphere","water","wave2d"],"shaders/"),await NotificationHandler.instance.init(),await audioHandler.init(),await player.init(),await visual.init(),await gui.init(),await imageUploader.init(),await playerConf.init(),await initHandler()}worker.addEventListener("message",t=>{"startup"!==t.data.status?eventHandler.handleEvent(t):startup.moduleLoaded(t.data.cmd)}),window.addEventListener("startupFin",t=>{setTimeout(t=>{$(".loading-screen").remove()},100)}),startUP().then(t=>{startup.moduleLoaded("startup")}); \ No newline at end of file diff --git a/out/js/worker.js b/out/js/worker.js new file mode 100644 index 0000000..1bd5655 --- /dev/null +++ b/out/js/worker.js @@ -0,0 +1,217 @@ +class EventHandler { + constructor(worker) { + this.events = {}; + this.worker = worker; + this.worker.addEventListener('message', this.handleEvent.bind(this)); + } + + addEvent(name, cb) { + this.events[name] = cb; + } + + sendData(name, status, data) { + this.worker.postMessage({ + cmd: name, + status: status, + data: data + }); + } + + handleEvent(event) { + let data = event.data; + if (!data.cmd) { + return; + } + + if (this.events[data.cmd]) { + this.events[data.cmd](data.data); + } + } +} +class Database { + constructor(name, version) { + this.isInit = false; + this.name = name; + this.version = version; + this.errored = false; + this.db; + this.prepareDB(); + } + + async prepareDB() { + if (this.isInit || this.errored) { + return; + } + let req = this.db = indexedDB.open(this.name, this.version); + req.onerror = DatabaseHandler.onError.bind(this); + req.onsuccess = DatabaseHandler.onSuccess.bind(this); + req.onupgradeneeded = DatabaseHandler.onUpgrade.bind(this); + req.onblocked = DatabaseHandler.onBlocked.bind(this); + } + + async set(key, data, store) { + data['key'] = key; + return await this.run('put', data, store); + } + + async get(key, store) { + return await this.run('get', key, store); + } + + async remove(key, store) { + return await this.run('delete', key, store); + } + + check() { + return !(!this.isInit || this.errored); + } + + async getTX(store) { + return await this.db.transaction([store], "readwrite") + } + + async getObjectStore(tx, store) { + return await tx.objectStore(store) + } + + async run(action, key, store) { + if (this.check()) { + let tx = await this.getTX(store); + let obj = await this.getObjectStore(tx, store); + let data = await this.request(obj[action](key)); + await tx.complete + return await data; + } + return null; + } + + request(req) { + return new Promise((resolve, reject) => { + req.onsuccess = () => resolve(req.result); + req.onerror = () => reject(req.error); + }); + } +} + +class DatabaseHandler { + static onError(e) { + this.errored = true; + eventHandler.sendData("databaseError", "error", e.message); + } + + static onSuccess(e) { + this.db = this.db.result; + this.isInit = true; + eventHandler.sendData("databaseCreated", "success", ""); + eventHandler.handleEvent({ + data: { + cmd: 'dbReady-' + this.name, + data: this.db + } + }); + } + + static onUpgrade(e) { + eventHandler.sendData("databaseUpgradeNeeded", "info", e.message); + eventHandler.handleEvent({ + data: { + cmd: 'dbUpgrade-' + this.name, + data: this.db + } + }); + } + + static onBlocked(e) { + eventHandler.sendData("databaseBlocked", "error", e.message); + } +} +self.importScripts('jsmediatags.min.js'); + +class Tagger { + constructor(worker) { + this.db = new Database("SongLib", 1); + } + + static prepareName(data) { + let name = data.name || ''; + return name.replace(/[^\w\s]/gi, '').split(" ").join("") + } + + init() { + eventHandler.addEvent('getData', this.getData.bind(this)); + eventHandler.addEvent('removeData', this.getData.bind(this)); + eventHandler.addEvent('setData', this.getData.bind(this)); + eventHandler.addEvent('dbReady-SongLib', this.ready.bind(this)); + eventHandler.addEvent('dbUpgrade-SongLib', this.upgrade.bind(this)); + } + + async getData(data) { + let key = Tagger.prepareName(data), + newData = await this.db.get(key, 'songs'), + handlerName = data.force ? 'id3-request-force' : 'id3-request'; + if (newData) { + newData['index'] = data['index']; + eventHandler.sendData(handlerName, 'success', newData); + } else { + this.parseData(data, key).then(r => { + r['index'] = data['index']; + eventHandler.sendData(handlerName, 'success', r); + }); + eventHandler.sendData(handlerName, 'waiting', data); + } + } + + async removeData(data) { + let key = Tagger.prepareName(data), + newData = await this.db.remove(key, 'songs'); + eventHandler.sendData('id3-remove', 'success', newData); + } + + async setData(data, key) { + let newData = await this.db.set(key, data, 'songs'); + eventHandler.sendData('id3-set', 'success', newData); + } + + ready(data) { + console.log("[ID3] > Song Database Ready"); + eventHandler.sendData('id3-ready', "startup", ""); + } + + upgrade(data) { + let db = data.result, + songs = db.createObjectStore("songs", {keyPath: 'key'}); + songs.createIndex("name", "name", {unique: false}); + } + + //if not found in key-value storage read it! this take some time so this is async! + async parseData(data, key) { + let tag = await new Promise((resolve, reject) => { + new jsmediatags.Reader(data.file) + .read({ + onSuccess: (tag) => { + resolve(tag); + }, + onError: (error) => { + console.log(`[ID3] > Error Parsing Data!`); + resolve({ + tags: {} + }); + } + }); + }) + let tags = tag.tags, + values = { + title: tags.title || data.name, + artist: tags.artist || 'VA', + genre: tags.genre || 'Unknown', + year: tags.year || 1970, + key: key + }; + await this.setData(values, key); + return values; + } +} +const tagger = new Tagger(self), + eventHandler = new EventHandler(self); + +tagger.init(); \ No newline at end of file diff --git a/out/js/worker.min.js b/out/js/worker.min.js new file mode 100644 index 0000000..78573ff --- /dev/null +++ b/out/js/worker.min.js @@ -0,0 +1 @@ +class EventHandler{constructor(e){this.events={},this.worker=e,this.worker.addEventListener("message",this.handleEvent.bind(this))}addEvent(e,t){this.events[e]=t}sendData(e,t,a){this.worker.postMessage({cmd:e,status:t,data:a})}handleEvent(e){let t=e.data;t.cmd&&this.events[t.cmd]&&this.events[t.cmd](t.data)}}class Database{constructor(e,t){this.isInit=!1,this.name=e,this.version=t,this.errored=!1,this.db,this.prepareDB()}async prepareDB(){if(this.isInit||this.errored)return;let e=this.db=indexedDB.open(this.name,this.version);e.onerror=DatabaseHandler.onError.bind(this),e.onsuccess=DatabaseHandler.onSuccess.bind(this),e.onupgradeneeded=DatabaseHandler.onUpgrade.bind(this),e.onblocked=DatabaseHandler.onBlocked.bind(this)}async set(e,t,a){return t.key=e,await this.run("put",t,a)}async get(e,t){return await this.run("get",e,t)}async remove(e,t){return await this.run("delete",e,t)}check(){return!(!this.isInit||this.errored)}async getTX(e){return await this.db.transaction([e],"readwrite")}async getObjectStore(e,t){return await e.objectStore(t)}async run(e,t,a){if(this.check()){let s=await this.getTX(a),n=await this.getObjectStore(s,a),r=await this.request(n[e](t));return await s.complete,await r}return null}request(e){return new Promise((t,a)=>{e.onsuccess=()=>t(e.result),e.onerror=()=>a(e.error)})}}class DatabaseHandler{static onError(e){this.errored=!0,eventHandler.sendData("databaseError","error",e.message)}static onSuccess(e){this.db=this.db.result,this.isInit=!0,eventHandler.sendData("databaseCreated","success",""),eventHandler.handleEvent({data:{cmd:"dbReady-"+this.name,data:this.db}})}static onUpgrade(e){eventHandler.sendData("databaseUpgradeNeeded","info",e.message),eventHandler.handleEvent({data:{cmd:"dbUpgrade-"+this.name,data:this.db}})}static onBlocked(e){eventHandler.sendData("databaseBlocked","error",e.message)}}self.importScripts("jsmediatags.min.js");class Tagger{constructor(e){this.db=new Database("SongLib",1)}static prepareName(e){return(e.name||"").replace(/[^\w\s]/gi,"").split(" ").join("")}init(){eventHandler.addEvent("getData",this.getData.bind(this)),eventHandler.addEvent("removeData",this.getData.bind(this)),eventHandler.addEvent("setData",this.getData.bind(this)),eventHandler.addEvent("dbReady-SongLib",this.ready.bind(this)),eventHandler.addEvent("dbUpgrade-SongLib",this.upgrade.bind(this))}async getData(e){let t=Tagger.prepareName(e),a=await this.db.get(t,"songs"),s=e.force?"id3-request-force":"id3-request";a?(a.index=e.index,eventHandler.sendData(s,"success",a)):(this.parseData(e,t).then(t=>{t.index=e.index,eventHandler.sendData(s,"success",t)}),eventHandler.sendData(s,"waiting",e))}async removeData(e){let t=Tagger.prepareName(e),a=await this.db.remove(t,"songs");eventHandler.sendData("id3-remove","success",a)}async setData(e,t){let a=await this.db.set(t,e,"songs");eventHandler.sendData("id3-set","success",a)}ready(e){console.log("[ID3] > Song Database Ready"),eventHandler.sendData("id3-ready","startup","")}upgrade(e){e.result.createObjectStore("songs",{keyPath:"key"}).createIndex("name","name",{unique:!1})}async parseData(e,t){let a=(await new Promise((t,a)=>{new jsmediatags.Reader(e.file).read({onSuccess:e=>{t(e)},onError:e=>{console.log("[ID3] > Error Parsing Data!"),t({tags:{}})}})})).tags,s={title:a.title||e.name,artist:a.artist||"VA",genre:a.genre||"Unknown",year:a.year||1970,key:t};return await this.setData(s,t),s}}const tagger=new Tagger(self),eventHandler=new EventHandler(self);tagger.init(); \ No newline at end of file diff --git a/out/theme/style.css b/out/theme/style.css index 0aec36b..c24237e 100644 --- a/out/theme/style.css +++ b/out/theme/style.css @@ -1 +1 @@ -@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 +@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;width:100vw;height:100vh}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}canvas{position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none}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;z-index:100}.grey-screen.hide{display:none!important}modal-footer playlist{flex-direction:row-reverse;flex-wrap:wrap}modal-footer playlist .pagination{margin-left:auto}#image-upload form{display:flex;flex-direction:column;width:90%;margin:10px auto}.audio-item{display:flex;flex-direction:column}.audio-item .artist{font-size:.75em;color:#dcdcdc}.now-playing{font-size:.8em;display:block;margin-bottom:10px}.menus{z-index:10}.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:100%;position:relative;display:inline-block;margin-top:1rem;background-color:transparent}.input-range{margin-bottom:20px}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:#5ff507;font-size:.7rem}.floating-label input:valid~.input-label{transform:translateY(-.72rem);color:#ff0089;font-size:.7rem}.focus{content:'';width:0;background-color:#ff0089;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{display:flex;flex-direction:row;padding:5px}switch input{position:absolute;appearance:none;opacity:0}switch input:checked+label:after{transform:translateX(20px)}switch span{margin-left:10px}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:#3949ab;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{text-align:center;user-select:none;border-radius:5px;border:1px solid #3949ab;padding:.5em 1em;cursor:pointer;transition:.5s}.button.spaced{margin-bottom:10px}.button:hover{border-color:#ff0089;border-radius:7px;box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23)}.input .current,.input .max,.input .min{font-size:.8em;color:#ff0089;position:absolute;bottom:-1rem}.input .current{display:block;width:100%;text-align:center}.input .min{left:0}.input .max{right:0}custom-select{width:auto;min-width:200px;display:block}custom-select label{color:#ff0089;font-size:.7rem}custom-select input{display:none}custom-select .label{padding:10px 30px 10px 10px;cursor:pointer;position:relative;transition:all .3s;box-shadow:inset 0 0 3px #5e5e5e}custom-select .label::after{content:'';border:solid #dcdcdc;border-width:0 3px 3px 0;display:inline-block;padding:3px;transform:rotate(45deg);-webkit-transform:rotate(45deg);transition:all .3s ease-in-out;position:absolute;right:10px;top:calc(50% - 6px)}custom-select custom-options{max-height:0;overflow:hidden;transition:all .5s ease;font-size:.9em;display:block;border:1px solid transparent}custom-select custom-option{display:block;width:100%;padding:10px 5px;cursor:pointer;box-shadow:0 -1px 0 0 rgba(0,0,0,.08)}custom-select custom-option:hover{background-color:#3949ab;color:#fff}custom-select custom-option.active{font-weight:700}custom-select.open .label{background-color:rgba(0,0,0,.1)}custom-select.open .label::after{transform:rotate(-135deg);-webkit-transform:rotate(-135deg);top:calc(50% - 3px)}custom-select.open custom-options{max-height:none;border:1px solid #1b1b1b!important}.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;display:flex;align-items:center}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.lightMode{background-color:rgba(0,0,0,.1)}#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)}#modal modal-footer .inner{padding:5px;width:calc(100% - 40px);margin:0 auto}.notification{right:10px;top:0;height:100%;display:flex;flex-direction:column-reverse;width:90%;max-width:400px;pointer-events:none}.notification .notification-item{margin-top:10px}.notification div{position:unset;padding:unset}.notification div.notification-item{position:relative}.notification-item{width:100%;border-radius:5px;box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23);overflow:hidden}.notification-item.success{background-color:rgba(74,177,11,.6)}.notification-item.error{background-color:rgba(255,50,50,.6)}.notification-item.warning{background-color:rgba(255,177,89,.6)}.notification-item.info{background-color:rgba(71,73,171,.6)}.notification-item .message{padding:1em}.notification-item .fade-bar{animation:fadeOut ease-in-out 3s;height:100%;width:100%;position:absolute;top:0;z-index:-1;opacity:.4;transform-origin:left}.notification-item .fade-bar:after{content:'';z-index:1;bottom:0;height:4px;width:100%;position:absolute;background-color:#fff}.notification-item .fade-bar.endless{animation:endlessFade ease-in-out .5s infinite}.notification-item .fade-bar.success{background-color:#60ff00}.notification-item .fade-bar.error{background-color:#fa0000}.notification-item .fade-bar.warning{background-color:#f70}.notification-item .fade-bar.info{background-color:#3949ab}@keyframes fadeOut{from{transform:scaleX(1)}to{transform:scaleX(0)}}@keyframes endlessFade{0%{transform:scaleX(1);transform-origin:right}49%{transform-origin:right}50%{transform:scaleX(0);transform-origin:left}100%{transform:scaleX(1)}}.config-nav{display:flex;flex-direction:row;background-color:#1b1b1b;box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23)}.config-nav div{padding:10px 5px;cursor:pointer;user-select:none;border-bottom:1px solid transparent}.config-nav div.active{border-color:#5ff507}.config-nav div:hover{background-color:rgba(0,0,0,.4)}.config-content{padding:1em;min-height:200px}.config-content .visuals{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-around}.config-content .visuals .visual-item{display:flex;align-items:center;justify-content:center;padding:1em;min-width:100px;min-height:100px;background-color:#212121;box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23);transition:.5s;cursor:pointer}.config-content .visuals .visual-item:hover{background-color:#1b1b1b}.config-content .visuals .visual-item.active{border:1px solid #3949ab} \ No newline at end of file diff --git a/out/tpl/audio-information.tpl b/out/tpl/audio-information.tpl new file mode 100644 index 0000000..ea9a862 --- /dev/null +++ b/out/tpl/audio-information.tpl @@ -0,0 +1,4 @@ +
+ $artist$ + $title$ +
\ No newline at end of file diff --git a/out/tpl/config/content.tpl b/out/tpl/config/content.tpl new file mode 100644 index 0000000..1cb830a --- /dev/null +++ b/out/tpl/config/content.tpl @@ -0,0 +1,3 @@ +
+ $content$ +
\ No newline at end of file diff --git a/out/tpl/config/nav.tpl b/out/tpl/config/nav.tpl new file mode 100644 index 0000000..e8b6136 --- /dev/null +++ b/out/tpl/config/nav.tpl @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/out/tpl/config/visualitem.tpl b/out/tpl/config/visualitem.tpl new file mode 100644 index 0000000..025de23 --- /dev/null +++ b/out/tpl/config/visualitem.tpl @@ -0,0 +1,8 @@ +
+ + $title$ +
\ No newline at end of file diff --git a/out/tpl/image.tpl b/out/tpl/image.tpl index bf02e73..2e2dd7a 100644 --- a/out/tpl/image.tpl +++ b/out/tpl/image.tpl @@ -1,27 +1,29 @@
- - -
+ + +

You only want to change the color? do it!

-
\ No newline at end of file diff --git a/out/tpl/inputs/color.tpl b/out/tpl/inputs/color.tpl new file mode 100644 index 0000000..6a8a581 --- /dev/null +++ b/out/tpl/inputs/color.tpl @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/out/tpl/inputs/input.tpl b/out/tpl/inputs/input.tpl new file mode 100644 index 0000000..af54767 --- /dev/null +++ b/out/tpl/inputs/input.tpl @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/out/tpl/inputs/option.tpl b/out/tpl/inputs/option.tpl new file mode 100644 index 0000000..83a9a38 --- /dev/null +++ b/out/tpl/inputs/option.tpl @@ -0,0 +1,3 @@ + + $value$ + \ No newline at end of file diff --git a/out/tpl/inputs/select.tpl b/out/tpl/inputs/select.tpl new file mode 100644 index 0000000..8cb6aae --- /dev/null +++ b/out/tpl/inputs/select.tpl @@ -0,0 +1,8 @@ + + +
$value$
+ + + $options$ + +
\ No newline at end of file diff --git a/out/tpl/inputs/slider.tpl b/out/tpl/inputs/slider.tpl new file mode 100644 index 0000000..bf18d71 --- /dev/null +++ b/out/tpl/inputs/slider.tpl @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/out/tpl/inputs/switch.tpl b/out/tpl/inputs/switch.tpl new file mode 100644 index 0000000..5083844 --- /dev/null +++ b/out/tpl/inputs/switch.tpl @@ -0,0 +1,5 @@ + + + + $showName$ + \ No newline at end of file diff --git a/out/tpl/notification.tpl b/out/tpl/notification.tpl new file mode 100644 index 0000000..031e551 --- /dev/null +++ b/out/tpl/notification.tpl @@ -0,0 +1,4 @@ +
+ $message$ +
+
\ No newline at end of file diff --git a/out/tpl/playlist-footer.tpl b/out/tpl/playlist-footer.tpl index bf2dac5..de05b5a 100644 --- a/out/tpl/playlist-footer.tpl +++ b/out/tpl/playlist-footer.tpl @@ -14,4 +14,7 @@
+
+ Force Tagger +
\ No newline at end of file diff --git a/raw/gui/base.json b/raw/gui/base.json new file mode 100644 index 0000000..61321b6 --- /dev/null +++ b/raw/gui/base.json @@ -0,0 +1,39 @@ +[ + { + "group": "", + "name": "showSeekbar", + "showName": "Show Seekbar", + "type": "checkbox", + "value": true, + "tooltip": "Showing SeekBar at the Bottom of the Screen" + }, + { + "group": "", + "name": "showPlaying", + "showName": "Show Playing", + "type": "checkbox", + "value": true, + "tooltip": "Showing \"Now Playing\" Notification" + }, + { + "group": "", + "name": "showPlayingTime", + "showName": "Now Playing Display Time", + "type": "slider", + "value": 1000, + "max": 5000, + "min": 500, + "tooltip": "How long should the Now Playing Notification shown", + "stepSize": 100, + "dataType": "int" + }, + { + "group": "", + "name": "seekColor", + "showName": "SeekBar Color", + "type": "color", + "value": "#ffffff", + "dataType": "rgb", + "tooltip": "SeekBar Color!" + } +] \ No newline at end of file diff --git a/raw/gui/sphere.json b/raw/gui/sphere.json index 2f8936b..f4d3e11 100644 --- a/raw/gui/sphere.json +++ b/raw/gui/sphere.json @@ -53,7 +53,8 @@ ], "type": "slider", "max": 255, - "min": 0 + "min": 0, + "value": 255 }, { "group": "light", diff --git a/raw/gui/wave.json b/raw/gui/wave.json index 9e26dfe..660127b 100644 --- a/raw/gui/wave.json +++ b/raw/gui/wave.json @@ -1 +1,72 @@ -{} \ No newline at end of file +[ + { + "group": "", + "name": "fftSize", + "showName": "FFT-Size", + "options": [ + 2048, + 4096, + 8192, + 16384 + ], + "value": 4096, + "type": "select", + "tooltip": "How Many Items should the FFT Capture and Render", + "dataType": "int" + }, + { + "group": "rotation", + "name": [ + "X", + "Y", + "Z" + ], + "props": [ + "x", + "y", + "z" + ], + "type": "slider", + "max": 360, + "min": -360, + "value": 0, + "dataType": "int" + }, + { + "group": "rotation", + "name": [ + "X-Inc", + "Y-Inc", + "Z-Inc" + ], + "props": [ + "x-inc", + "y-inc", + "z-inc" + ], + "type": "slider", + "max": 1, + "min": -1, + "value": 0, + "stepSize": 0.01, + "dataType": "float" + }, + { + "group": "", + "name": "baseColor", + "showName": "Base Color", + "type": "color", + "value": "#0089ff", + "dataType": "rgb", + "tooltip": "Base Color!" + }, + { + "group": "", + "name": "gradientToColor", + "showName": "Second Color", + "type": "color", + "value": "#ff0000", + "dataType": "rgb", + "tooltip": "Second Color!" + } +] \ No newline at end of file diff --git a/raw/gui/wave2d.json b/raw/gui/wave2d.json new file mode 100644 index 0000000..2cec793 --- /dev/null +++ b/raw/gui/wave2d.json @@ -0,0 +1,73 @@ +[ + { + "group": "", + "name": "fftSize", + "showName": "FFT-Size", + "options": [ + 2048, + 4096, + 8192, + 16384, + 32768 + ], + "value": 16384, + "type": "select", + "tooltip": "How Many Items should the FFT Capture and Render", + "dataType": "int" + }, + { + "group": "rotation", + "name": [ + "X", + "Y", + "Z" + ], + "props": [ + "x", + "y", + "z" + ], + "type": "slider", + "max": 360, + "min": -360, + "value": 0, + "dataType": "int" + }, + { + "group": "rotation", + "name": [ + "X-Inc", + "Y-Inc", + "Z-Inc" + ], + "props": [ + "x-inc", + "y-inc", + "z-inc" + ], + "type": "slider", + "max": 1, + "min": -1, + "value": 0, + "stepSize": 0.01, + "dataType": "float" + }, + { + "group": "", + "name": "baseColor", + "showName": "Base Color", + "type": "color", + "value": "#0089ff", + "dataType": "rgb", + "tooltip": "Base Color!" + }, + { + "group": "", + "name": "gradientToColor", + "showName": "Second Color", + "type": "color", + "value": "#ff0000", + "dataType": "rgb", + "tooltip": "Second Color!" + } +] \ No newline at end of file diff --git a/raw/javascript/FileHandler.js b/raw/javascript/FileHandler.js new file mode 100644 index 0000000..41f2785 --- /dev/null +++ b/raw/javascript/FileHandler.js @@ -0,0 +1,37 @@ +// Handler around the Playlist file to keep track on the ID3 and more! +class AudioPlayerFile { + constructor(file, index) { + this.file = file; + this.name = this.getName(); + this.id3 = null; + this.index = index; + } + + getName() { + let name = this.file.name.split("."); + name.pop(); + name = name.join("."); + return name; + } + + getID3Tag(force) { + if (!force && this.id3 !== null) { + return this.id3; + } + eventHandler.sendData('getData', { + file: this.file, + name: this.name, + index: this.index, + force: force === true + }); + return { + title: this.name, + artist: 'VA', + } + } + + getAudioName() { + let tag = this.getID3Tag(); + return template.parseTemplate('audio-information', tag); + } +} \ No newline at end of file diff --git a/raw/javascript/app.js b/raw/javascript/app.js index 3f564c8..eca438b 100644 --- a/raw/javascript/app.js +++ b/raw/javascript/app.js @@ -4,32 +4,51 @@ const shaderHandler = new ShaderHandler(null), visual = new VisualDrawer(), template = new Template(), player = new Player(), - vConf = new Config(), - pConf = new Config(); + vConf = new Config("visual"), + pConf = new Config("player"), + worker = new Worker('/out/js/worker.min.js'), + startup = new Startup(), + eventHandler = new EventHandler(), + playerConf = new PlayerConfigHandler(); -let c = null, - gl = null; +let c, gl, cInfo, ctx; + +worker.addEventListener('message', e => { + if (e.data.status === 'startup') { + startup.moduleLoaded(e.data.cmd); + return; + } + eventHandler.handleEvent(e); +}); + +window.addEventListener('startupFin', e => { + setTimeout(e => { + $('.loading-screen').remove(); + }, 100) +}) async function startUP() { pConf.loadConfigByName('default'); - c = document.body.querySelector('#c'), - gl = c.getContext("webgl2"); + c = $('#c'), + gl = c.getContext("webgl2"), + cInfo = $('#cInfo'), + ctx = cInfo.getContext('2d'); if (!gl) { alert("SORRY THE BROWSER DOESN'T SUPPORT WEBGL2"); return false; } shaderHandler.setGL(gl) - await shaderHandler.loadArray(["wave", "sphere", "water"], 'shaders/'); + await shaderHandler.loadArray(["wave", "sphere", "water", "wave2d"], 'shaders/'); + await NotificationHandler.instance.init(); await audioHandler.init(); await player.init(); await visual.init(); await gui.init(); await imageUploader.init(); + await playerConf.init(); await initHandler(); } startUP().then(r => { - setTimeout(e => { - $('.loading-screen').remove(); - }, 100) + startup.moduleLoaded('startup'); }); \ No newline at end of file diff --git a/raw/javascript/audio.js b/raw/javascript/audio.js index a0c7ae8..8f438df 100644 --- a/raw/javascript/audio.js +++ b/raw/javascript/audio.js @@ -45,8 +45,13 @@ class AudioHandler { this.analyser.smoothingTimeConstant = float; } - loadSong(src) { - let self = this; + loadSong(file) { + if (!file) { + NotificationHandler.createNotification("Sorry!
Currently no Song is uploaded!", "error", 2000); + return false; + } + let self = this, + src = file.file; if (self.lastSong) { URL.revokeObjectURL(self.lastSong); } @@ -55,8 +60,12 @@ class AudioHandler { this.start().catch(alert); } this.audioFile.play().then(e => { + if (pConf.get("showPlaying", "true")) { + NotificationHandler.createNotification("Now Playing:" + file.getAudioName(), "info", pConf.get("showPlayingTime", 1000)); + } window.dispatchEvent(new CustomEvent('playSong')); }).catch(e => { + NotificationHandler.createNotification(e.message, "error", 1000); player.nextSong(); }); } diff --git a/raw/javascript/config.js b/raw/javascript/config.js index 0ddffad..476a09b 100644 --- a/raw/javascript/config.js +++ b/raw/javascript/config.js @@ -1,7 +1,11 @@ class Config { - constructor() { + static allConfigs = {}; + + constructor(type) { this.config = {}; this.name = '' + this.type = type; + Config.allConfigs[type] = this; } loadConfigByName(name) { @@ -35,4 +39,10 @@ class Config { } return value; } + + reset() { + NotificationHandler.createNotification(`CONFIG REQUEST SUCCESS FOR ${this.type}`, "success", 2000); + this.config = {}; + this.save(); + } } \ No newline at end of file diff --git a/raw/javascript/eventHandler.js b/raw/javascript/eventHandler.js index d68bf18..b4d216a 100644 --- a/raw/javascript/eventHandler.js +++ b/raw/javascript/eventHandler.js @@ -1,3 +1,31 @@ +class EventHandler { + constructor() { + this.events = {}; + } + + addEvent(name, cb) { + this.events[name] = cb; + } + + sendData(name, data) { + worker.postMessage({ + cmd: name, + data: data + }); + } + + handleEvent(event) { + let data = event.data; + if (!data.cmd) { + return; + } + + if (this.events[data.cmd]) { + this.events[data.cmd](data.data); + } + } +} + async function initHandler() { let body = $('body'); $('.playlist.menu-icon').addEventListener('click', e => { @@ -31,6 +59,72 @@ async function initHandler() { }); window.addEventListener('playSong', setActiveOnPlaylist); $('.upload-image').addEventListener('click', imageUploader.renderModal.bind(imageUploader)); + body.addDelegatedEventListener('click', '.readAll', e => { + let playlist = player.playlist.list; + for (let i = 0; i < playlist.length; i++) { + playlist[i].getID3Tag(true); + } + }) + + body.addDelegatedEventListener('input', '.input-range input[type="range"]', (e, el) => { + let current = $('.current', el.parentNode); + current.innerText = el.value; + }); + + body.addDelegatedEventListener('input', 'input[type="color"]', (e, el) => { + let parent = el.parentNode; + $('.colorBlob', parent).style.backgroundColor = el.value; + }) + + body.addDelegatedEventListener('click', '.visual-item', (e, el) => { + visual.switch(el.dataset.id || 'wave'); + $('modal-content .visuals .active').removeClass('active'); + el.addClass('active'); + }) + + body.addDelegatedEventListener('input', 'section.base input', (e, el) => { + if (el.type === 'checkbox') { + pConf.set(el.name, el.checked); + } else { + setValue(el.name, el.value, pConf, el.dataset.type); + } + pConf.save(); + }) + body.addDelegatedEventListener('input', 'section.visual input', (e, el) => { + if (el.type === 'checkbox') { + vConf.set(el.name, el.checked); + } else { + setValue(el.name, el.value, vConf, el.dataset.type); + } + vConf.save(); + }) + + body.addDelegatedEventListener('click', '.button[data-action]', (e, el) => { + switch (el.dataset.action) { + case 'resetVConf': + vConf.reset(); + setTimeout(e => { + playerConf.handleById(); + }, 30); + break; + case 'makeModalTransparent': + $('#modal').toggleClass('lightMode') + break; + } + }) +} + + +function setValue(name, value, conf, type) { + switch (type) { + case 'float': + value = parseFloat(value); + break; + case 'int': + value = parseInt(value); + break; + } + conf.set(name, value); } function setActiveOnPlaylist(e) { @@ -47,6 +141,8 @@ function setActiveOnPlaylist(e) { function toggleShuffle() { let active = player.playlist.isShuffle; $('#shuffle').toggleCheck('active', active); + let status = active ? 'enabled' : 'disabled'; + NotificationHandler.createNotification("Shuffle: " + status, "info", 500); } function togglePlayButton(status) { diff --git a/raw/javascript/gl/glUtils.js b/raw/javascript/gl/glUtils.js index 2c2e304..65f0f40 100644 --- a/raw/javascript/gl/glUtils.js +++ b/raw/javascript/gl/glUtils.js @@ -1,5 +1,7 @@ // 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 lastMatrix = {m: null}; + static multiply(a, b) { let b00 = b[0]; let b01 = b[1]; @@ -93,9 +95,10 @@ class TDUtils { return dst; } - static xRotation(angleInRadians) { - let c = Math.cos(angleInRadians); - let s = Math.sin(angleInRadians); + static xRotation(angle) { + angle = TDUtils.degToRad(angle); + let c = Math.cos(angle); + let s = Math.sin(angle); return [ 1, 0, 0, 0, @@ -105,9 +108,10 @@ class TDUtils { ]; } - static yRotation(angleInRadians) { - let c = Math.cos(angleInRadians); - let s = Math.sin(angleInRadians); + static yRotation(angle) { + angle = TDUtils.degToRad(angle); + let c = Math.cos(angle); + let s = Math.sin(angle); return [ c, 0, -s, 0, @@ -117,9 +121,10 @@ class TDUtils { ]; } - static zRotation(angleInRadians) { - let c = Math.cos(angleInRadians); - let s = Math.sin(angleInRadians); + static zRotation(angle) { + angle = TDUtils.degToRad(angle); + let c = Math.cos(angle); + let s = Math.sin(angle); return [ c, s, 0, 0, @@ -300,8 +305,6 @@ class TDUtils { ] } - static lastMatrix = {m: null}; - static getMatrix(fov, aspect, near, far, camAngle, radius) { let lMat = this.lastMatrix, u = TDUtils; @@ -330,4 +333,14 @@ class TDUtils { } return true; } + + static updateRotate(rotation, def) { + let value = vConf.get(rotation, def) + vConf.get(rotation + '-inc', 0) + if (value > 360) { + value -= 360; + } else if (value < -360) { + value += 360; + } + vConf.set(rotation, value); + } } \ No newline at end of file diff --git a/raw/javascript/gui.js b/raw/javascript/gui.js index d400bf4..475fa95 100644 --- a/raw/javascript/gui.js +++ b/raw/javascript/gui.js @@ -3,23 +3,21 @@ class GUI { this.data = {}; this.modal = new Modal(); // load first vis window! - await this.loadForVis(); await template.loadArray([ 'playlist-item', 'playlist', - 'playlist-footer' + 'playlist-footer', + 'audio-information', + 'inputs/color', + 'inputs/input', + 'inputs/slider', + 'inputs/switch', + 'inputs/select', + 'inputs/option' ]); this.initDropZone(); } - async loadForVis() { - let c = visual.c, - d = this.data[c]; - if (d == null) { - this.data[c] = await fetch("out/gui/" + c + ".json").then((r) => r.json()); - } - } - initDropZone() { let items = 'drag dragstart dragend dragover dragenter dragleave drop'.split(' '); items.forEach(el => { @@ -39,6 +37,109 @@ class GUI { }; } +// create config Inputs from JSON +class GUIHelper { + static fromJSON(json, conf) { + let data = []; + for (let item of json) { + switch (item.type) { + case 'slider': + data.push(GUIHelper.createSliders(item, conf)); + break; + case 'color': + data.push(GUIHelper.createColorPicker(item, conf)); + break; + case 'checkbox': + data.push(GUIHelper.createCheckbox(item, conf)); + break; + case 'input': + data.push(GUIHelper.createInputField(item, conf)); + break; + case 'select': + data.push(GUIHelper.createSelect(item, conf)); + break; + case 'button': + data.push(GUIHelper.createButton(item, conf)); + break; + default: + console.error(`Unknown Type: ${item.type}`); + } + } + return data.join(" "); + } + + static createSliders(data, conf) { + let content = ""; + if (typeof data.name === "object") { + for (let i = 0; i < data.name.length; i++) { + let newData = {}; + Object.assign(newData, data); + newData.showName = data.name[i]; + newData.name = GUIHelper.richName(data, data.props[i]); + content += GUIHelper.createSlider(newData, conf); + } + } else { + content = GUIHelper.createSlider(data, conf); + } + return content; + } + + static createSlider(data, conf) { + let newData = {}; + Object.assign(newData, data); + newData.value = conf.get(newData.name, newData.value); + return template.parseTemplate('inputs/slider', newData) + } + + static createColorPicker(data, conf) { + let newData = {}; + Object.assign(newData, data); + newData.value = conf.get(newData.name, newData.value); + return template.parseTemplate('inputs/color', newData) + } + + static createCheckbox(data, conf) { + let newData = {}; + Object.assign(newData, data); + newData.value = conf.get(newData.name, newData.value) ? 'checked' : ''; + return template.parseTemplate('inputs/switch', newData) + } + + static createInputField(data, conf) { + let newData = {}; + Object.assign(newData, data); + newData.value = conf.get(newData.name, newData.value); + return template.parseTemplate('inputs/input', newData) + } + + static createSelect(data, conf) { + let newData = {}; + Object.assign(newData, data); + newData.value = conf.get(newData.name, newData.value); + let options = ''; + for (let i = 0; i < newData.options.length; i++) { + options += template.parseTemplate('inputs/option', { + value: newData.options[i] + }) + } + newData.options = options; + newData.event = 'visualConf'; + newData.conf = conf.type; + return template.parseTemplate('inputs/select', newData) + } + + static richName(data, name) { + if (data.group !== "") { + return data.group + "-" + name + } + return name; + } + + static createButton(item, conf) { + return `
${item.name}
` + } +} + class Modal { constructor() { let self = this; @@ -53,6 +154,7 @@ class Modal { } renderModal(title, content, footer) { + $('#modal').removeClass('lightMode') this.currentModal = title; this.renderHeader(title); this.renderContent(content); @@ -70,8 +172,8 @@ class Modal { } renderFooter(footer) { - let con = $('modal-footer', this.modal); - con.innerHTML = footer; + let con = $('modal-footer .inner', this.modal); + con.innerHTML = footer || "by VersusTuneZ"; } closeModal() { diff --git a/raw/javascript/imageUploader.js b/raw/javascript/imageUploader.js index bf25d8a..a8867bc 100644 --- a/raw/javascript/imageUploader.js +++ b/raw/javascript/imageUploader.js @@ -53,10 +53,6 @@ class ImageUploader { 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() { diff --git a/raw/javascript/notification.js b/raw/javascript/notification.js index 09e53be..8d18275 100644 --- a/raw/javascript/notification.js +++ b/raw/javascript/notification.js @@ -1,5 +1,77 @@ -class Notification { - constructor() { +class NotificationHandler { + static instance = new NotificationHandler(); + constructor() { + this.outer = $('.notification'); + this.notifications = []; + } + + async init() { + await template.loadTemplate('notification'); + } + + static createNotification(message, type, time) { + time = parseInt(time || "3000"); + let handler = NotificationHandler.instance, + not = new Notification(message, type, time); + handler.notifications.push(not); + not.show(); + return not; + } +} + +class Notification { + constructor(message, type, time) { + this.outer = NotificationHandler.instance.outer; + this.message = message; + this.type = type; + this.time = time; + this.isRemoved = false; + } + + async show() { + let self = this, + endless = self.time === -1; + + self.item = create('div'); + self.item.addClass('notification-item, ' + self.type); + if (endless) { + self.type += ' endless'; + } + self.updateContent(self.message); + this.outer.appendChild(self.item); + if (!endless) { + setTimeout(this.remove.bind(this), self.time) + } + } + + async remove() { + if (this.isRemoved) { + return; + } + this.isRemoved = true; + this.outer.removeChild(this.item); + let not = NotificationHandler.instance.notifications, + index = not.indexOf(this); + not.splice(index, 1); + delete this; + } + + updateContent(message) { + let self = this, + isEndless = self.time === -1, + data = { + message: message, + time: isEndless ? 1000 : self.time + 1, + type: self.type, + } + this.item.innerHTML = template.parseTemplate('notification', data); + } + + updateMessageOnly(message) { + let item = $('.message', this.item); + if (item) { + item.innerHTML = message; + } } } \ No newline at end of file diff --git a/raw/javascript/player.js b/raw/javascript/player.js index 9332899..af94b4f 100644 --- a/raw/javascript/player.js +++ b/raw/javascript/player.js @@ -5,18 +5,18 @@ class Player { nextSong() { let next = this.playlist.getNext(); - audioHandler.loadSong(next.file); + audioHandler.loadSong(next); } prevSong() { let next = this.playlist.getPrevious(); - audioHandler.loadSong(next.file); + audioHandler.loadSong(next); } playStop() { if (!audioHandler.lastSong) { let next = this.playlist.getCurrent(); - audioHandler.loadSong(next.file); + audioHandler.loadSong(next); return; } let audioFile = audioHandler.audioFile; @@ -31,7 +31,7 @@ class Player { playByID(number) { this.playlist.index = number; let next = this.playlist.getCurrent(); - audioHandler.loadSong(next.file); + audioHandler.loadSong(next); } } @@ -46,13 +46,16 @@ class Playlist { this.isShuffle = false; $('body').addDelegatedEventListener('change', 'input[type="file"]', this.changeFiles.bind(this)); $('body').addDelegatedEventListener('click', '.pagination .item', this.handlePagination.bind(this)); + eventHandler.addEvent('id3-request', this.handle.bind(this)); + eventHandler.addEvent('id3-request-force', this.forceID3.bind(this)); } shuffle() { // only shuffle if more then 2 elements are in let len = this.list.length; if (len < 3) { - this.shuffled = this.list; + this.shuffled = [0, 1, 2]; + return; } // the current-list need to be shuffled... for (let i = 0; i < len; i++) { @@ -62,40 +65,40 @@ class Playlist { } swap(a, b) { - this.shuffled[a] = this.list[b]; - this.shuffled[b] = this.list[a]; + this.shuffled[a] = b; + this.shuffled[b] = a; } getNext() { - let items = this.isShuffle ? this.shuffled : this.list, + let items = this.list, len = items.length - 1, next = this.index + 1; if (next > len) { next = 0; } this.index = next; - return items[next]; + return items[this.getRealIndex()]; } getPrevious() { - let items = this.isShuffle ? this.shuffled : this.list, + let items = this.list, len = items.length - 1, next = this.index - 1; if (next < 0) { next = len; } this.index = next; - return items[next]; + return items[this.getRealIndex()]; } getCurrent() { - let items = this.isShuffle ? this.shuffled : this.list; - return items[this.index]; + return this.list[this.getRealIndex()]; } // on new upload... this has to be an array! setPlaylist(files) { this.index = 0; + this.forceData = undefined; this.list = files; this.shuffle(); } @@ -112,6 +115,9 @@ class Playlist { } renderPagination(page) { + if (page === undefined) { + page = this.page; + } let length = this.list.length, maxSite = Math.ceil(length / PAGINATIONLIMIT) - 1; if (page < 0) { @@ -128,12 +134,12 @@ class Playlist { e = length; } if (length > 0) { - let items = this.isShuffle ? this.shuffled : this.list; + let items = this.list; for (let i = s; i < e; i++) { let obj = { index: i.toString(), nr: i + 1, - title: items[i].name, + title: items[this.getRealIndex(i)].getAudioName(), active: !audioHandler.audioFile.paused && i === this.index ? 'active' : '' } data += template.parseTemplate("playlist-item", obj); @@ -165,21 +171,57 @@ class Playlist { let i = 0; for (let file of el.files) { if (file && file.type.indexOf('audio') !== -1 && file.name.match(".m3u") === null) { - let name = file.name.split("."); - name.pop(); - name = name.join("."); - files.push({ - file: file, - name: name, - index: i++ - }); + let audioFile = new AudioPlayerFile(file, i++); + files.push(audioFile); } } this.setPlaylist(files); if (files.length > 0) { + NotificationHandler.createNotification("Songs added successfully!
Songs: " + files.length, "success", 3000); this.renderPagination(0); } else { - alert("No Valid AudioFiles found!"); + NotificationHandler.createNotification("File Upload failed!", "error", 3000); + } + } + + getRealIndex(index) { + if (index === undefined) { + index = this.index; + } + if (this.isShuffle) { + return this.shuffled[index]; + } + return index; + } + + handle(data) { + let index = data.index; + if (data.status === "waiting") { + return; + } + this.list[index].id3 = data; + if (this.timeout) { + window.clearTimeout(this.timeout); + } + this.timeout = setTimeout(this.renderPagination.bind(this), 100); + } + + forceID3(data) { + let self = this; + if (!self.forceData) { + self.forceData = {}; + self.forceNotification = NotificationHandler.createNotification("TagReader -> 0 / " + self.list.length, "info", -1); + } + let index = data.index; + if (data.status === "waiting") { + return; + } + self.list[index].id3 = data; + self.forceData[index] = true; + let leng = Object.keys(self.forceData).length; + this.forceNotification.updateMessageOnly("TagReader -> " + leng + " / " + self.list.length); + if (leng === self.list.length) { + self.forceNotification.remove() } } } \ No newline at end of file diff --git a/raw/javascript/playerConfigHandler.js b/raw/javascript/playerConfigHandler.js new file mode 100644 index 0000000..44c36c2 --- /dev/null +++ b/raw/javascript/playerConfigHandler.js @@ -0,0 +1,106 @@ +class PlayerConfigHandler { + async init() { + await template.loadArray([ + 'config/nav', + 'config/content', + 'config/visualitem' + ]); + this.last = 'base'; + $('.settings-icon').addEventListener('click', this.open.bind(this)); + $('modal-content').addDelegatedEventListener('click', '.config-nav .item', this.navHandler.bind(this)); + } + + open() { + if (this.content === undefined) { + let content = template.parseTemplate('config/nav', {}); + content += template.parseTemplate('config/content', {content: ""}); + this.content = content; + } + gui.modal.renderModal('Settings', this.content, "by VersusTuneZ"); + this.handleById(); + gui.modal.showModal(); + } + + navHandler(e, el) { + this.last = el.dataset.id; + this.handleById(); + } + + handleById() { + let id = this.last; + new VisualConfig(id === 'visual', id === 'base'); + let active = $('.config-nav .item.active'), + current = $('.config-nav .item[data-id="' + id + '"]'); + if (active) { + active.removeClass('active'); + } + if (current) { + current.addClass('active'); + } + } +} + +class VisualConfig { + static visualTemplates = {}; + + constructor(showVisual, renderBase) { + this.content = $('modal-content .config-content'); + if (showVisual) { + this.renderVisualConfig(visual.c); + } else { + if (renderBase) { + this.renderBase(); + } else { + this.renderVisuals(); + } + } + } + + renderVisuals() { + let keys = Object.keys(visual.visuals), + content = '
'; + for (let i = 0; i < keys.length; i++) { + content += template.parseTemplate('config/visualitem', { + title: visual.visuals[keys[i]].name, + id: keys[i], + active: keys[i] === visual.c ? 'active' : '' + }) + } + content += ''; + this.content.innerHTML = content; + } + + async renderBase() { + let data = await this.loadVisualConfig('base'), + div = create('section'); + div.addClass('base'); + div.innerHTML = GUIHelper.fromJSON(data, pConf); + this.content.innerHTML = div.outerHTML; + } + + // the name loads the json and handle it! + async renderVisualConfig(name) { + let data = await this.loadVisualConfig(name, vConf), + div = create('section'); + div.addClass('visual'); + div.innerHTML = GUIHelper.fromJSON(data, vConf); + div.innerHTML += GUIHelper.createButton({ + action: "resetVConf", + name: "Reset Visual Config" + }); + div.innerHTML += GUIHelper.createButton({ + action: "makeModalTransparent", + name: "toggle Modal Opacity" + }) + this.content.innerHTML = div.outerHTML; + } + + async loadVisualConfig(name) { + let tem = VisualConfig.visualTemplates; + if (!tem[name]) { + //load config and save it + tem[name] = await fetch('/out/gui/' + name + ".json").then((res) => res.json()); + } + return tem[name]; + } +} diff --git a/raw/javascript/select.js b/raw/javascript/select.js new file mode 100644 index 0000000..546190f --- /dev/null +++ b/raw/javascript/select.js @@ -0,0 +1,64 @@ +(function () { + const body = $('body'); + body.addDelegatedEventListener('click', 'custom-select .label', (e, el) => { + let parent = el.parentNode; + let options = $$('custom-option', parent), + optionsDiv = $('custom-options', parent); + if (parent.hasClass('open')) { + optionsDiv.style.maxHeight = ''; + parent.removeClass('open'); + } else { + let sum = 0; + options.forEach(function (element) { + sum += element.offsetHeight; + }); + optionsDiv.style.maxHeight = sum + 'px'; + parent.addClass('open'); + } + }) + body.addDelegatedEventListener('click', 'custom-select custom-option', (e, el) => { + let select = el.closest('custom-select'), + input = $('input', select); + $$('custom-option.active').forEach(activeEl => { + activeEl.removeClass('active'); + }) + el.addClass('active'); + if (input) { + input.value = el.dataset.value || el.innerText; + $('.label', select).innerText = el.innerText; + select.removeClass('open'); + el.parentNode.style.maxHeight = ''; + window.dispatchEvent(new CustomEvent('selectChanged', { + detail: { + select: select, + event: select.dataset.event, + value: input.value, + name: input.name + } + })); + } + }) + + window.addEventListener('selectChanged', (e) => { + if (e.detail.event === 'visualConf') { + e.preventDefault(); + e.stopPropagation(); + handleVisualConf(e.detail); + } + }) + + function handleVisualConf(e) { + try { + let value = e.value, + config = Config.allConfigs[e.select.dataset.conf]; + if (e.name === 'fftSize') { + value = parseInt(e.value); + visual.visuals[visual.c].updateFFT(value); + } + config.set(e.name, value); + config.save(); + } catch (e) { + console.error(e); + } + } +})() diff --git a/raw/javascript/startup.js b/raw/javascript/startup.js new file mode 100644 index 0000000..76987af --- /dev/null +++ b/raw/javascript/startup.js @@ -0,0 +1,23 @@ +class Startup { + constructor() { + this.modules = { + 'startup': false, + 'id3-ready': false + }; + } + + moduleLoaded(name) { + this.modules[name] = true + this.allModulesLoaded(); + } + + allModulesLoaded() { + for (let module in this.modules) { + if (!this.modules[module]) { + return false; + } + } + window.dispatchEvent(new CustomEvent('startupFin')); + return true; + } +} \ No newline at end of file diff --git a/raw/javascript/template.js b/raw/javascript/template.js index 06ac580..e951fa8 100644 --- a/raw/javascript/template.js +++ b/raw/javascript/template.js @@ -28,7 +28,11 @@ class Template { templateEx.lastIndex++; } let key = m[0]; - d = d.replace(key, data[m[1]] || "") + let value = data[m[1]]; + if (value === undefined || value === null) { + value = ""; + } + d = d.replace(key, value) } return d; } diff --git a/raw/javascript/utils.js b/raw/javascript/utils.js index 5f4f58f..d2ff433 100644 --- a/raw/javascript/utils.js +++ b/raw/javascript/utils.js @@ -159,7 +159,12 @@ Node.prototype.addDelegatedEventListener = function (type, aim, cb) { } else { let parent = target.closest(aim); if (parent) { - cb(event, parent); + try { + cb(event, parent); + } catch (e) { + NotificationHandler.createNotification("FATAL ERROR WITHIN HANDLER!", "error", 1000); + //nothing! + } } } }) @@ -249,23 +254,12 @@ function append(to, array) { } } -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; - }) +function hexToRgb(hex) { + hex = hex.replace("#", ""); + let bigint = parseInt(hex, 16), + r = (bigint >> 16) & 255, + g = (bigint >> 8) & 255, + b = bigint & 255; + + return [r / 255, g / 255, b / 255]; } \ No newline at end of file diff --git a/raw/javascript/visual.js b/raw/javascript/visual.js index 14c0356..8e4fc8a 100644 --- a/raw/javascript/visual.js +++ b/raw/javascript/visual.js @@ -2,10 +2,13 @@ class Visual { constructor() { this.data = []; //for drawing this.dataArray = []; + this.name = "Default"; } updateData() { + } + updateFFT(fftSize) { } draw() { @@ -18,14 +21,23 @@ class Visual { class VisualDrawer { constructor() { this.visuals = { - "sphere": new Sphere(), + //"sphere": new Sphere(), "wave": new Wave(), - "water": new Water() + "wave2d": new Wave2D(), + //"water": new Water() + } + this.lastMainColor = { + base: '#-1', + color: [0, 0, 0] + }; + this.lastSecondColor = { + base: '#-1', + color: [0, 0, 0] } } init() { - this.switch('wave'); + this.switch(pConf.get("visual", "wave2d")); this.updateLoop(); } @@ -34,20 +46,38 @@ class VisualDrawer { this.c = visual; vConf.loadConfigByName(this.c); this.visuals[this.c].setup(); + pConf.set("visual", this.c); + pConf.save(); } } updateLoop() { let self = this; - let pro = shaderHandler.use(self.c); let vis = self.visuals[self.c]; + let pro = shaderHandler.use(self.c); + this.updateSeekbar(); + this.prepare(pro); vis.updateData(); - this.prepare(); vis.draw(pro); requestAnimationFrame(self.updateLoop.bind(self)) } - prepare() { + updateSeekbar() { + cInfo.width = window.innerWidth; + cInfo.height = window.innerHeight; + let audioFile = audioHandler.audioFile; + ctx.clearRect(0, 0, cInfo.width, cInfo.height); + if (!audioFile.paused && pConf.get("showSeekbar", true)) { + //show seekbar + let dur = audioFile.duration, + cur = audioFile.currentTime, + percent = cur / dur * cInfo.width; + ctx.fillStyle = pConf.get("seekColor", '#fff'); + ctx.fillRect(0, c.height - 10, percent, c.height); + } + } + + prepare(pro) { c.width = window.innerWidth; c.height = window.innerHeight; gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); @@ -56,5 +86,29 @@ class VisualDrawer { gl.enable(gl.DEPTH_TEST); gl.depthFunc(gl.LEQUAL); gl.enable(gl.CULL_FACE); + + // u_baseColor || u_maxColor + this.setColor(pro); + } + + setColor(program) { + let baseColor = gl.getUniformLocation(program, "u_baseColor"), + maxColor = gl.getUniformLocation(program, "u_maxColor"), + self = this, + mainColor = self.lastMainColor, + secondColor = self.lastSecondColor; + this.updateColor('lastMainColor', 'baseColor'); + this.updateColor('lastSecondColor', 'gradientToColor'); + gl.uniform3fv(baseColor, mainColor.color); + gl.uniform3fv(maxColor, secondColor.color); + } + + updateColor(index, col) { + let color = this[index], + value = vConf.get(col, '#ffffff') + if(value !== color.base) { + color.color = hexToRgb(value); + color.base = value; + } } } \ No newline at end of file diff --git a/raw/javascript/visuals/sphere.js b/raw/javascript/visuals/sphere.js index 73c8b2c..92f7062 100644 --- a/raw/javascript/visuals/sphere.js +++ b/raw/javascript/visuals/sphere.js @@ -1,4 +1,9 @@ class Sphere extends Visual { + constructor() { + super(); + this.name = "Sphere"; + } + draw() { } diff --git a/raw/javascript/visuals/water.js b/raw/javascript/visuals/water.js index 848f19c..ecf596e 100644 --- a/raw/javascript/visuals/water.js +++ b/raw/javascript/visuals/water.js @@ -1,5 +1,10 @@ //animate Water the way like the Audio is Coming... 256FFT-Size max! class Water extends Visual { + constructor() { + super(); + this.name = "Water"; + } + draw() { } diff --git a/raw/javascript/visuals/wave.js b/raw/javascript/visuals/wave.js index 4552e78..6dfe578 100644 --- a/raw/javascript/visuals/wave.js +++ b/raw/javascript/visuals/wave.js @@ -1,10 +1,11 @@ // 3D Audio-Waves -> maybe also 2D? class Wave extends Visual { + constructor() { + super(); + this.name = "3D Wave"; + } + updateData() { - //only for debug! remove pls - if (window.stopUpdate) { - return; - } let data = audioHandler.getFloatArray(); let add = 2 / data.length, x = -1; @@ -36,6 +37,7 @@ class Wave extends Visual { gl.enableVertexAttribArray(position); gl.vertexAttribPointer(position, 3, gl.FLOAT, true, 0, 0); gl.drawArrays(vConf.get("waveForm", gl.TRIANGLES), 0, this.data.length / 3); + this.afterDraw(); } rotate(program) { @@ -46,27 +48,36 @@ class Wave extends Visual { 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))); + matrix = TDUtils.multiply(matrix, TDUtils.xRotation(vConf.get("rotation-x", 10))); + matrix = TDUtils.multiply(matrix, TDUtils.yRotation(vConf.get("rotation-y", 50))); + matrix = TDUtils.multiply(matrix, TDUtils.zRotation(vConf.get("rotation-z", -30))); 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)); + this.updateFFT(vConf.get('fftSize', 4096)); + vConf.get("rotation-z", -30); + vConf.get("rotation-y", 50); + vConf.get("rotation-x", 10); + } + + updateFFT(fftSize) { + audioHandler.fftSize(fftSize); + this.data = new Float32Array(fftSize * 9); } 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"); + let lightPos = gl.getUniformLocation(program, "u_lightPos"); gl.uniform3fv(lightPos, vConf.get("light", [0, 5, -56])); - //gl.uniformMatrix4fv(matrix, false, TDUtils.getMatrix(90, c.width / c.height, 1, 2000, 200, 200)); + } + + afterDraw() { + TDUtils.updateRotate('rotation-x', 10); + TDUtils.updateRotate('rotation-y', 50); + TDUtils.updateRotate('rotation-z', -30); + vConf.save(); } } \ No newline at end of file diff --git a/raw/javascript/visuals/wave2d.js b/raw/javascript/visuals/wave2d.js new file mode 100644 index 0000000..0a48e12 --- /dev/null +++ b/raw/javascript/visuals/wave2d.js @@ -0,0 +1,73 @@ +class Wave2D extends Visual { + constructor() { + super(); + this.name = "2D Wave"; + } + + updateData() { + let data = audioHandler.getFloatArray(); + let add = 2 / data.length, + x = -1; + let outerLoop = 0; + for (let i = 0; i < data.length; i++) { + //first + this.data[outerLoop] = x; + this.data[outerLoop + 1] = data[i]; + this.data[outerLoop + 2] = data[i]; + outerLoop += 3; + x += add; + } + } + + draw(program) { + this.prepare(program); + let position = this.position, + positionBuffer = gl.createBuffer(); + this.rotate(program); + gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.data), gl.DYNAMIC_DRAW); + let vao = gl.createVertexArray(); + gl.bindVertexArray(vao); + gl.enableVertexAttribArray(position); + gl.vertexAttribPointer(position, 3, gl.FLOAT, true, 0, 0); + gl.drawArrays(vConf.get("waveForm", gl.LINE_STRIP), 0, this.data.length / 3); + this.afterDraw(); + } + + rotate(program) { + let matrix = [ + 1, 0, 0, 0, + 0, 0.6, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ] + matrix = TDUtils.multiply(matrix, TDUtils.xRotation(vConf.get("rotation-x", 0))); + matrix = TDUtils.multiply(matrix, TDUtils.yRotation(vConf.get("rotation-y", 0))); + matrix = TDUtils.multiply(matrix, TDUtils.zRotation(vConf.get("rotation-z", 0))); + let rotate = gl.getUniformLocation(program, "u_matrix"); + gl.uniformMatrix4fv(rotate, false, matrix); + } + + setup() { + this.updateFFT(vConf.get('fftSize', 16384)); + } + + updateFFT(fftSize) { + audioHandler.fftSize(fftSize); + this.data = new Float32Array(fftSize * 3); + } + + prepare(program) { + this.position = gl.getAttribLocation(program, "a_position"); + this.color = gl.getUniformLocation(program, "u_color"); + let lightPos = gl.getUniformLocation(program, "u_lightPos"); + gl.uniform3fv(lightPos, vConf.get("light", [0, 5, -56])); + } + + afterDraw() { + TDUtils.updateRotate('rotation-x', 0); + TDUtils.updateRotate('rotation-y', 0); + TDUtils.updateRotate('rotation-z', 0); + vConf.save(); + } +} \ No newline at end of file diff --git a/raw/scss/_config.scss b/raw/scss/_config.scss new file mode 100644 index 0000000..b93f643 --- /dev/null +++ b/raw/scss/_config.scss @@ -0,0 +1,54 @@ +.config-nav { + display: flex; + flex-direction: row; + background-color: $nav; + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); + + div { + padding: 10px 5px; + cursor: pointer; + user-select: none; + border-bottom: 1px solid transparent; + + &.active { + border-color: $active; + } + + &:hover { + background-color: rgba(0, 0, 0, .4); + } + } +} + +.config-content { + padding: 1em; + min-height: 200px; +} + +.config-content .visuals { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: space-around; + + .visual-item { + display: flex; + align-items: center; + justify-content: center; + padding: 1em; + min-width: 100px; + min-height: 100px; + background-color: $darker; + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); + transition: .5s; + cursor: pointer; + + &:hover { + background-color: $nav; + } + + &.active { + border: 1px solid $primary; + } + } +} \ No newline at end of file diff --git a/raw/scss/_gui.scss b/raw/scss/_gui.scss index c335b35..c8e40cb 100644 --- a/raw/scss/_gui.scss +++ b/raw/scss/_gui.scss @@ -1,6 +1,10 @@ -#c { - width: 100%; - height: 100%; +canvas { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + pointer-events: none; } group { @@ -118,15 +122,45 @@ group-input { display: flex; justify-content: center; align-items: center; + z-index: 100; &.hide { display: none !important; } } +modal-footer playlist { + flex-direction: row-reverse; + flex-wrap: wrap; + + .pagination { + margin-left: auto; + } +} + #image-upload form { - margin-top: 20px; display: flex; flex-direction: column; - align-items: center; + width: 90%; + margin: 10px auto; +} + +.audio-item { + display: flex; + flex-direction: column; + + .artist { + font-size: .75em; + color: #dcdcdc; + } +} + +.now-playing { + font-size: .8em; + display: block; + margin-bottom: 10px; +} + +.menus { + z-index: 10; } \ No newline at end of file diff --git a/raw/scss/_input.scss b/raw/scss/_input.scss index fe41f33..5f81312 100644 --- a/raw/scss/_input.scss +++ b/raw/scss/_input.scss @@ -98,11 +98,15 @@ } .input { - width: 90%; + width: 100%; position: relative; display: inline-block; margin-top: 1rem; background-color: transparent; + + &-range { + margin-bottom: 20px; + } } input:focus + .input:after { @@ -148,20 +152,20 @@ input:focus + .input:after { .floating-label input:focus ~ .input-label, .floating-label input:valid ~ .input-label { transform: translateY(-0.72rem); - color: #ff0089; + color: $active; font-size: .7rem; } .floating-label input:valid ~ .input-label { transform: translateY(-0.72rem); - color: #3949ab; + color: $second; font-size: .7rem; } .focus { content: ''; width: 0; - background-color: #ff0a8e; + background-color: $second; position: absolute; bottom: 0; left: 0; @@ -180,6 +184,10 @@ input:focus ~ .focus { } switch { + display: flex; + flex-direction: row; + padding: 5px; + input { position: absolute; appearance: none; @@ -190,6 +198,10 @@ switch { } } + span { + margin-left: 10px; + } + label { display: block; border-radius: 10px; @@ -202,7 +214,7 @@ switch { &:after { content: ''; - background-color: #ff3232; + background-color: $primary; position: absolute; top: 2px; left: 2px; @@ -234,7 +246,7 @@ input[type="color"] { } } -#colorBlob { +.colorBlob { display: block; width: 100%; height: 20px; @@ -242,12 +254,124 @@ input[type="color"] { } .button { + text-align: center; + user-select: none; + border-radius: 5px; border: 1px solid $primary; padding: 0.5em 1em; cursor: pointer; transition: .5s; + &.spaced { + margin-bottom: 10px; + } + &:hover { border-color: $second; + border-radius: 7px; + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); + } +} + +.input { + .max, .min, .current { + font-size: .8em; + color: $second; + position: absolute; + bottom: -1rem; + } + + .current { + display: block; + width: 100%; + text-align: center; + } + + .min { + left: 0; + } + + .max { + right: 0; + } +} + +custom-select { + width: auto; + min-width: 200px; + display: block; + + label { + color: #ff0089; + font-size: .7rem; + } + + input { + display: none; + } + + .label { + padding: 10px 30px 10px 10px; + cursor: pointer; + position: relative; + transition: all .3s; + box-shadow: inset 0 0 3px rgba(94, 94, 94, 1); + + &::after { + content: ''; + border: solid #dcdcdc; + border-width: 0 3px 3px 0; + display: inline-block; + padding: 3px; + transform: rotate(45deg); + -webkit-transform: rotate(45deg); + transition: all .3s ease-in-out; + position: absolute; + right: 10px; + top: calc(50% - 6px); + } + } + + custom-options { + max-height: 0; + overflow: hidden; + transition: all .5s ease; + font-size: .9em; + display: block; + border: 1px solid transparent; + } + + custom-option { + display: block; + width: 100%; + padding: 10px 5px; + cursor: pointer; + box-shadow: 0px -1px 0px 0px rgba(0, 0, 0, .08); + + &:hover { + background-color: $primary; + color: #fff; + } + } + + custom-option.active { + font-weight: bold; + } +} + +custom-select.open { + .label { + &::after { + transform: rotate(-135deg); + -webkit-transform: rotate(-135deg); + top: calc(50% - 3px); + } + + background-color: rgba(0, 0, 0, .1); + } + + custom-options { + max-height: none; + border: 1px solid $nav !important; } } \ No newline at end of file diff --git a/raw/scss/_modal.scss b/raw/scss/_modal.scss index 3c45c4d..cd6cfe3 100644 --- a/raw/scss/_modal.scss +++ b/raw/scss/_modal.scss @@ -7,6 +7,10 @@ border-radius: 15px; overflow: hidden; + &.lightMode { + background-color: rgba(0, 0, 0, .1); + } + div { position: unset; } @@ -45,5 +49,11 @@ modal-footer { display: block; box-shadow: 0 -3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); + + .inner { + padding: 5px; + width: calc(100% - 40px); + margin: 0 auto; + } } } \ No newline at end of file diff --git a/raw/scss/_notification.scss b/raw/scss/_notification.scss index f7ad355..b9d0b23 100644 --- a/raw/scss/_notification.scss +++ b/raw/scss/_notification.scss @@ -1,6 +1,117 @@ .notification { - right: 0; - bottom: 0; + right: 10px; + top: 0; + height: 100%; display: flex; - width: 320px; + flex-direction: column-reverse; + width: 90%; + max-width: 400px; + pointer-events: none; + + .notification-item { + margin-top: 10px; + } + + div { + position: unset; + padding: unset; + } + + div.notification-item { + position: relative; + } +} + +.notification-item { + width: 100%; + border-radius: 5px; + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); + overflow: hidden; + + &.success { + background-color: $success; + } + + &.error { + background-color: $error; + } + + &.warning { + background-color: $warning; + } + + &.info { + background-color: $info; + } + + .message { + padding: 1em; + } +} + +.notification-item .fade-bar { + animation: fadeOut ease-in-out 3000ms; + height: 100%; + width: 100%; + position: absolute; + top: 0; + z-index: -1; + opacity: 0.4; + transform-origin: left; + + &:after { + content: ''; + z-index: 1; + bottom: 0; + height: 4px; + width: 100%; + position: absolute; + background-color: #fff; + } + + &.endless { + animation: endlessFade ease-in-out 500ms infinite; + } + + &.success { + background-color: $successBorder; + } + + &.error { + background-color: $errorBorder; + } + + &.warning { + background-color: $warningBorder; + } + + &.info { + background-color: $primary; + } +} + +@keyframes fadeOut { + from { + transform: scaleX(1); + } + to { + transform: scaleX(0); + } +} + +@keyframes endlessFade { + 0% { + transform: scaleX(1); + transform-origin: right; + } + 49% { + transform-origin: right; + } + 50% { + transform: scaleX(0); + transform-origin: left; + } + 100% { + transform: scaleX(1); + } } \ No newline at end of file diff --git a/raw/scss/_playlist.scss b/raw/scss/_playlist.scss index bd171c8..fb60422 100644 --- a/raw/scss/_playlist.scss +++ b/raw/scss/_playlist.scss @@ -65,6 +65,8 @@ playlist { &-number { padding: 5px 10px 5px 5px; width: 50px; + display: flex; + align-items: center; } &:hover { diff --git a/raw/scss/_variables.scss b/raw/scss/_variables.scss index f55368c..b229076 100644 --- a/raw/scss/_variables.scss +++ b/raw/scss/_variables.scss @@ -1,6 +1,16 @@ $bg: #303030; $darker: #212121; +$nav: #1b1b1b; $primary: #3949ab; $second: #ff0089; -$active: #5ff507; \ No newline at end of file +$active: #5ff507; + +$info: rgba(71,73,171,.6); +$success: rgba(74, 177, 11,.6); +$error: rgba(255,50,50,.6); +$warning: rgba(255, 177, 89, 0.6); + +$successBorder: #60ff00; +$errorBorder: #fa0000; +$warningBorder: #ff7700; \ No newline at end of file diff --git a/raw/scss/style.scss b/raw/scss/style.scss index c8de357..8b65c80 100644 --- a/raw/scss/style.scss +++ b/raw/scss/style.scss @@ -19,6 +19,8 @@ html, body { background-repeat: no-repeat; background-position: center; background-size: cover; + width: 100vw; + height: 100vh; } div { @@ -50,4 +52,5 @@ div { @import "controls"; @import "playlist"; @import "modal"; -@import "notification"; \ No newline at end of file +@import "notification"; +@import "config"; \ No newline at end of file diff --git a/raw/worker/app.js b/raw/worker/app.js new file mode 100644 index 0000000..41f18b1 --- /dev/null +++ b/raw/worker/app.js @@ -0,0 +1,4 @@ +const tagger = new Tagger(self), + eventHandler = new EventHandler(self); + +tagger.init(); \ No newline at end of file diff --git a/raw/worker/database.js b/raw/worker/database.js new file mode 100644 index 0000000..e8d88bc --- /dev/null +++ b/raw/worker/database.js @@ -0,0 +1,97 @@ +class Database { + constructor(name, version) { + this.isInit = false; + this.name = name; + this.version = version; + this.errored = false; + this.db; + this.prepareDB(); + } + + async prepareDB() { + if (this.isInit || this.errored) { + return; + } + let req = this.db = indexedDB.open(this.name, this.version); + req.onerror = DatabaseHandler.onError.bind(this); + req.onsuccess = DatabaseHandler.onSuccess.bind(this); + req.onupgradeneeded = DatabaseHandler.onUpgrade.bind(this); + req.onblocked = DatabaseHandler.onBlocked.bind(this); + } + + async set(key, data, store) { + data['key'] = key; + return await this.run('put', data, store); + } + + async get(key, store) { + return await this.run('get', key, store); + } + + async remove(key, store) { + return await this.run('delete', key, store); + } + + check() { + return !(!this.isInit || this.errored); + } + + async getTX(store) { + return await this.db.transaction([store], "readwrite") + } + + async getObjectStore(tx, store) { + return await tx.objectStore(store) + } + + async run(action, key, store) { + if (this.check()) { + let tx = await this.getTX(store); + let obj = await this.getObjectStore(tx, store); + let data = await this.request(obj[action](key)); + await tx.complete + return await data; + } + return null; + } + + request(req) { + return new Promise((resolve, reject) => { + req.onsuccess = () => resolve(req.result); + req.onerror = () => reject(req.error); + }); + } +} + +class DatabaseHandler { + static onError(e) { + this.errored = true; + eventHandler.sendData("databaseError", "error", e.message); + } + + static onSuccess(e) { + this.db = this.db.result; + this.isInit = true; + eventHandler.sendData("databaseCreated", "success", ""); + eventHandler.handleEvent({ + data: { + cmd: 'dbReady-' + this.name, + data: this.db + } + }); + } + + static onUpgrade(e) { + eventHandler.sendData("databaseUpgradeNeeded", "info", e.message); + eventHandler.handleEvent({ + data: { + cmd: 'dbUpgrade-' + this.name, + data: this.db + } + }); + } + + static onBlocked(e) { + eventHandler.sendData("databaseBlocked", "error", e.message); + } +} \ No newline at end of file diff --git a/raw/worker/eventHandler.js b/raw/worker/eventHandler.js new file mode 100644 index 0000000..f1b2445 --- /dev/null +++ b/raw/worker/eventHandler.js @@ -0,0 +1,30 @@ +class EventHandler { + constructor(worker) { + this.events = {}; + this.worker = worker; + this.worker.addEventListener('message', this.handleEvent.bind(this)); + } + + addEvent(name, cb) { + this.events[name] = cb; + } + + sendData(name, status, data) { + this.worker.postMessage({ + cmd: name, + status: status, + data: data + }); + } + + handleEvent(event) { + let data = event.data; + if (!data.cmd) { + return; + } + + if (this.events[data.cmd]) { + this.events[data.cmd](data.data); + } + } +} \ No newline at end of file diff --git a/raw/worker/id3.js b/raw/worker/id3.js new file mode 100644 index 0000000..0609715 --- /dev/null +++ b/raw/worker/id3.js @@ -0,0 +1,86 @@ +self.importScripts('jsmediatags.min.js'); + +class Tagger { + constructor(worker) { + this.db = new Database("SongLib", 1); + } + + static prepareName(data) { + let name = data.name || ''; + return name.replace(/[^\w\s]/gi, '').split(" ").join("") + } + + init() { + eventHandler.addEvent('getData', this.getData.bind(this)); + eventHandler.addEvent('removeData', this.getData.bind(this)); + eventHandler.addEvent('setData', this.getData.bind(this)); + eventHandler.addEvent('dbReady-SongLib', this.ready.bind(this)); + eventHandler.addEvent('dbUpgrade-SongLib', this.upgrade.bind(this)); + } + + async getData(data) { + let key = Tagger.prepareName(data), + newData = await this.db.get(key, 'songs'), + handlerName = data.force ? 'id3-request-force' : 'id3-request'; + if (newData) { + newData['index'] = data['index']; + eventHandler.sendData(handlerName, 'success', newData); + } else { + this.parseData(data, key).then(r => { + r['index'] = data['index']; + eventHandler.sendData(handlerName, 'success', r); + }); + eventHandler.sendData(handlerName, 'waiting', data); + } + } + + async removeData(data) { + let key = Tagger.prepareName(data), + newData = await this.db.remove(key, 'songs'); + eventHandler.sendData('id3-remove', 'success', newData); + } + + async setData(data, key) { + let newData = await this.db.set(key, data, 'songs'); + eventHandler.sendData('id3-set', 'success', newData); + } + + ready(data) { + console.log("[ID3] > Song Database Ready"); + eventHandler.sendData('id3-ready', "startup", ""); + } + + upgrade(data) { + let db = data.result, + songs = db.createObjectStore("songs", {keyPath: 'key'}); + songs.createIndex("name", "name", {unique: false}); + } + + //if not found in key-value storage read it! this take some time so this is async! + async parseData(data, key) { + let tag = await new Promise((resolve, reject) => { + new jsmediatags.Reader(data.file) + .read({ + onSuccess: (tag) => { + resolve(tag); + }, + onError: (error) => { + console.log(`[ID3] > Error Parsing Data!`); + resolve({ + tags: {} + }); + } + }); + }) + let tags = tag.tags, + values = { + title: tags.title || data.name, + artist: tags.artist || 'VA', + genre: tags.genre || 'Unknown', + year: tags.year || 1970, + key: key + }; + await this.setData(values, key); + return values; + } +} \ No newline at end of file diff --git a/shaders/wave.frag b/shaders/wave.frag index 04a1395..5df05a3 100644 --- a/shaders/wave.frag +++ b/shaders/wave.frag @@ -6,6 +6,8 @@ precision mediump float; in vec4 pos; in vec3 v_surfaceToLight; +in vec3 baseColor; +in vec3 maxColor; uniform vec4 u_color; out vec4 outColor; @@ -14,8 +16,6 @@ void main() { 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; diff --git a/shaders/wave.vert b/shaders/wave.vert index bafb157..f52b8fd 100644 --- a/shaders/wave.vert +++ b/shaders/wave.vert @@ -3,12 +3,18 @@ in vec3 a_position; uniform mat4 u_matrix; uniform vec3 u_lightPos; +uniform vec3 u_baseColor; +uniform vec3 u_maxColor; out vec4 pos; out vec3 v_surfaceToLight; +out vec3 maxColor; +out vec3 baseColor; void main() { pos = u_matrix * vec4(a_position, 1); gl_Position = pos; v_surfaceToLight = u_lightPos - pos.xyz; + maxColor = u_maxColor; + baseColor = u_baseColor; } \ No newline at end of file diff --git a/shaders/wave2d.frag b/shaders/wave2d.frag new file mode 100644 index 0000000..d830860 --- /dev/null +++ b/shaders/wave2d.frag @@ -0,0 +1,19 @@ +#version 300 es + +// fragment shaders don't have a default precision so we need +// to pick one. mediump is a good default. It means "medium precision" +precision mediump float; + +in vec4 pos; +in vec3 baseColor; +in vec3 maxColor; +out vec4 outColor; + +void main() { + float y = pos.z; + if (y < 0.0) { + y = y * -1.0; + } + vec3 color = mix(baseColor, maxColor, y); + outColor = vec4(color, 1.0); +} \ No newline at end of file diff --git a/shaders/wave2d.vert b/shaders/wave2d.vert new file mode 100644 index 0000000..051e608 --- /dev/null +++ b/shaders/wave2d.vert @@ -0,0 +1,19 @@ +#version 300 es + +in vec3 a_position; +uniform mat4 u_matrix; +uniform vec3 u_baseColor; +uniform vec3 u_maxColor; + +out vec4 pos; +out vec3 maxColor; +out vec3 baseColor; + +void main() { + pos = u_matrix * vec4(a_position, 1); + pos.y = pos.y * 0.6; + gl_Position = pos; + + maxColor = u_maxColor; + baseColor = u_baseColor; +} \ No newline at end of file From 07b35b96679ffc7a79cca2902406de9b8d848368 Mon Sep 17 00:00:00 2001 From: versustunez Date: Fri, 7 Aug 2020 19:31:30 +0200 Subject: [PATCH 5/7] WIP --- build/task/js.js | 1 + build/task/spriteBuilder.js | 1 + index.html | 5 ++ out/gui/wave.json | 2 +- out/icon-sprite.svg | 2 +- out/js/scripts.js | 158 ++++++++++++++++++++++++++++----- out/js/scripts.min.js | 2 +- out/theme/style.css | 2 +- out/tpl/help.tpl | 69 ++++++++++++++ out/tpl/image.tpl | 3 +- raw/gui/wave.json | 30 +++++++ raw/javascript/app.js | 5 +- raw/javascript/eventHandler.js | 58 ++++++++---- raw/javascript/gui.js | 18 +++- raw/javascript/keys.js | 59 ++++++++++++ raw/javascript/notification.js | 2 +- raw/javascript/player.js | 2 +- raw/javascript/utils.js | 4 + raw/javascript/visuals/wave.js | 10 ++- raw/scss/_gui.scss | 30 +++++++ raw/scss/_playlist.scss | 6 ++ shaders/sphere.frag | 2 - shaders/water.frag | 2 - shaders/wave.frag | 11 +-- shaders/wave.vert | 6 -- shaders/wave2d.frag | 11 ++- shaders/wave2d.vert | 7 -- 27 files changed, 429 insertions(+), 79 deletions(-) create mode 100644 out/tpl/help.tpl create mode 100644 raw/javascript/keys.js diff --git a/build/task/js.js b/build/task/js.js index a295e68..ec727ac 100644 --- a/build/task/js.js +++ b/build/task/js.js @@ -30,6 +30,7 @@ const config = { ...visuals, basePath + 'eventHandler.js', basePath + 'select.js', + basePath + 'keys.js', basePath + 'startup.js', basePath + 'app.js' ], diff --git a/build/task/spriteBuilder.js b/build/task/spriteBuilder.js index edd45f9..5da45df 100644 --- a/build/task/spriteBuilder.js +++ b/build/task/spriteBuilder.js @@ -43,6 +43,7 @@ function buildIconSprites() { fal.faFolderUpload, fal.faListMusic, fal.faFileImage, + fal.faQuestionCircle, ], vt: [] }; diff --git a/index.html b/index.html index 3e9cf34..62a7feb 100644 --- a/index.html +++ b/index.html @@ -36,6 +36,11 @@ +
`}}class Modal{constructor(){this.currentModal="",this.modal=$("#modal"),this.parent=this.modal.parentNode,this.modal.addDelegatedEventListener("click","header .close",this.closeModal.bind(this))}resetModal(){this.renderModal("","","")}renderModal(t,e,a){$("#modal").removeClass("lightMode"),this.currentModal=t,this.renderHeader(t),this.renderContent(e),this.renderFooter(a)}renderHeader(t){$("header .headline",this.modal).innerHTML=t}renderContent(t){$("modal-content",this.modal).innerHTML=t}renderFooter(t){$("modal-footer .inner",this.modal).innerHTML=t||"by VersusTuneZ"}closeModal(){this.parent.addClass("hide")}isCurrent(t){return t===this.currentModal}showModal(){this.parent.removeClass("hide")}}class Visual{constructor(){this.data=[],this.dataArray=[],this.name="Default"}updateData(){}updateFFT(t){}draw(){}setup(){}}class VisualDrawer{constructor(){this.visuals={wave:new Wave,wave2d:new Wave2D},this.lastMainColor={base:"#-1",color:[0,0,0]},this.lastSecondColor={base:"#-1",color:[0,0,0]}}init(){this.switch(pConf.get("visual","wave2d")),this.updateLoop()}switch(t){null!=this.visuals[t]&&(this.c=t,vConf.loadConfigByName(this.c),this.visuals[this.c].setup(),pConf.set("visual",this.c),pConf.save())}updateLoop(){let t=this.visuals[this.c],e=shaderHandler.use(this.c);this.updateSeekbar(),this.prepare(e),t.updateData(),t.draw(e),requestAnimationFrame(this.updateLoop.bind(this))}updateSeekbar(){cInfo.width=window.innerWidth,cInfo.height=window.innerHeight;let t=audioHandler.audioFile;if(ctx.clearRect(0,0,cInfo.width,cInfo.height),!t.paused&&pConf.get("showSeekbar",!0)){let e=t.duration,a=t.currentTime/e*cInfo.width;ctx.fillStyle=pConf.get("seekColor","#fff"),ctx.fillRect(0,c.height-10,a,c.height)}}prepare(t){c.width=window.innerWidth,c.height=window.innerHeight,gl.viewport(0,0,gl.canvas.width,gl.canvas.height),gl.clearColor(0,0,0,parseFloat(pConf.get("alphaValue",0))),gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT),gl.enable(gl.DEPTH_TEST),gl.depthFunc(gl.LEQUAL),gl.enable(gl.CULL_FACE),this.setColor(t)}setColor(t){let e=gl.getUniformLocation(t,"u_baseColor"),a=gl.getUniformLocation(t,"u_maxColor"),i=this.lastMainColor,s=this.lastSecondColor;this.updateColor("lastMainColor","baseColor"),this.updateColor("lastSecondColor","gradientToColor"),gl.uniform3fv(e,i.color),gl.uniform3fv(a,s.color)}updateColor(t,e){let a=this[t],i=vConf.get(e,"#ffffff");i!==a.base&&(a.color=hexToRgb(i),a.base=i)}}class ImageUploader{async init(){this.image=pConf.get("bgURL",""),this.color=pConf.get("bgColour","#000000"),this.alpha=pConf.get("alphaValue",.5),this.getRealImage(),this.applyValues(),$("#modal").addDelegatedEventListener("change",'#image-upload input:not([type="color"])',this.changeHandler.bind(this)),$("#modal").addDelegatedEventListener("input","#image-upload input#color",this.changeHandler.bind(this))}async renderModal(){await template.loadTemplate("image"),gui.modal.resetModal(),gui.modal.renderModal("Background-Image",template.parseTemplate("image",{value:this.image,bgValue:this.color,alphaValue:this.alpha}),""),gui.modal.showModal()}changeHandler(t,e){"color"===e.id?this.color=e.value:"alphaValue"===e.id?this.alpha=e.value:(pConf.set("bgMode",e.id),"image"===e.id?(e.files[0].toBase64((t,e)=>{e?alert("Error converting image!"):(pConf.set("bgURL",t.currentTarget.result),pConf.save())}),this.image=URL.createObjectURL(e.files[0])):(this.image=e.value,pConf.set("bgURL",this.image))),pConf.set("bgColour",this.color),pConf.set("alphaValue",this.alpha),this.applyValues(),pConf.save()}applyValues(){let t=$("body");t.style.backgroundImage="url("+this.image+")",t.style.backgroundColor=this.color}getRealImage(){let t=pConf.get("bgMode"),e=pConf.get("bgURL","");if("image"===t){if(""!==e&&e.startsWith("data:image")){let t=e.split(";"),a=t.shift(),i=t.join(";").replace("base64,","");this.image=URL.createObjectURL(b64toBlob(i,a))}}else this.image=e}}const imageUploader=new ImageUploader;class NotificationHandler{static instance=new NotificationHandler;constructor(){this.outer=$(".notification"),this.notifications=[]}async init(){await template.loadTemplate("notification")}static createNotification(t,e,a){a=parseInt(a||"3000");let i=NotificationHandler.instance,s=new Notification(t,e,a);return i.notifications.push(s),s.show(),s}}class Notification{constructor(t,e,a){this.outer=NotificationHandler.instance.outer,this.message=t,this.type=e,this.time=a,this.isRemoved=!1}async show(){let t=this,e=-1===t.time;t.item=create("div"),t.item.addClass("notification-item, "+t.type),e&&(t.type+=" endless"),t.updateContent(t.message),this.outer.appendChild(t.item),e||setTimeout(this.remove.bind(this),t.time)}async remove(){if(this.isRemoved)return;this.isRemoved=!0,this.outer.removeChild(this.item);let t=NotificationHandler.instance.notifications,e=t.indexOf(this);t.splice(e,1)}updateContent(t){let e={message:t,time:-1===this.time?1e3:this.time+1,type:this.type};this.item.innerHTML=template.parseTemplate("notification",e)}updateMessageOnly(t){let e=$(".message",this.item);e&&(e.innerHTML=t)}}class Config{static allConfigs={};constructor(t){this.config={},this.name="",this.type=t,Config.allConfigs[t]=this}loadConfigByName(t){this.save(),this.name="config-"+t;let e=localStorage.getItem(this.name);e&&(this.config=JSON.parse(e))}save(){""!==this.name&&localStorage.setItem(this.name,JSON.stringify(this.config))}set(t,e){this.config[t]=e}remove(t){delete this.config[t]}get(t,e){let a=this.config[t];return null==a&&(this.config[t]=e,a=e),a}reset(){NotificationHandler.createNotification("CONFIG REQUEST SUCCESS FOR "+this.type,"success",2e3),this.config={},this.save()}}class Sphere extends Visual{constructor(){super(),this.name="Sphere"}draw(){}setup(){}}class Wave extends Visual{constructor(){super(),this.name="3D Wave"}updateData(){let t=audioHandler.getFloatArray(),e=2/t.length,a=-1,i=0;for(let s=0;s{player.playlist.renderPagination(player.playlist.page),gui.modal.showModal()}),t.addDelegatedEventListener("click",".playlist-item",(t,e)=>{let a=e.dataset.index;player.playByID(parseInt(a)),togglePlayButton("pause")}),t.addDelegatedEventListener("click",".controls button",(t,e)=>{switch(e.id){case"previous":player.prevSong();break;case"next":player.nextSong();break;case"play":player.playStop();break;case"shuffle":player.playlist.isShuffle=!player.playlist.isShuffle,toggleShuffle()}togglePlayButton(audioHandler.audioFile.paused?"play":"pause")}),window.addEventListener("playSong",setActiveOnPlaylist),$(".upload-image").addEventListener("click",imageUploader.renderModal.bind(imageUploader)),t.addDelegatedEventListener("click",".readAll",t=>{let e=player.playlist.list;for(let t=0;t{$(".current",e.parentNode).innerText=e.value}),t.addDelegatedEventListener("input",'input[type="color"]',(t,e)=>{$(".colorBlob",e.parentNode).style.backgroundColor=e.value}),t.addDelegatedEventListener("click",".visual-item",(t,e)=>{visual.switch(e.dataset.id||"wave"),$("modal-content .visuals .active").removeClass("active"),e.addClass("active")}),t.addDelegatedEventListener("input","section.base input",(t,e)=>{"checkbox"===e.type?pConf.set(e.name,e.checked):setValue(e.name,e.value,pConf,e.dataset.type),pConf.save()}),t.addDelegatedEventListener("input","section.visual input",(t,e)=>{"checkbox"===e.type?vConf.set(e.name,e.checked):setValue(e.name,e.value,vConf,e.dataset.type),vConf.save()}),t.addDelegatedEventListener("click",".button[data-action]",(t,e)=>{switch(e.dataset.action){case"resetVConf":vConf.reset(),setTimeout(t=>{playerConf.handleById()},30);break;case"makeModalTransparent":$("#modal").toggleClass("lightMode")}})}function setValue(t,e,a,i){switch(i){case"float":e=parseFloat(e);break;case"int":e=parseInt(e)}a.set(t,e)}function setActiveOnPlaylist(t){let e=$('.playlist-item[data-index="'+player.playlist.index+'"]'),a=$(".playlist-item.active");a&&a.removeClass("active"),e&&e.addClass("active")}function toggleShuffle(){let t=player.playlist.isShuffle;$("#shuffle").toggleCheck("active",t);let e=t?"enabled":"disabled";NotificationHandler.createNotification("Shuffle: "+e,"info",500)}function togglePlayButton(t){$$("#play .icon").forEach(e=>{e.dataset.name===t?e.removeClass("hide"):e.addClass("hide")})}!function(){const t=$("body");t.addDelegatedEventListener("click","custom-select .label",(t,e)=>{let a=e.parentNode,i=$$("custom-option",a),s=$("custom-options",a);if(a.hasClass("open"))s.style.maxHeight="",a.removeClass("open");else{let t=0;i.forEach((function(e){t+=e.offsetHeight})),s.style.maxHeight=t+"px",a.addClass("open")}}),t.addDelegatedEventListener("click","custom-select custom-option",(t,e)=>{let a=e.closest("custom-select"),i=$("input",a);$$("custom-option.active").forEach(t=>{t.removeClass("active")}),e.addClass("active"),i&&(i.value=e.dataset.value||e.innerText,$(".label",a).innerText=e.innerText,a.removeClass("open"),e.parentNode.style.maxHeight="",window.dispatchEvent(new CustomEvent("selectChanged",{detail:{select:a,event:a.dataset.event,value:i.value,name:i.name}})))}),window.addEventListener("selectChanged",t=>{"visualConf"===t.detail.event&&(t.preventDefault(),t.stopPropagation(),function(t){try{let e=t.value,a=Config.allConfigs[t.select.dataset.conf];"fftSize"===t.name&&(e=parseInt(t.value),visual.visuals[visual.c].updateFFT(e)),a.set(t.name,e),a.save()}catch(t){console.error(t)}}(t.detail))})}();class Startup{constructor(){this.modules={startup:!1,"id3-ready":!1}}moduleLoaded(t){this.modules[t]=!0,this.allModulesLoaded()}allModulesLoaded(){for(let t in this.modules)if(!this.modules[t])return!1;return window.dispatchEvent(new CustomEvent("startupFin")),!0}}const shaderHandler=new ShaderHandler(null),audioHandler=new AudioHandler,gui=new GUI,visual=new VisualDrawer,template=new Template,player=new Player,vConf=new Config("visual"),pConf=new Config("player"),worker=new Worker("/out/js/worker.min.js"),startup=new Startup,eventHandler=new EventHandler,playerConf=new PlayerConfigHandler;let c,gl,cInfo,ctx;async function startUP(){if(pConf.loadConfigByName("default"),c=$("#c"),gl=c.getContext("webgl2"),cInfo=$("#cInfo"),ctx=cInfo.getContext("2d"),!gl)return alert("SORRY THE BROWSER DOESN'T SUPPORT WEBGL2"),!1;shaderHandler.setGL(gl),await shaderHandler.loadArray(["wave","sphere","water","wave2d"],"shaders/"),await NotificationHandler.instance.init(),await audioHandler.init(),await player.init(),await visual.init(),await gui.init(),await imageUploader.init(),await playerConf.init(),await initHandler()}worker.addEventListener("message",t=>{"startup"!==t.data.status?eventHandler.handleEvent(t):startup.moduleLoaded(t.data.cmd)}),window.addEventListener("startupFin",t=>{setTimeout(t=>{$(".loading-screen").remove()},100)}),startUP().then(t=>{startup.moduleLoaded("startup")}); \ No newline at end of file +class VTUtils{static random(e,t){let a=Math.random();if(void 0===e)return a;if(void 0===t)return e instanceof Array?e[Math.floor(a*e.length)]:a*e;if(e>t){let a=e;e=t,t=a}return a*(t-e)+e}static randomInt(e,t){return Math.floor(VTUtils.random(e,t))}static normalize(e,t,a){return(e-a)/(t-a)}static distance(e,t,a,i){let s=e-a,n=t-i;return Math.sqrt(s*s+n*n)}static map(e,t,a,i,s,n){let l=(e-t)/(a-t)*(s-i)+i;return n?i>16&255)/255,(t>>8&255)/255,(255&t)/255]}Node.prototype.addDelegatedEventListener=function(e,t,a){this.addEventListener(e,e=>{let i=e.target;if(i.matches(t))a(e,i);else{let s=i.closest(t);if(s)try{a(e,s)}catch(e){NotificationHandler.createNotification("FATAL ERROR WITHIN HANDLER!","error",1e3)}}})},Node.prototype.hasClass=function(e){let t=e.split(","),a=null;for(let e of t){if(!1===a)break;a=this.classList.contains(e.trim())}return!0===a},Node.prototype.addClass=function(e){let t=e.split(",");for(let e of t)this.classList.add(e.trim());return this},Node.prototype.removeClass=function(e){let t=e.split(",");for(let e of t)this.classList.remove(e.trim());return this},Node.prototype.toggleClass=function(e,t){let a=e.split(",");for(let e of a)this.classList.toggle(e.trim(),t)},Node.prototype.switchClass=function(e,t,a){let i=this.classList;a?(i.remove(e),i.add(t)):(i.remove(t),i.add(e))},Node.prototype.toggleCheck=function(e,t){let a=this.classList,i=e.split(",");for(let e of i){let i=e.trim();t?a.add(i):a.remove(i)}},String.prototype.firstUpper=function(){return this.charAt(0).toUpperCase()+this.slice(1)},File.prototype.toBase64=function(e){const t=new FileReader;t.onloadend=e,t.readAsDataURL(this)};class TDUtils{static lastMatrix={m:null};static multiply(e,t){let a=t[0],i=t[1],s=t[2],n=t[3],l=t[4],r=t[5],o=t[6],d=t[7],c=t[8],h=t[9],u=t[10],p=t[11],g=t[12],f=t[13],m=t[14],y=t[15],v=e[0],C=e[1],w=e[2],T=e[3],S=e[4],b=e[5],H=e[6],x=e[7],U=e[8],D=e[9],A=e[10],E=e[11],L=e[12],R=e[13],F=e[14],M=e[15];return[a*v+i*S+s*U+n*L,a*C+i*b+s*D+n*R,a*w+i*H+s*A+n*F,a*T+i*x+s*E+n*M,l*v+r*S+o*U+d*L,l*C+r*b+o*D+d*R,l*w+r*H+o*A+d*F,l*T+r*x+o*E+d*M,c*v+h*S+u*U+p*L,c*C+h*b+u*D+p*R,c*w+h*H+u*A+p*F,c*T+h*x+u*E+p*M,g*v+f*S+m*U+y*L,g*C+f*b+m*D+y*R,g*w+f*H+m*A+y*F,g*T+f*x+m*E+y*M]}static translate(e,t,a,i,s){s=s||new Float32Array(16);let n=e[0],l=e[1],r=e[2],o=e[3],d=e[4],c=e[5],h=e[6],u=e[7],p=e[8],g=e[9],f=e[10],m=e[11],y=e[12],v=e[13],C=e[14],w=e[15];return s[0]=n,s[1]=l,s[2]=r,s[3]=o,s[4]=d,s[5]=c,s[6]=h,s[7]=u,s[8]=p,s[9]=g,s[10]=f,s[11]=m,s[12]=n*t+d*a+p*i+y,s[13]=l*t+c*a+g*i+v,s[14]=r*t+h*a+f*i+C,s[15]=o*t+u*a+m*i+w,s}static xRotation(e){e=TDUtils.degToRad(e);let t=Math.cos(e),a=Math.sin(e);return[1,0,0,0,0,t,a,0,0,-a,t,0,0,0,0,1]}static yRotation(e){e=TDUtils.degToRad(e);let t=Math.cos(e),a=Math.sin(e);return[t,0,-a,0,0,1,0,0,a,0,t,0,0,0,0,1]}static zRotation(e){e=TDUtils.degToRad(e);let t=Math.cos(e),a=Math.sin(e);return[t,a,0,0,-a,t,0,0,0,0,1,0,0,0,0,1]}static degToRad(e){return e*Math.PI/180}static scale(e,t,a,i){return(i=i||new Float32Array(16))[0]=e,i[5]=t,i[10]=a,i}static lookAt(e,t,a,i){i=i||new Float32Array(16);let s=TDUtils.normalize(TDUtils.subtractVectors(e,t)),n=TDUtils.normalize(TDUtils.cross(a,s)),l=TDUtils.normalize(TDUtils.cross(s,n));return i[0]=n[0],i[1]=n[1],i[2]=n[2],i[4]=l[0],i[5]=l[1],i[6]=l[2],i[8]=s[0],i[9]=s[1],i[10]=s[2],i[12]=e[0],i[13]=e[1],i[14]=e[2],i[15]=1,i}static cross(e,t,a){return(a=a||new Float32Array(3))[0]=e[1]*t[2]-e[2]*t[1],a[1]=e[2]*t[0]-e[0]*t[2],a[2]=e[0]*t[1]-e[1]*t[0],a}static normalize(e,t){t=t||new Float32Array(3);let a=Math.sqrt(e[0]*e[0]+e[1]*e[1]+e[2]*e[2]);return a>1e-5&&(t[0]=e[0]/a,t[1]=e[1]/a,t[2]=e[2]/a),t}static subtractVectors(e,t,a){return(a=a||new Float32Array(3))[0]=e[0]-t[0],a[1]=e[1]-t[1],a[2]=e[2]-t[2],a}static perspective(e,t,a,i,s){s=s||new Float32Array(16);let n=Math.tan(.5*Math.PI-.5*e),l=1/(a-i);return s[0]=n/t,s[5]=n,s[10]=(a+i)*l,s[11]=-1,s[14]=a*i*l*2,s}static inverse(e,t){t=t||new Float32Array(16);let a=e[0],i=e[1],s=e[2],n=e[3],l=e[4],r=e[5],o=e[6],d=e[7],c=e[8],h=e[9],u=e[10],p=e[11],g=e[12],f=e[13],m=e[14],y=e[15],v=u*y,C=m*p,w=o*y,T=m*d,S=o*p,b=u*d,H=s*y,x=m*n,U=s*p,D=u*n,A=s*d,E=o*n,L=c*f,R=g*h,F=l*f,M=g*r,k=l*h,I=c*r,N=a*f,P=g*i,V=a*h,$=c*i,B=a*r,O=l*i,z=v*r+T*h+S*f-(C*r+w*h+b*f),_=C*i+H*h+D*f-(v*i+x*h+U*f),G=w*i+x*r+A*f-(T*i+H*r+E*f),j=b*i+U*r+E*h-(S*i+D*r+A*h),K=1/(a*z+l*_+c*G+g*j);return t[0]=K*z,t[1]=K*_,t[2]=K*G,t[3]=K*j,t[4]=K*(C*l+w*c+b*g-(v*l+T*c+S*g)),t[5]=K*(v*a+x*c+U*g-(C*a+H*c+D*g)),t[6]=K*(T*a+H*l+E*g-(w*a+x*l+A*g)),t[7]=K*(S*a+D*l+A*c-(b*a+U*l+E*c)),t[8]=K*(L*d+M*p+k*y-(R*d+F*p+I*y)),t[9]=K*(R*n+N*p+$*y-(L*n+P*p+V*y)),t[10]=K*(F*n+P*d+B*y-(M*n+N*d+O*y)),t[11]=K*(I*n+V*d+O*p-(k*n+$*d+B*p)),t[12]=K*(F*u+I*m+R*o-(k*m+L*o+M*u)),t[13]=K*(V*m+L*s+P*u-(N*u+$*m+R*s)),t[14]=K*(N*o+O*m+M*s-(B*m+F*s+P*o)),t[15]=K*(B*u+k*s+$*o-(V*o+O*u+I*s)),t}static aspectView(e){return[1*e,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]}static getMatrix(e,t,a,i,s,n){let l=this.lastMatrix,r=TDUtils;if(!(r.isSame("fov",e)&&r.isSame("aspect",t)&&r.isSame("near",a)&&r.isSame("far",i)&&r.isSame("cam",s)&&r.isSame("radius",n))){let r=TDUtils.perspective(TDUtils.degToRad(e),t,a,i),o=TDUtils.yRotation(TDUtils.degToRad(s));o=TDUtils.translate(o,0,0,1.5*n);let d=TDUtils.inverse(o);r=TDUtils.multiply(r,d),l.m=r}return l.m}static isSame(e,t){let a=this.lastMatrix;return a[e]===t||(a[e]=t,!1)}static updateRotate(e,t){let a=vConf.get(e,t)+vConf.get(e+"-inc",0);a>360?a-=360:a<-360&&(a+=360),vConf.set(e,a)}}class Template{constructor(){this.tpl={}}async loadTemplate(e){let t=this;this.tpl[e]||await fetch(templateDir+e+".tpl").then(e=>e.text()).then(a=>{t.tpl[e]=a})}async loadArray(e){for(let t of e)await this.loadTemplate(t)}parseTemplate(e,t){if(!this.tpl[e])return"";let a,i=this.tpl[e];for(;null!==(a=templateEx.exec(i));){a.index===templateEx.lastIndex&&templateEx.lastIndex++;let e=a[0],s=t[a[1]];null==s&&(s=""),i=i.replace(e,s)}return i}parseFromAPI(e,t,a){fetch(e).then(e=>e.json()).then(e=>{a(this.parseTemplate(t,e))}).catch(console.error)}}const templateEx=/\$(.*?)\$/gm,templateDir="out/tpl/";class ShaderHandler{constructor(e){this.gl=e,this.shaderNames=[],this.shaders={},this.programs={}}setGL(e){this.gl=e}async loadShader(e,t){this.shaderNames.push(e),await this.load(e,t+e+".vert",this.gl.VERTEX_SHADER),await this.load(e,t+e+".frag",this.gl.FRAGMENT_SHADER)}async load(e,t,a){let i=e+"_"+a;if(!this.shaders[i]){let e=await fetch(t),s=this.createShader(await e.text(),a);s&&(this.shaders[i]=s)}return!!this.shaders[i]}getShader(e,t){let a=e+"_"+t;return this.shaders[a]}getAllShaders(){return this.shaderNames}async createProgramForEach(e){e=e||this.shaderNames;for(let t=0;t Currently no Song is uploaded!","error",2e3),!1;let t=this,a=e.file;t.lastSong&&URL.revokeObjectURL(t.lastSong),t.lastSong=this.audioFile.src=URL.createObjectURL(a),this.isStarted||this.start().catch(alert),this.audioFile.play().then(t=>{pConf.get("showPlaying","true")&&NotificationHandler.createNotification("Now Playing:"+e.getAudioName(),"info",pConf.get("showPlayingTime",1e3)),window.dispatchEvent(new CustomEvent("playSong"))}).catch(e=>{NotificationHandler.createNotification(e.message,"error",1e3),player.nextSong()})}getIntArray(e){let t=new Uint8Array(e);return this.analyser.getByteFrequencyData(t),t}getFloatArray(){let e=new Float32Array(this.analyser.fftSize);return this.analyser.getFloatTimeDomainData(e),e}}class AudioPlayerFile{constructor(e,t){this.file=e,this.name=this.getName(),this.id3=null,this.index=t}getName(){let e=this.file.name.split(".");return e.pop(),e=e.join("."),e}getID3Tag(e){return e||null===this.id3?(eventHandler.sendData("getData",{file:this.file,name:this.name,index:this.index,force:!0===e}),{title:this.name,artist:"VA"}):this.id3}getAudioName(){let e=this.getID3Tag();return template.parseTemplate("audio-information",e)}}class PlayerConfigHandler{async init(){await template.loadArray(["config/nav","config/content","config/visualitem"]),this.last="base",$(".settings-icon").addEventListener("click",this.open.bind(this)),$("modal-content").addDelegatedEventListener("click",".config-nav .item",this.navHandler.bind(this))}open(){if(void 0===this.content){let e=template.parseTemplate("config/nav",{});e+=template.parseTemplate("config/content",{content:""}),this.content=e}gui.modal.renderModal("Settings",this.content,"by VersusTuneZ"),this.handleById(),gui.modal.showModal()}navHandler(e,t){this.last=t.dataset.id,this.handleById()}handleById(){let e=this.last;new VisualConfig("visual"===e,"base"===e);let t=$(".config-nav .item.active"),a=$('.config-nav .item[data-id="'+e+'"]');t&&t.removeClass("active"),a&&a.addClass("active")}}class VisualConfig{static visualTemplates={};constructor(e,t){this.content=$("modal-content .config-content"),e?this.renderVisualConfig(visual.c):t?this.renderBase():this.renderVisuals()}renderVisuals(){let e=Object.keys(visual.visuals),t='
';for(let a=0;a",this.content.innerHTML=t}async renderBase(){let e=await this.loadVisualConfig("base"),t=create("section");t.addClass("base"),t.innerHTML=GUIHelper.fromJSON(e,pConf),this.content.innerHTML=t.outerHTML}async renderVisualConfig(e){let t=await this.loadVisualConfig(e,vConf),a=create("section");a.addClass("visual"),a.innerHTML=GUIHelper.fromJSON(t,vConf),a.innerHTML+=GUIHelper.createButton({action:"resetVConf",name:"Reset Visual Config"}),a.innerHTML+=GUIHelper.createButton({action:"makeModalTransparent",name:"toggle Modal Opacity"}),this.content.innerHTML=a.outerHTML}async loadVisualConfig(e){let t=VisualConfig.visualTemplates;return t[e]||(t[e]=await fetch("/out/gui/"+e+".json").then(e=>e.json())),t[e]}}class Player{async init(){this.playlist=new Playlist}nextSong(){let e=this.playlist.getNext();audioHandler.loadSong(e)}prevSong(){let e=this.playlist.getPrevious();audioHandler.loadSong(e)}playStop(){if(!audioHandler.lastSong){let e=this.playlist.getCurrent();return void audioHandler.loadSong(e)}let e=audioHandler.audioFile;e.paused?e.play():e.pause(),window.dispatchEvent(new CustomEvent("playSong"))}playByID(e){this.playlist.index=e;let t=this.playlist.getCurrent();audioHandler.loadSong(t)}}const PAGINATIONLIMIT=50;class Playlist{constructor(){this.list=[],this.shuffled=[],this.index=0,this.page=0,this.isShuffle=pConf.get("shuffle",!1),$("body").addDelegatedEventListener("change",'input[type="file"]',this.changeFiles.bind(this)),$("body").addDelegatedEventListener("click",".pagination .item",this.handlePagination.bind(this)),eventHandler.addEvent("id3-request",this.handle.bind(this)),eventHandler.addEvent("id3-request-force",this.forceID3.bind(this))}shuffle(){let e=this.list.length;if(e<3)this.shuffled=[0,1,2];else for(let t=0;tt&&(a=0),this.index=a,e[this.getRealIndex()]}getPrevious(){let e=this.list,t=e.length-1,a=this.index-1;return a<0&&(a=t),this.index=a,e[this.getRealIndex()]}getCurrent(){return this.list[this.getRealIndex()]}setPlaylist(e){this.index=0,this.forceData=void 0,this.list=e,this.shuffle()}handlePagination(e,t){t.hasClass("inactive")||(t.hasClass("next-site")?this.renderPagination(this.page+1):this.renderPagination(this.page-1))}renderPagination(e){void 0===e&&(e=this.page);let t=this.list.length,a=Math.ceil(t/50)-1;e<0&&(e=0),e>a&&(e=a);let i=50*e,s=i+50,n="";if(this.page=e,s>=t&&(s=t),t>0){let e=this.list;for(let t=i;t1&&e0?"active":"inactive",nextActive:l?"active":"inactive",page:e+1+" / "+parseInt(a+1)}))}changeFiles(e,t){if("upload-dir"!==t.id)return;let a=[],i=0;for(let e of t.files)if(e&&-1!==e.type.indexOf("audio")&&null===e.name.match(".m3u")){let t=new AudioPlayerFile(e,i++);a.push(t)}this.setPlaylist(a),a.length>0?(NotificationHandler.createNotification("Songs added successfully!
Songs: "+a.length,"success",3e3),this.renderPagination(0)):NotificationHandler.createNotification("File Upload failed!","error",3e3)}getRealIndex(e){return void 0===e&&(e=this.index),this.isShuffle?this.shuffled[e]:e}handle(e){let t=e.index;"waiting"!==e.status&&(this.list[t].id3=e,this.timeout&&window.clearTimeout(this.timeout),this.timeout=setTimeout(this.renderPagination.bind(this),100))}forceID3(e){let t=this;t.forceData||(t.forceData={},t.forceNotification=NotificationHandler.createNotification("TagReader -> 0 / "+t.list.length,"info",-1));let a=e.index;if("waiting"===e.status)return;t.list[a].id3=e,t.forceData[a]=!0;let i=Object.keys(t.forceData).length;this.forceNotification.updateMessageOnly("TagReader -> "+i+" / "+t.list.length),i===t.list.length&&t.forceNotification.remove()}}class GUI{async init(){this.data={},this.modal=new Modal,await template.loadArray(["playlist-item","playlist","playlist-footer","audio-information","inputs/color","inputs/input","inputs/slider","inputs/switch","inputs/select","inputs/option","help"]),this.initDropZone()}openHelp(){gui.modal.renderModal("Help",template.parseTemplate("help",{})),gui.modal.showModal()}initDropZone(){"drag dragstart dragend dragover dragenter dragleave drop".split(" ").forEach(e=>{window.addEventListener(e,async e=>{e.preventDefault(),e.stopPropagation(),"drop"===e.type&&(e.dataTransfer.files.length>0?(e.dataTransfer.id="upload-dir",player.playlist.changeFiles(e,e.dataTransfer)):alert("Sorry you need to upload files!"))})})}}class GUIHelper{static fromJSON(e,t){let a=[];for(let i of e)switch(i.type){case"slider":a.push(GUIHelper.createSliders(i,t));break;case"color":a.push(GUIHelper.createColorPicker(i,t));break;case"checkbox":a.push(GUIHelper.createCheckbox(i,t));break;case"input":a.push(GUIHelper.createInputField(i,t));break;case"select":a.push(GUIHelper.createSelect(i,t));break;case"button":a.push(GUIHelper.createButton(i,t));break;default:console.error("Unknown Type: "+i.type)}return a.join(" ")}static createSliders(e,t){let a="";if("object"==typeof e.name)for(let i=0;i${e.name}`}}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(e,t,a){$("#modal").removeClass("lightMode"),this.currentModal=e,this.renderHeader(e),this.renderContent(t),this.renderFooter(a)}renderHeader(e){$("header .headline",this.modal).innerHTML=e}renderContent(e){$("modal-content",this.modal).innerHTML=e}renderFooter(e){$("modal-footer .inner",this.modal).innerHTML=e||"by VersusTuneZ"}closeModal(){this.parent.addClass("hide")}isCurrent(e){return e===this.currentModal}showModal(){this.parent.removeClass("hide")}}class Visual{constructor(){this.data=[],this.dataArray=[],this.name="Default"}updateData(){}updateFFT(e){}draw(){}setup(){}}class VisualDrawer{constructor(){this.visuals={wave:new Wave,wave2d:new Wave2D},this.lastMainColor={base:"#-1",color:[0,0,0]},this.lastSecondColor={base:"#-1",color:[0,0,0]}}init(){this.switch(pConf.get("visual","wave2d")),this.updateLoop()}switch(e){null!=this.visuals[e]&&(this.c=e,vConf.loadConfigByName(this.c),this.visuals[this.c].setup(),pConf.set("visual",this.c),pConf.save())}updateLoop(){let e=this.visuals[this.c],t=shaderHandler.use(this.c);this.updateSeekbar(),this.prepare(t),e.updateData(),e.draw(t),requestAnimationFrame(this.updateLoop.bind(this))}updateSeekbar(){cInfo.width=window.innerWidth,cInfo.height=window.innerHeight;let e=audioHandler.audioFile;if(ctx.clearRect(0,0,cInfo.width,cInfo.height),!e.paused&&pConf.get("showSeekbar",!0)){let t=e.duration,a=e.currentTime/t*cInfo.width;ctx.fillStyle=pConf.get("seekColor","#fff"),ctx.fillRect(0,c.height-10,a,c.height)}}prepare(e){c.width=window.innerWidth,c.height=window.innerHeight,gl.viewport(0,0,gl.canvas.width,gl.canvas.height),gl.clearColor(0,0,0,parseFloat(pConf.get("alphaValue",0))),gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT),gl.enable(gl.DEPTH_TEST),gl.depthFunc(gl.LEQUAL),gl.enable(gl.CULL_FACE),this.setColor(e)}setColor(e){let t=gl.getUniformLocation(e,"u_baseColor"),a=gl.getUniformLocation(e,"u_maxColor"),i=this.lastMainColor,s=this.lastSecondColor;this.updateColor("lastMainColor","baseColor"),this.updateColor("lastSecondColor","gradientToColor"),gl.uniform3fv(t,i.color),gl.uniform3fv(a,s.color)}updateColor(e,t){let a=this[e],i=vConf.get(t,"#ffffff");i!==a.base&&(a.color=hexToRgb(i),a.base=i)}}class ImageUploader{async init(){this.image=pConf.get("bgURL",""),this.color=pConf.get("bgColour","#000000"),this.alpha=pConf.get("alphaValue",.5),this.getRealImage(),this.applyValues(),$("#modal").addDelegatedEventListener("change",'#image-upload input:not([type="color"])',this.changeHandler.bind(this)),$("#modal").addDelegatedEventListener("input","#image-upload input#color",this.changeHandler.bind(this))}async renderModal(){await template.loadTemplate("image"),gui.modal.resetModal(),gui.modal.renderModal("Background-Image",template.parseTemplate("image",{value:this.image,bgValue:this.color,alphaValue:this.alpha}),""),gui.modal.showModal()}changeHandler(e,t){"color"===t.id?this.color=t.value:"alphaValue"===t.id?this.alpha=t.value:(pConf.set("bgMode",t.id),"image"===t.id?(t.files[0].toBase64((e,t)=>{t?alert("Error converting image!"):(pConf.set("bgURL",e.currentTarget.result),pConf.save())}),this.image=URL.createObjectURL(t.files[0])):(this.image=t.value,pConf.set("bgURL",this.image))),pConf.set("bgColour",this.color),pConf.set("alphaValue",this.alpha),this.applyValues(),pConf.save()}applyValues(){let e=$("body");e.style.backgroundImage="url("+this.image+")",e.style.backgroundColor=this.color}getRealImage(){let e=pConf.get("bgMode"),t=pConf.get("bgURL","");if("image"===e){if(""!==t&&t.startsWith("data:image")){let e=t.split(";"),a=e.shift(),i=e.join(";").replace("base64,","");this.image=URL.createObjectURL(b64toBlob(i,a))}}else this.image=t}}const imageUploader=new ImageUploader;class NotificationHandler{static instance=new NotificationHandler;constructor(){this.outer=$(".notification"),this.notifications=[]}async init(){await template.loadTemplate("notification")}static createNotification(e,t,a){a=parseInt(a||"3000");let i=NotificationHandler.instance,s=new Notification(e,t,a);return i.notifications.push(s),s.show(),s}}class Notification{constructor(e,t,a){this.outer=NotificationHandler.instance.outer,this.message=e,this.type=t,this.time=a,this.isRemoved=!1}async show(){let e=this,t=-1===e.time;e.item=create("div"),e.item.addClass("notification-item, "+e.type),t&&(e.type+=" endless"),e.updateContent(e.message),this.outer.prepend(e.item),t||setTimeout(this.remove.bind(this),e.time)}async remove(){if(this.isRemoved)return;this.isRemoved=!0,this.outer.removeChild(this.item);let e=NotificationHandler.instance.notifications,t=e.indexOf(this);e.splice(t,1)}updateContent(e){let t={message:e,time:-1===this.time?1e3:this.time+1,type:this.type};this.item.innerHTML=template.parseTemplate("notification",t)}updateMessageOnly(e){let t=$(".message",this.item);t&&(t.innerHTML=e)}}class Config{static allConfigs={};constructor(e){this.config={},this.name="",this.type=e,Config.allConfigs[e]=this}loadConfigByName(e){this.save(),this.name="config-"+e;let t=localStorage.getItem(this.name);t&&(this.config=JSON.parse(t))}save(){""!==this.name&&localStorage.setItem(this.name,JSON.stringify(this.config))}set(e,t){this.config[e]=t}remove(e){delete this.config[e]}get(e,t){let a=this.config[e];return null==a&&(this.config[e]=t,a=t),a}reset(){NotificationHandler.createNotification("CONFIG REQUEST SUCCESS FOR "+this.type,"success",2e3),this.config={},this.save()}}class Sphere extends Visual{constructor(){super(),this.name="Sphere"}draw(){}setup(){}}class Wave extends Visual{constructor(){super(),this.name="3D Wave"}updateData(){let e=audioHandler.getFloatArray(),t=2/e.length,a=-1,i=0;for(let s=0;s "+e.message)}return!0}return!1}}async function initHandler(){let e=$("body");$(".playlist.menu-icon").addEventListener("click",e=>{player.playlist.renderPagination(player.playlist.page),gui.modal.showModal()}),e.addDelegatedEventListener("click",".playlist-item",(e,t)=>{let a=t.dataset.index;player.playByID(parseInt(a)),togglePlayButton("pause")}),e.addDelegatedEventListener("click",".controls button",(e,t)=>{switch(t.id){case"previous":player.prevSong();break;case"next":player.nextSong();break;case"play":player.playStop();break;case"shuffle":toggleShuffle()}}),window.addEventListener("playSong",setActiveOnPlaylist),window.addEventListener("playSong",e=>{togglePlayButton(audioHandler.audioFile.paused?"play":"pause")}),$(".upload-image").addEventListener("click",imageUploader.renderModal.bind(imageUploader)),e.addDelegatedEventListener("click",".readAll",forceAllRead),e.addDelegatedEventListener("input",'.input-range input[type="range"]',(e,t)=>{$(".current",t.parentNode).innerText=t.value}),e.addDelegatedEventListener("input",'input[type="color"]',(e,t)=>{$(".colorBlob",t.parentNode).style.backgroundColor=t.value}),e.addDelegatedEventListener("click",".visual-item",(e,t)=>{visual.switch(t.dataset.id||"wave"),$("modal-content .visuals .active").removeClass("active"),t.addClass("active")}),e.addDelegatedEventListener("input","section.base input",(e,t)=>{"checkbox"===t.type?pConf.set(t.name,t.checked):setValue(t.name,t.value,pConf,t.dataset.type),pConf.save()}),e.addDelegatedEventListener("input","section.visual input",(e,t)=>{"checkbox"===t.type?vConf.set(t.name,t.checked):setValue(t.name,t.value,vConf,t.dataset.type),vConf.save()}),e.addDelegatedEventListener("click",".button[data-action]",(e,t)=>{switch(t.dataset.action){case"resetVConf":vConf.reset(),setTimeout(e=>{playerConf.handleById()},30);break;case"makeModalTransparent":$("#modal").toggleClass("lightMode")}}),$(".help.menu-icon").addEventListener("click",gui.openHelp),document.onfullscreenchange=t=>{e.hasClass("fullscreen")?e.removeClass("fullscreen"):e.addClass("fullscreen")}}function forceAllRead(){let e=player.playlist.list;for(let t=0;t{t.dataset.name===e?t.removeClass("hide"):t.addClass("hide")})}!function(){const e=$("body");e.addDelegatedEventListener("click","custom-select .label",(e,t)=>{let a=t.parentNode,i=$$("custom-option",a),s=$("custom-options",a);if(a.hasClass("open"))s.style.maxHeight="",a.removeClass("open");else{let e=0;i.forEach((function(t){e+=t.offsetHeight})),s.style.maxHeight=e+"px",a.addClass("open")}}),e.addDelegatedEventListener("click","custom-select custom-option",(e,t)=>{let a=t.closest("custom-select"),i=$("input",a);$$("custom-option.active").forEach(e=>{e.removeClass("active")}),t.addClass("active"),i&&(i.value=t.dataset.value||t.innerText,$(".label",a).innerText=t.innerText,a.removeClass("open"),t.parentNode.style.maxHeight="",window.dispatchEvent(new CustomEvent("selectChanged",{detail:{select:a,event:a.dataset.event,value:i.value,name:i.name}})))}),window.addEventListener("selectChanged",e=>{"visualConf"===e.detail.event&&(e.preventDefault(),e.stopPropagation(),function(e){try{let t=e.value,a=Config.allConfigs[e.select.dataset.conf];"fftSize"===e.name&&(t=parseInt(e.value),visual.visuals[visual.c].updateFFT(t)),a.set(e.name,t),a.save()}catch(e){console.error(e)}}(e.detail))})}();class KeyHandler{async init(){await this.mediaKeys(),await this.addKeyHandler(),window.addEventListener("keydown",this.keyHandler.bind(this))}async mediaKeys(){if("mediaSession"in navigator){let e=navigator.mediaSession;e.setActionHandler("play",player.playStop.bind(player)),e.setActionHandler("pause",player.playStop.bind(player)),e.setActionHandler("previoustrack",player.prevSong.bind(player)),e.setActionHandler("nexttrack",player.prevSong.bind(player))}}async addKeyHandler(){eventHandler.addEvent("keys-Space",player.playStop.bind(player)),eventHandler.addEvent("keys-KeyN",player.nextSong.bind(player)),eventHandler.addEvent("keys-KeyV",player.prevSong.bind(player)),eventHandler.addEvent("keys-KeyS",playerConf.open.bind(playerConf)),eventHandler.addEvent("keys-KeyS-shift",toggleShuffle),eventHandler.addEvent("keys-KeyB",imageUploader.renderModal.bind(imageUploader)),eventHandler.addEvent("keys-KeyF-shift",forceAllRead),eventHandler.addEvent("keys-KeyH",gui.openHelp),eventHandler.addEvent("keys-KeyP",e=>{player.playlist.renderPagination(player.playlist.page),gui.modal.showModal()}),eventHandler.addEvent("keys-Escape, keys-KeyC-shift",e=>{gui.modal.resetModal(),gui.modal.closeModal()}),eventHandler.addEvent("keys-F11",e=>{document.fullscreenElement?document.exitFullscreen().catch(console.error):document.body.requestFullscreen().catch(console.error)})}async keyHandler(e){let t="keys-"+e.code+(e.shiftKey?"-shift":"")+(e.ctrlKey?"-ctrl":"");eventHandler.handleEvent({data:{cmd:t}})&&(e.preventDefault(),e.stopPropagation())}}class Startup{constructor(){this.modules={startup:!1,"id3-ready":!1}}moduleLoaded(e){this.modules[e]=!0,this.allModulesLoaded()}allModulesLoaded(){for(let e in this.modules)if(!this.modules[e])return!1;return window.dispatchEvent(new CustomEvent("startupFin")),!0}}const shaderHandler=new ShaderHandler(null),audioHandler=new AudioHandler,gui=new GUI,visual=new VisualDrawer,template=new Template,player=new Player,vConf=new Config("visual"),pConf=new Config("player"),worker=new Worker("/out/js/worker.min.js"),startup=new Startup,eventHandler=new EventHandler,playerConf=new PlayerConfigHandler,keyHandler=new KeyHandler;let c,gl,cInfo,ctx;async function startUP(){if(pConf.loadConfigByName("default"),c=$("#c"),gl=c.getContext("webgl2"),cInfo=$("#cInfo"),ctx=cInfo.getContext("2d"),!gl)return alert("SORRY THE BROWSER DOESN'T SUPPORT WEBGL2"),!1;shaderHandler.setGL(gl),await shaderHandler.loadArray(["wave","sphere","water","wave2d"],"shaders/"),await NotificationHandler.instance.init(),await audioHandler.init(),await player.init(),await visual.init(),await gui.init(),await imageUploader.init(),await playerConf.init(),await keyHandler.init(),await initHandler(),toggleShuffle(!1)}worker.addEventListener("message",e=>{"startup"!==e.data.status?eventHandler.handleEvent(e):startup.moduleLoaded(e.data.cmd)}),window.addEventListener("startupFin",e=>{setTimeout(e=>{$(".loading-screen").remove()},100)}),startUP().then(e=>{startup.moduleLoaded("startup")}); \ No newline at end of file diff --git a/out/theme/style.css b/out/theme/style.css index c24237e..474fa50 100644 --- a/out/theme/style.css +++ b/out/theme/style.css @@ -1 +1 @@ -@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;width:100vw;height:100vh}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}canvas{position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none}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;z-index:100}.grey-screen.hide{display:none!important}modal-footer playlist{flex-direction:row-reverse;flex-wrap:wrap}modal-footer playlist .pagination{margin-left:auto}#image-upload form{display:flex;flex-direction:column;width:90%;margin:10px auto}.audio-item{display:flex;flex-direction:column}.audio-item .artist{font-size:.75em;color:#dcdcdc}.now-playing{font-size:.8em;display:block;margin-bottom:10px}.menus{z-index:10}.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:100%;position:relative;display:inline-block;margin-top:1rem;background-color:transparent}.input-range{margin-bottom:20px}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:#5ff507;font-size:.7rem}.floating-label input:valid~.input-label{transform:translateY(-.72rem);color:#ff0089;font-size:.7rem}.focus{content:'';width:0;background-color:#ff0089;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{display:flex;flex-direction:row;padding:5px}switch input{position:absolute;appearance:none;opacity:0}switch input:checked+label:after{transform:translateX(20px)}switch span{margin-left:10px}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:#3949ab;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{text-align:center;user-select:none;border-radius:5px;border:1px solid #3949ab;padding:.5em 1em;cursor:pointer;transition:.5s}.button.spaced{margin-bottom:10px}.button:hover{border-color:#ff0089;border-radius:7px;box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23)}.input .current,.input .max,.input .min{font-size:.8em;color:#ff0089;position:absolute;bottom:-1rem}.input .current{display:block;width:100%;text-align:center}.input .min{left:0}.input .max{right:0}custom-select{width:auto;min-width:200px;display:block}custom-select label{color:#ff0089;font-size:.7rem}custom-select input{display:none}custom-select .label{padding:10px 30px 10px 10px;cursor:pointer;position:relative;transition:all .3s;box-shadow:inset 0 0 3px #5e5e5e}custom-select .label::after{content:'';border:solid #dcdcdc;border-width:0 3px 3px 0;display:inline-block;padding:3px;transform:rotate(45deg);-webkit-transform:rotate(45deg);transition:all .3s ease-in-out;position:absolute;right:10px;top:calc(50% - 6px)}custom-select custom-options{max-height:0;overflow:hidden;transition:all .5s ease;font-size:.9em;display:block;border:1px solid transparent}custom-select custom-option{display:block;width:100%;padding:10px 5px;cursor:pointer;box-shadow:0 -1px 0 0 rgba(0,0,0,.08)}custom-select custom-option:hover{background-color:#3949ab;color:#fff}custom-select custom-option.active{font-weight:700}custom-select.open .label{background-color:rgba(0,0,0,.1)}custom-select.open .label::after{transform:rotate(-135deg);-webkit-transform:rotate(-135deg);top:calc(50% - 3px)}custom-select.open custom-options{max-height:none;border:1px solid #1b1b1b!important}.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;display:flex;align-items:center}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.lightMode{background-color:rgba(0,0,0,.1)}#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)}#modal modal-footer .inner{padding:5px;width:calc(100% - 40px);margin:0 auto}.notification{right:10px;top:0;height:100%;display:flex;flex-direction:column-reverse;width:90%;max-width:400px;pointer-events:none}.notification .notification-item{margin-top:10px}.notification div{position:unset;padding:unset}.notification div.notification-item{position:relative}.notification-item{width:100%;border-radius:5px;box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23);overflow:hidden}.notification-item.success{background-color:rgba(74,177,11,.6)}.notification-item.error{background-color:rgba(255,50,50,.6)}.notification-item.warning{background-color:rgba(255,177,89,.6)}.notification-item.info{background-color:rgba(71,73,171,.6)}.notification-item .message{padding:1em}.notification-item .fade-bar{animation:fadeOut ease-in-out 3s;height:100%;width:100%;position:absolute;top:0;z-index:-1;opacity:.4;transform-origin:left}.notification-item .fade-bar:after{content:'';z-index:1;bottom:0;height:4px;width:100%;position:absolute;background-color:#fff}.notification-item .fade-bar.endless{animation:endlessFade ease-in-out .5s infinite}.notification-item .fade-bar.success{background-color:#60ff00}.notification-item .fade-bar.error{background-color:#fa0000}.notification-item .fade-bar.warning{background-color:#f70}.notification-item .fade-bar.info{background-color:#3949ab}@keyframes fadeOut{from{transform:scaleX(1)}to{transform:scaleX(0)}}@keyframes endlessFade{0%{transform:scaleX(1);transform-origin:right}49%{transform-origin:right}50%{transform:scaleX(0);transform-origin:left}100%{transform:scaleX(1)}}.config-nav{display:flex;flex-direction:row;background-color:#1b1b1b;box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23)}.config-nav div{padding:10px 5px;cursor:pointer;user-select:none;border-bottom:1px solid transparent}.config-nav div.active{border-color:#5ff507}.config-nav div:hover{background-color:rgba(0,0,0,.4)}.config-content{padding:1em;min-height:200px}.config-content .visuals{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-around}.config-content .visuals .visual-item{display:flex;align-items:center;justify-content:center;padding:1em;min-width:100px;min-height:100px;background-color:#212121;box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23);transition:.5s;cursor:pointer}.config-content .visuals .visual-item:hover{background-color:#1b1b1b}.config-content .visuals .visual-item.active{border:1px solid #3949ab} \ 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;width:100vw;height:100vh}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}canvas{position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none}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;z-index:100}.grey-screen.hide{display:none!important}modal-footer playlist{flex-direction:row-reverse;flex-wrap:wrap}modal-footer playlist .pagination{margin-left:auto}#image-upload form{display:flex;flex-direction:column;width:90%;margin:10px auto}.audio-item{display:flex;flex-direction:column}.audio-item .artist{font-size:.75em;color:#dcdcdc}.now-playing{font-size:.8em;display:block;margin-bottom:10px}.menus{z-index:10}body.fullscreen .menus{display:none}.help-list{display:flex;flex-direction:column}.help-list .item{padding:0 1em}.help-list .item:not(:last-child){border-bottom:1px solid #232323}.help-list .item .h2{font-size:1.2em;margin:10px 0 5px;display:block}.help-list .item p{font-size:.8em;color:#aaa}.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:100%;position:relative;display:inline-block;margin-top:1rem;background-color:transparent}.input-range{margin-bottom:20px}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:#5ff507;font-size:.7rem}.floating-label input:valid~.input-label{transform:translateY(-.72rem);color:#ff0089;font-size:.7rem}.focus{content:'';width:0;background-color:#ff0089;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{display:flex;flex-direction:row;padding:5px}switch input{position:absolute;appearance:none;opacity:0}switch input:checked+label:after{transform:translateX(20px)}switch span{margin-left:10px}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:#3949ab;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{text-align:center;user-select:none;border-radius:5px;border:1px solid #3949ab;padding:.5em 1em;cursor:pointer;transition:.5s}.button.spaced{margin-bottom:10px}.button:hover{border-color:#ff0089;border-radius:7px;box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23)}.input .current,.input .max,.input .min{font-size:.8em;color:#ff0089;position:absolute;bottom:-1rem}.input .current{display:block;width:100%;text-align:center}.input .min{left:0}.input .max{right:0}custom-select{width:auto;min-width:200px;display:block}custom-select label{color:#ff0089;font-size:.7rem}custom-select input{display:none}custom-select .label{padding:10px 30px 10px 10px;cursor:pointer;position:relative;transition:all .3s;box-shadow:inset 0 0 3px #5e5e5e}custom-select .label::after{content:'';border:solid #dcdcdc;border-width:0 3px 3px 0;display:inline-block;padding:3px;transform:rotate(45deg);-webkit-transform:rotate(45deg);transition:all .3s ease-in-out;position:absolute;right:10px;top:calc(50% - 6px)}custom-select custom-options{max-height:0;overflow:hidden;transition:all .5s ease;font-size:.9em;display:block;border:1px solid transparent}custom-select custom-option{display:block;width:100%;padding:10px 5px;cursor:pointer;box-shadow:0 -1px 0 0 rgba(0,0,0,.08)}custom-select custom-option:hover{background-color:#3949ab;color:#fff}custom-select custom-option.active{font-weight:700}custom-select.open .label{background-color:rgba(0,0,0,.1)}custom-select.open .label::after{transform:rotate(-135deg);-webkit-transform:rotate(-135deg);top:calc(50% - 3px)}custom-select.open custom-options{max-height:none;border:1px solid #1b1b1b!important}.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 .playlist-content h1{padding:0 1em}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;display:flex;align-items:center}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.lightMode{background-color:rgba(0,0,0,.1)}#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)}#modal modal-footer .inner{padding:5px;width:calc(100% - 40px);margin:0 auto}.notification{right:10px;top:0;height:100%;display:flex;flex-direction:column-reverse;width:90%;max-width:400px;pointer-events:none}.notification .notification-item{margin-top:10px}.notification div{position:unset;padding:unset}.notification div.notification-item{position:relative}.notification-item{width:100%;border-radius:5px;box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23);overflow:hidden}.notification-item.success{background-color:rgba(74,177,11,.6)}.notification-item.error{background-color:rgba(255,50,50,.6)}.notification-item.warning{background-color:rgba(255,177,89,.6)}.notification-item.info{background-color:rgba(71,73,171,.6)}.notification-item .message{padding:1em}.notification-item .fade-bar{animation:fadeOut ease-in-out 3s;height:100%;width:100%;position:absolute;top:0;z-index:-1;opacity:.4;transform-origin:left}.notification-item .fade-bar:after{content:'';z-index:1;bottom:0;height:4px;width:100%;position:absolute;background-color:#fff}.notification-item .fade-bar.endless{animation:endlessFade ease-in-out .5s infinite}.notification-item .fade-bar.success{background-color:#60ff00}.notification-item .fade-bar.error{background-color:#fa0000}.notification-item .fade-bar.warning{background-color:#f70}.notification-item .fade-bar.info{background-color:#3949ab}@keyframes fadeOut{from{transform:scaleX(1)}to{transform:scaleX(0)}}@keyframes endlessFade{0%{transform:scaleX(1);transform-origin:right}49%{transform-origin:right}50%{transform:scaleX(0);transform-origin:left}100%{transform:scaleX(1)}}.config-nav{display:flex;flex-direction:row;background-color:#1b1b1b;box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23)}.config-nav div{padding:10px 5px;cursor:pointer;user-select:none;border-bottom:1px solid transparent}.config-nav div.active{border-color:#5ff507}.config-nav div:hover{background-color:rgba(0,0,0,.4)}.config-content{padding:1em;min-height:200px}.config-content .visuals{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-around}.config-content .visuals .visual-item{display:flex;align-items:center;justify-content:center;padding:1em;min-width:100px;min-height:100px;background-color:#212121;box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23);transition:.5s;cursor:pointer}.config-content .visuals .visual-item:hover{background-color:#1b1b1b}.config-content .visuals .visual-item.active{border:1px solid #3949ab} \ No newline at end of file diff --git a/out/tpl/help.tpl b/out/tpl/help.tpl new file mode 100644 index 0000000..8e1432a --- /dev/null +++ b/out/tpl/help.tpl @@ -0,0 +1,69 @@ + +
+
+ h +

+ Open Help Modal +

+
+
+ Space +

+ Switch between pause and play +

+
+
+ n +

+ Play next song +

+
+
+ v +

+ Play previous song +

+
+
+ s +

+ Open Settings Modal +

+
+
+ Shift + s +

+ Toggle Shuffle +

+
+
+ b +

+ Open background settings Modal +

+
+
+ Shift + f +

+ Force Song Tagger! +

+
+
+ p +

+ Show Playlist Modal +

+
+
+ ESC or Shift + C +

+ Close current open Modal +

+
+
+ F11 +

+ Toggle Fullscreen and hide GUI +

+
+
\ No newline at end of file diff --git a/out/tpl/image.tpl b/out/tpl/image.tpl index 2e2dd7a..f06a0d1 100644 --- a/out/tpl/image.tpl +++ b/out/tpl/image.tpl @@ -19,10 +19,11 @@ -