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