diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..dfe0770
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# Auto detect text files and perform LF normalization
+* text=auto
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..f0e2bfd
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,12 @@
+{
+ "files.exclude": {
+ "**/.git": true,
+ "**/.svn": true,
+ "**/.hg": true,
+ "**/CVS": true,
+ "**/.DS_Store": true,
+ "**/Thumbs.db": true,
+ "**/.ruby-lsp": true
+ },
+ "hide-files.files": []
+}
\ No newline at end of file
diff --git a/LivelyInfo.json b/LivelyInfo.json
new file mode 100644
index 0000000..deb7018
--- /dev/null
+++ b/LivelyInfo.json
@@ -0,0 +1,17 @@
+{
+ "AppVersion": "1.1.0.0",
+ "Title": "Clean Audio",
+ "Thumbnail": "ypfdr3lc.jpg",
+ "Preview": "bqqlz4yh.gif",
+ "Desc": "Smooth Audio Spectrum Visualizer reacting to media audio and cover",
+ "Author": "Liam S.",
+ "License": "MIT",
+ "Contact": null,
+ "Type": 1,
+ "FileName": "index.html",
+ "Arguments": "--system-nowplaying --audio",
+ "IsAbsolutePath": false,
+ "Id": null,
+ "Tags": null,
+ "Version": 0
+}
\ No newline at end of file
diff --git a/LivelyProperties.json b/LivelyProperties.json
new file mode 100644
index 0000000..0db9a4f
--- /dev/null
+++ b/LivelyProperties.json
@@ -0,0 +1,135 @@
+{
+ "amplitude": {
+ "max": 10.0,
+ "min": 0.1,
+ "tick": 1,
+ "step": 0.1,
+ "text": "Audio Amplification",
+ "type": "slider",
+ "value": 2.5
+ },
+ "samples": {
+ "max": 50,
+ "min": 5,
+ "tick": 1,
+ "step": 1,
+ "text": "Sample Points",
+ "type": "slider",
+ "value": 15
+ },
+ "highres": {
+ "type": "checkbox",
+ "value": true,
+ "text": "High Res."
+ },
+ "sampleType": {
+ "type": "dropdown",
+ "value": 2,
+ "text": "Sample Type",
+ "items": [
+ "Points",
+ "Average",
+ "Maximum"
+ ]
+ },
+ "horizontal": {
+ "type": "dropdown",
+ "value": 0,
+ "text": "Horizontal mode",
+ "items": [
+ "None",
+ "Arm",
+ "Position",
+ "Position + Arm"
+ ]
+ },
+ "smoothing": {
+ "max": 50.0,
+ "min": 1.0,
+ "tick": 1,
+ "step": 1.0,
+ "text": "Visual Smoothing",
+ "type": "slider",
+ "value": 10.0
+ },
+ "colorMode": {
+ "type": "dropdown",
+ "value": 1,
+ "text": "Color Mode",
+ "items": [
+ "Cover",
+ "Suggestive",
+ "Magic",
+ "Generated",
+ "Dominant",
+ "Custom"
+ ]
+ },
+ "mainColor1": {
+ "text": "Main Color",
+ "type": "color",
+ "value": "#E29578"
+ },
+ "mainColor2": {
+ "type": "color",
+ "value": "#FFDDD2"
+ },
+ "bgColor1": {
+ "text": "Backgroud Color",
+ "type": "color",
+ "value": "#006D77"
+ },
+ "bgColor2": {
+ "type": "color",
+ "value": "#83C5BE"
+ },
+
+ "ProjType" : {
+ "type": "dropdown",
+ "value": 1,
+ "text": "Projection Type",
+ "items": [
+ "Normal",
+ "Squared",
+ "freq > id"
+ ]
+ },
+ "gradient" : {
+ "type": "checkbox",
+ "value": true,
+ "text": "Main Gradient"
+ },
+ "gradientBG" : {
+ "type": "checkbox",
+ "value": true,
+ "text": "Background Gradient"
+ },
+ "outlineOnly": {
+ "type": "checkbox",
+ "value": false,
+ "text": "Outline"
+ },
+ "screenshake": {
+ "max": 2.5,
+ "min": 0.0,
+ "tick": 0.1,
+ "step": 0.1,
+ "text": "Screenshake",
+ "type": "slider",
+ "value": 0.0
+ },
+ "shadow": {
+ "type": "checkbox",
+ "value": false,
+ "text": "Shadow"
+ },
+ "layout": {
+ "type": "dropdown",
+ "value": 0,
+ "text": "Layout",
+ "items": [
+ "Centre",
+ "Top-Down"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/bqqlz4yh.gif b/bqqlz4yh.gif
new file mode 100644
index 0000000..c62a8dc
Binary files /dev/null and b/bqqlz4yh.gif differ
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..bdb730f
--- /dev/null
+++ b/index.html
@@ -0,0 +1,22 @@
+
+
+
+
+ Juicy Bubble Sound
+
+
+
+
+
+
+
+
+
diff --git a/js/color-thief.umd.js b/js/color-thief.umd.js
new file mode 100644
index 0000000..c32f693
--- /dev/null
+++ b/js/color-thief.umd.js
@@ -0,0 +1 @@
+!function(t,r){"object"==typeof exports&&"undefined"!=typeof module?module.exports=r():"function"==typeof define&&define.amd?define(r):t.ColorThief=r()}(this,function(){if(!t)var t={map:function(t,r){var n={};return r?t.map(function(t,o){return n.index=o,r.call(n,t)}):t.slice()},naturalOrder:function(t,r){return tr?1:0},sum:function(t,r){var n={};return t.reduce(r?function(t,o,e){return n.index=e,t+r.call(n,o)}:function(t,r){return t+r},0)},max:function(r,n){return Math.max.apply(null,n?t.map(r,n):r)}};var r=function(){var r=5,n=8-r,o=1e3;function e(t,n,o){return(t<<2*r)+(n<f/2){for(e=n.copy(),i=n.copy(),u=(r=a-n[s])<=(o=n[h]-a)?Math.min(n[h]-1,~~(a+o/2)):Math.max(n[s],~~(a-1-r/2));!v[u];)u++;for(c=l[u];!c&&v[u-1];)c=l[--u];return e[h]=u,i[s]=e[h]+1,[e,i]}}(u==o?"r":u==i?"g":"b")}}return u.prototype={volume:function(t){return this._volume&&!t||(this._volume=(this.r2-this.r1+1)*(this.g2-this.g1+1)*(this.b2-this.b1+1)),this._volume},count:function(t){var r=this.histo;if(!this._count_set||t){var n,o,i,u=0;for(n=this.r1;n<=this.r2;n++)for(o=this.g1;o<=this.g2;o++)for(i=this.b1;i<=this.b2;i++)u+=r[e(n,o,i)]||0;this._count=u,this._count_set=!0}return this._count},copy:function(){return new u(this.r1,this.r2,this.g1,this.g2,this.b1,this.b2,this.histo)},avg:function(t){var n=this.histo;if(!this._avg||t){var o,i,u,a,s=0,h=1<<8-r,c=0,f=0,v=0;for(i=this.r1;i<=this.r2;i++)for(u=this.g1;u<=this.g2;u++)for(a=this.b1;a<=this.b2;a++)s+=o=n[e(i,u,a)]||0,c+=o*(i+.5)*h,f+=o*(u+.5)*h,v+=o*(a+.5)*h;this._avg=s?[~~(c/s),~~(f/s),~~(v/s)]:[~~(h*(this.r1+this.r2+1)/2),~~(h*(this.g1+this.g2+1)/2),~~(h*(this.b1+this.b2+1)/2)]}return this._avg},contains:function(t){var r=t[0]>>n;return gval=t[1]>>n,bval=t[2]>>n,r>=this.r1&&r<=this.r2&&gval>=this.g1&&gval<=this.g2&&bval>=this.b1&&bval<=this.b2}},a.prototype={push:function(t){this.vboxes.push({vbox:t,color:t.avg()})},palette:function(){return this.vboxes.map(function(t){return t.color})},size:function(){return this.vboxes.size()},map:function(t){for(var r=this.vboxes,n=0;n251&&e[1]>251&&e[2]>251&&(r[o].color=[255,255,255])}},{quantize:function(h,c){if(!h.length||c<2||c>256)return!1;var f=function(t){var o,i=new Array(1<<3*r);return t.forEach(function(t){o=e(t[0]>>n,t[1]>>n,t[2]>>n),i[o]=(i[o]||0)+1}),i}(h);f.forEach(function(){});var v=function(t,r){var o,e,i,a=1e6,s=0,h=1e6,c=0,f=1e6,v=0;return t.forEach(function(t){(o=t[0]>>n)s&&(s=o),(e=t[1]>>n)c&&(c=e),(i=t[2]>>n)v&&(v=i)}),new u(a,s,h,c,f,v,r)}(h,f),l=new i(function(r,n){return t.naturalOrder(r.count(),n.count())});function g(t,r){for(var n,e=t.size(),i=0;i=r)return;if(i++>o)return;if((n=t.pop()).count()){var u=s(f,n),a=u[0],h=u[1];if(!a)return;t.push(a),h&&(t.push(h),e++)}else t.push(n),i++}}l.push(v),g(l,.75*c);for(var p=new i(function(r,n){return t.naturalOrder(r.count()*r.volume(),n.count()*n.volume())});l.size();)p.push(l.pop());g(p,c);for(var d=new a;p.size();)d.push(p.pop());return d}}}().quantize,n=function(t){this.canvas=document.createElement("canvas"),this.context=this.canvas.getContext("2d"),this.width=this.canvas.width=t.width,this.height=this.canvas.height=t.height,this.context.drawImage(t,0,0,this.width,this.height)};n.prototype.getImageData=function(){return this.context.getImageData(0,0,this.width,this.height)};var o=function(){};return o.prototype.getColor=function(t,r){return void 0===r&&(r=10),this.getPalette(t,5,r)[0]},o.prototype.getPalette=function(t,o,e){var i=function(t){var r=t.colorCount,n=t.quality;if(void 0!==r&&Number.isInteger(r)){if(1===r)throw new Error("colorCount should be between 2 and 20. To get one color, call getColor() instead of getPalette()");r=Math.max(r,2),r=Math.min(r,20)}else r=10;return void 0===n||Number.isInteger(n)?n=10:n<1&&(n=10),{colorCount:r,quality:n}}({colorCount:o,quality:e}),u=new n(t),a=function(t,r,n){for(var o=t,e=[],i=0,u=void 0,a=void 0,s=void 0,h=void 0,c=void 0;i=125)&&(a>250&&s>250&&h>250||e.push([a,s,h]));return e}(u.getImageData().data,u.width*u.height,i.quality),s=r(a,i.colorCount);return s?s.palette():null},o.prototype.getColorFromUrl=function(t,r,n){var o=document.createElement("img"),e=this;o.addEventListener("load",function(){var i=e.getPalette(o,5,n);r(i[0],t)}),o.src=t},o.prototype.getImageData=function(t,r){var n=new XMLHttpRequest;n.open("GET",t,!0),n.responseType="arraybuffer",n.onload=function(){if(200==this.status){var t=new Uint8Array(this.response);o=t.length;for(var n=new Array(o),o=0;o Watch tutorial on how to make audio visualizer in Adobe AfterEffects
+// > Or Offset each sample based on difference to next sample
+// Prefer Accent colors with the most hue and bg colors with the least contrast / hue ( Match colors with simmilar contrast, but most different saturation, value and hue)
+
+function setSize() {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ max_height = window.innerHeight * 0.25;
+ startPos = window.innerWidth * 0.1;
+ vizWidth = window.innerWidth * 0.8;
+ vizHeight = window.innerHeight * 0.85;
+ midY = canvas.height - canvas.height / 2;
+ midX = canvas.width - canvas.width / 2;
+}
+
+window.onload = () => {
+ setSize();
+};
+
+window.onresize = () => {
+ setSize();
+};
+
+function livelyPropertyListener(name, val) {
+ switch(name) {
+ case "amplitude":
+ amplitude = val;
+ break;
+ case "samples":
+ sampleCount = val;
+ break;
+ case "highres":
+ high_res = val;
+ break;
+ case "sampleType":
+ sampleType = val;
+ break;
+ case "horizontal":
+ horizonatl_arm = (val == 1 || val == 3);
+ horizonatl_position = (val == 2 || val == 3);
+ break;
+ case "smoothing":
+ smoothing = val;
+ break;
+ case "colorMode":
+ colorMode = val;
+ break;
+ case "mainColor1":
+ mainColor1 = hexToRgb(val);
+ break;
+ case "mainColor2":
+ mainColor2 = hexToRgb(val);
+ break;
+ case "bgColor1":
+ bgColor1 = hexToRgb(val);
+ break;
+ case "bgColor2":
+ bgColor2 = hexToRgb(val);
+ break;
+ case "outlineOnly":
+ outlineOnly = val;
+ break;
+ case "gradient":
+ gradientMain = val;
+ break;
+ case "gradientBG":
+ gradientBG = val;
+ break;
+ case "ProjType":
+ projType = val;
+ break;
+ case "screenshake":
+ screenshake = val;
+ break;
+ case "shadow":
+ shadow = val;
+ break;
+ case "layout":
+ layout = val;
+ break;
+ }
+}
+
+
+let time = 0.0;
+
+let activeArr = []
+function livelyAudioListener(audioArray) {
+ /* SCREENSHAKE */
+ let pos_offset = [0, 0];
+ let noise_ferq = 0.4;
+ if (screenshake > 0.0) {
+ let bass = 0.0;
+ for (let i = 0; i < 25; i++){ bass += audioArray[i] * amplitude * screenshake; }
+ bass = bass * bass;
+
+ pos_offset = [
+ noise(time * noise_ferq) * bass,
+ noise(10000.0 + time * noise_ferq) * bass,
+ ];
+ }
+
+ time += 0.01;
+
+ /* COLORS */
+ /*
+ if ( last_cover_data != "" ) {
+ setColorDataShit(last_cover_data)
+ }*/
+
+ if ( cover.src == null ) {
+ finCols = setColorsToPicked()
+ } else {
+ try {
+ col = colorThief.getPalette(cover, 8);
+ // col.unshift(colorThief.getColor(cover));
+ switch(colorMode) {
+ case 0: // cover
+ finCols = doColorStuff(col);
+ break;
+ case 1: // suggestive
+ finCols = colorStuffSuggestive(col);
+ break;
+ case 2: // suggestive
+ finCols = colorMagic(col);
+ break;
+ case 3: // magic
+ finCols = generatedColors();
+ break;
+ case 4: // dominant
+ finCols = colorDominant(col, colorThief.getColor(cover));
+ break;
+ case 5: // custom
+ finCols = setColorsToPicked()
+ break;
+ default:
+ finCols = generatedColors();
+ break;
+ }
+ } catch (_err) {
+ finCols = setColorsToPicked()
+ }
+ }
+
+ console.log("[" + finCols + "]")
+ console.log("{" + last_cover_data + "}")
+
+ linesColor = finCols[0];
+ backgroundColor = finCols[1];
+
+ /* AUDIO ARRAY */
+ // Smooth Audio Array
+ while (activeArr.length < audioArray.length) { activeArr.push(0.0); }
+ for (let i = 0; i < audioArray.length; i++) {
+ activeArr[i] = lerp(activeArr[i], audioArray[i], smoothing * 0.05);
+ }
+
+ // BG
+ ctx.fillStyle = (gradientBG ? BGgradient : backgroundColor);
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+ // Setter
+ let start = startPos;
+ let end = startPos + vizWidth;
+
+
+ // Modify Array for Caps and correct sampling type and scaling
+ let arr = [0.0];
+ let arrx = [0.0];
+ let interval = Math.floor(activeArr.length / sampleCount);
+ switch (sampleType) {
+ case 0: // Normal
+ for (let i = 0; i < activeArr.length; i++) {
+ let j = i;
+
+ if (projType == 1) { j = bassProjFn(i, activeArr.length); }
+ if (projType == 2) { j = id2FreqFn(i, activeArr.length); }
+
+ let xp = parseFloat(i) / parseFloat(activeArr.length);
+ let yp = activeArr[j];
+
+ arr.push(yp);
+ arrx.push(xp);
+ }
+ break;
+ case 1: // Average
+ for (let i = 0; i < activeArr.length - interval; i += interval) {
+ let a = i;
+ let b = i + interval;
+ let val = 0.0;
+
+ // Average
+ for (let j = a; j < b; j++) {
+ let id = j;
+ if (projType == 1) { id = bassProjFn(j, activeArr.length); }
+ if (projType == 2) { id = id2FreqFn(j, activeArr.length); }
+ val += activeArr[id];
+ } val /= b - a;
+
+ let xp = parseFloat(i) / parseFloat(activeArr.length);
+ let yp = val;
+
+ arr.push(yp);
+ arrx.push(xp);
+ }
+ break;
+ case 2: // Maximum
+ for (let i = 0; i < activeArr.length - interval; i += interval) {
+ let a = i;
+ let b = i + interval;
+ let val = 0.0;
+
+ let xoff = 0.0;
+
+ // Average
+ for (let j = a; j < b; j++) {
+ let id = j;
+ if (projType == 1) { id = bassProjFn(j, activeArr.length); }
+ if (projType == 2) { id = id2FreqFn(j, activeArr.length); }
+ val = Math.max(val, activeArr[id]);
+
+ let hor_dir = Math.sign(j - (a + b / 2.0));
+ if (j - (a + b / 2.0) < 1.0) { hor_dir = 0.0; }
+
+ xoff += val * hor_dir * horizonatl_power;
+ }
+
+ let xp = parseFloat(i) / parseFloat(activeArr.length) + xoff;
+ let yp = val;
+
+ arr.push(yp);
+ arrx.push(xp);
+ }
+ break;
+ case 3: // Minimum
+ for (let i = 0; i < activeArr.length - interval; i += interval) {
+ let a = i;
+ let b = i + interval;
+ let val = 0.0;
+
+ let xoff = 0.0;
+
+ // Average
+ for (let j = a; j < b; j++) {
+ let id = j;
+ if (projType == 1) { id = bassProjFn(j, activeArr.length); }
+ if (projType == 2) { id = id2FreqFn(j, activeArr.length); }
+ val = Math.min(val, activeArr[id]);
+
+ let hor_dir = Math.sign(j - (a + b / 2.0));
+ if (j - (a + b / 2.0) < 1.0) { hor_dir = 0.0; }
+
+ xoff += val * hor_dir * horizonatl_power;
+ }
+
+ let xp = parseFloat(i) / parseFloat(activeArr.length) + xoff;
+ let yp = val;
+
+ arr.push(yp);
+ arrx.push(xp);
+ }
+ break;
+ }
+ arr.push(0.0);
+ arrx.push(1.0);
+
+
+ // Bezier Test
+ // let sample_points = 2;
+ let BEZIER_RES = high_res ? 35 : 15;
+ let SAMPLES = sampleCount;
+
+
+ // Move first point to be more clean, yk
+ arrx[0] = arrx[1] - (1.0 / SAMPLES);
+
+
+ let pts = []
+
+ ctx.beginPath();
+ ctx.lineJoin = "round";
+ ctx.moveTo(startPos, midY);
+
+
+ // Compute
+ // TODO : Precompute every point and then calculate smoothing bezier arms
+ for (let i = 0; i < SAMPLES; i++) {
+ let progFrom = parseFloat(i+0) / parseFloat(SAMPLES);
+ let progTo = parseFloat(i+1) / parseFloat(SAMPLES);
+
+ // let prog0 = parseFloat(Math.max(i-1), 0) / parseFloat(SAMPLES);
+ // let prog3 = parseFloat(Math.min(i+2), SAMPLES) / parseFloat(SAMPLES);
+
+ let posFrom = (layout == 0) ? lerp(start, end, progFrom) : (lerp(0, canvas.width, progFrom));
+ let posTo = (layout == 0) ? lerp(start, end, progTo) : (lerp(0, canvas.width, progTo));
+
+ if (horizonatl_position) {
+ posFrom = lerp(start, start, arrx[i]);
+ posTo = lerp(start, start, arrx[i+1]);
+ }
+
+ //let valFrom = Math.floor(progTo * (arr.length-1));
+ //let valTo = Math.floor(prog3 * (arr.length-1));
+
+ let valFrom = Math.floor(progFrom * (arr.length-1));
+ let valTo = Math.floor(progTo * (arr.length-1));
+
+ // let val0 = Math.floor(prog0 * (arr.length-1));
+ // let val3 = Math.floor(prog3 * (arr.length-1));
+ // TODO : Set Arm height to average of samples left and right by offset ( 5 - 0 - 5)
+ // 10 16 12
+ let x1 = posFrom;
+ let x2 = posTo;
+ // TODO : Get Max at sample point
+ let y1 = arr[valFrom] * amplitude * max_height;
+ let y2 = arr[valTo] * amplitude * max_height;
+
+ // TODO : try calculating bezier handles with next and previous audio array points
+ let y0 = y1; // arr[val0] * amplitude * max_height;
+ let y3 = y2; // arr[val3] * amplitude * max_height;
+
+ if (horizonatl_arm) {
+ if (i > 0) { y0 = y1 - arrx[i] * 20.0; }
+ if (i < SAMPLES-1) { y3 = y2 + arrx[i] * 20.0; }
+ }
+
+ // let div = valTo - valFrom;
+
+ // Get average volume at sample range
+ /*
+ let y1 = 0.0;
+ for (let k = valFrom; k < valFrom + div; k++) {
+ // y1 += arr[i];
+ y1 = Math.max(y1, arr[k]);
+ }// y1 /= parseFloat(div);
+ let y2 = 0.0;
+ for (let k = valTo; k < valTo + div; k++) {
+ // y2 += arr[i];
+ y2 = Math.max(y2, arr[k]);
+ }// y2 /= parseFloat(div);
+
+ y1 *= amplitude * max_height;
+ y2 *= amplitude * max_height;
+
+ // Cap volumes at end
+ if (i == 0) {
+ y1 = 0.0;
+ }
+ if (i == SAMPLES-1) {
+ y2 = 0.0;
+ }
+ */
+
+ // TODO : Make seperate function
+ // Make it work with multiple sample points
+ for (let j = 0; j < BEZIER_RES; j++) {
+ let t = parseFloat(j) / parseFloat(BEZIER_RES);
+ let aM = lerp(x1, x2, 0.5); // Arm Middle
+ let p0 = [x1, y1];
+ let p1 = [aM, y0]; // arm left to right // Add y0 and y3 calculation here 0
+ let p2 = [aM, y3]; // arm right to left // Add y0 and y3 calculation here 3
+ let p3 = [x2, y2];
+
+
+ let px = Math.pow(1-t,3)*p0[0] + 3*t*Math.pow(1-t,2)*p1[0] + 3*t*t*(1-t)*p2[0] + t*t*t*p3[0];
+ let py = Math.pow(1-t,3)*p0[1] + 3*t*Math.pow(1-t,2)*p1[1] + 3*t*t*(1-t)*p2[1] + t*t*t*p3[1];
+
+ py = Math.max(0.0, py); // Clamp to be at least 0
+
+ // if (py < 5.0) { py = 0.0; }
+
+ // ctx.lineTo(px, py);
+ pts.push([px, py]);
+ }
+ }
+
+
+
+
+ BGgradient = ctx.createLinearGradient(0, 0, window.innerWidth, window.innerHeight);
+ BGgradient.addColorStop(0, finCols[1]);
+ BGgradient.addColorStop(1, finCols[4]);
+
+ gradient = ctx.createLinearGradient(0, 0, Math.max(window.innerWidth, window.innerHeight), Math.max(window.innerWidth, window.innerHeight));
+ gradient.addColorStop(0, finCols[0]);
+ gradient.addColorStop(1, finCols[3]);
+
+
+
+ // Shadows
+ let shadow_pslit_colors = finCols[1].replace("rgb(", "").replace("rgba(", "").replace(")", "").replace(" ", "").split(",");
+ let sr = parseInt(shadow_pslit_colors[0]);
+ let sg = parseInt(shadow_pslit_colors[1]);
+ let sb = parseInt(shadow_pslit_colors[2]);
+ let sd = parseFloat((sr + sg + sb) / 3) / 255.0;
+ const shadow_darkening = parseInt( lerp(0, 150, Math.pow(sd, 1.6)) );
+ let shadow_col = `rgb(${sr - shadow_darkening}, ${sg - shadow_darkening}, ${sb - shadow_darkening})`;
+
+ if (shadow) {
+ ctx.shadowColor = shadow_col;
+ ctx.shadowBlur = 10;
+ ctx.shadowOffsetX = 28;
+ ctx.shadowOffsetY = 22;
+ } else {
+ ctx.shadowColor = 'rgba(0, 0, 0, 0)';
+ ctx.shadowColor = null;
+ ctx.shadowBlur = 0;
+ ctx.shadowOffsetX = 0;
+ ctx.shadowOffsetY = 0;
+ }
+
+
+ /*
+ >> Alternative Interpolation Method using Easing
+
+ if (false) {
+ let NEIGHBORS = 10; // Neighbors to scan left & right
+ let RES = 10; // Steps between two sample points
+
+ for (let i = 0; i < arr.length * RES; i++) {
+ let pos = parseFloat(i) / parseFloat(RES);
+ let fpos = Math.floor(pos); // flat
+ let v = 0;
+ for (let j = -NEIGHBORS; j < NEIGHBORS; j++) {
+ let p = Math.min(Math.max(pos + j, 0), arr.length); // Clamp
+
+ let prev = arr[Math.floor(p)];
+ let next = arr[Math.ceil(p)];
+ let fract = p - Math.trunc(p);
+ let rv = lerp(prev, next, easeInOutQuint(fract)); // TODO : Use easeing / smooth lerp function
+ v += rv;
+ }
+
+ let px = lerp(start, end, parseFloat(fpos) / parseFloat(arr.length));
+
+ if (i == 0) { v = 0; }
+ let py = v * amplitude * max_height * 0.1;
+ pts.push([px, py]);
+ }
+ }
+ */
+
+ // Draw Lines
+ // Bottom
+ if (layout == 0) {
+ ctx.beginPath();
+ ctx.lineJoin = "round";
+ ctx.moveTo(startPos + pos_offset[0], midY + pos_offset[1]);
+
+ for (let i = 0; i < pts.length; i++) {
+ let p = pts[i];
+ ctx.lineTo(p[0] + pos_offset[0], midY + p[1] + pos_offset[1]);
+ }
+
+ // Top
+ // ctx.moveTo(startPos + pos_offset[0], midY + pos_offset[1]);
+ for (let i = pts.length-1; i >= 0; i--) {
+ let p = pts[i];
+ ctx.lineTo(p[0] + pos_offset[0], midY - p[1] + pos_offset[1]);
+ }
+
+
+ if (outlineOnly) {
+ ctx.shadowBlur = 10.0;
+ ctx.shadowColor = gradientMain ? gradient : linesColor;
+
+ ctx.lineWidth = 5;
+ ctx.strokeStyle = gradientMain ? gradient : linesColor;
+ ctx.stroke();
+ }
+ else {
+ ctx.shadowBlur = 10.0;
+ ctx.fillStyle = gradientMain ? gradient : linesColor;
+ ctx.fill();
+ }
+
+ /*
+ // Top
+ ctx.beginPath();
+ ctx.lineJoin = "round";
+ ctx.moveTo(startPos + pos_offset[0], midY + pos_offset[1]);
+
+ for (let i = 0; i < pts.length; i++) {
+ let p = pts[i];
+ ctx.lineTo(p[0] + pos_offset[0], midY - p[1] + pos_offset[1]);
+ }
+ ctx.lineTo(end + pos_offset[0], midY + pos_offset[1]);
+
+ if (outlineOnly) {
+ ctx.shadowBlur = 10.0;
+ ctx.shadowColor = gradientMain ? gradient : linesColor;
+
+ ctx.lineWidth = 5;
+ ctx.strokeStyle = gradientMain ? gradient : linesColor;
+ ctx.stroke();
+ }
+ else {
+ ctx.shadowBlur = 0.0;
+
+ ctx.fillStyle = gradientMain ? gradient : linesColor;
+ ctx.fill();
+ }
+ */
+
+ // Song Data
+ ctx.textAlign = "center";
+ // BG
+ ctx.fillStyle = gradientMain ? gradient : linesColor; // gradientMain ? gradient : linesColor
+ ctx.strokeStyle = gradientBG ? BGgradient : backgroundColor; // gradientMain ? gradient : linesColor
+ ctx.lineWidth = 4.0;
+
+ ctx.font = "700 48px Verdana";
+ ctx.strokeText(title, midX + pos_offset[0], vizHeight + pos_offset[1]);
+ ctx.fillText(title, midX + pos_offset[0], vizHeight + pos_offset[1]);
+ ctx.font = "300 32px Verdana";
+ ctx.strokeText(artist, midX + pos_offset[0], vizHeight + 72 + pos_offset[1]);
+ ctx.fillText(artist, midX + pos_offset[0], vizHeight + 72 + pos_offset[1]);
+ } else if (layout == 1) {
+ ctx.beginPath();
+ ctx.lineJoin = "round";
+ ctx.moveTo(startPos + pos_offset[0], 0);
+
+ for (let i = 0; i < pts.length; i++) {
+ let p = pts[i];
+ ctx.lineTo(p[0] + pos_offset[0], p[1]*0.5);
+ }
+
+ // Top
+ ctx.moveTo(canvas.width, canvas.height);
+
+ for (let i = pts.length-1; i >= 0; i--) {
+ let p = pts[i];
+ ctx.lineTo(p[0] + pos_offset[0], canvas.height - p[1]*0.5 - bottomCap);
+ }
+
+
+ if (outlineOnly) {
+ ctx.shadowBlur = 10.0;
+ ctx.shadowColor = gradientMain ? gradient : linesColor;
+
+ ctx.lineWidth = 5;
+ ctx.strokeStyle = gradientMain ? gradient : linesColor;
+ ctx.stroke();
+ }
+ else {
+ ctx.shadowBlur = 10.0;
+ ctx.fillStyle = gradientMain ? gradient : linesColor;
+ ctx.fill();
+ }
+
+
+ // Song Data
+ ctx.textAlign = "center";
+ // BG
+ ctx.fillStyle = gradientMain ? gradient : linesColor; // gradientMain ? gradient : linesColor
+ ctx.strokeStyle = gradientBG ? BGgradient : backgroundColor; // gradientMain ? gradient : linesColor
+ ctx.lineWidth = 4.0;
+
+ ctx.font = "700 48px Verdana";
+ ctx.strokeText(title, midX + pos_offset[0], canvas.height / 2.0 + pos_offset[1]);
+ ctx.fillText(title, midX + pos_offset[0], canvas.height / 2.0 + pos_offset[1]);
+ ctx.font = "300 32px Verdana";
+ ctx.strokeText(artist, midX + pos_offset[0], canvas.height / 2.0 + 72 + pos_offset[1]);
+ ctx.fillText(artist, midX + pos_offset[0], canvas.height / 2.0 + 72 + pos_offset[1]);
+ }
+
+
+ // Bottom Bar for better readabillity
+ ctx.fillStyle = finCols[2] ? (gradientBG ? BGgradient : backgroundColor) : (gradientMain ? gradient : linesColor);
+ ctx.fillRect(0, window.innerHeight-bottomCap, window.innerWidth, bottomCap);
+}
+
+
+
+
+
+
+function livelyCurrentTrack(data) {
+ if ( data == "" ) { return }
+ setColorDataShit(data)
+ last_cover_data = data
+}
+
+// Set all visual data, based on the given .json data
+function setColorDataShit(data) {
+ let obj = JSON.parse(data);
+ if (obj != null) {
+ // console.log(data)
+
+ if (obj.Thumbnail != null) {
+ cover.src = "data:image/png;base64, " + obj.Thumbnail;
+ } else {
+ cover.src = null
+ }
+
+ if (obj.Title != null) {
+ title = obj.Title;
+ }
+ if (obj.Artist != null) {
+ artist = obj.Artist;
+ }
+
+ } else {
+ cover.src = null
+ title = "ERROR - FUuUCK"
+ artist = "joe mama"
+ }
+
+
+}
+
+
+
+// ID COnversions
+function bassProjFn(i, l) {
+ return Math.floor( ( Math.pow(parseFloat(i) / parseFloat(l), 2.0) ) * l );
+}
+
+const max_freq = 5000;
+function id2FreqFn(i, l) {
+ let freq = parseFloat(i) / parseFloat(l) * max_freq;
+ return Math.floor(parseFloat(12 * Math.log2(freq / 20.0)) / 95.0 * l);
+}
+
+// EASINGS
+// In Put Quint
+function easeInOutQuint(x) {
+ return x < 0.5 ? 16 * x * x * x * x * x : 1 - Math.pow(-2 * x + 2, 5) / 2;
+}
+
+
+//ref: https://stackoverflow.com/questions/9733288/how-to-programmatically-calculate-the-contrast-ratio-between-two-colors
+function luminance(r, g, b) {
+ var a = [r, g, b].map(function (v) {
+ v /= 255;
+ return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
+ });
+ return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
+}
+
+function contrast(rgb1, rgb2) {
+ // let hsv1 = rgbToHsv(rgb1);
+ // let hsv2 = rgbToHsv(rgb2);
+ let lum1 = luminance(rgb1[0], rgb1[1], rgb1[2]);
+ let lum2 = luminance(rgb2[0], rgb2[1], rgb2[2]);
+ let brightest = Math.max(lum1, lum2);
+ let darkest = Math.min(lum1, lum2);
+ return (brightest + 0.05) / (darkest + 0.05);
+}
+
+// TODO : Add setting , where you can select the amount of influence the music cover has on the visualizer ( colors ) > full, suggesive, generated
+
+function setColorsToPicked() {
+ return [
+ `rgb(${mainColor1.r}, ${mainColor1.g}, ${mainColor1.b})`,
+ `rgb(${bgColor1.r}, ${bgColor1.g}, ${bgColor1.b})`,
+ (bgValue([mainColor1.r, mainColor1.g, mainColor1.b]) > bgValue([bgColor1.r, bgColor1.g, bgColor1.b])),
+ `rgb(${mainColor2.r}, ${mainColor2.g}, ${mainColor2.b})`,
+ `rgb(${bgColor2.r}, ${bgColor2.g}, ${bgColor2.b})`
+ ];
+}
+
+function doColorStuff(color) {
+ let mainColor = color[1]; // assume
+ let minc = 0;
+ for (let i = 1; i < color.length; i++) {
+ let c = rgbToHsv(color[i])
+ let tmp = contrast(mainColor, c); // (Math.abs(c[0] - rgbToHsv(mainColor)[0])) + ( c[1] + c[2] ) * 0.75;
+ if (tmp > minc) {
+ minc = tmp;
+ mainColor = color[i];
+ }
+ }
+
+ let bgColor = color[0]; // assume
+ if (bgColor == mainColor) {
+ bgColor = color[1];
+ }
+
+ if (rgbDifference(mainColor, bgColor) < 0.07) {
+ let mincs = 0;
+ for (let i = 0; i < color.length; i++) {
+ // let c = rgbToHsv(color[i]);
+ let tmp = contrast(mainColor, color[i]); // (Math.abs(c[0] - rgbToHsv(mainColor)[0])) + ( c[1] + c[2] ) * 0.75;
+ if (tmp > mincs && tmp > 0.01) {
+ mincs = tmp;
+ bgColor = color[i];
+ }
+ }
+ }
+
+ let simmilarColor = mainColor; // assume
+ let mincs = 1.0;
+ for (let i = 1; i < color.length; i++) {
+ let tmp = rgbDifference(mainColor, color[i]);
+ if (tmp < mincs && tmp > 0.03) {
+ mincs = tmp;
+ simmilarColor = color[i];
+ }
+ }
+
+ let simmilarColorBG = bgColor; // assume
+ let mincsbg = 1.0;
+ for (let i = 0; i < color.length; i++) {
+ let tmp = rgbDifference(bgColor, color[i]);
+ if (tmp < mincsbg && tmp > 0.03) {
+ mincsbg = tmp;
+ simmilarColorBG = color[i];
+ }
+ }
+
+
+ let darkColor = bgValue(mainColor) > bgValue(bgColor);
+
+ // [0] > gradient > [3] , [1] show background at bottom true / false , [2] darker Main / BG , [1] > gradient > [4]
+ return [`rgb(${mainColor.toString()}`, `rgb(${bgColor.toString()}`, darkColor, `rgb(${simmilarColor.toString()}`, `rgb(${simmilarColorBG.toString()}`];
+}
+
+function colorStuffSuggestive(color) {
+
+ // [0] > gradient > [3] , [1] show background at bottom true / false , [2] darker Main / BG , [1] > gradient > [4]
+ // return [`rgb(${mainColor.toString()}`, `rgb(${bgColor.toString()}`, darkColor, `rgb(${simmilarColor.toString()}`, `rgb(${simmilarColorBG.toString()}`];
+
+ let mainA = [0, 0, 0]
+ let mainInterest = 0.0;
+ let mainB = [0, 0, 0]
+ let mainBDiff = 1.0
+ let bgA = [0, 0, 0]
+ let bgDifference = 0.0;
+ let bgB = [0, 0, 0]
+ let bgBDiff = 1.0
+
+ let isBorderMain = false
+
+
+ // Find most interesting color
+ for (let i = 0; i < color.length; i++) {
+ let c = color[i];
+ let chsv = rgbToHsv(c);
+ let interest = ( chsv[1] * 5.0 + chsv[2] * 2.0 ) / 7.0 / 256.0;
+ if (interest > mainInterest) {
+ mainInterest = interest;
+ mainA = c;
+ }
+ }
+
+ // Find color with most difference to main color
+ for (let i = 0; i < color.length; i++) {
+ let c = color[i];
+ let chsv = rgbToHsv(c);
+ let mainhsv = rgbToHsv(mainA);
+
+ if (c == mainA) { continue; }
+
+ // TODO : Add check to figure out if a light- or dark background would fit better
+ let idealHDiff = ( mainhsv[0] + 128.0 ) % 256.0;
+ let idealDiff = (idealHDiff + ( Math.abs(mainhsv[1] - chsv[1]) ) + ( Math.abs(mainhsv[2] - chsv[2]) ) ) / 3.0;
+ if (idealDiff > bgDifference) {
+ bgDifference = idealDiff;
+ bgA = c;
+ }
+ }
+
+ // Find colors most simmilar to main and bg
+ for (let i = 0; i < color.length; i++) {
+ let c = color[i];
+ let chsv = rgbToHsv(c);
+
+ if (c != mainA) {
+ let diff = ( Math.abs(c[0] - mainA[0]) + Math.abs(c[1] - mainA[1]) + Math.abs(c[2] - mainA[2]) ) / 3.0 / 256.0;
+ if (diff < mainBDiff) {
+ mainBDiff = diff;
+ mainB = c;
+ }
+ }
+
+ if (c != bgA) {
+ let diff = ( Math.abs(c[0] - bgA[0]) + Math.abs(c[1] - bgA[1]) + Math.abs(c[2] - bgA[2]) ) / 3.0 / 256.0;
+ if (diff < bgBDiff) {
+ bgBDiff = diff;
+ bgB = c;
+ }
+ }
+ }
+
+ isBorderMain = bgValue(mainA) > bgValue(bgA);
+
+ // 1. Determine how "interesting" the cover is by checking how many of the generated colors have a different hue / saturation / value to eachother
+ // 2. Use that data to determine what kind of color theorie type ( color wheel ) should be used
+ // 3. Determine the main color based on the most interesting color in the shot
+ // 4. Generate a secondary color based on the color theorie
+ // 5. Generate 2 gradient colors based on more color theorie
+
+ return [`rgb(${mainA.toString()}`, `rgb(${bgA.toString()}`, isBorderMain, `rgb(${mainB.toString()}`, `rgb(${bgB.toString()}`];
+}
+
+/* Returns 2 color gradients based on 1 selected main color */
+function colorMagic(color) {
+ // Get most interesting color
+ let mainA = [0, 0, 0];
+ let mainA_interest = 0.0;
+
+ for (let i = 0; i < color.length; i++) {
+ let c = color[i];
+ let chsv = rgbToHsv(color[i]);
+ let interest = (chsv[1] * 2.5) * chsv[2] * 1.7;
+ // TODO : interest should get multiplier if there's another color of similar ( hue and value ) in 'color' array
+
+ // Quantitiy multiplier
+ let quant = 1.0;
+ for (let j = 0; j < color.length; j++) {
+ let c2 = color[i];
+ let diff = ( Math.abs(c[0] - mainA[0]) + Math.abs(c[1] - mainA[1]) + Math.abs(c[2] - mainA[2]) ) / 3.0 / 256.0;
+ quant += diff < 0.25 ? diff : 0.0;
+ }
+ interest *= quant;
+
+ if (interest > mainA_interest) {
+ mainA = c;
+ mainA_interest = interest;
+ }
+ }
+
+ // Get BG Color
+ // Select as BG color if contrast OR colorshift big enough
+ let bgA = [0, 0, 0];
+ let bgA_contrast = 0.0;
+ let bgA_shift = 0.0; // Closest distance to complimentary hue of main color
+
+ for (let i = 0; i < color.length; i++) {
+ let c = color[i];
+ let chsv = rgbToHsv(color[i]);
+ let ctr = contrast(mainA, c);
+ let shift = Math.abs( (( rgbToHsv(c)[0] + 180 ) % 360 ) - ( chsv[0] ) ) ;
+
+ if (ctr > bgA_contrast || shift > bgA_shift) {
+ bgA = c;
+ bgA_contrast = ctr;
+ bgA_shift = shift;
+ }
+ }
+
+
+ // Get gradient colors
+ let mainB = [0, 0, 0];
+ let mainBDiff = 1.0;
+ let bgB = [0, 0, 0];
+ let bgBDiff = 1.0;
+
+ // Find colors most simmilar to main and bg
+ // TODO : Make gradient function 2.0 . focusing on more color contrast
+ for (let i = 0; i < color.length; i++) {
+ let c = color[i];
+ let chsv = rgbToHsv(c);
+
+ if (c != mainA) {
+ let diff = ( Math.abs(c[0] - mainA[0]) + Math.abs(c[1] - mainA[1]) + Math.abs(c[2] - mainA[2]) ) / 3.0 / 256.0;
+ if (diff < mainBDiff) {
+ mainBDiff = diff;
+ mainB = c;
+ }
+ }
+
+ if (c != bgA) {
+ let diff = ( Math.abs(c[0] - bgA[0]) + Math.abs(c[1] - bgA[1]) + Math.abs(c[2] - bgA[2]) ) / 3.0 / 256.0;
+ if (diff < bgBDiff) {
+ bgBDiff = diff;
+ bgB = c;
+ }
+ }
+ }
+
+ let isBorderMain = bgValue(mainA) > bgValue(bgA);
+
+
+
+ return [`rgb(${mainA.toString()}`, `rgb(${bgA.toString()}`, isBorderMain, `rgb(${mainB.toString()}`, `rgb(${bgB.toString()}`];
+}
+
+let mainColorGen = [0, 0, 0];
+let bgColorGen = [0, 0, 0];
+let similarMainColorGen = [0, 0, 0];
+let similarBgColorGen = [0, 0, 0];
+let lastTitle = "";
+function generatedColors() {
+ // [0] > gradient > [3] , [2] show background at bottom true / false , [1] > gradient > [4]
+ // return [`rgb(${mainColor.toString()}`, `rgb(${bgColor.toString()}`, darkColor, `rgb(${simmilarColor.toString()}`, `rgb(${simmilarColorBG.toString()}`];
+ if (lastTitle != title) {
+ mainColorGen = hsvToRgb([Math.floor(Math.random() * 256), 128 + Math.floor(Math.random() * 128), 128 + Math.floor(Math.random() * 128)]);
+ bgColorGen = hsvToRgb([mainColorGen[0] + Math.floor((Math.random()*2-1) * 10), 5 + Math.floor(Math.random() * 85), 5 + Math.floor(Math.random() * 85)]);
+
+ similarMainColorGen = hsvToRgb([(mainColorGen[0] + Math.floor((Math.random()*2-1) * 15)) % 256, mainColorGen[1], mainColorGen[2]]);
+ similarBgColorGen = hsvToRgb([(bgColorGen[0] + Math.floor((Math.random()*2-1) * 15)) % 256, bgColorGen[1], bgColorGen[2]]);
+ }
+
+ lastTitle = title;
+
+
+ return [`rgb(${mainColorGen.toString()}`, `rgb(${bgColorGen.toString()}`, true, `rgb(${similarMainColorGen.toString()}`, `rgb(${similarBgColorGen.toString()}`];
+}
+
+
+function colorDominant(shades, main_col) {
+ /*
+ let main_col = colorThief.getColor(cover)
+ let shades = colorThief.getPalette(cover, 8);
+ */
+
+ let bgA = [0, 0, 0];
+ let bgA_contrast = 0.0;
+ let bgA_shift = 0.0; // Closest distance to complimentary hue of main color
+
+ for (let i = 0; i < shades.length; i++) {
+ let c = shades[i];
+ let chsv = rgbToHsv(shades[i]);
+ let ctr = contrast(main_col, c);
+ let shift = Math.abs( (( rgbToHsv(c)[0] + 180 ) % 360 ) - ( chsv[0] ) ) ;
+
+ if (ctr > bgA_contrast || shift > bgA_shift) {
+ bgA = c;
+ bgA_contrast = ctr;
+ bgA_shift = shift;
+ }
+ }
+
+
+
+
+ let simmilarColor = main_col; // assume
+ let mincs = 1.0;
+ for (let i = 1; i < shades.length; i++) {
+ let tmp = rgbDifference(main_col, shades[i]);
+ if (tmp < mincs && tmp > 0.03) {
+ mincs = tmp;
+ simmilarColor = shades[i];
+ }
+ }
+
+ let simmilarColorBG = bgA; // assume
+ let mincsbg = 1.0;
+ for (let i = 0; i < shades.length; i++) {
+ let tmp = rgbDifference(bgA, shades[i]);
+ if (tmp < mincsbg && tmp > 0.03) {
+ mincsbg = tmp;
+ simmilarColorBG = shades[i];
+ }
+ }
+
+
+ let darkColor = bgValue(main_col) > bgValue(bgA);
+
+
+
+ return [`rgb(${main_col.toString()}`, `rgb(${bgA.toString()}`, darkColor, `rgb(${simmilarColor.toString()}`, `rgb(${simmilarColorBG.toString()}`];
+}
+
+
+function rgbToHsv(rgb) {
+ let r = rgb[0] / 255,
+ g = rgb[1] / 255,
+ b = rgb[2] / 255,
+ max = Math.max(r, g, b),
+ min = Math.min(r, g, b),
+ delta = max - min,
+ h, s, v = max;
+
+ if (max === min) {
+ h = 0; // achromatic
+ } else {
+ switch (max) {
+ case r: h = (g - b) / delta + (g < b ? 6 : 0); break;
+ case g: h = (b - r) / delta + 2; break;
+ case b: h = (r - g) / delta + 4; break;
+ }
+ h *= 60; // degrees
+ }
+
+ s = max === 0 ? 0 : delta / max;
+ v = max; // max is already in the range 0-1
+
+ return [Math.round(h), Math.round(s * 255), Math.round(v * 255)];
+}
+
+function hsvToRgb(hsv) {
+ let [h, s, v] = hsv;
+ h = h * 360 / 255; // Convert h from 0-255 to 0-360 degrees
+ s /= 255; // Convert s from 0-255 to 0-1
+ v /= 255; // Convert v from 0-255 to 0-1
+
+ let r, g, b;
+
+ let i = Math.floor(h / 60);
+ let f = h / 60 - i;
+ let p = v * (1 - s);
+ let q = v * (1 - f * s);
+ let 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;
+ }
+
+ // Convert r, g, b from 0-1 to 0-255
+ r = Math.round(r * 255);
+ g = Math.round(g * 255);
+ b = Math.round(b * 255);
+
+ return [r, g, b];
+}
+
+function bgValue(col) {
+ return (col[0] + col[1] + col[2]) / 3.0 / 255.0;
+}
+
+function lerp(a, b, weight) {
+ return a * (1.0 - weight) + b * weight;
+}
+
+function rgbDifference(rgb1, rgb2) {
+ let hsv1 = rgbToHsv(rgb1);
+ let hsv2 = rgbToHsv(rgb2);
+
+ // Calculate the squared differences for each color component
+ let deltaR = Math.pow(rgb1[0] - rgb2[0], 2);
+ let deltaG = Math.pow(rgb1[1] - rgb2[1], 2);
+ let deltaB = Math.pow(rgb1[2] - rgb2[2], 2);
+ // let deltaS = Math.pow(hsv1[1] - hsv2[1], 2);
+
+ // Calculate the Euclidean distance between the two colors
+ let distance = Math.sqrt(deltaR + deltaG + deltaB); // + deltaS
+
+ // Normalize the distance to a 0.0 - 1.0 scale
+ // The maximum possible distance is sqrt(3 * 255^2), since RGB values range from 0 to 255
+ let maxDistance = Math.sqrt(3 * Math.pow(255, 2));
+ let normalizedDistance = distance / maxDistance;
+
+ return normalizedDistance;
+}
+
+function hexToRgb(hex) {
+ var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
+ return result ? {
+ r: parseInt(result[1], 16),
+ g: parseInt(result[2], 16),
+ b: parseInt(result[3], 16)
+ } : null;
+}
+
+
+
+
+
+
+var gradients = [];
+var permutation = [];
+
+
+function fade(t) {
+ return t * t * t * (t * (t * 6 - 15) + 10);
+}
+
+function lerp(a, b, t) {
+ return (1 - t) * a + t * b;
+}
+
+function noise(x) {
+ x *= 100.0;
+
+ let a = Math.sin(x * 1.5 + 25.9 );
+ let b = Math.sin(x * 3.9 + 4.0 );
+ let c = Math.sin(x * 11 + 187.69 );
+ let d = Math.sin(x * 29 + 10.2 );
+ let e = Math.sin(x * 58 + 0.0 );
+
+ return ( a + b + c + d + e ) / 5.0;
+}
diff --git a/ypfdr3lc.jpg b/ypfdr3lc.jpg
new file mode 100644
index 0000000..f74023e
Binary files /dev/null and b/ypfdr3lc.jpg differ