Compare commits

...

7 commits

Author SHA1 Message Date
170a6cc716 - added VTepL => is not WIP but works better then Template.js and will replace 90% of all templates with one do the for feature 2020-09-18 23:40:45 +02:00
4c4afe3bdf WIP 2020-08-08 21:58:15 +02:00
07b35b9667 WIP 2020-08-07 19:31:30 +02:00
25fcefcb50 WIP 2020-08-06 23:44:37 +02:00
9d5259767c - 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
2020-08-05 11:24:59 +02:00
300b6c4106 WIP 2020-08-01 21:51:54 +02:00
VersusTune
d1ae2059f7 WIP 2020-04-07 21:44:46 +02:00
111 changed files with 8188 additions and 453 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
/webGLTest.iml /webGLTest.iml
/.idea/ /.idea/
/build/node_modules/

23
build/gulpfile.js Normal file
View file

@ -0,0 +1,23 @@
const gulp = require('gulp'),
spriteBuild = require('./task/spriteBuilder').buildIconSprites,
scss = require('./task/scss').buildCSS,
js = require('./task/js').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', 'workerJS'));
gulp.task('watch', gulp.parallel('js', 'scss', 'sprite', 'gui', 'workerJS', 'watchMe'));

36
build/package.json Normal file
View file

@ -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"
}
}

51
build/task/js.js Normal file
View file

@ -0,0 +1,51 @@
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 + 'wave2d.js',
visualPath + 'water.js',
//visualPath + 'experimental.js',
]
const config = {
src: [
basePath + 'utils.js',
basePath + 'gl/glUtils.js',
basePath + 'gl/Camera.js',
basePath + 'template.js',
basePath + 'gl/handler.js',
basePath + 'audio.js',
basePath + 'FileHandler.js',
basePath + 'FetchHandler.js',
basePath + 'playerConfigHandler.js',
basePath + 'player.js',
basePath + 'gui.js',
basePath + 'visual.js',
basePath + 'imageUploader.js',
basePath + 'notification.js',
basePath + 'config.js',
...visuals,
basePath + 'eventHandler.js',
basePath + 'select.js',
basePath + 'keys.js',
basePath + 'startup.js',
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().on('error', console.error))
.pipe(gulp.dest(config.dest));
}
module.exports.build = build;

View file

@ -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;

17
build/task/scss.js Normal file
View file

@ -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;

106
build/task/spriteBuilder.js Normal file
View file

@ -0,0 +1,106 @@
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,
fal.faFileImage,
fal.faQuestionCircle,
],
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 = '<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg">';
for (let item of toMergeData) {
data += item.replace('</svg>', '').replace(/(<svg)([^<]*|[^>]*)/g, '');
}
data += '</svg>';
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;

26
build/task/worker.js Normal file
View file

@ -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;

0
empty.txt Normal file
View file

BIN
favicon.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
favicon/apple-touch-icon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
favicon/favicon-16x16.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

BIN
favicon/favicon-32x32.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 B

BIN
favicon/mstile-150x150.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 890 B

1
favicon/safari-pinned-tab.svg Executable file
View file

@ -0,0 +1 @@
<svg version="1" xmlns="http://www.w3.org/2000/svg" width="893.333" height="893.333" viewBox="0 0 670.000000 670.000000"><path d="M48 256.8c0 .4 10.7 23.9 23.9 52.2 13.1 28.3 25.5 55.1 27.5 59.5l3.7 7.9 5.8-12.7 5.9-12.7L93 303.7l-21.9-47.2-11.5-.3c-6.4-.1-11.6.1-11.6.6zM178.2 262.2c-4.6 10-42.4 91.1-53.8 115.5L114 399.8l5.7 12.4c3.1 6.7 5.8 12.5 5.9 12.7.2.2 1.6-2.3 3.1-5.5 1.4-3.2 18.8-40.6 38.6-82.9 19.7-42.4 36.1-77.8 36.4-78.8.5-1.5-.5-1.7-11.1-1.7H181l-2.8 6.2zM255 257.9c-8.2 2.6-11.8 4.8-18.3 10.9-10.9 10.4-15.2 21.4-14.5 37.1.3 8.6.7 10.5 4.1 17.4 6.5 13.7 18.8 23.5 33.5 26.6 2.9.6 9.5 1.1 14.6 1.1 11.4 0 17.1 1.8 22.6 7.2 5.7 5.4 8 10.8 8 18.8 0 11.1-5.4 19.5-15.5 24.1-3.6 1.7-7.2 1.9-35.7 1.9H222v22h31.3c24.9 0 32.5-.3 37.2-1.5 23.9-6.3 39.7-30 35.7-53.8-2.7-15.8-11.6-27.9-25.6-34.7-9-4.4-16.6-6-29.6-6-9.7 0-14.5-1.7-19.8-7.1-5.8-5.7-7.7-10.4-7.6-18.9 0-9 2.2-14 8.5-19.3 7.4-6.4 9.6-6.7 44.1-6.7H327v-21l-33.2.1c-29.1 0-34 .2-38.8 1.8zM360 266.5V277h113v-21H360v10.5zM495 266.5V277h62.5l5.8-9.9c3.1-5.4 5.7-10.1 5.7-10.5 0-.3-16.6-.6-37-.6h-37v10.5zM575.1 286.3c-9.6 16.6-31.4 54.2-48.3 83.5-16.9 29.4-30.8 53.8-30.8 54.3s24.3.8 60.8.7l60.7-.3v-21l-42.2-.3c-23.3-.1-42.3-.6-42.3-1s.6-1.6 1.4-2.7c.7-1.1 8.7-14.8 17.6-30.5 9-15.7 26.7-46.5 39.5-68.5s23.7-41 24.4-42.3l1.2-2.2h-12.3l-12.3.1-17.4 30.2zM407 361v64h21V297h-21v64z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -2,33 +2,109 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>WEBGL Test</title> <meta name="viewport"
<link rel="stylesheet" href="style.css"> content="width=device-width, user-scalable=yes, initial-scale=1.0, maximum-scale=5.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="description" content="WebAudio Player created by VersusTuneZ"/>
<link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png">
<link rel="manifest" href="/manifest.json">
<link rel="mask-icon" href="/favicon/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#3949ab">
<meta name="theme-color" content="#3949ab">
<title>VIS3D</title>
<link rel="stylesheet" href="out/theme/style.css">
</head> </head>
<body> <body>
<canvas id="c" width="1900" height="1000"></canvas> <div class="loading-screen">
<div class="off-can closed"> <loader></loader>
<group id="rotate"> <loader class="delay"></loader>
<group-label>Rotation</group-label> <span>Loading</span>
</group>
<group id="translate">
<group-label>Transform</group-label>
</group>
<group id="color">
<group-label>Color</group-label>
</group>
<group id="world">
<group-label>World</group-label>
</group>
<group id="draw">
<group-label>Draw</group-label>
</group>
</div> </div>
<div class="settings-icon">&#9776;</div> <div class="menus">
<script src="js/utils.js" defer></script> <div class="top-menu-left">
<script src="js/handler.js"></script> <div class="settings-icon menu-icon">
<script src="js/index.js"></script> <svg role="img" class="icon">
<script src="js/sphere.js"></script> <use href="out/icon-sprite.svg#fal-fa-cogs"></use>
<script src="js/gui.js"></script> </svg>
</div>
<div class="upload-image menu-icon">
<svg role="img" class="icon">
<use href="out/icon-sprite.svg#fal-fa-file-image"></use>
</svg>
</div>
<div class="upload menu-icon">
<label for="upload-dir">
<svg role="img" class="icon">
<use href="out/icon-sprite.svg#fal-fa-folder-upload"></use>
</svg>
</label>
<input type="file" multiple directory webkitdirectory accept="audio/*" id="upload-dir">
</div>
<div class="playlist menu-icon">
<svg role="img" class="icon">
<use href="out/icon-sprite.svg#fal-fa-list-music"></use>
</svg>
</div>
<div class="help menu-icon">
<svg role="img" class="icon">
<use href="out/icon-sprite.svg#fal-fa-question-circle"></use>
</svg>
</div>
</div>
<div class="controls">
<button id="previous">
<svg role="img" class="icon">
<use href="out/icon-sprite.svg#fal-fa-caret-left"></use>
</svg>
</button>
<button id="play">
<svg role="img" data-name="pause" class="pause icon hide">
<use href="out/icon-sprite.svg#fal-fa-pause"></use>
</svg>
<svg role="img" data-name="play" class="icon">
<use href="out/icon-sprite.svg#fal-fa-play"></use>
</svg>
</button>
<button id="next">
<svg role="img" class="icon">
<use href="out/icon-sprite.svg#fal-fa-caret-right"></use>
</svg>
</button>
<button id="shuffle">
<svg role="img" class="icon">
<use href="out/icon-sprite.svg#fal-fa-random"></use>
</svg>
</button>
</div>
</div>
<canvas id="c"></canvas>
<canvas id="cInfo"></canvas>
<div class="grey-screen hide">
<div id="modal">
<header>
<span class="headline">Playlist</span>
<span class="close">X</span>
</header>
<modal-content>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et
dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet,
consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no
sea takimata sanctus est Lorem ipsum dolor sit amet.
</modal-content>
<modal-footer>
<div class="inner">
</div>
</modal-footer>
</div>
</div>
<div class="notification">
</div>
<script src="out/js/scripts.min.js?v=1"></script>
</body> </body>
</html> </html>

View file

@ -80,4 +80,13 @@ class Shaders {
getProgram(name) { getProgram(name) {
return this.programs[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;
}
} }

View file

@ -1,22 +1,35 @@
let shaderHandler, gl, c, actx, analyser, peak; let shaderHandler, gl, c, actx, analyser, peak, isInit = false;
let positionData = [];
let positionSize = 8192 * 2 * 2;
function createAudioContextStream(stream) { function createAudioContextStream(audio) {
let AudioContext = window.AudioContext || window.webkitAudioContext; let AudioContext = window.AudioContext || window.webkitAudioContext;
if (actx) { if (actx) {
actx.close(); actx.close();
} }
actx = new AudioContext(); actx = new AudioContext();
analyser = actx.createAnalyser(); analyser = actx.createAnalyser();
let MEDIA_ELEMENT_NODES = new WeakMap();
let Source; let Source;
analyser.fftSize = 4096; analyser.fftSize = 4096;
analyser.maxDecibels = 0; analyser.maxDecibels = 0;
analyser.smoothingTimeConstant = .4; 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; return null;
} }
@ -28,26 +41,38 @@ async function init() {
return false; return false;
} }
shaderHandler = new Shaders(gl); shaderHandler = new Shaders(gl);
await shaderHandler.loadShader("test", "shaders/"); await shaderHandler.loadShader("wave", "shaders/");
//sphere shader //sphere shader
await shaderHandler.loadShader("sphere", "shaders/", gl.VERTEX_SHADER); await shaderHandler.loadShader("sphere", "shaders/", gl.VERTEX_SHADER);
return shaderHandler.createProgramForEach(["test", "sphere"]); return shaderHandler.createProgramForEach(["wave", "sphere"]);
} }
(function () { function createView() {
navigator.mediaDevices.getUserMedia({audio: true, video: false}) if (!isInit) {
.then(createAudioContextStream).then(e => { createAudioContextStream(audioFile);
init().then(b => {
if (b) {
loadConfig();
draw();
}
})
isInit = true;
}
}
function initGUI() {
generateRotationSliders(); generateRotationSliders();
generateColorSliders(); generateColorSliders();
generateWorldSliders(); generateWorldSliders();
generateDrawSliders(); generateDrawSliders();
generateTranslateSliders(); generateTranslateSliders();
init().then(b => {
if (b) {
sphereObject.drawMode = gl.POINTS;
draw();
} }
})
}); (function () {
})(); if (document.readyState === "complete" || document.readyState === "interactive") {
initGUI()
} else {
document.addEventListener('DOMContentLoaded', initGUI);
}
})()

View file

@ -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]];
}

View file

