Multiple JavaScript audio visualizers - javascript

This is my first post here as a noob in coding. I'm trying JavaScript tricks with audio visualizers with the tutorial of FrankLaboratory's YouTube channel as a starting point.
I tried to make several zones on the page (overlays) with a click event and a different sound for each one. Everything runs fine but I only had one shape of visualizer for every zone which I found annoying (with one drawVisualizer function called for each overlay).
So I'm trying to modify this to have one sound and one visualizer for each zone.
But it acts strangely: I click a zone and it's ok, I click the second and it's ok too. But if I click the first one again, sound still plays but no more visualizer. Different combinations are possible, I mean if I keep playing the second one, it's working, same if I keep playing the first but as soon as I change, I can't go back on the first which was played.
Here is the code
I just let the two first zones for the example but the goal is to have 16 one the page.
I have the error " Failed to execute 'createMediaElementSource' on 'AudioContext': HTMLMediaElement already connected previously to a different MediaElementSourceNode."
But actually I had it also on the first version of my script with only one visualizer for all zone with the drawVisualizer function and everything run smooth nonetheless, so...
I'm missing something but I don't know what, so any help would be appreciated.
I put the code here too:
const container = document.getElementById('container');
const canvas = document.getElementById('canvas1');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ctx = canvas.getContext('2d');
let audioSource;
let analyser;
const overlay = document.getElementsByClassName('overlay')[0];
overlay.addEventListener('click' , function(){
const audio0 = document.getElementById('audio0');
audio0.src = 'sound1.mp3 which I put in base64 on the jsfiddle';
const audioContext = new AudioContext();
audio0.play();
audioSource = audioContext.createMediaElementSource(audio0);
analyser = audioContext.createAnalyser();
audioSource.connect(analyser);
analyser.connect(audioContext.destination);
analyser.fftSize = 2048;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
const barWidth = canvas.width/bufferLength;
let barHeight;
let x;
function animate(){
x = 0;
ctx.clearRect(0, 0, canvas.width, canvas.height);
analyser.getByteFrequencyData(dataArray);
for (let i = 0; i < bufferLength; i++){
barHeight = dataArray[i];
const red = i * 50;
const green = i ;
const blue = i / 2;
ctx.fillStyle = 'rgb('+ red + ',' + green + ',' + blue + ')';
ctx.fillRect(x, canvas.height - barHeight, barWidth * 4, barHeight / 2);
x += barWidth;
}
requestAnimationFrame(animate);
}
animate();
});
const overlay1 = document.getElementsByClassName('overlay')[1];
overlay1.addEventListener('click' , function(){
const audio1 = document.getElementById('audio1');
audio1.src = 'sound2.mp3 which I put in base64 on the jsfiddle';
const audioContext = new AudioContext();
audio1.play();
audioSource = audioContext.createMediaElementSource(audio1);
analyser = audioContext.createAnalyser();
audioSource.connect(analyser);
analyser.connect(audioContext.destination);
analyser.fftSize = 2048;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
const barWidth = canvas.width/bufferLength;
let barHeight;
let x;
function animate(){
x = 0;
ctx.clearRect(0, 0, canvas.width, canvas.height);
analyser.getByteFrequencyData(dataArray);
for (let i = 0; i < bufferLength; i++){
barHeight = dataArray[i]*10;
ctx.save();
ctx.translate(canvas.width, canvas.height);
ctx.rotate(i * Math.PI *24 / bufferLength);
const red = i * 50;
const green = i ;
const blue = i / 2;
ctx.fillStyle = 'rgb('+ red + ',' + green + ',' + blue + ')';
ctx.fillRect(x, canvas.height - barHeight, barWidth * 4, barHeight / 2);
x += barWidth;
ctx.restore();
}
requestAnimationFrame(animate);
}
animate();
});

Related

Can not change the color of canvas bars with audio progress

