Customizable HTML canvas audio visualizer
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
Hyperlyze/script.js

131 lines
6.0 KiB

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();
}