@ -7,20 +7,21 @@ let sphereObject = {
rotation: [0, 0, 0], //radians rotation: [0, 0, 0], //radians
rotateInc: [0.0, 0.0, 0.0], //degreesInc rotateInc: [0.0, 0.0, 0.0], //degreesInc
rotateByBeat: true, rotateByBeat: true,
translate: [1, 1, 1], translate: [0, 0, 0],
total: 50, total: 50,
radius: 500, radius: 500,
color: {r: 0, g: 0, b: 0}, color: {r: 0, g: 0, b: 0},
colorByBeat: true, colorByBeat: true,
drawMode: 0, drawMode: 5,
sphereMode: 0, sphereMode: 0,
lightPos: [0, 0, 0], lightPos: [0, 0, 0],
pointSize: 2, pointSize: 2,
steps: 512, steps: 512,
dirtyMode: false dirtyMode: false,
light: 0.3
} }
function readData() { function readDataBar() {
let items = sphereObject.steps; let items = sphereObject.steps;
let dataArray = new Uint8Array(items); let dataArray = new Uint8Array(items);
analyser.getByteFrequencyData(dataArray); analyser.getByteFrequencyData(dataArray);
@ -36,6 +37,7 @@ function readData() {
let sphereDataVectors = [], lastTotal = 0; let sphereDataVectors = [], lastTotal = 0;
// avoid garbage collection each run
function prepareData() { function prepareData() {
let total = sphereObject.total; let total = sphereObject.total;
if (lastTotal !== total) { if (lastTotal !== total) {
@ -52,7 +54,7 @@ function prepareData() {
function setupSphere() { function setupSphere() {
sphereData = []; sphereData = [];
let data = readData(), let data = readDataBar(),
radData = data[0], radData = data[0],
map = VTUtils.map, map = VTUtils.map,
total = sphereObject.total, total = sphereObject.total,
@ -94,14 +96,21 @@ function setupSphere() {
function getAddRad(counter, data, total) { function getAddRad(counter, data, total) {
let mapping, rAdd, map = VTUtils.map; let mapping, rAdd, map = VTUtils.map;
if (sphereObject.sphereMode === 3) {
let h = total / 2; let h = total / 2;
if (sphereObject.sphereMode === 3) {
if (counter > h) { if (counter > h) {
mapping = map(counter, h, total, data.length - 1, 0); mapping = map(counter, h, total, data.length - 1, 0);
} else { } else {
mapping = map(counter, 0, h, 0, data.length - 1); mapping = map(counter, 0, h, 0, data.length - 1);
} }
rAdd = data[Math.round(mapping)] || 0; 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 { } else {
mapping = map(counter, 0, total, 0, data.length - 1); mapping = map(counter, 0, total, 0, data.length - 1);
rAdd = data[Math.round(mapping)] || 0; rAdd = data[Math.round(mapping)] || 0;
@ -109,18 +118,16 @@ function getAddRad(counter, data, total) {
return rAdd; return rAdd;
} }
let position, world, color, rotate, light, lightPos, lightAngle = 90; let position, color, rotate, light, lightPos;
function prepare(program) { function prepare(program) {
position = gl.getAttribLocation(program, "a_position"); position = gl.getAttribLocation(program, "a_position");
world = gl.getUniformLocation(program, 'u_world');
color = gl.getUniformLocation(program, "u_color"); color = gl.getUniformLocation(program, "u_color");
light = gl.getUniformLocation(program, "u_light"); light = gl.getUniformLocation(program, "u_light");
rotate = gl.getUniformLocation(program, "u_matrix"); rotate = gl.getUniformLocation(program, "u_matrix");
lightPos = gl.getUniformLocation(program, "u_lightPos"); lightPos = gl.getUniformLocation(program, "u_lightPos");
let pointSize = gl.getUniformLocation(program, "u_pointSize"); let pointSize = gl.getUniformLocation(program, "u_pointSize");
gl.uniformMatrix4fv(world, false, TDUtils.yRotation(TDUtils.degToRad(lightAngle))); gl.uniform3fv(light, [sphereObject.light, 0, 0]);
gl.uniform3fv(light, [0.95, 0.62, 0.094]);
gl.uniform3fv(lightPos, sphereObject.lightPos); gl.uniform3fv(lightPos, sphereObject.lightPos);
gl.uniform1f(pointSize, sphereObject.pointSize); gl.uniform1f(pointSize, sphereObject.pointSize);
} }
@ -135,16 +142,15 @@ function draw() {
gl.useProgram(program); gl.useProgram(program);
prepare(program); prepare(program);
let matrix = [ let matrix = [
sphereObject.translate[0] / aspect, 0, 0, 0, 1 / aspect, 0, 0, 0,
0, sphereObject.translate[1], 0, 0, 0, 1, 0, 0,
0, 0, sphereObject.translate[2], 0, 0, 0, 1, 0,
0, 0, 0, 1 sphereObject.translate[0], sphereObject.translate[1], sphereObject.translate[2], 1
] ]
matrix = TDUtils.multiply(matrix, TDUtils.xRotation(sphereObject.rotation[0])); matrix = TDUtils.multiply(matrix, TDUtils.xRotation(sphereObject.rotation[0]));
matrix = TDUtils.multiply(matrix, TDUtils.yRotation(sphereObject.rotation[1])); matrix = TDUtils.multiply(matrix, TDUtils.yRotation(sphereObject.rotation[1]));
matrix = TDUtils.multiply(matrix, TDUtils.zRotation(sphereObject.rotation[2])); matrix = TDUtils.multiply(matrix, TDUtils.zRotation(sphereObject.rotation[2]));
gl.uniformMatrix4fv(rotate, false, matrix); gl.uniformMatrix4fv(rotate, false, matrix);
let positionBuffer = gl.createBuffer(); let positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(sphereData), gl.DYNAMIC_DRAW); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(sphereData), gl.DYNAMIC_DRAW);
@ -152,7 +158,6 @@ function draw() {
gl.bindVertexArray(vao); gl.bindVertexArray(vao);
gl.enableVertexAttribArray(position); gl.enableVertexAttribArray(position);
gl.vertexAttribPointer(position, 3, gl.FLOAT, true, 0, 0); gl.vertexAttribPointer(position, 3, gl.FLOAT, true, 0, 0);
gl.clearColor(0, 0, 0, 1); gl.clearColor(0, 0, 0, 1);
gl.enable(gl.DEPTH_TEST); gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL); gl.depthFunc(gl.LEQUAL);
@ -186,12 +191,6 @@ function sphereMode(lat, lon, i, counter, rx, ry) {
sin = Math.sin, sin = Math.sin,
cos = Math.cos; cos = Math.cos;
switch (sphereObject.sphereMode) { 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: case 1:
x = rx * sin(lon) * cos(lat); x = rx * sin(lon) * cos(lat);
y = ry * sin(lon) * sin(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); y = ry * sin(lat) * sin(lat);
z = ry * cos(lon); z = ry * cos(lon);
break; 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}; return {x: x, y: y, z: z};
} }

View file

@ -196,9 +196,10 @@ class TDUtils {
return dst; return dst;
} }
static xRotation(angleInRadians) { static xRotation(angle) {
let c = Math.cos(angleInRadians); angle = TDUtils.degToRad(angle);
let s = Math.sin(angleInRadians); let c = Math.cos(angle);
let s = Math.sin(angle);
return [ return [
1, 0, 0, 0, 1, 0, 0, 0,
@ -208,9 +209,10 @@ class TDUtils {
]; ];
} }
static yRotation(angleInRadians) { static yRotation(angle) {
let c = Math.cos(angleInRadians); angle = TDUtils.degToRad(angle);
let s = Math.sin(angleInRadians); let c = Math.cos(angle);
let s = Math.sin(angle);
return [ return [
c, 0, -s, 0, c, 0, -s, 0,
@ -220,9 +222,10 @@ class TDUtils {
]; ];
} }
static zRotation(angleInRadians) { static zRotation(angle) {
let c = Math.cos(angleInRadians); angle = TDUtils.degToRad(angle);
let s = Math.sin(angleInRadians); let c = Math.cos(angle);
let s = Math.sin(angle);
return [ return [
c, s, 0, 0, c, s, 0, 0,
@ -236,3 +239,40 @@ class TDUtils {
return d * Math.PI / 180; 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]);
}
}

View file

@ -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() { function generateRotationSliders() {
let group = $('#rotate'); let group = $('#rotate');
generateSlider(["X", "Y", "Z", "X-Inc", "Y-Inc", "Z-Inc"], 0, 360, 0, 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() { function generateTranslateSliders() {
let group = $('#translate'); let group = $('#translate');
generateSlider(["X", "Y", "Z"], -1, 1, 1, group, "translate"); generateSlider(["X", "Y", "Z"], -1, 1, 0, group, "translate");
} }
function generateColorSliders() { function generateColorSliders() {
@ -54,20 +17,20 @@ function generateColorSliders() {
function generateWorldSliders() { function generateWorldSliders() {
let group = $('#world'); let group = $('#world');
generateSlider(["LightPos-X", "LightPos-Y", "LightPos-Z"], -1, 1, 1, group, "light"); generateSlider(["LightPos-X", "LightPos-Y", "LightPos-Z"], -100, 100, 0, group, "light");
generateSlider(["LightAngle"], 0, 360, 90, group, "light"); generateSlider(["Light"], 0, 1, 0.3, group, "light");
} }
function generateDrawSliders() { function generateDrawSliders() {
let group = $('#draw'), let group = $('#draw'),
g = "drawMode" g = "drawMode"
generateSlider(["DrawMode"], 0, 6, 0, group, g); 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(["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(["PointSize"], 1, 10, 2, group, g, .2);
generateSlider(["Smoothing"], 0, 1, 0.8, group, g, .05); generateSlider(["Smoothing"], 0, 100, 80, group, g, 1);
generateSlider(["Steps"], 512, analyser.fftSize / 2, 512, group, g, 256); generateSlider(["Steps"], 256, 2048, 512, group, g, 16);
generateCheckBox(["Dirty"], false, group, g) generateCheckBox(["Dirty"], false, group, g)
} }
@ -102,11 +65,10 @@ function setColors() {
function setWorld() { function setWorld() {
let group = $('#world'); let group = $('#world');
sphereObject.lightPos[0] = getValue($('#LightPos-X', group)) * 0.2; sphereObject.lightPos[0] = getValue($('#LightPos-X', group));
sphereObject.lightPos[1] = getValue($('#LightPos-Y', group)) * 0.2; sphereObject.lightPos[1] = getValue($('#LightPos-Y', group));
sphereObject.lightPos[2] = getValue($('#LightPos-Z', group)) * 0.2; sphereObject.lightPos[2] = getValue($('#LightPos-Z', group));
sphereObject.light = getValue($('#Light', group));
lightAngle = getValue($('#LightAngle', group))
} }
function setDraw() { function setDraw() {
@ -118,7 +80,7 @@ function setDraw() {
sphereObject.pointSize = getValue($('#PointSize', group)); sphereObject.pointSize = getValue($('#PointSize', group));
sphereObject.steps = getValue($('#Steps', group)); sphereObject.steps = getValue($('#Steps', group));
sphereObject.dirtyMode = $('#drawModeDirty', group).checked; sphereObject.dirtyMode = $('#drawModeDirty', group).checked;
analyser.smoothingTimeConstant = getValue($('#Smoothing', group)); analyser.smoothingTimeConstant = getValue($('#Smoothing', group)) / 100;
} }
function setTranslate() { function setTranslate() {
@ -183,6 +145,7 @@ function changeHandler(el) {
} else if (d === "drawMode") { } else if (d === "drawMode") {
setDraw(); setDraw();
} }
saveConfig();
} }
document.body.addDelegatedEventListener('input', 'group-input input', (ev, el) => { document.body.addDelegatedEventListener('input', 'group-input input', (ev, el) => {
@ -205,4 +168,60 @@ function getValue(slider) {
$('.settings-icon').addEventListener('click', function () { $('.settings-icon').addEventListener('click', function () {
$('.off-can').classList.toggle("closed"); $('.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)
});
}
}) })

21
manifest.json Executable file
View file

@ -0,0 +1,21 @@
{
"name": "VIS3D by VersusTuneZ",
"short_name": "VIS3D",
"icons": [
{
"src": "/favicon/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/favicon/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#3949ab",
"background_color": "#212121",
"display": "standalone",
"start_url": "/index.html",
"orientation": "landscape-primary"
}

0
out/gui/.gitkeep Normal file
View file

1
out/gui/base.json Normal file
View file

@ -0,0 +1 @@
[{"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!"}]

1
out/gui/sphere.json Normal file
View file

@ -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 <br> 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,"value":255},{"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"}]

1
out/gui/wave.json Normal file
View file

@ -0,0 +1 @@
[{"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":"light","name":["X","Y","Z"],"props":["x","y","z"],"type":"slider","max":100,"min":-100,"value":0,"dataType":"int"},{"group":"light","name":"light-strength","showName":"Light Brightness","type":"slider","value":0.3,"max":1,"min":0,"stepSize":0.05,"tooltip":"brightness of light-point","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!"}]

1
out/gui/wave2d.json Normal file
View file

@ -0,0 +1 @@
[{"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":"translate","name":["X","Y","Z"],"props":["x","y","z"],"type":"slider","max":1,"min":-1,"value":0,"stepSize":0.01,"dataType":"float"},{"group":"","name":"fudgeFactor","showName":"Fudge Factor","type":"slider","value":1,"max":2,"min":0,"tooltip":"z to w Fudge","stepSize":0.1,"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!"}]

1
out/icon-sprite.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.1 KiB

85
out/js/jsmediatags.min.js vendored Executable file
View file

@ -0,0 +1,85 @@
(function(y){"object"===typeof exports&&"undefined"!==typeof module?module.exports=y():"function"===typeof define&&define.amd?define([],y):("undefined"!==typeof window?window:"undefined"!==typeof global?global:"undefined"!==typeof self?self:this).jsmediatags=y()})(function(){return function f(p,q,m){function l(c,a){if(!q[c]){if(!p[c]){var b="function"==typeof require&&require;if(!a&&b)return b(c,!0);if(e)return e(c,!0);a=Error("Cannot find module '"+c+"'");throw a.code="MODULE_NOT_FOUND",a;}a=q[c]=
{exports:{}};p[c][0].call(a.exports,function(a){var b=p[c][1][a];return l(b?b:a)},a,a.exports,f,p,q,m)}return q[c].exports}for(var e="function"==typeof require&&require,d=0;d<m.length;d++)l(m[d]);return l}({1:[function(f,p,q){},{}],2:[function(f,p,q){p.exports=XMLHttpRequest},{}],3:[function(f,p,q){function m(e,d){if("function"!==typeof d&&null!==d)throw new TypeError("Super expression must either be null or a function, not "+typeof d);e.prototype=Object.create(d&&d.prototype,{constructor:{value:e,
enumerable:!1,writable:!0,configurable:!0}});d&&(Object.setPrototypeOf?Object.setPrototypeOf(e,d):e.__proto__=d)}var l=function(){function e(d,c){for(var a=0;a<c.length;a++){var b=c[a];b.enumerable=b.enumerable||!1;b.configurable=!0;"value"in b&&(b.writable=!0);Object.defineProperty(d,b.key,b)}}return function(d,c,a){c&&e(d.prototype,c);a&&e(d,a);return d}}();f=function(e){function d(c){if(!(this instanceof d))throw new TypeError("Cannot call a class as a function");var a=(d.__proto__||Object.getPrototypeOf(d)).call(this);
if(!this)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");a=!a||"object"!==typeof a&&"function"!==typeof a?this:a;a._array=c;a._size=c.length;a._isInitialized=!0;return a}m(d,e);l(d,[{key:"init",value:function(c){setTimeout(c.onSuccess,0)}},{key:"loadRange",value:function(c,a){setTimeout(a.onSuccess,0)}},{key:"getByteAt",value:function(c){if(c>=this._array.length)throw Error("Offset "+c+" hasn't been loaded yet.");return this._array[c]}}],[{key:"canReadFile",value:function(c){return Array.isArray(c)||
"function"===typeof Buffer&&Buffer.isBuffer(c)}}]);return d}(f("./MediaFileReader"));p.exports=f},{"./MediaFileReader":11}],4:[function(f,p,q){function m(d,c){if("function"!==typeof c&&null!==c)throw new TypeError("Super expression must either be null or a function, not "+typeof c);d.prototype=Object.create(c&&c.prototype,{constructor:{value:d,enumerable:!1,writable:!0,configurable:!0}});c&&(Object.setPrototypeOf?Object.setPrototypeOf(d,c):d.__proto__=c)}var l=function(){function d(c,a){for(var b=
0;b<a.length;b++){var d=a[b];d.enumerable=d.enumerable||!1;d.configurable=!0;"value"in d&&(d.writable=!0);Object.defineProperty(c,d.key,d)}}return function(c,a,b){a&&d(c.prototype,a);b&&d(c,b);return c}}(),e=f("./ChunkedFileData");f=function(d){function c(a){if(!(this instanceof c))throw new TypeError("Cannot call a class as a function");var b=(c.__proto__||Object.getPrototypeOf(c)).call(this);if(!this)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");b=!b||"object"!==
typeof b&&"function"!==typeof b?this:b;b._blob=a;b._fileData=new e;return b}m(c,d);l(c,[{key:"_init",value:function(a){this._size=this._blob.size;setTimeout(a.onSuccess,1)}},{key:"loadRange",value:function(a,b){var d=this,h=(this._blob.slice||this._blob.mozSlice||this._blob.webkitSlice).call(this._blob,a[0],a[1]+1),c=new FileReader;c.onloadend=function(h){h=new Uint8Array(c.result);d._fileData.addData(a[0],h);b.onSuccess()};c.onerror=c.onabort=function(a){if(b.onError)b.onError({type:"blob",info:c.error})};
c.readAsArrayBuffer(h)}},{key:"getByteAt",value:function(a){return this._fileData.getByteAt(a)}}],[{key:"canReadFile",value:function(a){return"undefined"!==typeof Blob&&a instanceof Blob||"undefined"!==typeof File&&a instanceof File}}]);return c}(f("./MediaFileReader"));p.exports=f},{"./ChunkedFileData":5,"./MediaFileReader":11}],5:[function(f,p,q){var m=function(){function f(e,d){for(var c=0;c<d.length;c++){var a=d[c];a.enumerable=a.enumerable||!1;a.configurable=!0;"value"in a&&(a.writable=!0);Object.defineProperty(e,
a.key,a)}}return function(e,d,c){d&&f(e.prototype,d);c&&f(e,c);return e}}();f=function(){function f(){if(!(this instanceof f))throw new TypeError("Cannot call a class as a function");this._fileData=[]}m(f,null,[{key:"NOT_FOUND",get:function(){return-1}}]);m(f,[{key:"addData",value:function(e,d){var c=e+d.length-1,a=this._getChunkRange(e,c);if(-1===a.startIx)this._fileData.splice(a.insertIx||0,0,{offset:e,data:d});else{var b=this._fileData[a.startIx],g=this._fileData[a.endIx];c=c<g.offset+g.data.length-
1;var h={offset:Math.min(e,b.offset),data:d};e>b.offset&&(e=this._sliceData(b.data,0,e-b.offset),h.data=this._concatData(e,d));c&&(e=this._sliceData(h.data,0,g.offset-h.offset),h.data=this._concatData(e,g.data));this._fileData.splice(a.startIx,a.endIx-a.startIx+1,h)}}},{key:"_concatData",value:function(e,d){if("undefined"!==typeof ArrayBuffer&&ArrayBuffer.isView&&ArrayBuffer.isView(e)){var c=new e.constructor(e.length+d.length);c.set(e,0);c.set(d,e.length);return c}return e.concat(d)}},{key:"_sliceData",
value:function(e,d,c){return e.slice?e.slice(d,c):e.subarray(d,c)}},{key:"_getChunkRange",value:function(e,d){for(var c,a,b=-1,g=-1,h=0,k=0;k<this._fileData.length;k++,h=k){a=this._fileData[k].offset;c=a+this._fileData[k].data.length;if(d<a-1)break;if(e<=c+1&&d>=a-1){b=k;break}}if(-1===b)return{startIx:-1,endIx:-1,insertIx:h};for(k=b;k<this._fileData.length&&!(a=this._fileData[k].offset,c=a+this._fileData[k].data.length,d>=a-1&&(g=k),d<=c+1);k++);-1===g&&(g=b);return{startIx:b,endIx:g}}},{key:"hasDataRange",
value:function(e,d){for(var c=0;c<this._fileData.length;c++){var a=this._fileData[c];if(d<a.offset)break;if(e>=a.offset&&d<a.offset+a.data.length)return!0}return!1}},{key:"getByteAt",value:function(e){for(var d,c=0;c<this._fileData.length;c++){var a=this._fileData[c].offset,b=a+this._fileData[c].data.length-1;if(e>=a&&e<=b){d=this._fileData[c];break}}if(d)return d.data[e-d.offset];throw Error("Offset "+e+" hasn't been loaded yet.");}}]);return f}();p.exports=f},{}],6:[function(f,p,q){function m(a,
b){if("function"!==typeof b&&null!==b)throw new TypeError("Super expression must either be null or a function, not "+typeof b);a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,enumerable:!1,writable:!0,configurable:!0}});b&&(Object.setPrototypeOf?Object.setPrototypeOf(a,b):a.__proto__=b)}var l=function(){function a(a,c){for(var b=0;b<c.length;b++){var d=c[b];d.enumerable=d.enumerable||!1;d.configurable=!0;"value"in d&&(d.writable=!0);Object.defineProperty(a,d.key,d)}}return function(b,
d,h){d&&a(b.prototype,d);h&&a(b,h);return b}}(),e=[4,132],d=[6,134],c="Other;32x32 pixels 'file icon' (PNG only);Other file icon;Cover (front);Cover (back);Leaflet page;Media (e.g. label side of CD);Lead artist/lead performer/soloist;Artist/performer;Conductor;Band/Orchestra;Composer;Lyricist/text writer;Recording Location;During recording;During performance;Movie/video screen capture;A bright coloured fish;Illustration;Band/artist logotype;Publisher/Studio logotype".split(";");f=function(a){function b(){if(!(this instanceof
b))throw new TypeError("Cannot call a class as a function");var a=(b.__proto__||Object.getPrototypeOf(b)).apply(this,arguments);if(!this)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!a||"object"!==typeof a&&"function"!==typeof a?this:a}m(b,a);l(b,[{key:"_loadData",value:function(a,b){var h=this;a.loadRange([4,7],{onSuccess:function(){h._loadBlock(a,4,b)}})}},{key:"_loadBlock",value:function(a,b,c){var h=this,g=a.getByteAt(b),k=a.getInteger24At(b+1,!0);
if(-1!==e.indexOf(g)){var n=b+4;a.loadRange([n,n+k],{onSuccess:function(){h._commentOffset=n;h._nextBlock(a,b,g,k,c)}})}else-1!==d.indexOf(g)?(n=b+4,a.loadRange([n,n+k],{onSuccess:function(){h._pictureOffset=n;h._nextBlock(a,b,g,k,c)}})):h._nextBlock(a,b,g,k,c)}},{key:"_nextBlock",value:function(a,b,d,c,e){var h=this;if(127<d)if(h._commentOffset)e.onSuccess();else e.onError({type:"loadData",info:"Comment block could not be found."});else a.loadRange([b+4+c,b+4+4+c],{onSuccess:function(){h._loadBlock(a,
b+4+c,e)}})}},{key:"_parseData",value:function(a,b){var h=a.getLongAt(this._commentOffset,!1)+(this._commentOffset+4);b=a.getLongAt(h,!1);h+=4;for(var d,g,e,n,x,f,t=0;t<b;t++){var w=a.getLongAt(h,!1),l=a.getStringWithCharsetAt(h+4,w,"utf-8").toString(),m=l.indexOf("=");l=[l.slice(0,m),l.slice(m+1)];switch(l[0]){case "TITLE":d=l[1];break;case "ARTIST":g=l[1];break;case "ALBUM":e=l[1];break;case "TRACKNUMBER":n=l[1];break;case "GENRE":x=l[1]}h+=4+w}this._pictureOffset&&(f=a.getLongAt(this._pictureOffset,
!0),b=this._pictureOffset+4,h=a.getLongAt(b,!0),t=b+4,b=a.getStringAt(t,h),h=t+h,t=a.getLongAt(h,!0),w=h+4,h=a.getStringWithCharsetAt(w,t,"utf-8").toString(),t=w+t+16,w=a.getLongAt(t,!0),a=a.getBytesAt(t+4,w,!0),f={format:b,type:c[f],description:h,data:a});return{type:"FLAC",version:"1",tags:{title:d,artist:g,album:e,track:n,genre:x,picture:f}}}}],[{key:"getTagIdentifierByteRange",value:function(){return{offset:0,length:4}}},{key:"canReadTagFormat",value:function(a){return"fLaC"===String.fromCharCode.apply(String,
a.slice(0,4))}}]);return b}(f("./MediaTagReader"));p.exports=f},{"./MediaTagReader":12}],7:[function(f,p,q){function m(d,c){if("function"!==typeof c&&null!==c)throw new TypeError("Super expression must either be null or a function, not "+typeof c);d.prototype=Object.create(c&&c.prototype,{constructor:{value:d,enumerable:!1,writable:!0,configurable:!0}});c&&(Object.setPrototypeOf?Object.setPrototypeOf(d,c):d.__proto__=c)}var l=function(){function d(d,a){for(var b=0;b<a.length;b++){var c=a[b];c.enumerable=
c.enumerable||!1;c.configurable=!0;"value"in c&&(c.writable=!0);Object.defineProperty(d,c.key,c)}}return function(c,a,b){a&&d(c.prototype,a);b&&d(c,b);return c}}();q=f("./MediaTagReader");f("./MediaFileReader");f=function(d){function c(){if(!(this instanceof c))throw new TypeError("Cannot call a class as a function");var a=(c.__proto__||Object.getPrototypeOf(c)).apply(this,arguments);if(!this)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!a||"object"!==
typeof a&&"function"!==typeof a?this:a}m(c,d);l(c,[{key:"_loadData",value:function(a,b){var d=a.getSize();a.loadRange([d-128,d-1],b)}},{key:"_parseData",value:function(a,b){var d=a.getSize()-128,h=a.getStringWithCharsetAt(d+3,30).toString(),c=a.getStringWithCharsetAt(d+33,30).toString(),r=a.getStringWithCharsetAt(d+63,30).toString(),u=a.getStringWithCharsetAt(d+93,4).toString();var f=a.getByteAt(d+97+28);b=a.getByteAt(d+97+29);if(0==f&&0!=b){var n="1.1";f=a.getStringWithCharsetAt(d+97,28).toString()}else n=
"1.0",f=a.getStringWithCharsetAt(d+97,30).toString(),b=0;a=a.getByteAt(d+97+30);a={type:"ID3",version:n,tags:{title:h,artist:c,album:r,year:u,comment:f,genre:255>a?e[a]:""}};b&&(a.tags.track=b);return a}}],[{key:"getTagIdentifierByteRange",value:function(){return{offset:-128,length:128}}},{key:"canReadTagFormat",value:function(a){return"TAG"===String.fromCharCode.apply(String,a.slice(0,3))}}]);return c}(q);var e="Blues;Classic Rock;Country;Dance;Disco;Funk;Grunge;Hip-Hop;Jazz;Metal;New Age;Oldies;Other;Pop;R&B;Rap;Reggae;Rock;Techno;Industrial;Alternative;Ska;Death Metal;Pranks;Soundtrack;Euro-Techno;Ambient;Trip-Hop;Vocal;Jazz+Funk;Fusion;Trance;Classical;Instrumental;Acid;House;Game;Sound Clip;Gospel;Noise;AlternRock;Bass;Soul;Punk;Space;Meditative;Instrumental Pop;Instrumental Rock;Ethnic;Gothic;Darkwave;Techno-Industrial;Electronic;Pop-Folk;Eurodance;Dream;Southern Rock;Comedy;Cult;Gangsta;Top 40;Christian Rap;Pop/Funk;Jungle;Native American;Cabaret;New Wave;Psychadelic;Rave;Showtunes;Trailer;Lo-Fi;Tribal;Acid Punk;Acid Jazz;Polka;Retro;Musical;Rock & Roll;Hard Rock;Folk;Folk-Rock;National Folk;Swing;Fast Fusion;Bebob;Latin;Revival;Celtic;Bluegrass;Avantgarde;Gothic Rock;Progressive Rock;Psychedelic Rock;Symphonic Rock;Slow Rock;Big Band;Chorus;Easy Listening;Acoustic;Humour;Speech;Chanson;Opera;Chamber Music;Sonata;Symphony;Booty Bass;Primus;Porn Groove;Satire;Slow Jam;Club;Tango;Samba;Folklore;Ballad;Power Ballad;Rhythmic Soul;Freestyle;Duet;Punk Rock;Drum Solo;Acapella;Euro-House;Dance Hall".split(";");
p.exports=f},{"./MediaFileReader":11,"./MediaTagReader":12}],8:[function(f,p,q){function m(a){switch(a){case 0:a="iso-8859-1";break;case 1:a="utf-16";break;case 2:a="utf-16be";break;case 3:a="utf-8";break;default:a="iso-8859-1"}return a}function l(a,b,d,c){c=d.getStringWithCharsetAt(a+1,b-1,c);a=d.getStringWithCharsetAt(a+1+c.bytesReadCount,b-1-c.bytesReadCount);return{user_description:c.toString(),data:a.toString()}}var e=function(){function a(a,b){for(var d=0;d<b.length;d++){var c=b[d];c.enumerable=
c.enumerable||!1;c.configurable=!0;"value"in c&&(c.writable=!0);Object.defineProperty(a,c.key,c)}}return function(b,d,c){d&&a(b.prototype,d);c&&a(b,c);return b}}();f("./MediaFileReader");var d=f("./StringUtils"),c=f("./ArrayFileReader"),a={BUF:"Recommended buffer size",CNT:"Play counter",COM:"Comments",CRA:"Audio encryption",CRM:"Encrypted meta frame",ETC:"Event timing codes",EQU:"Equalization",GEO:"General encapsulated object",IPL:"Involved people list",LNK:"Linked information",MCI:"Music CD Identifier",
MLL:"MPEG location lookup table",PIC:"Attached picture",POP:"Popularimeter",REV:"Reverb",RVA:"Relative volume adjustment",SLT:"Synchronized lyric/text",STC:"Synced tempo codes",TAL:"Album/Movie/Show title",TBP:"BPM (Beats Per Minute)",TCM:"Composer",TCO:"Content type",TCR:"Copyright message",TDA:"Date",TDY:"Playlist delay",TEN:"Encoded by",TFT:"File type",TIM:"Time",TKE:"Initial key",TLA:"Language(s)",TLE:"Length",TMT:"Media type",TOA:"Original artist(s)/performer(s)",TOF:"Original filename",TOL:"Original Lyricist(s)/text writer(s)",
TOR:"Original release year",TOT:"Original album/Movie/Show title",TP1:"Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group",TP2:"Band/Orchestra/Accompaniment",TP3:"Conductor/Performer refinement",TP4:"Interpreted, remixed, or otherwise modified by",TPA:"Part of a set",TPB:"Publisher",TRC:"ISRC (International Standard Recording Code)",TRD:"Recording dates",TRK:"Track number/Position in set",TSI:"Size",TSS:"Software/hardware and settings used for encoding",TT1:"Content group description",TT2:"Title/Songname/Content description",
TT3:"Subtitle/Description refinement",TXT:"Lyricist/text writer",TXX:"User defined text information frame",TYE:"Year",UFI:"Unique file identifier",ULT:"Unsychronized lyric/text transcription",WAF:"Official audio file webpage",WAR:"Official artist/performer webpage",WAS:"Official audio source webpage",WCM:"Commercial information",WCP:"Copyright/Legal information",WPB:"Publishers official webpage",WXX:"User defined URL link frame",AENC:"Audio encryption",APIC:"Attached picture",ASPI:"Audio seek point index",
CHAP:"Chapter",CTOC:"Table of contents",COMM:"Comments",COMR:"Commercial frame",ENCR:"Encryption method registration",EQU2:"Equalisation (2)",EQUA:"Equalization",ETCO:"Event timing codes",GEOB:"General encapsulated object",GRID:"Group identification registration",IPLS:"Involved people list",LINK:"Linked information",MCDI:"Music CD identifier",MLLT:"MPEG location lookup table",OWNE:"Ownership frame",PRIV:"Private frame",PCNT:"Play counter",POPM:"Popularimeter",POSS:"Position synchronisation frame",
RBUF:"Recommended buffer size",RVA2:"Relative volume adjustment (2)",RVAD:"Relative volume adjustment",RVRB:"Reverb",SEEK:"Seek frame",SYLT:"Synchronized lyric/text",SYTC:"Synchronized tempo codes",TALB:"Album/Movie/Show title",TBPM:"BPM (beats per minute)",TCOM:"Composer",TCON:"Content type",TCOP:"Copyright message",TDAT:"Date",TDLY:"Playlist delay",TDRC:"Recording time",TDRL:"Release time",TDTG:"Tagging time",TENC:"Encoded by",TEXT:"Lyricist/Text writer",TFLT:"File type",TIME:"Time",TIPL:"Involved people list",
TIT1:"Content group description",TIT2:"Title/songname/content description",TIT3:"Subtitle/Description refinement",TKEY:"Initial key",TLAN:"Language(s)",TLEN:"Length",TMCL:"Musician credits list",TMED:"Media type",TMOO:"Mood",TOAL:"Original album/movie/show title",TOFN:"Original filename",TOLY:"Original lyricist(s)/text writer(s)",TOPE:"Original artist(s)/performer(s)",TORY:"Original release year",TOWN:"File owner/licensee",TPE1:"Lead performer(s)/Soloist(s)",TPE2:"Band/orchestra/accompaniment",TPE3:"Conductor/performer refinement",
TPE4:"Interpreted, remixed, or otherwise modified by",TPOS:"Part of a set",TPRO:"Produced notice",TPUB:"Publisher",TRCK:"Track number/Position in set",TRDA:"Recording dates",TRSN:"Internet radio station name",TRSO:"Internet radio station owner",TSOA:"Album sort order",TSOP:"Performer sort order",TSOT:"Title sort order",TSIZ:"Size",TSRC:"ISRC (international standard recording code)",TSSE:"Software/Hardware and settings used for encoding",TSST:"Set subtitle",TYER:"Year",TXXX:"User defined text information frame",
UFID:"Unique file identifier",USER:"Terms of use",USLT:"Unsychronized lyric/text transcription",WCOM:"Commercial information",WCOP:"Copyright/Legal information",WOAF:"Official audio file webpage",WOAR:"Official artist/performer webpage",WOAS:"Official audio source webpage",WORS:"Official internet radio station homepage",WPAY:"Payment",WPUB:"Publishers official webpage",WXXX:"User defined URL link frame"};f=function(){function d(){if(!(this instanceof d))throw new TypeError("Cannot call a class as a function");
}e(d,null,[{key:"getFrameReaderFunction",value:function(a){return a in b?b[a]:"T"===a[0]?b["T*"]:"W"===a[0]?b["W*"]:null}},{key:"readFrames",value:function(a,b,c,h,g){for(var k={},e=this._getFrameHeaderSize(h);a<b-e;){var r=this._readFrameHeader(c,a,h),n=r.id;if(!n)break;var f=r.flags,u=r.size,v=a+r.headerSize,l=c;a+=r.headerSize+r.size;if(!g||-1!==g.indexOf(n)){if("MP3e"===n||"\x00MP3"===n||"\x00\x00MP"===n||" MP3"===n)break;f&&f.format.unsynchronisation&&(l=this.getUnsyncFileReader(l,v,u),v=0,u=
l.getSize());f&&f.format.data_length_indicator&&(v+=4,u-=4);f=(r=d.getFrameReaderFunction(n))?r.apply(this,[v,u,l,f,h]):null;v=this._getFrameDescription(n);u={id:n,size:u,description:v,data:f};n in k?(k[n].id&&(k[n]=[k[n]]),k[n].push(u)):k[n]=u}}return k}},{key:"_getFrameHeaderSize",value:function(a){a=a.major;return 2==a?6:3==a||4==a?10:0}},{key:"_readFrameHeader",value:function(a,b,d){var c=d.major,h=null;d=this._getFrameHeaderSize(d);switch(c){case 2:var g=a.getStringAt(b,3);var k=a.getInteger24At(b+
3,!0);break;case 3:g=a.getStringAt(b,4);k=a.getLongAt(b+4,!0);break;case 4:g=a.getStringAt(b,4),k=a.getSynchsafeInteger32At(b+4)}if(g==String.fromCharCode(0,0,0)||g==String.fromCharCode(0,0,0,0))g="";g&&2<c&&(h=this._readFrameFlags(a,b+8));return{id:g||"",size:k||0,headerSize:d||0,flags:h}}},{key:"_readFrameFlags",value:function(a,b){return{message:{tag_alter_preservation:a.isBitSetAt(b,6),file_alter_preservation:a.isBitSetAt(b,5),read_only:a.isBitSetAt(b,4)},format:{grouping_identity:a.isBitSetAt(b+
1,7),compression:a.isBitSetAt(b+1,3),encryption:a.isBitSetAt(b+1,2),unsynchronisation:a.isBitSetAt(b+1,1),data_length_indicator:a.isBitSetAt(b+1,0)}}}},{key:"_getFrameDescription",value:function(b){return b in a?a[b]:"Unknown"}},{key:"getUnsyncFileReader",value:function(a,b,d){a=a.getBytesAt(b,d);for(b=0;b<a.length-1;b++)255===a[b]&&0===a[b+1]&&a.splice(b+1,1);return new c(a)}}]);return d}();var b={APIC:function(a,b,d,c,e){c=a;var h=m(d.getByteAt(a));switch(e&&e.major){case 2:e=d.getStringAt(a+1,
3);a+=4;break;case 3:case 4:e=d.getStringWithCharsetAt(a+1,b-1);a+=1+e.bytesReadCount;break;default:throw Error("Couldn't read ID3v2 major version.");}var k=d.getByteAt(a);k=g[k];h=d.getStringWithCharsetAt(a+1,b-(a-c)-1,h);a+=1+h.bytesReadCount;return{format:e.toString(),type:k,description:h.toString(),data:d.getBytesAt(a,c+b-a)}},CHAP:function(a,b,c,g,e){g=a;var h={},k=d.readNullTerminatedString(c.getBytesAt(a,b));h.id=k.toString();a+=k.bytesReadCount;h.startTime=c.getLongAt(a,!0);a+=4;h.endTime=
c.getLongAt(a,!0);a+=4;h.startOffset=c.getLongAt(a,!0);a+=4;h.endOffset=c.getLongAt(a,!0);a+=4;h.subFrames=this.readFrames(a,a+(b-(a-g)),c,e);return h},CTOC:function(a,b,c,g,e){g=a;var h={childElementIds:[],id:void 0,topLevel:void 0,ordered:void 0,entryCount:void 0,subFrames:void 0},k=d.readNullTerminatedString(c.getBytesAt(a,b));h.id=k.toString();a+=k.bytesReadCount;h.topLevel=c.isBitSetAt(a,1);h.ordered=c.isBitSetAt(a,0);a++;h.entryCount=c.getByteAt(a);a++;for(k=0;k<h.entryCount;k++){var f=d.readNullTerminatedString(c.getBytesAt(a,
b-(a-g)));h.childElementIds.push(f.toString());a+=f.bytesReadCount}h.subFrames=this.readFrames(a,a+(b-(a-g)),c,e);return h},COMM:function(a,b,c,d,g){var h=a,e=m(c.getByteAt(a));d=c.getStringAt(a+1,3);g=c.getStringWithCharsetAt(a+4,b-4,e);a+=4+g.bytesReadCount;a=c.getStringWithCharsetAt(a,h+b-a,e);return{language:d,short_description:g.toString(),text:a.toString()}}};b.COM=b.COMM;b.PIC=function(a,c,d,g,e){return b.APIC(a,c,d,g,e)};b.PCNT=function(a,b,c,d,g){return c.getLongAt(a,!1)};b.CNT=b.PCNT;b["T*"]=
function(a,b,c,d,g){d=m(c.getByteAt(a));return c.getStringWithCharsetAt(a+1,b-1,d).toString()};b.TXXX=function(a,b,c,d,g){d=m(c.getByteAt(a));return l(a,b,c,d)};b.WXXX=function(a,b,c,d,g){if(0===b)return null;d=m(c.getByteAt(a));return l(a,b,c,d)};b["W*"]=function(a,b,c,d,g){return 0===b?null:c.getStringWithCharsetAt(a,b,"iso-8859-1").toString()};b.TCON=function(a,c,d,g){return b["T*"].apply(this,arguments).replace(/^\(\d+\)/,"")};b.TCO=b.TCON;b.USLT=function(a,b,c,d,g){var h=a,e=m(c.getByteAt(a));
d=c.getStringAt(a+1,3);g=c.getStringWithCharsetAt(a+4,b-4,e);a+=4+g.bytesReadCount;a=c.getStringWithCharsetAt(a,h+b-a,e);return{language:d,descriptor:g.toString(),lyrics:a.toString()}};b.ULT=b.USLT;b.UFID=function(a,b,c,g,e){g=d.readNullTerminatedString(c.getBytesAt(a,b));a+=g.bytesReadCount;a=c.getBytesAt(a,b-g.bytesReadCount);return{ownerIdentifier:g.toString(),identifier:a}};var g="Other;32x32 pixels 'file icon' (PNG only);Other file icon;Cover (front);Cover (back);Leaflet page;Media (e.g. label side of CD);Lead artist/lead performer/soloist;Artist/performer;Conductor;Band/Orchestra;Composer;Lyricist/text writer;Recording Location;During recording;During performance;Movie/video screen capture;A bright coloured fish;Illustration;Band/artist logotype;Publisher/Studio logotype".split(";");
p.exports=f},{"./ArrayFileReader":3,"./MediaFileReader":11,"./StringUtils":13}],9:[function(f,p,q){function m(c,a){if("function"!==typeof a&&null!==a)throw new TypeError("Super expression must either be null or a function, not "+typeof a);c.prototype=Object.create(a&&a.prototype,{constructor:{value:c,enumerable:!1,writable:!0,configurable:!0}});a&&(Object.setPrototypeOf?Object.setPrototypeOf(c,a):c.__proto__=a)}var l=function(){function c(a,b){for(var c=0;c<b.length;c++){var d=b[c];d.enumerable=d.enumerable||
!1;d.configurable=!0;"value"in d&&(d.writable=!0);Object.defineProperty(a,d.key,d)}}return function(a,b,d){b&&c(a.prototype,b);d&&c(a,d);return a}}();q=f("./MediaTagReader");f("./MediaFileReader");var e=f("./ID3v2FrameReader");f=function(c){function a(){if(!(this instanceof a))throw new TypeError("Cannot call a class as a function");var b=(a.__proto__||Object.getPrototypeOf(a)).apply(this,arguments);if(!this)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!b||
"object"!==typeof b&&"function"!==typeof b?this:b}m(a,c);l(a,[{key:"_loadData",value:function(a,c){a.loadRange([6,9],{onSuccess:function(){a.loadRange([0,10+a.getSynchsafeInteger32At(6)-1],c)},onError:c.onError})}},{key:"_parseData",value:function(a,c){var b,g=0,f=a.getByteAt(g+3);if(4<f)return{type:"ID3",version:">2.4",tags:{}};var l=a.getByteAt(g+4),m=a.isBitSetAt(g+5,7),n=a.isBitSetAt(g+5,6),x=a.isBitSetAt(g+5,5),p=a.getSynchsafeInteger32At(g+6);g+=10;if(n)if(4===f){var t=a.getSynchsafeInteger32At(g);
g+=t}else t=a.getLongAt(g,!0),g+=t+4;t={type:"ID3",version:"2."+f+"."+l,major:f,revision:l,flags:{unsynchronisation:m,extended_header:n,experimental_indicator:x,footer_present:!1},size:p,tags:{}};c&&(b=this._expandShortcutTags(c));c=p+10;t.flags.unsynchronisation&&(a=e.getUnsyncFileReader(a,g,p),g=0,c=a.getSize());a=e.readFrames(g,c,a,t,b);for(var q in d)d.hasOwnProperty(q)&&(b=this._getFrameData(a,d[q]))&&(t.tags[q]=b);for(var z in a)a.hasOwnProperty(z)&&(t.tags[z]=a[z]);return t}},{key:"_getFrameData",
value:function(a,c){for(var b=0,d;d=c[b];b++)if(d in a)return a=a[d]instanceof Array?a[d][0]:a[d],a.data}},{key:"getShortcuts",value:function(){return d}}],[{key:"getTagIdentifierByteRange",value:function(){return{offset:0,length:10}}},{key:"canReadTagFormat",value:function(a){return"ID3"===String.fromCharCode.apply(String,a.slice(0,3))}}]);return a}(q);var d={title:["TIT2","TT2"],artist:["TPE1","TP1"],album:["TALB","TAL"],year:["TYER","TYE"],comment:["COMM","COM"],track:["TRCK","TRK"],genre:["TCON",
"TCO"],picture:["APIC","PIC"],lyrics:["USLT","ULT"]};p.exports=f},{"./ID3v2FrameReader":8,"./MediaFileReader":11,"./MediaTagReader":12}],10:[function(f,p,q){function m(a,b){if("function"!==typeof b&&null!==b)throw new TypeError("Super expression must either be null or a function, not "+typeof b);a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,enumerable:!1,writable:!0,configurable:!0}});b&&(Object.setPrototypeOf?Object.setPrototypeOf(a,b):a.__proto__=b)}var l=function(){function a(a,
c){for(var b=0;b<c.length;b++){var d=c[b];d.enumerable=d.enumerable||!1;d.configurable=!0;"value"in d&&(d.writable=!0);Object.defineProperty(a,d.key,d)}}return function(b,c,d){c&&a(b.prototype,c);d&&a(b,d);return b}}();q=f("./MediaTagReader");f("./MediaFileReader");f=function(a){function b(){if(!(this instanceof b))throw new TypeError("Cannot call a class as a function");var a=(b.__proto__||Object.getPrototypeOf(b)).apply(this,arguments);if(!this)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
return!a||"object"!==typeof a&&"function"!==typeof a?this:a}m(b,a);l(b,[{key:"_loadData",value:function(a,b){var c=this;a.loadRange([0,16],{onSuccess:function(){c._loadAtom(a,0,"",b)},onError:b.onError})}},{key:"_loadAtom",value:function(a,b,c,d){if(b>=a.getSize())d.onSuccess();else{var g=this,e=a.getLongAt(b,!0);if(0==e||isNaN(e))d.onSuccess();else{var h=a.getStringAt(b+4,4);if(this._isContainerAtom(h)){"meta"==h&&(b+=4);var f=(c?c+".":"")+h;"moov.udta.meta.ilst"===f?a.loadRange([b,b+e],d):a.loadRange([b+
8,b+8+8],{onSuccess:function(){g._loadAtom(a,b+8,f,d)},onError:d.onError})}else a.loadRange([b+e,b+e+8],{onSuccess:function(){g._loadAtom(a,b+e,c,d)},onError:d.onError})}}}},{key:"_isContainerAtom",value:function(a){return 0<=["moov","udta","meta","ilst"].indexOf(a)}},{key:"_canReadAtom",value:function(a){return"----"!==a}},{key:"_parseData",value:function(a,b){var d={};b=this._expandShortcutTags(b);this._readAtom(d,a,0,a.getSize(),b);for(var e in c)c.hasOwnProperty(e)&&(b=d[c[e]])&&(d[e]="track"===
e?b.data.track:b.data);return{type:"MP4",ftyp:a.getStringAt(8,4),version:a.getLongAt(12,!0),tags:d}}},{key:"_readAtom",value:function(a,b,d,c,e,f,n){n=void 0===n?"":n+" ";for(var g=d;g<d+c;){var h=b.getLongAt(g,!0);if(0==h)break;var k=b.getStringAt(g+4,4);if(this._isContainerAtom(k)){"meta"==k&&(g+=4);this._readAtom(a,b,g+8,h-8,e,(f?f+".":"")+k,n);break}(!e||0<=e.indexOf(k))&&"moov.udta.meta.ilst"===f&&this._canReadAtom(k)&&(a[k]=this._readMetadataAtom(b,g));g+=h}}},{key:"_readMetadataAtom",value:function(a,
b){var c=a.getLongAt(b,!0),g=a.getStringAt(b+4,4),h=a.getInteger24At(b+16+1,!0);h=e[h];if("trkn"==g)var f={track:a.getByteAt(b+16+11),total:a.getByteAt(b+16+13)};else if("disk"==g)f={disk:a.getByteAt(b+16+11),total:a.getByteAt(b+16+13)};else{b+=24;var n=c-24;"covr"===g&&"uint8"===h&&(h="jpeg");switch(h){case "text":f=a.getStringWithCharsetAt(b,n,"utf-8").toString();break;case "uint8":f=a.getShortAt(b,!1);break;case "int":case "uint":f=("int"==h?1==n?a.getSByteAt:2==n?a.getSShortAt:4==n?a.getSLongAt:
a.getLongAt:1==n?a.getByteAt:2==n?a.getShortAt:a.getLongAt).call(a,b+(8==n?4:0),!0);break;case "jpeg":case "png":f={format:"image/"+h,data:a.getBytesAt(b,n)}}}return{id:g,size:c,description:d[g]||"Unknown",data:f}}},{key:"getShortcuts",value:function(){return c}}],[{key:"getTagIdentifierByteRange",value:function(){return{offset:0,length:16}}},{key:"canReadTagFormat",value:function(a){return"ftyp"===String.fromCharCode.apply(String,a.slice(4,8))}}]);return b}(q);var e={0:"uint8",1:"text",13:"jpeg",
14:"png",21:"int",22:"uint"},d={"\u00a9alb":"Album","\u00a9ART":"Artist",aART:"Album Artist","\u00a9day":"Release Date","\u00a9nam":"Title","\u00a9gen":"Genre",gnre:"Genre",trkn:"Track Number","\u00a9wrt":"Composer","\u00a9too":"Encoding Tool","\u00a9enc":"Encoded By",cprt:"Copyright",covr:"Cover Art","\u00a9grp":"Grouping",keyw:"Keywords","\u00a9lyr":"Lyrics","\u00a9cmt":"Comment",tmpo:"Tempo",cpil:"Compilation",disk:"Disc Number",tvsh:"TV Show Name",tven:"TV Episode ID",tvsn:"TV Season",tves:"TV Episode",
tvnn:"TV Network",desc:"Description",ldes:"Long Description",sonm:"Sort Name",soar:"Sort Artist",soaa:"Sort Album",soco:"Sort Composer",sosn:"Sort Show",purd:"Purchase Date",pcst:"Podcast",purl:"Podcast URL",catg:"Category",hdvd:"HD Video",stik:"Media Type",rtng:"Content Rating",pgap:"Gapless Playback",apID:"Purchase Account",sfID:"Country Code",atID:"Artist ID",cnID:"Catalog ID",plID:"Collection ID",geID:"Genre ID","xid ":"Vendor Information",flvr:"Codec Flavor"},c={title:"\u00a9nam",artist:"\u00a9ART",
album:"\u00a9alb",year:"\u00a9day",comment:"\u00a9cmt",track:"trkn",genre:"\u00a9gen",picture:"covr",lyrics:"\u00a9lyr"};p.exports=f},{"./MediaFileReader":11,"./MediaTagReader":12}],11:[function(f,p,q){var m=function(){function e(d,c){for(var a=0;a<c.length;a++){var b=c[a];b.enumerable=b.enumerable||!1;b.configurable=!0;"value"in b&&(b.writable=!0);Object.defineProperty(d,b.key,b)}}return function(d,c,a){c&&e(d.prototype,c);a&&e(d,a);return d}}(),l=f("./StringUtils");f=function(){function e(d){if(!(this instanceof
e))throw new TypeError("Cannot call a class as a function");this._isInitialized=!1;this._size=0}m(e,[{key:"init",value:function(d){var c=this;if(this._isInitialized)setTimeout(d.onSuccess,1);else return this._init({onSuccess:function(){c._isInitialized=!0;d.onSuccess()},onError:d.onError})}},{key:"_init",value:function(d){throw Error("Must implement init function");}},{key:"loadRange",value:function(d,c){throw Error("Must implement loadRange function");}},{key:"getSize",value:function(){if(!this._isInitialized)throw Error("init() must be called first.");
return this._size}},{key:"getByteAt",value:function(d){throw Error("Must implement getByteAt function");}},{key:"getBytesAt",value:function(d,c){for(var a=Array(c),b=0;b<c;b++)a[b]=this.getByteAt(d+b);return a}},{key:"isBitSetAt",value:function(d,c){return 0!=(this.getByteAt(d)&1<<c)}},{key:"getSByteAt",value:function(d){d=this.getByteAt(d);return 127<d?d-256:d}},{key:"getShortAt",value:function(d,c){d=c?(this.getByteAt(d)<<8)+this.getByteAt(d+1):(this.getByteAt(d+1)<<8)+this.getByteAt(d);0>d&&(d+=
65536);return d}},{key:"getSShortAt",value:function(d,c){d=this.getShortAt(d,c);return 32767<d?d-65536:d}},{key:"getLongAt",value:function(d,c){var a=this.getByteAt(d),b=this.getByteAt(d+1),e=this.getByteAt(d+2);d=this.getByteAt(d+3);c=c?(((a<<8)+b<<8)+e<<8)+d:(((d<<8)+e<<8)+b<<8)+a;0>c&&(c+=4294967296);return c}},{key:"getSLongAt",value:function(d,c){d=this.getLongAt(d,c);return 2147483647<d?d-4294967296:d}},{key:"getInteger24At",value:function(d,c){var a=this.getByteAt(d),b=this.getByteAt(d+1);
d=this.getByteAt(d+2);c=c?((a<<8)+b<<8)+d:((d<<8)+b<<8)+a;0>c&&(c+=16777216);return c}},{key:"getStringAt",value:function(d,c){for(var a=[],b=d,e=0;b<d+c;b++,e++)a[e]=String.fromCharCode(this.getByteAt(b));return a.join("")}},{key:"getStringWithCharsetAt",value:function(d,c,a){d=this.getBytesAt(d,c);switch((a||"").toLowerCase()){case "utf-16":case "utf-16le":case "utf-16be":a=l.readUTF16String(d,"utf-16be"===a);break;case "utf-8":a=l.readUTF8String(d);break;default:a=l.readNullTerminatedString(d)}return a}},
{key:"getCharAt",value:function(d){return String.fromCharCode(this.getByteAt(d))}},{key:"getSynchsafeInteger32At",value:function(d){var c=this.getByteAt(d),a=this.getByteAt(d+1),b=this.getByteAt(d+2);return this.getByteAt(d+3)&127|(b&127)<<7|(a&127)<<14|(c&127)<<21}}],[{key:"canReadFile",value:function(d){throw Error("Must implement canReadFile function");}}]);return e}();p.exports=f},{"./StringUtils":13}],12:[function(f,p,q){var m=function(){function f(e,d){for(var c=0;c<d.length;c++){var a=d[c];
a.enumerable=a.enumerable||!1;a.configurable=!0;"value"in a&&(a.writable=!0);Object.defineProperty(e,a.key,a)}}return function(e,d,c){d&&f(e.prototype,d);c&&f(e,c);return e}}();f("./MediaFileReader");f=function(){function f(e){if(!(this instanceof f))throw new TypeError("Cannot call a class as a function");this._mediaFileReader=e;this._tags=null}m(f,[{key:"setTagsToRead",value:function(e){this._tags=e;return this}},{key:"read",value:function(e){var d=this;this._mediaFileReader.init({onSuccess:function(){d._loadData(d._mediaFileReader,
{onSuccess:function(){try{var c=d._parseData(d._mediaFileReader,d._tags)}catch(a){if(e.onError){e.onError({type:"parseData",info:a.message});return}}e.onSuccess(c)},onError:e.onError})},onError:e.onError})}},{key:"getShortcuts",value:function(){return{}}},{key:"_loadData",value:function(e,d){throw Error("Must implement _loadData function");}},{key:"_parseData",value:function(e,d){throw Error("Must implement _parseData function");}},{key:"_expandShortcutTags",value:function(e){if(!e)return null;for(var d=
[],c=this.getShortcuts(),a=0,b;b=e[a];a++)d=d.concat(c[b]||[b]);return d}}],[{key:"getTagIdentifierByteRange",value:function(){throw Error("Must implement");}},{key:"canReadTagFormat",value:function(e){throw Error("Must implement");}}]);return f}();p.exports=f},{"./MediaFileReader":11}],13:[function(f,p,q){var m=function(){function e(d,c){for(var a=0;a<c.length;a++){var b=c[a];b.enumerable=b.enumerable||!1;b.configurable=!0;"value"in b&&(b.writable=!0);Object.defineProperty(d,b.key,b)}}return function(d,
c,a){c&&e(d.prototype,c);a&&e(d,a);return d}}(),l=function(){function e(d,c){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function");this._value=d;this.bytesReadCount=c;this.length=d.length}m(e,[{key:"toString",value:function(){return this._value}}]);return e}();p.exports={readUTF16String:function(e,d,c){var a=0,b=1,g=0;c=Math.min(c||e.length,e.length);254==e[0]&&255==e[1]?(d=!0,a=2):255==e[0]&&254==e[1]&&(d=!1,a=2);d&&(b=0,g=1);d=[];for(var h=0;a<c;h++){var f=e[a+b],m=(f<<
8)+e[a+g];a+=2;if(0==m)break;else 216>f||224<=f?d[h]=String.fromCharCode(m):(f=(e[a+b]<<8)+e[a+g],a+=2,d[h]=String.fromCharCode(m,f))}return new l(d.join(""),a)},readUTF8String:function(e,d){var c=0;d=Math.min(d||e.length,e.length);239==e[0]&&187==e[1]&&191==e[2]&&(c=3);for(var a=[],b=0;c<d;b++){var g=e[c++];if(0==g)break;else if(128>g)a[b]=String.fromCharCode(g);else if(194<=g&&224>g){var h=e[c++];a[b]=String.fromCharCode(((g&31)<<6)+(h&63))}else if(224<=g&&240>g){h=e[c++];var f=e[c++];a[b]=String.fromCharCode(((g&
255)<<12)+((h&63)<<6)+(f&63))}else if(240<=g&&245>g){h=e[c++];f=e[c++];var m=e[c++];f=((g&7)<<18)+((h&63)<<12)+((f&63)<<6)+(m&63)-65536;a[b]=String.fromCharCode((f>>10)+55296,(f&1023)+56320)}}return new l(a.join(""),c)},readNullTerminatedString:function(e,d){var c=[];d=d||e.length;for(var a=0;a<d;){var b=e[a++];if(0==b)break;c[a-1]=String.fromCharCode(b)}return new l(c.join(""),a)}}},{}],14:[function(f,p,q){function m(d,c){if("function"!==typeof c&&null!==c)throw new TypeError("Super expression must either be null or a function, not "+
typeof c);d.prototype=Object.create(c&&c.prototype,{constructor:{value:d,enumerable:!1,writable:!0,configurable:!0}});c&&(Object.setPrototypeOf?Object.setPrototypeOf(d,c):d.__proto__=c)}var l=function(){function d(d,a){for(var b=0;b<a.length;b++){var c=a[b];c.enumerable=c.enumerable||!1;c.configurable=!0;"value"in c&&(c.writable=!0);Object.defineProperty(d,c.key,c)}}return function(c,a,b){a&&d(c.prototype,a);b&&d(c,b);return c}}(),e=f("./ChunkedFileData");q=function(d){function c(a){if(!(this instanceof
c))throw new TypeError("Cannot call a class as a function");var b=(c.__proto__||Object.getPrototypeOf(c)).call(this);if(!this)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");b=!b||"object"!==typeof b&&"function"!==typeof b?this:b;b._url=a;b._fileData=new e;return b}m(c,d);l(c,[{key:"_init",value:function(a){c._config.avoidHeadRequests?this._fetchSizeWithGetRequest(a):this._fetchSizeWithHeadRequest(a)}},{key:"_fetchSizeWithHeadRequest",value:function(a){var b=
this;this._makeXHRRequest("HEAD",null,{onSuccess:function(c){(c=b._parseContentLength(c))?(b._size=c,a.onSuccess()):b._fetchSizeWithGetRequest(a)},onError:a.onError})}},{key:"_fetchSizeWithGetRequest",value:function(a){var b=this,c=this._roundRangeToChunkMultiple([0,0]);this._makeXHRRequest("GET",c,{onSuccess:function(c){var d=b._parseContentRange(c);c=b._getXhrResponseContent(c);if(d){if(null==d.instanceLength){b._fetchEntireFile(a);return}b._size=d.instanceLength}else b._size=c.length;b._fileData.addData(0,
c);a.onSuccess()},onError:a.onError})}},{key:"_fetchEntireFile",value:function(a){var b=this;this._makeXHRRequest("GET",null,{onSuccess:function(c){c=b._getXhrResponseContent(c);b._size=c.length;b._fileData.addData(0,c);a.onSuccess()},onError:a.onError})}},{key:"_getXhrResponseContent",value:function(a){return a.responseBody||a.responseText||""}},{key:"_parseContentLength",value:function(a){a=this._getResponseHeader(a,"Content-Length");return null==a?a:parseInt(a,10)}},{key:"_parseContentRange",value:function(a){if(a=
this._getResponseHeader(a,"Content-Range")){var b=a.match(/bytes (\d+)-(\d+)\/(?:(\d+)|\*)/i);if(!b)throw Error("FIXME: Unknown Content-Range syntax: "+a);return{firstBytePosition:parseInt(b[1],10),lastBytePosition:parseInt(b[2],10),instanceLength:b[3]?parseInt(b[3],10):null}}return null}},{key:"loadRange",value:function(a,b){var c=this;c._fileData.hasDataRange(a[0],Math.min(c._size,a[1]))?setTimeout(b.onSuccess,1):(a=this._roundRangeToChunkMultiple(a),a[1]=Math.min(c._size,a[1]),this._makeXHRRequest("GET",
a,{onSuccess:function(d){d=c._getXhrResponseContent(d);c._fileData.addData(a[0],d);b.onSuccess()},onError:b.onError}))}},{key:"_roundRangeToChunkMultiple",value:function(a){return[a[0],a[0]+1024*Math.ceil((a[1]-a[0]+1)/1024)-1]}},{key:"_makeXHRRequest",value:function(a,b,d){var e=this._createXHRObject();e.open(a,this._url);var f=function(){if(200===e.status||206===e.status)d.onSuccess(e);else if(d.onError)d.onError({type:"xhr",info:"Unexpected HTTP status "+e.status+".",xhr:e});e=null};"undefined"!==
typeof e.onload?(e.onload=f,e.onerror=function(){if(d.onError)d.onError({type:"xhr",info:"Generic XHR error, check xhr object.",xhr:e})}):e.onreadystatechange=function(){4===e.readyState&&f()};c._config.timeoutInSec&&(e.timeout=1E3*c._config.timeoutInSec,e.ontimeout=function(){if(d.onError)d.onError({type:"xhr",info:"Timeout after "+e.timeout/1E3+"s. Use jsmediatags.Config.setXhrTimeout to override.",xhr:e})});e.overrideMimeType("text/plain; charset=x-user-defined");b&&this._setRequestHeader(e,"Range",
"bytes="+b[0]+"-"+b[1]);this._setRequestHeader(e,"If-Modified-Since","Sat, 01 Jan 1970 00:00:00 GMT");e.send(null)}},{key:"_setRequestHeader",value:function(a,b,d){0>c._config.disallowedXhrHeaders.indexOf(b.toLowerCase())&&a.setRequestHeader(b,d)}},{key:"_hasResponseHeader",value:function(a,b){a=a.getAllResponseHeaders();if(!a)return!1;a=a.split("\r\n");for(var c=[],d=0;d<a.length;d++)c[d]=a[d].split(":")[0].toLowerCase();return 0<=c.indexOf(b.toLowerCase())}},{key:"_getResponseHeader",value:function(a,
b){return this._hasResponseHeader(a,b)?a.getResponseHeader(b):null}},{key:"getByteAt",value:function(a){return this._fileData.getByteAt(a).charCodeAt(0)&255}},{key:"_isWebWorker",value:function(){return"undefined"!==typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope}},{key:"_createXHRObject",value:function(){if("undefined"===typeof window&&!this._isWebWorker())return new (f("xhr2").XMLHttpRequest);if("undefined"!==typeof XMLHttpRequest)return new XMLHttpRequest;throw Error("XMLHttpRequest is not supported");
}}],[{key:"canReadFile",value:function(a){return"string"===typeof a&&/^[a-z]+:\/\//i.test(a)}},{key:"setConfig",value:function(a){for(var b in a)a.hasOwnProperty(b)&&(this._config[b]=a[b]);a=this._config.disallowedXhrHeaders;for(b=0;b<a.length;b++)a[b]=a[b].toLowerCase()}}]);return c}(f("./MediaFileReader"));q._config={avoidHeadRequests:!1,disallowedXhrHeaders:[],timeoutInSec:30};p.exports=q},{"./ChunkedFileData":5,"./MediaFileReader":11,xhr2:2}],15:[function(f,p,q){function m(a,b){if(!(a instanceof
b))throw new TypeError("Cannot call a class as a function");}function l(a,b){var c=0>a.offset&&(-a.offset>b||0<a.offset+a.length);return!(0<=a.offset&&a.offset+a.length>=b||c)}var e=function(){function a(a,b){for(var c=0;c<b.length;c++){var d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;"value"in d&&(d.writable=!0);Object.defineProperty(a,d.key,d)}}return function(b,c,d){c&&a(b.prototype,c);d&&a(b,d);return b}}();f("./MediaFileReader");q=f("./NodeFileReader");var d=f("./XhrFileReader"),c=
f("./BlobFileReader"),a=f("./ArrayFileReader");f("./MediaTagReader");var b=f("./ID3v1TagReader"),g=f("./ID3v2TagReader"),h=f("./MP4TagReader");f=f("./FLACTagReader");var k=[],r=[],u=function(){function a(b){m(this,a);this._file=b}e(a,[{key:"setTagsToRead",value:function(a){this._tagsToRead=a;return this}},{key:"setFileReader",value:function(a){this._fileReader=a;return this}},{key:"setTagReader",value:function(a){this._tagReader=a;return this}},{key:"read",value:function(a){var b=new (this._getFileReader())(this._file),
c=this;b.init({onSuccess:function(){c._getTagReader(b,{onSuccess:function(d){(new d(b)).setTagsToRead(c._tagsToRead).read(a)},onError:a.onError})},onError:a.onError})}},{key:"_getFileReader",value:function(){return this._fileReader?this._fileReader:this._findFileReader()}},{key:"_findFileReader",value:function(){for(var a=0;a<k.length;a++)if(k[a].canReadFile(this._file))return k[a];throw Error("No suitable file reader found for "+this._file);}},{key:"_getTagReader",value:function(a,b){if(this._tagReader){var c=
this._tagReader;setTimeout(function(){b.onSuccess(c)},1)}else this._findTagReader(a,b)}},{key:"_findTagReader",value:function(a,b){for(var c=[],d=[],e=a.getSize(),f=0;f<r.length;f++){var g=r[f].getTagIdentifierByteRange();l(g,e)&&(0<=g.offset&&g.offset<e/2||0>g.offset&&g.offset<-e/2?c.push(r[f]):d.push(r[f]))}var h=!1;f={onSuccess:function(){if(h){for(var c=0;c<r.length;c++){var d=r[c].getTagIdentifierByteRange();if(l(d,e)){try{var f=a.getBytesAt(0<=d.offset?d.offset:d.offset+e,d.length)}catch(A){if(b.onError)b.onError({type:"fileReader",
info:A.message});return}if(r[c].canReadTagFormat(f)){b.onSuccess(r[c]);return}}}if(b.onError)b.onError({type:"tagFormat",info:"No suitable tag reader found"})}else h=!0},onError:b.onError};this._loadTagIdentifierRanges(a,c,f);this._loadTagIdentifierRanges(a,d,f)}},{key:"_loadTagIdentifierRanges",value:function(a,b,c){if(0===b.length)setTimeout(c.onSuccess,1);else{for(var d=[Number.MAX_VALUE,0],e=a.getSize(),f=0;f<b.length;f++){var g=b[f].getTagIdentifierByteRange(),h=0<=g.offset?g.offset:g.offset+
e;g=h+g.length-1;d[0]=Math.min(h,d[0]);d[1]=Math.max(g,d[1])}a.loadRange(d,c)}}}]);return a}(),v=function(){function a(){m(this,a)}e(a,null,[{key:"addFileReader",value:function(b){k.push(b);return a}},{key:"addTagReader",value:function(b){r.push(b);return a}},{key:"removeTagReader",value:function(b){b=r.indexOf(b);0<=b&&r.splice(b,1);return a}},{key:"EXPERIMENTAL_avoidHeadRequests",value:function(){d.setConfig({avoidHeadRequests:!0})}},{key:"setDisallowedXhrHeaders",value:function(a){d.setConfig({disallowedXhrHeaders:a})}},
{key:"setXhrTimeoutInSec",value:function(a){d.setConfig({timeoutInSec:a})}}]);return a}();v.addFileReader(d).addFileReader(c).addFileReader(a).addTagReader(g).addTagReader(b).addTagReader(h).addTagReader(f);"undefined"===typeof process||process.browser||v.addFileReader(q);p.exports={read:function(a,b){(new u(a)).read(b)},Reader:u,Config:v}},{"./ArrayFileReader":3,"./BlobFileReader":4,"./FLACTagReader":6,"./ID3v1TagReader":7,"./ID3v2TagReader":9,"./MP4TagReader":10,"./MediaFileReader":11,"./MediaTagReader":12,
"./NodeFileReader":1,"./XhrFileReader":14}]},{},[15])(15)});

2487
out/js/scripts.js Normal file

File diff suppressed because it is too large Load diff

1
out/js/scripts.min.js vendored Normal file

File diff suppressed because one or more lines are too long

217
out/js/worker.js Normal file
View file

@ -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();

1
out/js/worker.min.js vendored Normal file
View file

@ -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();

1
out/theme/style.css Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,4 @@
<div class="audio-item">
<span class="artist">$artist$</span>
<span class="title">$title$</span>
</div>

View file

@ -0,0 +1,3 @@
<div class="config-content">
$content$
</div>

5
out/tpl/config/nav.tpl Normal file
View file

@ -0,0 +1,5 @@
<nav class="config-nav">
<div class="item active" data-id="base">Base</div>
<div class="item" data-id="visuals">Visuals</div>
<div class="item" data-id="visual">Visual</div>
</nav>

View file

@ -0,0 +1,8 @@
<div class="visual-item $active$" data-id="$id$">
<!-- maybe add later a image?
<div class="image">
<img src="$image$">
</div>
-->
$title$
</div>

69
out/tpl/help.tpl Normal file
View file

@ -0,0 +1,69 @@
<!-- help for every key! -->
<div class="help-list">
<div class="item">
<span class="h2">h</span>
<p>
Open Help Modal
</p>
</div>
<div class="item">
<span class="h2">Space</span>
<p>
Switch between pause and play
</p>
</div>
<div class="item">
<span class="h2">n</span>
<p>
Play next song
</p>
</div>
<div class="item">
<span class="h2">v</span>
<p>
Play previous song
</p>
</div>
<div class="item">
<span class="h2">s</span>
<p>
Open Settings Modal
</p>
</div>
<div class="item">
<span class="h2">Shift + s</span>
<p>
Toggle Shuffle
</p>
</div>
<div class="item">
<span class="h2">b</span>
<p>
Open background settings Modal
</p>
</div>
<div class="item">
<span class="h2">Shift + f</span>
<p>
Force Song Tagger!
</p>
</div>
<div class="item">
<span class="h2">p</span>
<p>
Show Playlist Modal
</p>
</div>
<div class="item">
<span class="h2">ESC or Shift + C</span>
<p>
Close current open Modal
</p>
</div>
<div class="item">
<span class="h2">F11</span>
<p>
Toggle Fullscreen and hide GUI
</p>
</div>
</div>

30
out/tpl/image.tpl Normal file
View file

@ -0,0 +1,30 @@
<div id="image-upload">
<form>
<label for="image" class="button spaced">
Image-Upload (Only Local!)
</label>
<input type="file" id="image" accept="image/*|video/*">
<label class="input floating-label">
<input type="url" id="url" autocomplete="off" value="$value$">
<span class="input-label">URL</span>
<span class="focus"></span>
</label>
<p>You only want to change the color? do it!</p>
<label class="input floating-label color-picker" for="color">
<span class="colorBlob" style="background-color: $bgValue$"></span>
<input type="color" id="color" value="$bgValue$">
<span class="input-label">Color</span>
<span class="focus"></span>
</label>
<label class="input floating-label input-range">
<input class="range" type="range" id="alphaValue" min="0" max="1" value="$alphaValue$" step="0.1">
<span class="input-label">Alpha</span>
<span class="min">0</span>
<span class="current">$alphaValue$</span>
<span class="max">1</span>
</label>
</form>
</div>

6
out/tpl/inputs/color.tpl Normal file
View file

@ -0,0 +1,6 @@
<label class="input floating-label color-picker" for="$name$">
<span class="colorBlob" style="background-color: $value$"></span>
<input data-type="$dataType$" type="color" id="$name$" name="$name$" value="$value$">
<span class="input-label">$showName$</span>
<span class="focus"></span>
</label>

5
out/tpl/inputs/input.tpl Normal file
View file

@ -0,0 +1,5 @@
<label class="input floating-label" for="$name$">
<input data-type="$dataType$" type="$type$" id="$name$" value="$value$">
<span class="input-label">$showName$</span>
<span class="focus"></span>
</label>

View file

@ -0,0 +1,3 @@
<custom-option data-value="$value$">
$value$
</custom-option>

View file

@ -0,0 +1,8 @@
<custom-select data-event="$event$" data-conf="$conf$">
<label>$showName$</label>
<main class="label">$value$</main>
<input data-type="$dataType$" type="text" name="$name$" value="$value$" required>
<custom-options>
$options$
</custom-options>
</custom-select>

View file

@ -0,0 +1,7 @@
<label class="input floating-label input-range">
<input data-type="$dataType$" class="range" type="range" name="$name$" min="$min$" max="$max$" value="$value$" step="$stepSize$">
<span class="input-label">$showName$</span>
<span class="min">$min$</span>
<span class="current">$value$</span>
<span class="max">$max$</span>
</label>

View file

@ -0,0 +1,5 @@
<switch>
<input id="$name$" name="$name$" type="checkbox" $value$>
<label for="$name$"></label>
<span>$showName$</span>
</switch>

4
out/tpl/notification.tpl Normal file
View file

@ -0,0 +1,4 @@
<div class="message">
$message$
</div>
<div class="fade-bar $type$" style="animation-duration: $time$ms"></div>

View file

@ -0,0 +1,20 @@
<playlist>
<div class="pagination">
<div class="item prev-site $prevActive$">
<svg role="img" class="icon">
<use href="out/icon-sprite.svg#fal-fa-caret-left"></use>
</svg>
</div>
<div class="item current inactive">
$page$
</div>
<div class="item next-site $nextActive$">
<svg role="img" class="icon">
<use href="out/icon-sprite.svg#fal-fa-caret-right"></use>
</svg>
</div>
</div>
<div class="readAll button">
Force Tagger
</div>
</playlist>

View file

@ -0,0 +1,4 @@
<playlist-item class="playlist-item $active$" data-index="$index$">
<div class="playlist-item-number">$nr$</div>
<div class="playlist-item-title">$title$</div>
</playlist-item>

5
out/tpl/playlist.tpl Normal file
View file

@ -0,0 +1,5 @@
<playlist>
<div class="playlist-content">
$content$
</div>
</playlist>

39
raw/gui/base.json Normal file
View file

@ -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!"
}
]

135
raw/gui/sphere.json Normal file
View file

@ -0,0 +1,135 @@
[
{
"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 <br> 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,
"value": 255
},
{
"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"
}
]

102
raw/gui/wave.json Normal file
View file

@ -0,0 +1,102 @@
[
{
"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": "light",
"name": [
"X",
"Y",
"Z"
],
"props": [
"x",
"y",
"z"
],
"type": "slider",
"max": 100,
"min": -100,
"value": 0,
"dataType": "int"
},
{
"group": "light",
"name": "light-strength",
"showName": "Light Brightness",
"type": "slider",
"value": 0.3,
"max": 1,
"min": 0,
"stepSize": 0.05,
"tooltip": "brightness of light-point",
"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!"
}
]

104
raw/gui/wave2d.json Normal file
View file

@ -0,0 +1,104 @@
[
{
"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": "translate",
"name": [
"X",
"Y",
"Z"
],
"props": [
"x",
"y",
"z"
],
"type": "slider",
"max": 1,
"min": -1,
"value": 0,
"stepSize": 0.01,
"dataType": "float"
},
{
"group": "",
"name": "fudgeFactor",
"showName": "Fudge Factor",
"type": "slider",
"value": 1,
"max": 2,
"min": 0,
"tooltip": "z to w Fudge",
"stepSize": 0.1,
"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!"
}
]

0
raw/icons/.gitkeep Normal file
View file

View file

@ -0,0 +1,36 @@
class FetchHandler {
static files = {};
static async loadFiles(array, isJSON) {
let content = [];
for (let i = 0; i < array; i++) {
content.push(await FetchHandler.loadFile(array[i], isJSON));
}
return content;
}
static async loadFile(filename, isJSON) {
filename += '?v=' + version;
let files = FetchHandler.files;
if (files[filename]) {
return files[filename];
}
let data = await FetchHandler.tryFromCache(filename);
if (isJSON) {
data = JSON.parse(data);
}
files[filename] = data;
return data;
}
static async tryFromCache(filename) {
if (caches) {
let cache = await caches.open('vis3d-pwa-1');
let data = await cache.match(filename);
if (!data) {
data = await fetch(filename);
}
return await data.text();
}
}
}

View file

@ -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);
}
}

63
raw/javascript/app.js Normal file
View file

@ -0,0 +1,63 @@
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(),
version = 1,
camera = new Camera();
let c, gl, cInfo, ctx, sw;
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() {
if ('serviceWorker' in navigator) {
sw = await navigator.serviceWorker.register('/sw.js');
}
pConf.loadConfigByName('default');
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", "wave2d"], '/shaders/');
await NotificationHandler.instance.init();
await audioHandler.init();
await player.init();
await camera.init();
await visual.init();
await gui.init();
await imageUploader.init();
await playerConf.init();
await keyHandler.init();
await initHandler();
toggleShuffle(false);
}
startUP().then(r => {
startup.moduleLoaded('startup');
});

84
raw/javascript/audio.js Normal file
View file

@ -0,0 +1,84 @@
const AudioContext = window.AudioContext || window.webkitAudioContext;
class AudioHandler {
async init() {
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(file) {
if (!file) {
NotificationHandler.createNotification("Sorry!<br> Currently no Song is uploaded!", "error", 2000);
return false;
}
let self = this,
src = file.file;
if (self.lastSong) {
URL.revokeObjectURL(self.lastSong);
}
self.lastSong = this.audioFile.src = URL.createObjectURL(src);
if (!this.isStarted) {
this.start().catch(alert);
}
this.audioFile.play().then(e => {
if (pConf.get("showPlaying", "true")) {
NotificationHandler.createNotification("<span class='now-playing'>Now Playing:</span>" + file.getAudioName(), "info", pConf.get("showPlayingTime", 1000));
}
window.dispatchEvent(new CustomEvent('playSong'));
}).catch(e => {
NotificationHandler.createNotification(e.message, "error", 1000);
player.nextSong();
});
}
getIntArray(steps) {
let dataArray = new Uint8Array(steps);
this.analyser.getByteFrequencyData(dataArray);
return dataArray;
}
getFloatArray() {
let dataArray = new Float32Array(this.analyser.fftSize);
this.analyser.getFloatTimeDomainData(dataArray);
return dataArray;
}
}

48
raw/javascript/config.js Normal file
View file

@ -0,0 +1,48 @@
class Config {
static allConfigs = {};
constructor(type) {
this.config = {};
this.name = ''
this.type = type;
Config.allConfigs[type] = this;
}
loadConfigByName(name) {
this.save();
this.name = 'config-' + name;
let item = localStorage.getItem(this.name);
if (item) {
this.config = JSON.parse(item);
}
}
save() {
if (this.name !== '') {
localStorage.setItem(this.name, JSON.stringify(this.config));
}
}
set(name, value) {
this.config[name] = value;
}
remove(name) {
delete this.config[name];
}
get(name, def) {
let value = this.config[name];
if (value === undefined || value === null) {
this.config[name] = def;
value = def;
}
return value;
}
reset() {
NotificationHandler.createNotification(`CONFIG REQUEST SUCCESS FOR ${this.type}`, "success", 2000);
this.config = {};
this.save();
}
}

View file

@ -0,0 +1,185 @@
class EventHandler {
constructor() {
this.events = {};
}
addEvent(events, cb) {
let names = events.split(",");
for (let name of names) {
this.events[name.trim()] = cb;
}
}
sendData(name, data) {
worker.postMessage({
cmd: name,
data: data
});
}
handleEvent(event) {
let data = event.data;
if (!data.cmd) {
return false;
}
if (this.events[data.cmd]) {
try {
this.events[data.cmd](data.data);
} catch (e) {
console.error('[EventHandler] > ' + e.message);
}
return true;
}
return false;
}
}
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) => {
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;
case 'shuffle':
toggleShuffle();
break;
}
});
window.addEventListener('playSong', setActiveOnPlaylist);
window.addEventListener('playSong', e => {
togglePlayButton(audioHandler.audioFile.paused ? 'play' : 'pause');
});
$('.upload-image').addEventListener('click', imageUploader.renderModal.bind(imageUploader));
body.addDelegatedEventListener('click', '.readAll', forceAllRead);
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;
}
})
$('.help.menu-icon').addEventListener('click', gui.openHelp);
document.onfullscreenchange = e => {
if (body.hasClass('fullscreen')) {
body.removeClass('fullscreen')
} else {
body.addClass('fullscreen')
}
}
}
function forceAllRead() {
let playlist = player.playlist.list;
for (let i = 0; i < playlist.length; i++) {
playlist[i].getID3Tag(true);
}
}
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) {
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(updateGUI) {
let active = player.playlist.isShuffle;
if (updateGUI !== false) {
active = !active;
let status = active ? 'enabled' : 'disabled';
NotificationHandler.createNotification("Shuffle: " + status, "info", 500);
pConf.set("shuffle", active);
pConf.save();
player.playlist.isShuffle = active;
}
$('#shuffle').toggleCheck('active', active);
}
function togglePlayButton(status) {
let icons = $$('#play .icon');
icons.forEach(el => {
if (el.dataset.name === status) {
el.removeClass('hide');
} else {
el.addClass('hide');
}
})
}

View file

@ -0,0 +1,78 @@
class Camera {
constructor() {
this.mouse;
this.rotation = {
x: 0,
y: 0
}
this.lastMouse;
this.mousePressed = false;
this.translate = {
x: 0,
y: 0,
z: 0
}
}
async init() {
this.mouse = {
x: 0,
y: 0
}
window.addEventListener('mousedown', this.mouseDown.bind(this));
window.addEventListener('mouseup', this.mouseUp.bind(this));
window.addEventListener('mousemove', this.mouseMove.bind(this), {passive: true});
eventHandler.addEvent('keys-ArrowUp, keys-ArrowDown, keys-ArrowLeft, keys-ArrowRight, keys-KeyQ, keys-KeyE', this.keyPressed.bind(this));
}
mouseDown() {
this.mousePressed = true;
this.lastMouse = null;
}
mouseUp() {
this.mousePressed = false;
this.lastMouse = null;
}
mouseMove(event) {
if (!this.mousePressed || gui.modal.open) {
return;
}
if (this.lastMouse) {
let mouse = this.mouse,
rotate = this.rotation;
mouse.x += (this.lastMouse.x - event.clientX) * 0.2;
mouse.y += (this.lastMouse.y - event.clientY) * 0.2;
rotate.x = VTUtils.map(mouse.x, -c.width, c.width, 180, -180, false);
rotate.y = VTUtils.map(mouse.y, -c.height, c.height, 180, -180, false);
}
this.lastMouse = {
x: event.clientX,
y: event.clientY
}
}
keyPressed(data) {
switch (data) {
case 'keys-ArrowUp':
this.translate.z += 10;
break;
case 'keys-ArrowDown':
this.translate.z -= 10;
break;
case 'keys-ArrowLeft':
this.translate.x -= 10;
break;
case 'keys-ArrowRight':
this.translate.x += 10;
break;
case 'keys-KeyQ':
this.translate.y += 10;
break;
case 'keys-KeyE':
this.translate.y -= 10;
break;
}
}
}

View file

@ -0,0 +1,391 @@
// 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];
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(angle) {
angle = TDUtils.degToRad(angle);
let c = Math.cos(angle);
let s = Math.sin(angle);
return [
1, 0, 0, 0,
0, c, s, 0,
0, -s, c, 0,
0, 0, 0, 1,
];
}
static yRotation(angle) {
angle = TDUtils.degToRad(angle);
let c = Math.cos(angle);
let s = Math.sin(angle);
return [
c, 0, -s, 0,
0, 1, 0, 0,
s, 0, c, 0,
0, 0, 0, 1,
];
}
static zRotation(angle) {
angle = TDUtils.degToRad(angle);
let c = Math.cos(angle);
let s = Math.sin(angle);
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 projection(width, height, depth) {
return [
2 / width, 0, 0, 0,
0, -2 / height, 0, 0,
0, 0, 2 / depth, 0,
-1, 1, 0, 1,
];
}
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 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);
}
static makeZToWMatrix(fudgeFactor) {
return [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, fudgeFactor,
0, 0, 0, 1,
];
}
}
class GLHelper {
constructor(program) {
this.matrix = new Float32Array(16);
this.program = program;
}
static uniform4fv(program, name, data) {
let uniform = gl.getUniformLocation(program, name);
gl.uniform4fv(uniform, data);
}
static uniform3fv(program, name, data) {
let uniform = gl.getUniformLocation(program, name);
gl.uniform3fv(uniform, data);
}
static uniform1f(program, name, data) {
let uniform = gl.getUniformLocation(program, name);
gl.uniform1f(uniform, data);
}
rotateX(deg) {
this.matrix = TDUtils.multiply(this.matrix, TDUtils.xRotation(deg));
}
rotateY(deg) {
this.matrix = TDUtils.multiply(this.matrix, TDUtils.yRotation(deg));
}
rotateZ(deg) {
this.matrix = TDUtils.multiply(this.matrix, TDUtils.zRotation(deg));
}
scale(scaling) {
this.matrix = TDUtils.multiply(this.matrix, TDUtils.scale(scaling[0], scaling[1], scaling[2]))
}
project(depth) {
depth = depth || (c.width > c.height) ? c.width : c.height;
this.matrix = TDUtils.projection(c.width, c.height, depth)
}
translate(t) {
this.matrix = TDUtils.translate(this.matrix, t[0] || 0, t[1] || 0, t[2] || 0);
}
addFudgeFactor(fudgeFactor) {
this.matrix = TDUtils.multiply(TDUtils.makeZToWMatrix(fudgeFactor), this.matrix);
}
applyMatrix() {
let matrix = gl.getUniformLocation(this.program, "u_matrix");
gl.uniformMatrix4fv(matrix, false, this.matrix);
}
}

View file

@ -0,0 +1,100 @@
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 FetchHandler.loadFile(url, false);
let shader = this.createShader(data, 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);
if (gl.getProgramParameter(pro, gl.LINK_STATUS)) {
this.programs[name] = pro;
return pro;
}
console.log(gl.getProgramInfoLog(pro));
gl.deleteProgram(pro);
return null;
}
getProgram(name) {
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) {
await self.loadShader(e, path)
}
await self.createProgramForEach(list)
}
}