let ctx;
let source;
let context;
let analyser;
let fbcArray;
let bars;
let barX;
let barWidth;
let barHeight;
function initAnalyzer(audio) {
context = new AudioContext();
analyser = context.createAnalyser();
canvas = document.getElementById('analyser_render');
ctx = canvas.getContext('2d');
source = context.createMediaElementSource(audio);
source.connect(analyser);
analyser.connect(context.destination);
console.log(audio.duration)
frameLooper();
}
function frameLooper() {
window.requestAnimationFrame(frameLooper);
fbcArray = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(fbcArray);
// const fractionDone = audio.time / audio.duration;
ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas
const gradient = ctx.createLinearGradient(0, 0, 0, 512);
gradient.addColorStop(0.15, '#114357');
gradient.addColorStop(0.3, '#45B39D');
gradient.addColorStop(0.3, '#28B463');
ctx.fillStyle = '#45B39D';
bars = 100;
for (let i = 0; i < bars; i++) {
barX = i * 3;
barWidth = 1;
barHeight = -((fbcArray[i] + 10) / 2);
// fillRect( x, y, width, height ) // Explanation of the parameters below
ctx.fillRect(barX , canvas.height, barWidth, barHeight );
}
}
export default initAnalyzer;
I want to change the color of the bars as the audio progress changes, tried many times but failed to do it, the current code is fine the waves of the 2d bar is visualizing as audio pitch, if any one know the method please help, thanks :)
The green bar is the actual image and the back one is what i actually want
From the image you've provided we can spot a few important things:
the background should be black
every bar to the left of the red line should be gray (#454545)
every bar to the right of the red line should be white (#fafafa)
This can be done with a gradient of course.
const gradient = ctx.createLinearGradient(0, 0, 0, 512);
essentially creates a gradient from the top to the bottom. Unless your image isn't wrong, the gradient has to go from the left to the right.
const gradient = ctx.createLinearGradient(0, canvas.height/2, canvas.width, canvas.height/2);
No we need to figure out the percentages for the color stop. It's pretty easy. The whole image is 658 the red line is at 172, so 172/658=~0.26 rounded.
gradient.addColorStop(0.26, '#454545');
gradient.addColorStop(0.26, '#fafafa');
One last important step is missing. As we want to use the gradient as the fillStyle, we need to point the .fillStyle property of the context to the gradient:
ctx.fillStyle = gradient;
Here's an example:
let canvas = document.getElementById('analyser_render');
let ctx = canvas.getContext('2d');
let barX, bawrWidth, barHeight;
let values = [];
for (let a = 0; a < 256; a++) {
values.push(Math.floor(Math.random() * 128));
}
var fbcArray = new Uint8Array(256);
fbcArray = Uint8Array.from(values);
const gradient = ctx.createLinearGradient(0, canvas.height / 2, canvas.width, canvas.height / 2);
gradient.addColorStop(0.26, '#454545');
gradient.addColorStop(0.26, '#fafafa');
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = gradient;
let bars = 100;
for (let i = 0; i < bars; i++) {
barX = i * 3;
barWidth = 1;
barHeight = -((fbcArray[i] + 10) / 2);
ctx.fillRect(barX, canvas.height, barWidth, barHeight);
}
<canvas id="analyser_render"></canvas>

Javascript canvas behaving strangely in for loop

I'm trying to create a canvas graphic by rotating ellipses around a central point. When I do this however, the for loop creates a strange image. There are sporadic gaps in between ellipses, and the colors are strange. At first I thought I was calculating the angles wrong, but if that was the case, the gaps should not be sporadic as I am incrementing them in equal increments each iteration of the for loop. Then I thought maybe I was looping around too many times, but if that was the case, only the colors should be off and not the gaps. This makes me think it's an asynchronous problem where maybe the context didn't finish shifting before the next iteration started drawing the next circle, but that's just a guess. What could be wrong and how do I fix it?
Here's a jsfiddle, there's only 6 ellipses when there should be 10, what's happening?
https://jsfiddle.net/vj7csL3b/
<!DOCTYPE html>
<html>
<body>
<canvas id="canvas" width="200" height="200"></canvas>
<script>
const canvas = document.getElementById("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ctx = canvas.getContext("2d");
let color = "#0000ff";
let numberOfEllipses = 10;
let lineThickness = 3;
let middleW = 150;
let middleL = 150;
let eWidth = 50;
let eHeight = 50;
let fill = "rgba(0,0,255,0.2)";
let shift = 50;
let rotate = ((Math.PI / 180) * 360) / 10;
//console.log(middleW, middleL, dfc);
for (let i = 1; i < 10 + 1; i++) {
//translate to point of rotation, rotate and translate back
ctx.translate(middleW, middleL);
ctx.rotate(rotate * i);
ctx.translate(-1 * middleW, -1 * middleL);
ctx.beginPath();
// draws ellipse shifted from the middle we are rotating around.
ctx.ellipse(
middleW + shift,
middleL + shift,
eWidth,
eHeight,
0,
0,
2 * Math.PI
);
ctx.strokeStyle = color;
ctx.lineWidth = lineThickness;
ctx.fillStyle = fill;
ctx.stroke();
ctx.fill();
}
</script>
</body>
</html>
The circles were actually overlapping over each other.Altering the rotation angle and shift will give the output that u expect.
const canvas = document.getElementById("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ctx = canvas.getContext("2d");
let color = "#0000ff";
let numberOfEllipses = 10;
let lineThickness = 3;
let middleW = 150;
let middleL = 150;
let eWidth = 50;
let eHeight = 50;
let fill = "rgba(0,0,255,0.2)";
let shift = 60;
let rotate = ((Math.PI / 180) * 360) / 60;
//console.log(middleW, middleL, dfc);
for (let i = 1; i < 10 + 1; i++) {
//console.log("paiting circle" + i);
ctx.translate(middleW, middleL);
ctx.rotate(rotate * i);
ctx.translate(-1 * middleW, -1 * middleL);
ctx.beginPath();
ctx.ellipse(
middleW + shift,
middleL + shift,
eWidth,
eHeight,
0,
0,
2 * Math.PI
);
ctx.strokeStyle = color;
ctx.lineWidth = lineThickness;
ctx.fillStyle = fill;
ctx.stroke();
ctx.fill();
}

Drawing sinewave in JS canvas does not do anything

I wrote the following to draw a sound wave from AudioBuffer but what I get is a canvas with a straight horizontal line:
const { audioContext, analyser } = this.getAudioContext();
const source = audioContext.createBufferSource();
source.buffer = audioBuffer;
source.connect(audioContext.destination);
const canvas = document.getElementById('canvas');
const canvasCtx = canvas.getContext("2d");
let sinewaveDataArray = new Uint8Array(analyser.fftSize);
const drawSinewave = function() {
analyser.getByteTimeDomainData(sinewaveDataArray);
requestAnimationFrame(drawSinewave);
canvasCtx.fillStyle = 'white';
canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
canvasCtx.lineWidth = 2;
canvasCtx.strokeStyle = "black";
canvasCtx.beginPath();
const sliceWidth = canvas.width * 1.0 / analyser.fftSize;
let x = 0;
for(let i = 0; i < analyser.fftSize; i++) {
const v = sinewaveDataArray[i] / 128.0; // byte / 2 || 256 / 2
const y = v * canvas.height / 2;
if(i === 0) {
canvasCtx.moveTo(x, y);
} else {
canvasCtx.lineTo(x, y);
}
x += sliceWidth;
}
canvasCtx.lineTo(canvas.width, canvas.height / 2);
canvasCtx.stroke();
};
source.start();
drawSinewave();
getAudioContext = () => {
AudioContext = window.AudioContext || window.webkitAudioContext;
const audioContext = new AudioContext();
const analyser = audioContext.createAnalyser();
return { audioContext, analyser };
};
The end result I am looking for is something like the first image clip here: https://meyda.js.org/
Any idea what I am missing?
I think two little changes are necessary to make it work.
source.start() needs to be executed in response to a user event. A simple button with a click handler can be used to achieve that. This is necessary to deal with the autoplay policy which is present in some browsers.
aReferenceToAButton.addEventListener('click', async () => {
await audioContext.resume();
source.start();
});
Last but not least you need to pipe the signal through the AnalyserNode.
source
.connect(analyser)
.connect(audioContext.destination);
I hope this helps.

Trying to make audio visualization with circle ripples on Canvas and with javascript

Basically I'm trying to make an audio visualization with circle ripple explosions. I am not sure how to exactly name it, but it looks something like this: https://codepen.io/alek/pen/EyyLgp, except I'm looking for more like when you click a circle appears, expands and disappear. It doesn't necessarily have to be a filled circle.
Here's my js code for audio visualization (Note: I did take the code from Nick Jones - website: https://codepen.io/nfj525/pen/rVBaab, and I credit him for the cool audio visualizations made)
let file = document.querySelector("choose-file");
let audio = document.querySelector("audio-player");
file.onchange = function() {
let files = this.files;
audio.src = URL.createObjectURL(files[0]);
audio.load();
audio.play();
let context = new AudioContext();
let src = context.createMediaElementSource(audio);
let analyser = context.createAnalyser();
let canvas = document.querySelector("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
let ctx = canvas.getContext("2d");
src.connect(analyser);
analyser.connect(context.destination);
analyser.fftSize = 256;
let bufferLength = analyser.frequencyBinCount;
console.log(bufferLength);
let dataArray = new Uint8Array(bufferLength);
let WIDTH = canvas.width;
let HEIGHT = canvas.height;
let barWidth = (WIDTH / bufferLength) * 2.5;
let barHeight;
let x = 0;
function renderFrame() {
requestAnimationFrame(renderFrame);
x = 0;
analyser.getByteFrequencyData(dataArray);
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, WIDTH, HEIGHT);
for (let i = 0; i < bufferLength; i++) {
barHeight = dataArray[i];
let r = barHeight + (25 * (i/bufferLength));
let g = 250 * (i/bufferLength);
let b = 50;
ctx.fillStyle = "rgb(" + r + "," + g + "," + b + ")";
ctx.fillRect(x, HEIGHT - barHeight, barWidth, barHeight);
x += barWidth + 1;
}
}
audio.play();
renderFrame();
};
So currently this audio visualization is creating rectangles according to the music, but I would like to change that to circles instead. So on the canvas, circles would appear randomly on the screen and have this ripple, explosion effect, disappear and a new circle would appear. I thought maybe I could replace fillRect() with arc(), but I realize that there is more logic behind it. Does anybody have any ideas how I could possibly change the rectangles to circles?
usually i'm using p5.js, i think p5.js preety good for something like that, just a suggestion if you want to use that, this the link : https://p5js.org/libraries/

Divide whole canvas in equal columns

I am trying to divide my whole canvas in 20 equal columns and animate them individually on the y-axis. The goal is to create a similar wave scroll animation like here. First I tried to create the wave scroll effect with php generated images with the text on it and tried to animate them in single divs with the images as background-images. It technically worked but the performance and page load time was extremely bad. Now I want to create it with canvas: I already have the content with all the images and text in it and tried to animate it. I tried to save the whole content in columns (rectangles) with getImageData() then I created created rectangles with the ImageData and re-draw them in a loop but again the performance was terrible, especially on mobile devices. The animation loop looked as followed:
var animate = function(index, y) {
// The calculations required for the step function
var start = new Date().getTime();
var end = start + duration;
var current = rectangles[index].y;
var distance = y - current;
var step = function() {
// get our current progress
var timestamp = new Date().getTime();
var progress = Math.min((duration - (end - timestamp)) / duration, 1);
context.clearRect(rectangles[index].x, rectangles[index].y, columnWidth, canvas.height);
// update the rectangles y property
rectangles[index].y = current + (distance * progress);
context.putImageData(rectangles[index].imgData, rectangles[index].x, rectangles[index].y);
// if animation hasn't finished, repeat the step.
if (progress < 1)
requestAnimationFrame(step);
};
// start the animation
return step();
};
Now the question: How can I divide the whole canvas in equal columns and animate them on the y-axis with a good performance? Any suggestions? Maybe with the help of Pixi.js / Greensock? Thanks in advance!
Do not use getImageData and setImageData to do animation. The canvas is an image and can be rendered just like any image.
To do what you want
Create a second copy of the canvas and use that canvas a source for the strips you want to render.
Example.
const slices = 20;
var widthStep;
var canvas = document.createElement("canvas");
var canvas1 = document.createElement("canvas");
canvas.style.position = "absolute";
canvas.style.top = canvas.style.left = "0px";
var ctx = canvas.getContext("2d");
var ctx1 = canvas1.getContext("2d");
var w = canvas.width;
var h = canvas.height;
function resize(){
canvas.width = canvas1.width = innerWidth;
canvas.height = canvas1.height = innerHeight;
w = canvas.width;
h = canvas.height;
ctx1.font = "64px arial black";
ctx1.textAlign = "center"
ctx1.textBaseLine = "middle";
ctx1.fillStyle = "blue";
ctx1.fillRect(0,0,w,h);
ctx1.fillStyle = "red";
ctx1.fillRect(50,50,w-100,h-100);
ctx1.fillStyle = "black";
ctx1.strokeStyle = "white";
ctx1.lineWidth = 5;
ctx1.lineJoin = "round";
ctx1.strokeText("Waves and canvas",w / 2, h / 2);
ctx1.fillText("Waves and canvas",w / 2, h / 2);
widthStep = Math.ceil(w / slices);
}
resize();
window.addEventListener("resize",resize);
document.body.appendChild(canvas);
function update(time){
var y;
var x = 0;
ctx.clearRect(0,0,canvas.width,canvas.height);
for(var i = 0; i < slices; i ++){
y = Math.sin(time / 500 + i / 5) * (w / 8);
y += Math.sin(time / 700 + i / 7) * (w / 13);
y += Math.sin(time / 300 + i / 3) * (w / 17);
ctx.drawImage(canvas1,x,0,widthStep,h,x,y,widthStep,h);
x += widthStep;
}
requestAnimationFrame(update);
}
requestAnimationFrame(update);

Categories

Resources