Compare commits
7 commits
Author | SHA1 | Date | |
---|---|---|---|
170a6cc716 | |||
4c4afe3bdf | |||
07b35b9667 | |||
25fcefcb50 | |||
9d5259767c | |||
300b6c4106 | |||
|
d1ae2059f7 |
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
|||
/webGLTest.iml
|
||||
/.idea/
|
||||
/build/node_modules/
|
23
build/gulpfile.js
Normal 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
|
@ -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
|
@ -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;
|
26
build/task/jsonMinifier.js
Normal 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
|
@ -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
|
@ -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
|
@ -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
BIN
favicon.ico
Executable file
After Width: | Height: | Size: 15 KiB |
BIN
favicon/android-chrome-192x192.png
Executable file
After Width: | Height: | Size: 1.1 KiB |
BIN
favicon/android-chrome-512x512.png
Executable file
After Width: | Height: | Size: 2.4 KiB |
BIN
favicon/apple-touch-icon.png
Executable file
After Width: | Height: | Size: 1.1 KiB |
BIN
favicon/favicon-16x16.png
Executable file
After Width: | Height: | Size: 311 B |
BIN
favicon/favicon-32x32.png
Executable file
After Width: | Height: | Size: 407 B |
BIN
favicon/mstile-150x150.png
Executable file
After Width: | Height: | Size: 890 B |
1
favicon/safari-pinned-tab.svg
Executable 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 |
126
index.html
|
@ -2,33 +2,109 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>WEBGL Test</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<meta name="viewport"
|
||||
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>
|
||||
<body>
|
||||
<canvas id="c" width="1900" height="1000"></canvas>
|
||||
<div class="off-can closed">
|
||||
<group id="rotate">
|
||||
<group-label>Rotation</group-label>
|
||||
</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 class="loading-screen">
|
||||
<loader></loader>
|
||||
<loader class="delay"></loader>
|
||||
<span>Loading</span>
|
||||
</div>
|
||||
<div class="settings-icon">☰</div>
|
||||
<script src="js/utils.js" defer></script>
|
||||
<script src="js/handler.js"></script>
|
||||
<script src="js/index.js"></script>
|
||||
<script src="js/sphere.js"></script>
|
||||
<script src="js/gui.js"></script>
|
||||
<div class="menus">
|
||||
<div class="top-menu-left">
|
||||
<div class="settings-icon menu-icon">
|
||||
<svg role="img" class="icon">
|
||||
<use href="out/icon-sprite.svg#fal-fa-cogs"></use>
|
||||
</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>
|
||||
</html>
|
||||
|
|
|
@ -80,4 +80,13 @@ class Shaders {
|
|||
getProgram(name) {
|
||||
return this.programs[name];
|
||||
}
|
||||
|
||||
async loadArray(list, path) {
|
||||
let self = this;
|
||||
for (const e of list) {
|
||||
await self.loadShader(e, path)
|
||||
}
|
||||
await self.createProgramForEach(list)
|
||||
return true;
|
||||
}
|
||||
}
|
63
js/index.js
|
@ -1,22 +1,35 @@
|
|||
let shaderHandler, gl, c, actx, analyser, peak;
|
||||
let positionData = [];
|
||||
let positionSize = 8192 * 2 * 2;
|
||||
let shaderHandler, gl, c, actx, analyser, peak, isInit = false;
|
||||
|
||||
function createAudioContextStream(stream) {
|
||||
function createAudioContextStream(audio) {
|
||||
let AudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
if (actx) {
|
||||
actx.close();
|
||||
}
|
||||
actx = new AudioContext();
|
||||
analyser = actx.createAnalyser();
|
||||
let MEDIA_ELEMENT_NODES = new WeakMap();
|
||||
let Source;
|
||||
|
||||
analyser.fftSize = 4096;
|
||||
analyser.maxDecibels = 0;
|
||||
analyser.smoothingTimeConstant = .4;
|
||||
Source = actx.createMediaStreamSource(stream);
|
||||
if (audio) {
|
||||
if (MEDIA_ELEMENT_NODES.has(audio)) {
|
||||
Source = MEDIA_ELEMENT_NODES.get(audio);
|
||||
} else {
|
||||
Source = actx.createMediaElementSource(audio);
|
||||
MEDIA_ELEMENT_NODES.set(audio, Source);
|
||||
}
|
||||
|
||||
Source.connect(analyser);
|
||||
Source.connect(analyser);
|
||||
analyser.connect(actx.destination);
|
||||
audio.oncanplay = () => {
|
||||
actx.resume();
|
||||
};
|
||||
audio.onended = function () {
|
||||
actx.pause();
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -28,26 +41,38 @@ async function init() {
|
|||
return false;
|
||||
}
|
||||
shaderHandler = new Shaders(gl);
|
||||
await shaderHandler.loadShader("test", "shaders/");
|
||||
await shaderHandler.loadShader("wave", "shaders/");
|
||||
|
||||
//sphere shader
|
||||
await shaderHandler.loadShader("sphere", "shaders/", gl.VERTEX_SHADER);
|
||||
return shaderHandler.createProgramForEach(["test", "sphere"]);
|
||||
return shaderHandler.createProgramForEach(["wave", "sphere"]);
|
||||
}
|
||||
|
||||
(function () {
|
||||
navigator.mediaDevices.getUserMedia({audio: true, video: false})
|
||||
.then(createAudioContextStream).then(e => {
|
||||
generateRotationSliders();
|
||||
generateColorSliders();
|
||||
generateWorldSliders();
|
||||
generateDrawSliders();
|
||||
generateTranslateSliders();
|
||||
function createView() {
|
||||
if (!isInit) {
|
||||
createAudioContextStream(audioFile);
|
||||
init().then(b => {
|
||||
if (b) {
|
||||
sphereObject.drawMode = gl.POINTS;
|
||||
loadConfig();
|
||||
draw();
|
||||
}
|
||||
})
|
||||
});
|
||||
})();
|
||||
isInit = true;
|
||||
}
|
||||
}
|
||||
|
||||
function initGUI() {
|
||||
generateRotationSliders();
|
||||
generateColorSliders();
|
||||
generateWorldSliders();
|
||||
generateDrawSliders();
|
||||
generateTranslateSliders();
|
||||
}
|
||||
|
||||
(function () {
|
||||
if (document.readyState === "complete" || document.readyState === "interactive") {
|
||||
initGUI()
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', initGUI);
|
||||
}
|
||||
})()
|
88
js/old.js
|
@ -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]];
|
||||
}
|
48
js/sphere.js
|
@ -7,20 +7,21 @@ let sphereObject = {
|
|||
rotation: [0, 0, 0], //radians
|
||||
rotateInc: [0.0, 0.0, 0.0], //degreesInc
|
||||
rotateByBeat: true,
|
||||
translate: [1, 1, 1],
|
||||
translate: [0, 0, 0],
|
||||
total: 50,
|
||||
radius: 500,
|
||||
color: {r: 0, g: 0, b: 0},
|
||||
colorByBeat: true,
|
||||
drawMode: 0,
|
||||
drawMode: 5,
|
||||
sphereMode: 0,
|
||||
lightPos: [0, 0, 0],
|
||||
pointSize: 2,
|
||||
steps: 512,
|
||||
dirtyMode: false
|
||||
dirtyMode: false,
|
||||
light: 0.3
|
||||
}
|
||||
|
||||
function readData() {
|
||||
function readDataBar() {
|
||||
let items = sphereObject.steps;
|
||||
let dataArray = new Uint8Array(items);
|
||||
analyser.getByteFrequencyData(dataArray);
|
||||
|
@ -36,6 +37,7 @@ function readData() {
|
|||
|
||||
let sphereDataVectors = [], lastTotal = 0;
|
||||
|
||||
// avoid garbage collection each run
|
||||
function prepareData() {
|
||||
let total = sphereObject.total;
|
||||
if (lastTotal !== total) {
|
||||
|
@ -52,7 +54,7 @@ function prepareData() {
|
|||
|
||||
function setupSphere() {
|
||||
sphereData = [];
|
||||
let data = readData(),
|
||||
let data = readDataBar(),
|
||||
radData = data[0],
|
||||
map = VTUtils.map,
|
||||
total = sphereObject.total,
|
||||
|
@ -94,14 +96,21 @@ function setupSphere() {
|
|||
|
||||
function getAddRad(counter, data, total) {
|
||||
let mapping, rAdd, map = VTUtils.map;
|
||||
let h = total / 2;
|
||||
if (sphereObject.sphereMode === 3) {
|
||||
let h = total / 2;
|
||||
if (counter > h) {
|
||||
mapping = map(counter, h, total, data.length - 1, 0);
|
||||
} else {
|
||||
mapping = map(counter, 0, h, 0, data.length - 1);
|
||||
}
|
||||
rAdd = data[Math.round(mapping)] || 0;
|
||||
} else if (sphereObject.sphereMode === 4) {
|
||||
if (counter > h) {
|
||||
mapping = map(counter, h, total, 0, data.length - 1);
|
||||
} else {
|
||||
mapping = map(counter, 0, h, data.length - 1, 0);
|
||||
}
|
||||
rAdd = data[Math.round(mapping)] || 0;
|
||||
} else {
|
||||
mapping = map(counter, 0, total, 0, data.length - 1);
|
||||
rAdd = data[Math.round(mapping)] || 0;
|
||||
|
@ -109,18 +118,16 @@ function getAddRad(counter, data, total) {
|
|||
return rAdd;
|
||||
}
|
||||
|
||||
let position, world, color, rotate, light, lightPos, lightAngle = 90;
|
||||
let position, color, rotate, light, lightPos;
|
||||
|
||||
function prepare(program) {
|
||||
position = gl.getAttribLocation(program, "a_position");
|
||||
world = gl.getUniformLocation(program, 'u_world');
|
||||
color = gl.getUniformLocation(program, "u_color");
|
||||
light = gl.getUniformLocation(program, "u_light");
|
||||
rotate = gl.getUniformLocation(program, "u_matrix");
|
||||
lightPos = gl.getUniformLocation(program, "u_lightPos");
|
||||
let pointSize = gl.getUniformLocation(program, "u_pointSize");
|
||||
gl.uniformMatrix4fv(world, false, TDUtils.yRotation(TDUtils.degToRad(lightAngle)));
|
||||
gl.uniform3fv(light, [0.95, 0.62, 0.094]);
|
||||
gl.uniform3fv(light, [sphereObject.light, 0, 0]);
|
||||
gl.uniform3fv(lightPos, sphereObject.lightPos);
|
||||
gl.uniform1f(pointSize, sphereObject.pointSize);
|
||||
}
|
||||
|
@ -135,16 +142,15 @@ function draw() {
|
|||
gl.useProgram(program);
|
||||
prepare(program);
|
||||
let matrix = [
|
||||
sphereObject.translate[0] / aspect, 0, 0, 0,
|
||||
0, sphereObject.translate[1], 0, 0,
|
||||
0, 0, sphereObject.translate[2], 0,
|
||||
0, 0, 0, 1
|
||||
1 / aspect, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
sphereObject.translate[0], sphereObject.translate[1], sphereObject.translate[2], 1
|
||||
]
|
||||
matrix = TDUtils.multiply(matrix, TDUtils.xRotation(sphereObject.rotation[0]));
|
||||
matrix = TDUtils.multiply(matrix, TDUtils.yRotation(sphereObject.rotation[1]));
|
||||
matrix = TDUtils.multiply(matrix, TDUtils.zRotation(sphereObject.rotation[2]));
|
||||
gl.uniformMatrix4fv(rotate, false, matrix);
|
||||
|
||||
let positionBuffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(sphereData), gl.DYNAMIC_DRAW);
|
||||
|
@ -152,7 +158,6 @@ function draw() {
|
|||
gl.bindVertexArray(vao);
|
||||
gl.enableVertexAttribArray(position);
|
||||
gl.vertexAttribPointer(position, 3, gl.FLOAT, true, 0, 0);
|
||||
|
||||
gl.clearColor(0, 0, 0, 1);
|
||||
gl.enable(gl.DEPTH_TEST);
|
||||
gl.depthFunc(gl.LEQUAL);
|
||||
|
@ -186,12 +191,6 @@ function sphereMode(lat, lon, i, counter, rx, ry) {
|
|||
sin = Math.sin,
|
||||
cos = Math.cos;
|
||||
switch (sphereObject.sphereMode) {
|
||||
case 0:
|
||||
case 3:
|
||||
x = rx * sin(lat) * cos(lon);
|
||||
y = ry * sin(lat) * sin(lon);
|
||||
z = ry * cos(lat);
|
||||
break;
|
||||
case 1:
|
||||
x = rx * sin(lon) * cos(lat);
|
||||
y = ry * sin(lon) * sin(lat);
|
||||
|
@ -202,6 +201,11 @@ function sphereMode(lat, lon, i, counter, rx, ry) {
|
|||
y = ry * sin(lat) * sin(lat);
|
||||
z = ry * cos(lon);
|
||||
break;
|
||||
default:
|
||||
x = rx * sin(lat) * cos(lon);
|
||||
y = ry * sin(lat) * sin(lon);
|
||||
z = ry * cos(lat);
|
||||
break;
|
||||
}
|
||||
return {x: x, y: y, z: z};
|
||||
}
|
60
js/utils.js
|
@ -131,7 +131,7 @@ class VTVector {
|
|||
this.z += z;
|
||||
}
|
||||
|
||||
setXYZ(x,y,z) {
|
||||
setXYZ(x, y, z) {
|
||||
this.x = x || 0;
|
||||
this.y = y || 0;
|
||||
this.z = z || 0;
|
||||
|
@ -196,9 +196,10 @@ class TDUtils {
|
|||
return dst;
|
||||
}
|
||||
|
||||
static xRotation(angleInRadians) {
|
||||
let c = Math.cos(angleInRadians);
|
||||
let s = Math.sin(angleInRadians);
|
||||
static xRotation(angle) {
|
||||
angle = TDUtils.degToRad(angle);
|
||||
let c = Math.cos(angle);
|
||||
let s = Math.sin(angle);
|
||||
|
||||
return [
|
||||
1, 0, 0, 0,
|
||||
|
@ -208,9 +209,10 @@ class TDUtils {
|
|||
];
|
||||
}
|
||||
|
||||
static yRotation(angleInRadians) {
|
||||
let c = Math.cos(angleInRadians);
|
||||
let s = Math.sin(angleInRadians);
|
||||
static yRotation(angle) {
|
||||
angle = TDUtils.degToRad(angle);
|
||||
let c = Math.cos(angle);
|
||||
let s = Math.sin(angle);
|
||||
|
||||
return [
|
||||
c, 0, -s, 0,
|
||||
|
@ -220,9 +222,10 @@ class TDUtils {
|
|||
];
|
||||
}
|
||||
|
||||
static zRotation(angleInRadians) {
|
||||
let c = Math.cos(angleInRadians);
|
||||
let s = Math.sin(angleInRadians);
|
||||
static zRotation(angle) {
|
||||
angle = TDUtils.degToRad(angle);
|
||||
let c = Math.cos(angle);
|
||||
let s = Math.sin(angle);
|
||||
|
||||
return [
|
||||
c, s, 0, 0,
|
||||
|
@ -236,3 +239,40 @@ class TDUtils {
|
|||
return d * Math.PI / 180;
|
||||
}
|
||||
}
|
||||
|
||||
function $(sel, s) {
|
||||
return $$(sel, s)[0];
|
||||
}
|
||||
|
||||
function $$(sel, s) {
|
||||
s = s || document;
|
||||
return s.querySelectorAll(sel);
|
||||
}
|
||||
|
||||
Node.prototype.addDelegatedEventListener = function (type, aim, cb) {
|
||||
this.addEventListener(type, (event) => {
|
||||
let target = event.target;
|
||||
if (target.matches(aim)) {
|
||||
cb(event, target);
|
||||
} else {
|
||||
let parent = target.closest(aim);
|
||||
if (parent) {
|
||||
cb(event, parent);
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
function create(name, content) {
|
||||
let d = document.createElement(name);
|
||||
if (content) {
|
||||
d.innerHTML = content;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
function append(to, array) {
|
||||
for (let item in array) {
|
||||
to.appendChild(array[item]);
|
||||
}
|
||||
}
|
|
@ -1,40 +1,3 @@
|
|||
function $(sel, s) {
|
||||
return $$(sel, s)[0];
|
||||
}
|
||||
|
||||
function $$(sel, s) {
|
||||
s = s || document;
|
||||
return s.querySelectorAll(sel);
|
||||
}
|
||||
|
||||
Node.prototype.addDelegatedEventListener = function (type, aim, cb) {
|
||||
this.addEventListener(type, (event) => {
|
||||
let target = event.target;
|
||||
if (target.matches(aim)) {
|
||||
cb(event, target);
|
||||
} else {
|
||||
let parent = target.closest(aim);
|
||||
if (parent) {
|
||||
cb(event, parent);
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
function create(name, content) {
|
||||
let d = document.createElement(name);
|
||||
if (content) {
|
||||
d.innerHTML = content;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
function append(to, array) {
|
||||
for (let item in array) {
|
||||
to.appendChild(array[item]);
|
||||
}
|
||||
}
|
||||
|
||||
function generateRotationSliders() {
|
||||
let group = $('#rotate');
|
||||
generateSlider(["X", "Y", "Z", "X-Inc", "Y-Inc", "Z-Inc"], 0, 360, 0, group, "rotate");
|
||||
|
@ -43,7 +6,7 @@ function generateRotationSliders() {
|
|||
|
||||
function generateTranslateSliders() {
|
||||
let group = $('#translate');
|
||||
generateSlider(["X", "Y", "Z"], -1, 1, 1, group, "translate");
|
||||
generateSlider(["X", "Y", "Z"], -1, 1, 0, group, "translate");
|
||||
}
|
||||
|
||||
function generateColorSliders() {
|
||||
|
@ -54,20 +17,20 @@ function generateColorSliders() {
|
|||
|
||||
function generateWorldSliders() {
|
||||
let group = $('#world');
|
||||
generateSlider(["LightPos-X", "LightPos-Y", "LightPos-Z"], -1, 1, 1, group, "light");
|
||||
generateSlider(["LightAngle"], 0, 360, 90, group, "light");
|
||||
generateSlider(["LightPos-X", "LightPos-Y", "LightPos-Z"], -100, 100, 0, group, "light");
|
||||
generateSlider(["Light"], 0, 1, 0.3, group, "light");
|
||||
}
|
||||
|
||||
function generateDrawSliders() {
|
||||
let group = $('#draw'),
|
||||
g = "drawMode"
|
||||
generateSlider(["DrawMode"], 0, 6, 0, group, g);
|
||||
generateSlider(["Form"], 0, 3, 0, group, g);
|
||||
generateSlider(["Form"], 0, 4, 0, group, g);
|
||||
generateSlider(["Radius"], 20, 1500, 500, group, g);
|
||||
generateSlider(["Total"], 0, 200, 50, group, g);
|
||||
generateSlider(["Total"], 20, 250, 50, group, g);
|
||||
generateSlider(["PointSize"], 1, 10, 2, group, g, .2);
|
||||
generateSlider(["Smoothing"], 0, 1, 0.8, group, g, .05);
|
||||
generateSlider(["Steps"], 512, analyser.fftSize / 2, 512, group, g, 256);
|
||||
generateSlider(["Smoothing"], 0, 100, 80, group, g, 1);
|
||||
generateSlider(["Steps"], 256, 2048, 512, group, g, 16);
|
||||
generateCheckBox(["Dirty"], false, group, g)
|
||||
}
|
||||
|
||||
|
@ -102,11 +65,10 @@ function setColors() {
|
|||
|
||||
function setWorld() {
|
||||
let group = $('#world');
|
||||
sphereObject.lightPos[0] = getValue($('#LightPos-X', group)) * 0.2;
|
||||
sphereObject.lightPos[1] = getValue($('#LightPos-Y', group)) * 0.2;
|
||||
sphereObject.lightPos[2] = getValue($('#LightPos-Z', group)) * 0.2;
|
||||
|
||||
lightAngle = getValue($('#LightAngle', group))
|
||||
sphereObject.lightPos[0] = getValue($('#LightPos-X', group));
|
||||
sphereObject.lightPos[1] = getValue($('#LightPos-Y', group));
|
||||
sphereObject.lightPos[2] = getValue($('#LightPos-Z', group));
|
||||
sphereObject.light = getValue($('#Light', group));
|
||||
}
|
||||
|
||||
function setDraw() {
|
||||
|
@ -118,7 +80,7 @@ function setDraw() {
|
|||
sphereObject.pointSize = getValue($('#PointSize', group));
|
||||
sphereObject.steps = getValue($('#Steps', group));
|
||||
sphereObject.dirtyMode = $('#drawModeDirty', group).checked;
|
||||
analyser.smoothingTimeConstant = getValue($('#Smoothing', group));
|
||||
analyser.smoothingTimeConstant = getValue($('#Smoothing', group)) / 100;
|
||||
}
|
||||
|
||||
function setTranslate() {
|
||||
|
@ -183,6 +145,7 @@ function changeHandler(el) {
|
|||
} else if (d === "drawMode") {
|
||||
setDraw();
|
||||
}
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
document.body.addDelegatedEventListener('input', 'group-input input', (ev, el) => {
|
||||
|
@ -205,4 +168,60 @@ function getValue(slider) {
|
|||
|
||||
$('.settings-icon').addEventListener('click', function () {
|
||||
$('.off-can').classList.toggle("closed");
|
||||
$('.settings-icon').classList.toggle("open");
|
||||
})
|
||||
|
||||
window.addEventListener('keyup', e => {
|
||||
if (e.key === 'F11') {
|
||||
c.requestFullscreen();
|
||||
return;
|
||||
}
|
||||
if (e.key === 'Escape') {
|
||||
if (document.fullscreenElement) {
|
||||
document.exitFullscreen().then(console.log);
|
||||
}
|
||||
}
|
||||
if (e.key === 'p') {
|
||||
audioFile.play();
|
||||
}
|
||||
if (e.key === 's') {
|
||||
audioFile.pause();
|
||||
}
|
||||
});
|
||||
|
||||
function saveConfig() {
|
||||
localStorage.setItem('config-sphere', JSON.stringify(sphereObject));
|
||||
}
|
||||
|
||||
function loadConfig() {
|
||||
let item = localStorage.getItem('config-sphere');
|
||||
if (item && item !== "") {
|
||||
sphereObject = JSON.parse(item);
|
||||
}
|
||||
}
|
||||
|
||||
let uploadField = $('#upload');
|
||||
let audioFile = new Audio();
|
||||
let lastSong = null;
|
||||
uploadField.addEventListener('change', e => {
|
||||
let file = uploadField.files[0];
|
||||
if (file && file.type.indexOf('audio') !== -1 && file.name.match(".m3u") === null && file.name.match(".wma") === null) {
|
||||
if (lastSong) {
|
||||
URL.revokeObjectURL(lastSong);
|
||||
}
|
||||
audioFile.src = URL.createObjectURL(file);
|
||||
document.title = file.name;
|
||||
lastSong = audioFile.src;
|
||||
createView();
|
||||
}
|
||||
})
|
||||
|
||||
$('#play').addEventListener('click', e => {
|
||||
if (audioFile.src !== "") {
|
||||
c.requestFullscreen().then(r => {
|
||||
setTimeout(x => {
|
||||
audioFile.play();
|
||||
}, 1000)
|
||||
});
|
||||
}
|
||||
})
|
21
manifest.json
Executable 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
1
out/gui/base.json
Normal 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
|
@ -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
|
@ -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
|
@ -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
After Width: | Height: | Size: 9.1 KiB |
85
out/js/jsmediatags.min.js
vendored
Executable 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
1
out/js/scripts.min.js
vendored
Normal file
217
out/js/worker.js
Normal 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
|
@ -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
4
out/tpl/audio-information.tpl
Normal file
|
@ -0,0 +1,4 @@
|
|||
<div class="audio-item">
|
||||
<span class="artist">$artist$</span>
|
||||
<span class="title">$title$</span>
|
||||
</div>
|
3
out/tpl/config/content.tpl
Normal file
|
@ -0,0 +1,3 @@
|
|||
<div class="config-content">
|
||||
$content$
|
||||
</div>
|
5
out/tpl/config/nav.tpl
Normal 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>
|
8
out/tpl/config/visualitem.tpl
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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>
|
3
out/tpl/inputs/option.tpl
Normal file
|
@ -0,0 +1,3 @@
|
|||
<custom-option data-value="$value$">
|
||||
$value$
|
||||
</custom-option>
|
8
out/tpl/inputs/select.tpl
Normal 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>
|
7
out/tpl/inputs/slider.tpl
Normal 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>
|
5
out/tpl/inputs/switch.tpl
Normal 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
|
@ -0,0 +1,4 @@
|
|||
<div class="message">
|
||||
$message$
|
||||
</div>
|
||||
<div class="fade-bar $type$" style="animation-duration: $time$ms"></div>
|
20
out/tpl/playlist-footer.tpl
Normal 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>
|
4
out/tpl/playlist-item.tpl
Normal 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
|
@ -0,0 +1,5 @@
|
|||
<playlist>
|
||||
<div class="playlist-content">
|
||||
$content$
|
||||
</div>
|
||||
</playlist>
|
39
raw/gui/base.json
Normal 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
|
@ -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
|
@ -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
|
@ -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
36
raw/javascript/FetchHandler.js
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
37
raw/javascript/FileHandler.js
Normal 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
|
@ -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
|
@ -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
|
@ -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();
|
||||
}
|
||||
}
|
185
raw/javascript/eventHandler.js
Normal 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');
|
||||
}
|
||||
})
|
||||
}
|
78
raw/javascript/gl/Camera.js
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
391
raw/javascript/gl/glUtils.js
Normal 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);
|
||||
}
|
||||
}
|
100
raw/javascript/gl/handler.js
Normal 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
|
@ -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;
|
||||
}
|
||||
}
|
74
raw/javascript/imageUploader.js
Normal 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
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
77
raw/javascript/notification.js
Normal 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
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
106
raw/javascript/playerConfigHandler.js
Normal 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
|
@ -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
|
@ -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;
|
||||
}
|
||||
}
|
41
raw/javascript/template.js
Normal 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/"
|
34
raw/javascript/templateLang/VTepLCore.js
Normal 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);
|
||||
}
|
||||
}
|
133
raw/javascript/templateLang/VTepLInterpreter.js
Normal 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]];
|
||||
}
|
||||
}
|
178
raw/javascript/templateLang/VTepLParser.js
Normal 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 [];
|
||||
}
|
||||
}
|
18
raw/javascript/templateLang/VTepLTemplate.js
Normal 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
|
@ -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
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
12
raw/javascript/visuals/sphere.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
class Sphere extends Visual {
|
||||
constructor() {
|
||||
super();
|
||||
this.name = "Sphere";
|
||||
}
|
||||
|
||||
draw() {
|
||||
}
|
||||
|
||||
setup() {
|
||||
}
|
||||
}
|
15
raw/javascript/visuals/water.js
Normal 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)
|
||||
}
|
||||
|
||||
}
|
89
raw/javascript/visuals/wave.js
Normal 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();
|
||||
}
|
||||
}
|
75
raw/javascript/visuals/wave2d.js
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -0,0 +1,4 @@
|
|||
const tagger = new Tagger(self),
|
||||
eventHandler = new EventHandler(self);
|
||||
|
||||
tagger.init();
|
97
raw/worker/database.js
Normal 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);
|
||||
}
|
||||
}
|
30
raw/worker/eventHandler.js
Normal 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
|
@ -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;
|
||||
}
|
||||
}
|