207
raw/javascript/gui.js Normal file
View file

@ -0,0 +1,207 @@
class GUI {
async init() {
this.data = {};
this.modal = new Modal();
// load first vis window!
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() {
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) {
e.dataTransfer.id = 'upload-dir';
player.playlist.changeFiles(e, e.dataTransfer);
} else {
alert("Sorry you need to upload files!");
}
}
});
});
};
}
// create config Inputs from JSON
//@todo add support for gui grouping!
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 = GUIHelper.richShowName(data, data.name[i].firstUpper());
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 richShowName(data, name) {
if (data.group !== "") {
return data.group.firstUpper() + ' ' + name
}
return name;
}
static createButton(item, conf) {
return `<div class='button spaced' data-action="${item.action}">${item.name}</div>`
}
}
class Modal {
constructor() {
let self = this;
self.currentModal = '';
self.modal = $('#modal');
self.open = false;
self.parent = self.modal.parentNode;
self.modal.addDelegatedEventListener('click', 'header .close', this.closeModal.bind(this));
}
resetModal() {
this.renderModal('', '', '');
}
renderModal(title, content, footer) {
$('#modal').removeClass('lightMode')
this.currentModal = title;
this.renderHeader(title);
this.renderContent(content);
this.renderFooter(footer);
}
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 .inner', this.modal);
con.innerHTML = footer || "by VersusTuneZ";
}
closeModal() {
this.parent.addClass("hide")
this.open = false;
}
isCurrent(title) {
return title === this.currentModal;
}
showModal() {
this.parent.removeClass("hide")
this.open = true;
}
}

