commit 4dd1391b9ba37d4b327f304c40fbe3176b6d3d4d Author: WubzyGD Date: Tue Feb 7 20:35:19 2023 -0500 basic visualizer and hardcoded customization diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 0000000..d8e9561 --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml new file mode 100644 index 0000000..d23208f --- /dev/null +++ b/.idea/jsLibraryMappings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..4038b25 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..830a534 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/visual.iml b/.idea/visual.iml new file mode 100644 index 0000000..0c8867d --- /dev/null +++ b/.idea/visual.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..8ff77a9 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1675761666673 + + + + + + + + + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..efac598 --- /dev/null +++ b/index.html @@ -0,0 +1,26 @@ + + + + + + + Customizable Visualizer + + + + +
+
+ + + + + +
+
+ +
+
+ + diff --git a/script.js b/script.js new file mode 100644 index 0000000..918d947 --- /dev/null +++ b/script.js @@ -0,0 +1,131 @@ +let played = false; +let asrc = ""; +let audio; + +import tinygradient from 'https://cdn.jsdelivr.net/npm/tinygradient@1.1.5/+esm'; + +/*BASE CONTROLS*/ + +window.firstPlay = function() {return new Promise(async r => { + played = true; + document.getElementById('begin').classList.add('invis'); + asrc = document.getElementById('get-src').value; + audio = new Audio(asrc); + audio.loop = true; + audio.crossOrigin = "anonymous"; + await audio.play(); + document.getElementById('get-src').classList.add('invis'); + createVisualizer(); + r(0); +});} +window.play = async function() { + if (!played) {await firstPlay();} + document.getElementById('play').classList.add('invis'); + document.getElementById('reload').classList.add('invis'); + document.getElementById('pause').classList.remove('invis'); + if (played) {await audio.play();} +} +window.pause = function() { + document.getElementById('play').classList.remove('invis'); + document.getElementById('reload').classList.remove('invis'); + document.getElementById('pause').classList.add('invis'); + audio.pause(); +} +window.reload = function() { + audio.pause(); + played = false; + document.getElementById('begin').classList.remove('invis'); + document.getElementById('get-src').classList.remove('invis'); + document.getElementById('play').classList.add('invis'); + document.getElementById('reload').classList.add('invis'); +} + +/*ANIMATION*/ +window.createVisualizer = function() { + document.getElementById('visualizer').style.display = 'block'; // enable the visualizer canvas + + let canvas = document.getElementById("visualizer"); // setup canvas + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + let ctx = canvas.getContext("2d"); + + let context = new AudioContext(); // connect to an audio analyzer + let src = context.createMediaElementSource(audio); + let analyser = context.createAnalyser(); + src.connect(analyser); + analyser.connect(context.destination); + + analyser.fftSize = 2 ** 8; // CHANGE SECOND NUMBER ONLY || controls number of frequency channels. must be a power of 2 + + let bufferLength = analyser.frequencyBinCount; // the number of frequency channels being analyzed; max displayable bars + + /*CONFIG VARIABLES*/ + + let colors = [ + 'rgb(161, 114, 166)', + 'rgb(157, 187, 177)' + ]; // Uses tinygradient. hex, rgb, etc. Basically write it like you would in CSS and chances are it'll work + + let minChannel = 0; // cut off this many bars from the beginning; 0 = will render from first possible frequency channel till maxChannel + console.log(.65 * bufferLength); + let maxChannel = (Math.floor(.65 * bufferLength)) || bufferLength; // cut off this many bars from the end; 0 = will render from minChannel to end + // tl;dr this is a range of possible frequency channels to actually render a bar for. + // if fftSize is 2^8 (256) you should have 128 channels; min 10 : max 50 : render from channel 10 to 50, 40 bars. + // my default is the first 65% of bars + let barGap = 1; // gap between each bar in px. can be 0. + let forceCanvasFill = true; // because decimals exist and they suck, turning off bar gap while this is false can leave tiny inconsistent gaps + // this should be 'true' if barGap is anything except for 0. this will leave unnoticeable inconsistencies in the bar gap but fill the entire width + // set this to 'false' to make sure bar gap is actually 0, but that will leave a little free space at the end. + let maxHeight = 25; // highest percentage of the canvas the bar can take up + let minHeight = 20; // height in px to pad under the bars. i will not be apologizing for this being px and the other being % + + let brightFactor = 0; // brighten the entire gradient + let darkFactor = 0; // darken the entire gradient. + // KEEP ONE OR BOTH AT 0. max 100. See the tinycolor docs. + + + /*RENDERER SETUP*/ + let WIDTH = canvas.width; let HEIGHT = canvas.height; + + let barCount = maxChannel - minChannel; + let barWidth = forceCanvasFill ? WIDTH / barCount : Math.floor(WIDTH / barCount); // sets the base bar width on the basis that each frequency channel will be a member of the canvas + let barHeight; + let gradient = tinygradient(colors); + let barColors = [...Array(barCount).keys()].map(i => gradient.rgbAt((i + 1) / barCount)); // preset the gradient since it doesn't need to be evaluated each frame + let dataArray = new Uint8Array(maxChannel); // using maxChannel instead of bufferLength might help with performance in the event that you're not rendering some right channels + + if (brightFactor) {barColors.map(bar => bar.brighten(brightFactor));} + if (darkFactor) {barColors.map(bar => bar.darken(darkFactor));} + + let x = 0; + + function renderFrame() { + requestAnimationFrame(renderFrame); // probably shouldn't touch these + x = 0; + analyser.getByteFrequencyData(dataArray); + + ctx.clearRect(0, 0, WIDTH, HEIGHT); // clear the canvas + + ctx.fillStyle = "#ffffff00"; // set transparent background for the frame + ctx.fillRect(0, 0, WIDTH, HEIGHT); + + for (let i = minChannel; i < barCount; i++) { + /* + * loops through each bar from left to right, every frame + * i represents which bar the loop is currently on + * there are 128 bars in total + */ + + let strengthLuminance = 1 //2; // the factor to decide how reactive the brightness of the bar is to its own height, or the strength of that frequency. this number works through decay, and lower numbers mean brighter luminance. (1 to 2 is a much bigger difference than 9 to 10) + + barHeight = (dataArray[i] / 255) * ((maxHeight / 100) * HEIGHT); // sets the bar height based on the music for this frame + + ctx.fillStyle = barColors[i].toRgbString(); // paints the bar for the frame + ctx.fillRect(x, HEIGHT - barHeight - minHeight, barWidth - barGap, barHeight + minHeight); + + x += barWidth; // sets the gap between bars + } + } + + renderFrame(); +} \ No newline at end of file diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..ac3ad27 --- /dev/null +++ b/styles.css @@ -0,0 +1,61 @@ +html, body, controls { + padding: 0 0; + margin: 0 0; +} +body { + position: fixed; + top: 0; left: 0; + right: 0; bottom: 0; + z-index: -1; +} + +.invis {display: none;} + +#main { + margin: 0 0; + padding: 30px 35px; + height: 100%; +} + +#main, #main * { + background-color: black; + color: #dadada; + border-color: #a3a3a3; +} + +button { + padding: 8px 10px; + font-size: 1.2em; + color: white; +} +#controls .ctrl { + background-color: #3d3d3d; + border-radius: 3px 3px; + margin: 10px 6px; +} +#controls input { + padding: 6px 7px; + margin-left: 0!important; +} + +#controls button:not(.invis) {display: inline-block;} +#controls input:not(.invis) {display: block;} + +#visual-master { + /*position: fixed; + left: 0; right: 0; + height: max-content;*/ + padding: 0 0; margin: 0 0; +} +#visualizer { + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; + background: 0 0; + display: none; + pointer-events: none; + opacity: 1; + transition: opacity .75s ease-in; +} \ No newline at end of file