audio-vis/out/js/scripts.js

1463 lines
40 KiB
JavaScript
Raw Normal View History

2020-04-07 21:44:46 +02:00
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);
2020-04-07 21:44:46 +02:00
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) {
cb(event, parent);
}
}
})
};
Node.prototype.hasClass = function (className) {
let items = className.split(','),
has = null;
for (let item of items) {
if (has === false) {
break;
}
has = this.classList.contains(item.trim());
}
return has === true;
}
Node.prototype.addClass = function (className) {
let items = className.split(',');
for (let item of items) {
this.classList.add(item.trim());
}
return this;
}
Node.prototype.removeClass = function (className) {
let items = className.split(',');
for (let item of items) {
this.classList.remove(item.trim());
}
return this;
}
Node.prototype.toggleClass = function (className, force) {
let items = className.split(',');
for (let item of items) {
this.classList.toggle(item.trim(), force);
}
}
Node.prototype.switchClass = function (clOne, clTwo, twoOne) {
let cl = this.classList;
if (twoOne) {
cl.remove(clOne);
cl.add(clTwo)
} else {
cl.remove(clTwo)
cl.add(clOne)
}
}
Node.prototype.toggleCheck = function (className, force) {
let cl = this.classList;
let items = className.split(',');
for (let item of items) {
let clOne = item.trim();
if (force) {
cl.add(clOne);
} else {
cl.remove(clOne)
}
}
}
File.prototype.toBase64 = function (cb) {
const reader = new FileReader();
reader.onloadend = cb;
reader.readAsDataURL(this);
}
function b64toBlob(b64Data, type) {
const byteCharacters = atob(b64Data);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
return new Blob([byteArray], {type: type});
}
function create(name, content) {
let d = document.createElement(name);
if (content) {
d.innerHTML = content;
}
return d;
}
function append(to, array) {
for (let item of array) {
to.appendChild(item);
}
}
function loadFromJSONToVisualData(useKeys) {
fetch('/audio-vis/out/showCase.json').then((res) => {
return res.json()
}).then(e => {
let floatArray;
if (useKeys) {
let keys = Object.keys(e);
floatArray = new Float32Array(keys.length);
for (let i = 0; i < keys.length; i++) {
floatArray[i] = e[keys[i]];
}
} else {
floatArray = new Float32Array(e.length);
for (let i = 0; i < e.length; i++) {
floatArray[i] = e[i];
}
}
visual.visuals[visual.c].data = floatArray;
})
}
// most of the functions are from https://webglfundamentals.org/webgl/resources/m4.js! but i doesnt want to use them all and make some adjustment to them!
2020-04-07 21:44:46 +02:00
class TDUtils {
static multiply(a, b) {
let b00 = b[0];
let b01 = b[1];
let b02 = b[2];
let b03 = b[3];
let b10 = b[4];
let b11 = b[5];
let b12 = b[6];
let b13 = b[7];
let b20 = b[8];
let b21 = b[9];
let b22 = b[10];
let b23 = b[11];
let b30 = b[12];
let b31 = b[13];
let b32 = b[14];
let b33 = b[15];
let a00 = a[0];
let a01 = a[1];
let a02 = a[2];
let a03 = a[3];
let a10 = a[4];
let a11 = a[5];
let a12 = a[6];
let a13 = a[7];
let a20 = a[8];
let a21 = a[9];
let a22 = a[10];
let a23 = a[11];
let a30 = a[12];
let a31 = a[13];
let a32 = a[14];
let a33 = a[15];
return [
b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30,
b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31,
b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32,
b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33,
b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30,
b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31,
b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32,
b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33,
b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30,
b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31,
b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32,
b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33,
b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30,
b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31,
b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32,
b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33
];
}
static translate(m, tx, ty, tz, dst) {
dst = dst || new Float32Array(16);
let m00 = m[0],
m01 = m[1],
m02 = m[2],
m03 = m[3],
m10 = m[4],
m11 = m[5],
m12 = m[6],
m13 = m[7],
m20 = m[8],
m21 = m[9],
m22 = m[10],
m23 = m[11],
m30 = m[12],
m31 = m[13],
m32 = m[14],
m33 = m[15];
dst[0] = m00;
dst[1] = m01;
dst[2] = m02;
dst[3] = m03;
dst[4] = m10;
dst[5] = m11;
dst[6] = m12;
dst[7] = m13;
dst[8] = m20;
dst[9] = m21;
dst[10] = m22;
dst[11] = m23;
dst[12] = m00 * tx + m10 * ty + m20 * tz + m30;
dst[13] = m01 * tx + m11 * ty + m21 * tz + m31;
dst[14] = m02 * tx + m12 * ty + m22 * tz + m32;
dst[15] = m03 * tx + m13 * ty + m23 * tz + m33;
2020-04-07 21:44:46 +02:00
return dst;
}
static xRotation(angleInRadians) {
let c = Math.cos(angleInRadians);
let s = Math.sin(angleInRadians);
return [
1, 0, 0, 0,
0, c, s, 0,
0, -s, c, 0,
0, 0, 0, 1,
];
}
static yRotation(angleInRadians) {
let c = Math.cos(angleInRadians);
let s = Math.sin(angleInRadians);
return [
c, 0, -s, 0,
0, 1, 0, 0,
s, 0, c, 0,
0, 0, 0, 1,
];
}
static zRotation(angleInRadians) {
let c = Math.cos(angleInRadians);
let s = Math.sin(angleInRadians);
return [
c, s, 0, 0,
-s, c, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
];
}
static degToRad(d) {
return d * Math.PI / 180;
}
static scale(sx, sy, sz, dst) {
dst = dst || new Float32Array(16);
dst[0] = sx;
dst[5] = sy;
dst[10] = sz;
return dst;
}
2020-04-07 21:44:46 +02:00
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;
2020-04-07 21:44:46 +02:00
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;
2020-04-07 21:44:46 +02:00
}
return dst;
}
2020-04-07 21:44:46 +02:00
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;
}
2020-08-01 21:51:54 +02:00
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;
2020-04-07 21:44:46 +02:00
}
static inverse(m, dst) {
dst = dst || new Float32Array(16);
let m00 = m[0],
m01 = m[1],
m02 = m[2],
m03 = m[3],
m10 = m[4],
m11 = m[5],
m12 = m[6],
m13 = m[7],
m20 = m[8],
m21 = m[9],
m22 = m[10],
m23 = m[11],
m30 = m[12],
m31 = m[13],
m32 = m[14],
m33 = m[15],
tmp_0 = m22 * m33,
tmp_1 = m32 * m23,
tmp_2 = m12 * m33,
tmp_3 = m32 * m13,
tmp_4 = m12 * m23,
tmp_5 = m22 * m13,
tmp_6 = m02 * m33,
tmp_7 = m32 * m03,
tmp_8 = m02 * m23,
tmp_9 = m22 * m03,
tmp_10 = m02 * m13,
tmp_11 = m12 * m03,
tmp_12 = m20 * m31,
tmp_13 = m30 * m21,
tmp_14 = m10 * m31,
tmp_15 = m30 * m11,
tmp_16 = m10 * m21,
tmp_17 = m20 * m11,
tmp_18 = m00 * m31,
tmp_19 = m30 * m01,
tmp_20 = m00 * m21,
tmp_21 = m20 * m01,
tmp_22 = m00 * m11,
tmp_23 = m10 * m01,
t0 = (tmp_0 * m11 + tmp_3 * m21 + tmp_4 * m31) -
(tmp_1 * m11 + tmp_2 * m21 + tmp_5 * m31),
t1 = (tmp_1 * m01 + tmp_6 * m21 + tmp_9 * m31) -
(tmp_0 * m01 + tmp_7 * m21 + tmp_8 * m31),
t2 = (tmp_2 * m01 + tmp_7 * m11 + tmp_10 * m31) -
(tmp_3 * m01 + tmp_6 * m11 + tmp_11 * m31),
t3 = (tmp_5 * m01 + tmp_8 * m11 + tmp_11 * m21) -
(tmp_4 * m01 + tmp_9 * m11 + tmp_10 * m21),
d = 1.0 / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3);
dst[0] = d * t0;
dst[1] = d * t1;
dst[2] = d * t2;
dst[3] = d * t3;
dst[4] = d * ((tmp_1 * m10 + tmp_2 * m20 + tmp_5 * m30) -
(tmp_0 * m10 + tmp_3 * m20 + tmp_4 * m30));
dst[5] = d * ((tmp_0 * m00 + tmp_7 * m20 + tmp_8 * m30) -
(tmp_1 * m00 + tmp_6 * m20 + tmp_9 * m30));
dst[6] = d * ((tmp_3 * m00 + tmp_6 * m10 + tmp_11 * m30) -
(tmp_2 * m00 + tmp_7 * m10 + tmp_10 * m30));
dst[7] = d * ((tmp_4 * m00 + tmp_9 * m10 + tmp_10 * m20) -
(tmp_5 * m00 + tmp_8 * m10 + tmp_11 * m20));
dst[8] = d * ((tmp_12 * m13 + tmp_15 * m23 + tmp_16 * m33) -
(tmp_13 * m13 + tmp_14 * m23 + tmp_17 * m33));
dst[9] = d * ((tmp_13 * m03 + tmp_18 * m23 + tmp_21 * m33) -
(tmp_12 * m03 + tmp_19 * m23 + tmp_20 * m33));
dst[10] = d * ((tmp_14 * m03 + tmp_19 * m13 + tmp_22 * m33) -
(tmp_15 * m03 + tmp_18 * m13 + tmp_23 * m33));
dst[11] = d * ((tmp_17 * m03 + tmp_20 * m13 + tmp_23 * m23) -
(tmp_16 * m03 + tmp_21 * m13 + tmp_22 * m23));
dst[12] = d * ((tmp_14 * m22 + tmp_17 * m32 + tmp_13 * m12) -
(tmp_16 * m32 + tmp_12 * m12 + tmp_15 * m22));
dst[13] = d * ((tmp_20 * m32 + tmp_12 * m02 + tmp_19 * m22) -
(tmp_18 * m22 + tmp_21 * m32 + tmp_13 * m02));
dst[14] = d * ((tmp_18 * m12 + tmp_23 * m32 + tmp_15 * m02) -
(tmp_22 * m32 + tmp_14 * m02 + tmp_19 * m12));
dst[15] = d * ((tmp_22 * m22 + tmp_16 * m02 + tmp_21 * m12) -
(tmp_20 * m12 + tmp_23 * m22 + tmp_17 * m02));
return dst;
}
static aspectView(aspect) {
return [
1 * aspect, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]
}
static lastMatrix = {m: null};
static getMatrix(fov, aspect, near, far, camAngle, radius) {
let lMat = this.lastMatrix,
u = TDUtils;
if (!u.isSame('fov', fov)
|| !u.isSame('aspect', aspect)
|| !u.isSame('near', near)
|| !u.isSame('far', far)
|| !u.isSame('cam', camAngle)
|| !u.isSame('radius', radius)
) {
let matrix = TDUtils.perspective(TDUtils.degToRad(fov), aspect, near, far),
cameraMatrix = TDUtils.yRotation(TDUtils.degToRad(camAngle));
cameraMatrix = TDUtils.translate(cameraMatrix, 0, 0, radius * 1.5);
let viewMatrix = TDUtils.inverse(cameraMatrix);
matrix = TDUtils.multiply(matrix, viewMatrix)
lMat.m = matrix;
}
return lMat.m;
}
static isSame(key, value) {
let lMat = this.lastMatrix;
if (lMat[key] !== value) {
lMat[key] = value;
return false;
}
return true;
2020-04-07 21:44:46 +02:00
}
}
2020-08-01 21:51:54 +02:00
class Template {
constructor() {
this.tpl = {};
}
async loadTemplate(name) {
let self = this;
if (!this.tpl[name]) {
await fetch(templateDir + name + ".tpl").then((r) => r.text()).then(c => {
self.tpl[name] = c;
})
}
}
async loadArray(names) {
for (let name of names) {
await this.loadTemplate(name);
}
}
parseTemplate(name, data) {
if (!this.tpl[name]) {
return ""
}
let m, d = this.tpl[name];
while ((m = templateEx.exec(d)) !== null) {
if (m.index === templateEx.lastIndex) {
templateEx.lastIndex++;
}
let key = m[0];
d = d.replace(key, data[m[1]] || "")
}
return d;
}
parseFromAPI(url, name, cb) {
fetch(url).then((r) => r.json()).then(d => {
cb(this.parseTemplate(name, d))
}).catch(console.error)
}
}
const templateEx = /\$(.*?)\$/gm;
const templateDir = "out/tpl/"
2020-04-07 21:44:46 +02:00
class ShaderHandler {
constructor(gl) {
this.gl = gl;
this.shaderNames = [];
this.shaders = {};
this.programs = {};
}
setGL(gl) {
this.gl = gl;
}
async loadShader(name, path) {
this.shaderNames.push(name);
await this.load(name, path + name + ".vert", this.gl.VERTEX_SHADER);
await this.load(name, path + name + ".frag", this.gl.FRAGMENT_SHADER);
}
async load(name, url, type) {
let realName = name + "_" + type;
if (!this.shaders[realName]) {
let data = await fetch(url);
let shader = this.createShader(await data.text(), type);
if (shader) {
this.shaders[realName] = shader;
}
}
return !!this.shaders[realName];
}
getShader(name, type) {
let realName = name + "_" + type;
return this.shaders[realName];
}
getAllShaders() {
return this.shaderNames;
}
async createProgramForEach(arr) {
arr = arr || this.shaderNames;
for (let i = 0; i < arr.length; i++) {
let shader = arr[i];
let v = await shaderHandler.createProgram(shader, [shader])
if (!v) {
return false;
}
}
return true;
}
createShader(source, type) {
let gl = this.gl;
let shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
return shader;
}
console.error(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
createProgram(name, shaders) {
let gl = this.gl;
let pro = gl.createProgram();
for (let i = 0; i < shaders.length; i++) {
gl.attachShader(pro, this.getShader(shaders[i], gl.VERTEX_SHADER));
gl.attachShader(pro, this.getShader(shaders[i], gl.FRAGMENT_SHADER));
}
gl.linkProgram(pro);
var success = gl.getProgramParameter(pro, gl.LINK_STATUS);
if (success) {
this.programs[name] = pro;
return pro;
}
console.log(gl.getProgramInfoLog(pro));
gl.deleteProgram(pro);
return null;
}
getProgram(name) {
return this.programs[name];
}
2020-08-01 21:51:54 +02:00
use(name) {
let pro = this.programs[name];
this.gl.useProgram(pro);
return pro;
}
2020-04-07 21:44:46 +02:00
async loadArray(list, path) {
let self = this;
for (const e of list) {
await self.loadShader(e, path)
}
await self.createProgramForEach(list)
}
}
2020-08-01 21:51:54 +02:00
const AudioContext = window.AudioContext || window.webkitAudioContext;
2020-04-07 21:44:46 +02:00
class AudioHandler {
async init() {
2020-08-01 21:51:54 +02:00
let self = this;
self.isStarted = false;
self.audioFile = new Audio();
self.actx = new AudioContext();
self.analyser = self.actx.createAnalyser();
2020-08-01 21:51:54 +02:00
self.analyser.fftSize = 4096;
self.lastSong = null;
await self.connectAll();
}
async connectAll() {
let self = this;
self.source = self.actx.createMediaElementSource(self.audioFile);
self.source.connect(self.analyser);
self.analyser.connect(self.actx.destination);
self.audioFile.addEventListener('ended', player.nextSong.bind(player));
}
async start() {
if (this.audioFile.src === '') {
return;
}
if (!this.isStarted) {
this.isStarted = true;
await this.actx.resume();
}
}
async stop() {
if (this.isStarted) {
this.isStarted = false;
await this.actx.suspend();
}
}
fftSize(size) {
this.analyser.fftSize = size;
}
smoothing(float) {
this.analyser.smoothingTimeConstant = float;
}
loadSong(src) {
let self = this;
if (self.lastSong) {
URL.revokeObjectURL(self.lastSong);
}
self.lastSong = this.audioFile.src = URL.createObjectURL(src);
if (!this.isStarted) {
this.start().catch(alert);
2020-08-01 21:51:54 +02:00
}
this.audioFile.play().then(e => {
window.dispatchEvent(new CustomEvent('playSong'));
}).catch(e => {
player.nextSong();
});
2020-08-01 21:51:54 +02:00
}
getIntArray(steps) {
let dataArray = new Uint8Array(steps);
this.analyser.getByteFrequencyData(dataArray);
return dataArray;
}
getFloatArray() {
let dataArray = new Float32Array(this.analyser.fftSize);
2020-08-01 21:51:54 +02:00
this.analyser.getFloatTimeDomainData(dataArray);
return dataArray;
2020-04-07 21:44:46 +02:00
}
}
class Player {
async init() {
2020-08-01 21:51:54 +02:00
this.playlist = new Playlist();
}
nextSong() {
let next = this.playlist.getNext();
audioHandler.loadSong(next.file);
}
2020-04-07 21:44:46 +02:00
2020-08-01 21:51:54 +02:00
prevSong() {
let next = this.playlist.getPrevious();
audioHandler.loadSong(next.file);
}
playStop() {
if (!audioHandler.lastSong) {
let next = this.playlist.getCurrent();
audioHandler.loadSong(next.file);
return;
2020-08-01 21:51:54 +02:00
}
let audioFile = audioHandler.audioFile;
if (audioFile.paused) {
audioFile.play();
} else {
audioFile.pause();
}
window.dispatchEvent(new CustomEvent('playSong'));
2020-08-01 21:51:54 +02:00
}
playByID(number) {
this.playlist.index = number;
let next = this.playlist.getCurrent();
2020-08-01 21:51:54 +02:00
audioHandler.loadSong(next.file);
}
}
const PAGINATIONLIMIT = 50;
class Playlist {
constructor() {
this.list = [];
this.shuffled = [];
this.index = 0;
this.page = 0;
this.isShuffle = false;
$('body').addDelegatedEventListener('change', 'input[type="file"]', this.changeFiles.bind(this));
$('body').addDelegatedEventListener('click', '.pagination .item', this.handlePagination.bind(this));
}
shuffle() {
// only shuffle if more then 2 elements are in
let len = this.list.length;
if (len < 3) {
this.shuffled = this.list;
}
// the current-list need to be shuffled...
for (let i = 0; i < len; i++) {
let random = VTUtils.randomInt(0, len - 1);
this.swap(i, random);
}
}
swap(a, b) {
this.shuffled[a] = this.list[b];
this.shuffled[b] = this.list[a];
}
getNext() {
let items = this.isShuffle ? this.shuffled : this.list,
len = items.length - 1,
next = this.index + 1;
if (next > len) {
next = 0;
}
this.index = next;
return items[next];
}
getPrevious() {
let items = this.isShuffle ? this.shuffled : this.list,
len = items.length - 1,
next = this.index - 1;
if (next < 0) {
next = len;
}
this.index = next;
return items[next];
}
getCurrent() {
let items = this.isShuffle ? this.shuffled : this.list;
return items[this.index];
}
// on new upload... this has to be an array!
setPlaylist(files) {
this.index = 0;
this.list = files;
this.shuffle();
}
handlePagination(event, el) {
if (el.hasClass('inactive')) {
return;
}
if (el.hasClass('next-site')) {
this.renderPagination(this.page + 1);
} else {
this.renderPagination(this.page - 1);
}
}
renderPagination(page) {
let length = this.list.length,
maxSite = Math.ceil(length / PAGINATIONLIMIT) - 1;
if (page < 0) {
page = 0;
}
if (page > maxSite) {
page = maxSite;
}
let s = page * PAGINATIONLIMIT,
e = s + PAGINATIONLIMIT,
data = "";
this.page = page;
if (e >= length) {
e = length;
}
if (length > 0) {
let items = this.isShuffle ? this.shuffled : this.list;
for (let i = s; i < e; i++) {
let obj = {
index: i.toString(),
2020-08-01 21:51:54 +02:00
nr: i + 1,
title: items[i].name,
active: !audioHandler.audioFile.paused && i === this.index ? 'active' : ''
2020-08-01 21:51:54 +02:00
}
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;
}
2020-08-01 21:51:54 +02:00
let files = [];
let i = 0;
for (let file of el.files) {
if (file && file.type.indexOf('audio') !== -1 && file.name.match(".m3u") === null) {
let name = file.name.split(".");
name.pop();
name = name.join(".");
files.push({
file: file,
name: name,
index: i++
});
}
}
this.setPlaylist(files);
if (files.length > 0) {
this.renderPagination(0);
} else {
alert("No Valid AudioFiles found!");
}
2020-04-07 21:44:46 +02:00
}
}
class GUI {
2020-08-01 21:51:54 +02:00
async init() {
this.data = {};
this.modal = new Modal();
// load first vis window!
await this.loadForVis();
await template.loadArray([
'playlist-item',
'playlist',
'playlist-footer'
]);
this.initDropZone();
}
async loadForVis() {
let c = visual.c,
d = this.data[c];
if (d == null) {
this.data[c] = await fetch("out/gui/" + c + ".json").then((r) => r.json());
}
}
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';
2020-08-01 21:51:54 +02:00
player.playlist.changeFiles(e, e.dataTransfer);
} else {
alert("Sorry you need to upload files!");
}
}
});
});
};
}
class Modal {
constructor() {
let self = this;
self.currentModal = '';
self.modal = $('#modal');
self.parent = self.modal.parentNode;
self.modal.addDelegatedEventListener('click', 'header .close', this.closeModal.bind(this));
}
resetModal() {
this.renderModal('', '', '');
}
renderModal(title, content, footer) {
this.currentModal = title;
this.renderHeader(title);
this.renderContent(content);
this.renderFooter(footer);
}
renderHeader(header) {
let h = $('header .headline', this.modal);
h.innerHTML = header;
}
renderContent(content) {
let con = $('modal-content', this.modal);
con.innerHTML = content;
}
renderFooter(footer) {
let con = $('modal-footer', this.modal);
con.innerHTML = footer;
}
closeModal() {
this.parent.addClass("hide")
}
isCurrent(title) {
return title === this.currentModal;
}
2020-04-07 21:44:46 +02:00
2020-08-01 21:51:54 +02:00
showModal() {
this.parent.removeClass("hide")
2020-04-07 21:44:46 +02:00
}
}
class Visual {
constructor() {
this.data = []; //for drawing
this.dataArray = [];
}
2020-08-01 21:51:54 +02:00
updateData() {
}
2020-04-07 21:44:46 +02:00
draw() {
}
setup() {
}
}
class VisualDrawer {
2020-08-01 21:51:54 +02:00
constructor() {
this.visuals = {
"sphere": new Sphere(),
"wave": new Wave(),
"water": new Water()
}
}
init() {
this.switch('wave');
2020-08-01 21:51:54 +02:00
this.updateLoop();
}
2020-04-07 21:44:46 +02:00
2020-08-01 21:51:54 +02:00
switch(visual) {
if (this.visuals[visual] != null) {
this.c = visual;
vConf.loadConfigByName(this.c);
2020-08-01 21:51:54 +02:00
this.visuals[this.c].setup();
}
}
updateLoop() {
let self = this;
let pro = shaderHandler.use(self.c);
let vis = self.visuals[self.c];
vis.updateData();
this.prepare();
2020-08-01 21:51:54 +02:00
vis.draw(pro);
requestAnimationFrame(self.updateLoop.bind(self))
}
prepare() {
c.width = window.innerWidth;
c.height = window.innerHeight;
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor(0, 0, 0, parseFloat(pConf.get("alphaValue", 0)));
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.enable(gl.CULL_FACE);
}
}
class ImageUploader {
async init() {
this.image = pConf.get("bgURL", "");
this.color = pConf.get("bgColour", "#000000");
this.alpha = pConf.get("alphaValue", 0.5);
this.getRealImage();
this.applyValues();
$('#modal').addDelegatedEventListener('change', '#image-upload input:not([type="color"])', this.changeHandler.bind(this));
$('#modal').addDelegatedEventListener('input', '#image-upload input#color', this.changeHandler.bind(this));
}
async renderModal() {
await template.loadTemplate("image");
gui.modal.resetModal();
gui.modal.renderModal("Background-Image",
template.parseTemplate("image", {
value: this.image,
bgValue: this.color,
alphaValue: this.alpha
}), "");
gui.modal.showModal();
}
changeHandler(e, el) {
if (el.id === 'color') {
this.color = el.value;
} else if (el.id === "alphaValue") {
this.alpha = el.value;
} else {
pConf.set('bgMode', el.id);
if (el.id === 'image') {
el.files[0].toBase64((e, b) => {
if (b) {
alert("Error converting image!");
return;
}
pConf.set('bgURL', e.currentTarget.result);
pConf.save();
})
this.image = URL.createObjectURL(el.files[0]);
} else {
this.image = el.value;
pConf.set('bgURL', this.image);
}
}
pConf.set('bgColour', this.color);
pConf.set('alphaValue', this.alpha);
this.applyValues();
pConf.save();
}
applyValues() {
let body = $('body');
body.style.backgroundImage = 'url(' + this.image + ')';
body.style.backgroundColor = this.color;
let blob = $('#colorBlob');
if (blob) {
blob.style.backgroundColor = this.color;
}
}
getRealImage() {
let mode = pConf.get('bgMode'),
value = pConf.get("bgURL", "");
if (mode === 'image') {
if (value !== '' && value.startsWith('data:image')) {
let split = value.split(";"),
type = split.shift(),
message = split.join(";").replace("base64,", "");
this.image = URL.createObjectURL(b64toBlob(message, type));
}
} else {
this.image = value;
}
}
}
const imageUploader = new ImageUploader();
class Notification {
constructor() {
}
2020-08-01 21:51:54 +02:00
}
class Config {
constructor() {
this.config = {};
this.name = ''
}
loadConfigByName(name) {
this.save();
2020-08-01 21:51:54 +02:00
this.name = 'config-' + name;
let item = localStorage.getItem(this.name);
if (item) {
this.config = JSON.parse(item);
}
2020-08-01 21:51:54 +02:00
}
save() {
2020-08-01 21:51:54 +02:00
if (this.name !== '') {
localStorage.setItem(this.name, JSON.stringify(this.config));
}
}
set(name, value) {
2020-08-01 21:51:54 +02:00
this.config[name] = value;
}
remove(name) {
2020-08-01 21:51:54 +02:00
delete this.config[name];
}
get(name, def) {
2020-08-01 21:51:54 +02:00
let value = this.config[name];
if (value === undefined || value === null) {
this.config[name] = def;
value = def;
}
return value;
}
2020-04-07 21:44:46 +02:00
}
class Sphere extends Visual {
draw() {
}
setup() {
}
}
2020-08-01 21:51:54 +02:00
// 3D Audio-Waves -> maybe also 2D?
class Wave extends Visual {
updateData() {
//only for debug! remove pls
if (window.stopUpdate) {
return;
}
2020-08-01 21:51:54 +02:00
let data = audioHandler.getFloatArray();
let add = 2 / data.length,
x = -1;
let outerLoop = 0;
2020-08-01 21:51:54 +02:00
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;
2020-08-01 21:51:54 +02:00
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);
2020-08-01 21:51:54 +02:00
}
rotate(program) {
let aspect = c.width / c.height,
2020-08-01 21:51:54 +02:00
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("xRotate", 0)));
matrix = TDUtils.multiply(matrix, TDUtils.yRotation(vConf.get("yRotate", 0)));
matrix = TDUtils.multiply(matrix, TDUtils.zRotation(vConf.get("zRotate", 0)));
2020-08-01 21:51:54 +02:00
let rotate = gl.getUniformLocation(program, "u_matrix");
gl.uniformMatrix4fv(rotate, false, matrix);
}
setup() {
audioHandler.fftSize(16384)
this.data = new Float32Array(16384 * 9);
vConf.get("zRotate", TDUtils.degToRad(-30));
vConf.get("yRotate", TDUtils.degToRad(50));
vConf.get("xRotate", TDUtils.degToRad(10));
2020-08-01 21:51:54 +02:00
}
prepare(program) {
this.position = gl.getAttribLocation(program, "a_position");
this.color = gl.getUniformLocation(program, "u_color");
let lightPos = gl.getUniformLocation(program, "u_lightPos"),
matrix = gl.getUniformLocation(program, "u_matrix");
gl.uniform3fv(lightPos, vConf.get("light", [0, 5, -56]));
//gl.uniformMatrix4fv(matrix, false, TDUtils.getMatrix(90, c.width / c.height, 1, 2000, 200, 200));
2020-08-01 21:51:54 +02:00
}
}
//animate Water the way like the Audio is Coming... 256FFT-Size max!
class Water extends Visual {
draw() {
}
setup() {
audioHandler.fftSize(256)
}
}
async function initHandler() {
let body = $('body');
$('.playlist.menu-icon').addEventListener('click', e => {
player.playlist.renderPagination(player.playlist.page);
gui.modal.showModal();
2020-08-01 21:51:54 +02:00
});
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':
player.playlist.isShuffle = !player.playlist.isShuffle;
toggleShuffle();
break;
2020-08-01 21:51:54 +02:00
}
togglePlayButton(audioHandler.audioFile.paused ? 'play' : 'pause');
});
window.addEventListener('playSong', setActiveOnPlaylist);
$('.upload-image').addEventListener('click', imageUploader.renderModal.bind(imageUploader));
2020-08-01 21:51:54 +02:00
}
function setActiveOnPlaylist(e) {
let item = $('.playlist-item[data-index="' + player.playlist.index + '"]'),
active = $('.playlist-item.active');
if (active) {
active.removeClass('active');
}
if (item) {
item.addClass('active');
}
}
function toggleShuffle() {
let active = player.playlist.isShuffle;
$('#shuffle').toggleCheck('active', active);
}
2020-08-01 21:51:54 +02:00
function togglePlayButton(status) {
let icons = $$('#play .icon');
icons.forEach(el => {
if (el.dataset.name === status) {
2020-08-01 21:51:54 +02:00
el.removeClass('hide');
} else {
el.addClass('hide');
}
})
}
2020-04-07 21:44:46 +02:00
const shaderHandler = new ShaderHandler(null),
audioHandler = new AudioHandler(),
gui = new GUI(),
2020-08-01 21:51:54 +02:00
visual = new VisualDrawer(),
template = new Template(),
player = new Player(),
vConf = new Config(),
pConf = new Config();
2020-08-01 21:51:54 +02:00
let c = null,
gl = null;
2020-04-07 21:44:46 +02:00
async function startUP() {
pConf.loadConfigByName('default');
2020-08-01 21:51:54 +02:00
c = document.body.querySelector('#c'),
2020-04-07 21:44:46 +02:00
gl = c.getContext("webgl2");
if (!gl) {
alert("SORRY THE BROWSER DOESN'T SUPPORT WEBGL2");
return false;
}
shaderHandler.setGL(gl)
2020-08-01 21:51:54 +02:00
await shaderHandler.loadArray(["wave", "sphere", "water"], 'shaders/');
2020-04-07 21:44:46 +02:00
await audioHandler.init();
await player.init();
2020-08-01 21:51:54 +02:00
await visual.init();
await gui.init();
await imageUploader.init();
2020-08-01 21:51:54 +02:00
await initHandler();
2020-04-07 21:44:46 +02:00
}
startUP().then(r => {
setTimeout(e => {
$('.loading-screen').remove();
}, 100)
});