View file

@ -0,0 +1,74 @@
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;
}
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();

61
raw/javascript/keys.js Normal file
View file

@ -0,0 +1,61 @@
class KeyHandler {
async init() {
await this.mediaKeys();
await this.addKeyHandler();
window.addEventListener('keydown', this.keyHandler.bind(this));
}
async mediaKeys() {
if ('mediaSession' in navigator) {
let media = navigator.mediaSession;
media.setActionHandler('play', player.playStop.bind(player));
media.setActionHandler('pause', player.playStop.bind(player));
media.setActionHandler('previoustrack', player.prevSong.bind(player));
media.setActionHandler('nexttrack', player.nextSong.bind(player));
media.setActionHandler('stop', player.stop.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 => {
if (document.fullscreenElement) {
document.exitFullscreen().catch(console.error);
} else {
document.body.requestFullscreen().catch(console.error);
}
});
}
async keyHandler(event) {
let key = event.code,
shift = event.shiftKey ? '-shift' : '',
ctrl = event.ctrlKey ? '-ctrl' : '',
name = 'keys-' + key + shift + ctrl;
if (eventHandler.handleEvent({
data: {
cmd: name,
data: name
}
})) {
event.preventDefault();
event.stopPropagation();
}
}
}

View file

@ -0,0 +1,77 @@
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.prepend(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;
}
}
}

237
raw/javascript/player.js Normal file
View file

@ -0,0 +1,237 @@
class Player {
async init() {
this.playlist = new Playlist();
}
nextSong() {
let next = this.playlist.getNext();
audioHandler.loadSong(next);
}
prevSong() {
let next = this.playlist.getPrevious();
audioHandler.loadSong(next);
}
playStop() {
if (!audioHandler.lastSong) {
let next = this.playlist.getCurrent();
audioHandler.loadSong(next);
return;
}
let audioFile = audioHandler.audioFile;
if (audioFile.paused) {
audioFile.play();
} else {
audioFile.pause();
}
window.dispatchEvent(new CustomEvent('playSong'));
}
stop() {
if (!audioHandler.lastSong) {
return;
}
let audioFile = audioHandler.audioFile;
audioFile.pause();
audioFile.currentTime = 0;
window.dispatchEvent(new CustomEvent('playSong'));
}
playByID(number) {
this.playlist.index = number;
let next = this.playlist.getCurrent();
audioHandler.loadSong(next);
}
}
const PAGINATIONLIMIT = 50;
class Playlist {
constructor() {
this.list = [];
this.shuffled = [];
this.index = 0;
this.page = 0;
this.isShuffle = pConf.get("shuffle", 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 = [0, 1, 2];
return;
}
// 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] = b;
this.shuffled[b] = a;
}
getNext() {
let items = this.list,
len = items.length - 1,
next = this.index + 1;
if (next > len) {
next = 0;
}
this.index = next;
return items[this.getRealIndex()];
}
getPrevious() {
let items = this.list,
len = items.length - 1,
next = this.index - 1;
if (next < 0) {
next = len;
}
this.index = next;
return items[this.getRealIndex()];
}
getCurrent() {
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();
}
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) {
if (page === undefined) {
page = this.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.list;
for (let i = s; i < e; i++) {
let obj = {
index: i.toString(),
nr: i + 1,
title: items[this.getRealIndex(i)].getAudioName(),
active: !audioHandler.audioFile.paused && i === this.index ? 'active' : ''
}
data += template.parseTemplate("playlist-item", obj);
}
} else {
data = "<h1>No Songs uploaded!</h1>";
}
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) {
if (el.id !== 'upload-dir') {
return;
}
let files = [];
let i = 0;
for (let file of el.files) {
if (file && file.type.indexOf('audio') !== -1 && file.name.match(".m3u") === null) {
let audioFile = new AudioPlayerFile(file, i++);
files.push(audioFile);
}
}
this.setPlaylist(files);
if (files.length > 0) {
NotificationHandler.createNotification("Songs added successfully!<br> Songs: " + files.length, "success", 3000);
this.renderPagination(0);
} else {
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()
}
}
}

View file

@ -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 = '<section class="visuals">';
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 += '</div>';
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 FetchHandler.loadFile('/out/gui/' + name + ".json", true);
}
return tem[name];
}
}

64
raw/javascript/select.js Normal file
View file

@ -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);
}
}
})()

23
raw/javascript/startup.js Normal file
View file

@ -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;
}
}

View file

@ -0,0 +1,41 @@
/** @deprecated use VTepL instead! */
class Template {
constructor() {
this.tpl = {};
}
async loadTemplate(name) {
let self = this;
if (!this.tpl[name]) {
self.tpl[name] = await FetchHandler.loadFile(templateDir + name + '.tpl', false)
}
}
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];
let value = data[m[1]];
if (value === undefined || value === null) {
value = "";
}
d = d.replace(key, value)
}
return d;
}
}
const templateEx = /\$(.*?)\$/gm;
const templateDir = "/out/tpl/"

View file

@ -0,0 +1,34 @@
'use strict';
class VTpeLCore {
constructor(options = {}) {
this.templates = {};
this.dir = options.path || '/tpl/';
this.suffix = options.suffix || 'tpl';
this.path = options.template || `${this.dir}%s.${this.suffix}`;
this.cache = options.cache === undefined ? true : options.cache;
}
async loadTemplate(name) {
let rawData = await fetch(this.path.replace('%s', name));
if (rawData.ok) {
let data = await rawData.text();
this.addTpl(name, data);
}
}
async loadArray(names) {
for (let name of names) {
await this.loadTemplate(name);
}
}
addTpl(name, content) {
let temp = this.templates[name] = new VTpeLTemplate(name, content)
temp.parseContent(this.cache);
}
renderOn(name, data) {
return this.templates[name].render(data);
}
}

View file

@ -0,0 +1,133 @@
'use strict';
class VTepLInterpreter {
constructor(parser) {
this.parser = parser;
this.data = [];
this.content = '';
}
render(data) {
let self = this;
self.data = data;
let newData = self.interpreter(self.parser.parsed);
self.data = [];
return newData[0];
}
interpreter(parsed, index = 0) {
let self = this;
let types = VParserTypes;
let tplCont = '';
for (let i = index; i < parsed.length; i++) {
let item = parsed[i],
content = item.content;
switch (item.type) {
case types.content:
tplCont += content;
break;
case types.variable:
tplCont += self.getVariable(content)
break;
case types.assign:
let data = content.split("="),
key = data.shift();
self.setVariable(data.join("=").trim(), key.trim());
break;
case types.forEach:
let d = this.handleForEach(item, parsed, i);
i = d[0];
tplCont += d[1];
break;
case types.for:
let fd = this.handleFor(item, parsed, i);
i = fd[0];
tplCont += fd[1];
break;
case types.if:
let id = this.handleIf(item, parsed, i);
i = id[0];
tplCont += id[1];
break;
case types.ifEnd:
tplCont += content;
return [tplCont, i];
case types.forEnd:
tplCont += content;
return [tplCont, i];
default:
console.warn("Invalid Type found");
break;
}
}
//this.content = tplCont;
return [tplCont, parsed.length];
}
getVariable(variable) {
if (this.data[variable]) {
return this.data[variable];
}
let split = variable.split("."),
prevVar = this.data;
for (let i = 0; i < split.length; i++) {
prevVar = prevVar[split[i]] || prevVar;
}
if (typeof prevVar === 'string') {
return prevVar;
}
return '';
}
setVariable(value, variable) {
let c = this.getVariable(value);
if (c !== '') {
value = c;
}
this.data[variable] = value;
}
handleForEach(item, parsed, i) {
let content = item.content.split(" as ");
let root = this.getVariable(content[0].trim());
let addTo = 0,
isInvalid = false;
if (root === '') {
isInvalid = true;
root = [];
}
let d = Object.keys(root),
raw = '';
for (let x of d) {
this.setVariable(root[x], content[1].trim());
let data = this.interpreter(parsed, i + 1);
addTo = data[1];
raw += data[0];
}
if (isInvalid) {
raw = '';
}
return [addTo, raw];
}
handleFor(item, parsed, ind) {
let content = item.content.split(" as "),
addTo = 0,
count = content[0].trim().split(".."),
max = parseInt(count[1]),
min = parseInt(count[0]),
newContent = '';
for (let i = min; i < max; i++) {
this.setVariable(i.toString(), content[1]);
let data = this.interpreter(parsed, ind + 1);
addTo = data[1];
newContent += data[0];
}
return [addTo, newContent];
}
handleIf(item, parsed, i) {
let data = this.interpreter(parsed, i + 1);
return [data[1], data[0]];
}
}

View file

@ -0,0 +1,178 @@
'use strict';
const VParserTypes = {
content: 0,
variable: 1,
for: 2,
forEach: 3,
forContent: 4,
forEnd: 5,
if: 6,
ifContent: 7,
ifEnd: 8,
assign: 9,
none: -1,
// operators
'!=': 100,
'==': 101,
'&&': 102,
'||': 103,
'>=': 104,
'<=': 105,
'>': 106,
'<': 107,
'+': 108,
'-': 109,
'*': 110,
'/': 111,
'%': 112
};
class VTpeLParser {
constructor(name, content) {
let self = this;
self.name = name;
self.legex = content;
self.index = 0;
self.content = '';
self.parsed = [];
self.contexts = [0];
self.allowedOperators = ['!=', '==', '&&', '||', '>=', '<=', '>', '<', '+', '-', '*', '/', '%']
}
tokenize() {
let self = this;
for (self.index = 0; self.index < self.legex.length; self.index++) {
let i = self.index,
char = self.legex.charAt(i);
if (self.nextContains('/*', i, true)) {
self.extract('*/', VParserTypes.none)
} else if (self.nextContains('//', i, true)) {
self.extract('\n', VParserTypes.none);
} else if (self.nextContains('<!--', i, true)) {
self.extract('-->', VParserTypes.none);
} else if (self.nextContains('{for(', i, true)) {
self.extract(')}', VParserTypes.for);
self.contexts.push(VParserTypes.for);
} else if (self.nextContains('{foreach(', i, true)) {
self.extract(')}', VParserTypes.forEach);
self.contexts.push(VParserTypes.forEach);
} else if (self.nextContains('{/for}', i, true)) {
self.addType(VParserTypes.forEnd);
self.contexts.pop();
} else if (self.nextContains('{if(', i, true)) {
self.extract(')}', VParserTypes.if);
self.contexts.push(VParserTypes.if);
} else if (self.nextContains('{/if}', i, true)) {
self.addType(VParserTypes.ifEnd);
self.contexts.pop();
} else if (self.nextContains('$${', i, true)) {
self.extract('}', VParserTypes.assign);
} else if (self.nextContains('${', i, true)) {
self.extract('}', VParserTypes.variable);
} else {
self.content += char;
}
}
self.addType(VParserTypes.content);
return self.parsed;
}
addType(type) {
let self = this;
let content = self.content.trim(),
instructions = self.findInstructions(type);
self.content = '';
if (type !== VParserTypes.none) {
if (type === VParserTypes.content && content === '') {
return null;
}
return self.parsed.push({
content: content,
type: type,
context: self.contexts[self.contexts.length - 1],
instructions: instructions
});
}
return null;
}
nextContains(find, index, add = false) {
let count = this.nextContainsRaw(this.legex, find, index);
if (add && count > 0) {
this.index += count;
}
return count > 0 || count === -1;
}
nextContainsRaw(raw, find, index) {
if (typeof find === "string") {
find = find.split("");
}
let count = find.length;
if (count < 1) {
return -1;
}
for (let i = 0; i < count; i++) {
let nc = raw.charAt(index + i);
if ((find[i] === '\n' && nc === undefined)) {
return count;
}
if (find[i] !== nc) {
return 0;
}
}
return count;
}
extract(findUntil = '}', type = 1) {
let self = this;
self.addType(0);
findUntil = findUntil.split("")
let content = '',
index = self.index,
legex = self.legex,
firstFind = findUntil.shift();
for (let i = self.index; i < legex.length; i++) {
let char = legex.charAt(i);
if (char === firstFind && self.nextContains(findUntil, i + 1)) {
console.debug(`[Parser][${index} > ${i}] >> ${content}`);
self.index = i + findUntil.length;
self.content = content;
self.addType(type);
return;
}
content += char;
}
throw 'Template variable at Position: ' + index + ' not closed!';
}
// @todo implement split... is needed for if statements and math stuff or get it stupid!
getOperator(string) {
let operators = [];
for (let i = 0; i < string.length; i++) {
if (this.nextContainsRaw(string, "(", i)) {
let innerCon = '';
for (let x = 0; x < string.length; x++) {
let char = string.charAt(i + x);
if (char === ')') {
break;
}
innerCon += char;
}
operators = [...operators, this.getOperator(innerCon)];
} else {
}
}
return operators;
}
findInstructions(type) {
if (type === VParserTypes.if) {
return this.getOperator(this.content);
}
return [];
}
}

View file

@ -0,0 +1,18 @@
'use strict';
class VTpeLTemplate {
constructor(name, content) {
this.name = name;
this.tpl = content;
this.parser = new VTpeLParser(name, content);
this.interpreter = new VTepLInterpreter(this.parser);
}
render(data = {}) {
return this.interpreter.render(data);
}
parseContent() {
this.parser.tokenize();
}
}

269
raw/javascript/utils.js Normal file
View file

@ -0,0 +1,269 @@
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) {
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:
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);
}
}
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) {
try {
cb(event, parent);
} catch (e) {
NotificationHandler.createNotification("FATAL ERROR WITHIN HANDLER!", "error", 1000);
//nothing!
}
}
}
})
};
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)
}
}
}
String.prototype.firstUpper = function () {
return this.charAt(0).toUpperCase() + this.slice(1);
}
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 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];
}

114
raw/javascript/visual.js Normal file
View file

@ -0,0 +1,114 @@
class Visual {
constructor() {
this.data = []; //for drawing
this.dataArray = [];
this.name = "Default";
}
updateData() {
}
updateFFT(fftSize) {
}
draw() {
}
setup() {
}
}
class VisualDrawer {
constructor() {
this.visuals = {
//"sphere": new Sphere(),
"wave": new Wave(),
"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(pConf.get("visual", "wave2d"));
this.updateLoop();
}
switch(visual) {
if (this.visuals[visual] != null) {
this.c = visual;
vConf.loadConfigByName(this.c);
this.visuals[this.c].setup();
pConf.set("visual", this.c);
pConf.save();
}
}
updateLoop() {
let self = this;
let vis = self.visuals[self.c];
let pro = shaderHandler.use(self.c);
this.updateSeekbar();
this.prepare(pro);
vis.updateData();
vis.draw(pro);
requestAnimationFrame(self.updateLoop.bind(self))
}
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);
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);
// 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;
}
}
}

View file

@ -0,0 +1,12 @@
class Sphere extends Visual {
constructor() {
super();
this.name = "Sphere";
}
draw() {
}
setup() {
}
}

View file

@ -0,0 +1,15 @@
//animate Water the way like the Audio is Coming... 256FFT-Size max!
class Water extends Visual {
constructor() {
super();
this.name = "Water";
}
draw() {
}
setup() {
audioHandler.fftSize(256)
}
}

View file

@ -0,0 +1,89 @@
// 3D Audio-Waves -> maybe also 2D?
class Wave extends Visual {
constructor() {
super();
this.name = "3D 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] = 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) {
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.TRIANGLES), 0, this.data.length / 3);
this.afterDraw();
}
rotate(program) {
let aspect = c.width / c.height,
matrix = [
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("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() {
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"),
light = gl.getUniformLocation(program, "u_light");
gl.uniform3fv(lightPos, [
vConf.get("light-x", 0),
vConf.get("light-y", 5),
vConf.get("light-z", -56)
]);
gl.uniform1f(light, parseFloat(vConf.get("light-strength", 0.3)));
}
afterDraw() {
TDUtils.updateRotate('rotation-x', 10);
TDUtils.updateRotate('rotation-y', 50);
TDUtils.updateRotate('rotation-z', -30);
vConf.save();
}
}

View file

@ -0,0 +1,75 @@
class Wave2D extends Visual {
constructor() {
super();
this.name = "2D Wave";
}
updateData() {
let data = audioHandler.getFloatArray();
let add = c.width / data.length,
x = 0,
y = c.height / 2,
goTrough = y / 2;
let outerLoop = 0;
for (let i = 0; i < data.length; i++) {
//first
this.data[outerLoop] = x;
this.data[outerLoop + 1] = y + (data[i] * goTrough);
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 glHelper = new GLHelper(program);
glHelper.project();
glHelper.addFudgeFactor(vConf.get("fudgeFactor", 1));
glHelper.translate([
camera.translate.x,
camera.translate.y,
camera.translate.z
]);
glHelper.rotateX(camera.mouse.x);
glHelper.rotateY(camera.mouse.y);
glHelper.rotateZ(vConf.get("rotation-z", 0));
glHelper.applyMatrix();
}
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");
//GLHelper.uniform1f(program, "u_fudgeFactor", vConf.get("fudgeFactor", 1));
GLHelper.uniform3fv(program, "u_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();
}
}

54
raw/scss/_config.scss Normal file
View file

@ -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;
}
}
}

24
raw/scss/_controls.scss Normal file
View file

@ -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 $primary;
padding: 1.5rem;
cursor: pointer;
color: #fff;
transition: .5s;
&.active {
border-color: $second;
}
&:hover {
background-color: rgba(21, 21, 21, .7);
border-color: $active;
}
}

196
raw/scss/_gui.scss Normal file
View file

@ -0,0 +1,196 @@
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;
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: $primary;
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;
}
}
.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;
&.hide {
display: none !important;
}
}
modal-footer playlist {
flex-direction: row-reverse;
flex-wrap: wrap;
.pagination {
margin-left: auto;
}
}
#image-upload form {
display: flex;
flex-direction: column;
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;
}
body.fullscreen {
.menus {
display: none;
}
}
.help-list {
display: flex;
flex-direction: column;
.item {
padding: 0 1em;
&:not(:last-child) {
border-bottom: 1px solid #232323;
}
.h2 {
font-size: 1.2em;
margin: 10px 0 5px;
display: block;
}
p {
font-size: .8em;
color: #aaa;
}
}
}

377
raw/scss/_input.scss Normal file
View file

@ -0,0 +1,377 @@
.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: none;
}
.range::-webkit-slider-runnable-track {
width: 100%;
height: 5px;
cursor: pointer;
background: rgba(0, 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, 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: 100%;
position: relative;
display: inline-block;
margin-top: 1rem;
background-color: transparent;
&-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: 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: $active;
font-size: .7rem;
}
.floating-label input:valid ~ .input-label {
transform: translateY(-0.72rem);
color: $second;
font-size: .7rem;
}
.focus {
content: '';
width: 0;
background-color: $second;
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 {
display: flex;
flex-direction: row;
padding: 5px;
input {
position: absolute;
appearance: none;
opacity: 0;
&:checked + label:after {
transform: translateX(20px);
}
}
span {
margin-left: 10px;
}
label {
display: block;
border-radius: 10px;
width: 40px;
height: 20px;
background-color: #dcdcdc;
position: relative;
cursor: pointer;
padding: 0;
&:after {
content: '';
background-color: $primary;
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;
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 $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;
}
}

59
raw/scss/_modal.scss Normal file
View file

@ -0,0 +1,59 @@
#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;
&.lightMode {
background-color: rgba(0, 0, 0, .1);
}
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);
.inner {
padding: 5px;
width: calc(100% - 40px);
margin: 0 auto;
}
}
}

117
raw/scss/_notification.scss Normal file
View file

@ -0,0 +1,117 @@
.notification {
right: 10px;
top: 0;
height: 100%;
display: flex;
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);
}
}

82
raw/scss/_playlist.scss Normal file
View file

@ -0,0 +1,82 @@
playlist {
display: flex;
flex-direction: column;
div {
padding: unset;
position: unset;
}
.playlist-content {
h1 {
padding: 0 1em;
}
}
.pagination {
display: flex;
justify-content: flex-end;
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;
pointer-events: none;
cursor: not-allowed;
}
&:hover {
color: $primary;
}
}
}
.playlist-item {
display: flex;
padding: 5px;
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;
padding: 5px;
display: flex;
align-items: center;
}
&-number {
padding: 5px 10px 5px 5px;
width: 50px;
display: flex;
align-items: center;
}
&:hover {
background-color: rgba(0, 0, 0, .4);
}
}
}

42
raw/scss/_scrollbar.scss Normal file
View file

@ -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;
}

16
raw/scss/_variables.scss Normal file
View file

@ -0,0 +1,16 @@
$bg: #303030;
$darker: #212121;
$nav: #1b1b1b;
$primary: #3949ab;
$second: #ff0089;
$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;

56
raw/scss/style.scss Normal file
View file

@ -0,0 +1,56 @@
@import "variables";
* {
box-sizing: border-box;
}
*:focus {
outline: none;
}
html, body {
padding: 0;
margin: 0;
overflow: hidden;
font-size: 16px;
font-family: sans-serif;
background-color: $bg;
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;
}
@import "scrollbar";
@import "gui";
@import "input";
@import "controls";
@import "playlist";
@import "modal";
@import "notification";
@import "config";

4
raw/worker/app.js Normal file
View file

@ -0,0 +1,4 @@
const tagger = new Tagger(self),
eventHandler = new EventHandler(self);
tagger.init();

97
raw/worker/database.js Normal file
View file

@ -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);
}
}

View file

@ -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);
}
}
}

86
raw/worker/id3.js Normal file
View file

@ -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;
}
}

Some files were not shown because too many files have changed in this diff Show more