How to synchronize high audio frequencies with CSS changes? - javascript

I'm creating a program to interact with music, and I was trying to synchronize the drum beats or the bass sounds with CSS, so in every high frequency sound, the background would change just in time.
My first idea was create an spectrum of the audio frequency, to understand how does it interacts with the sound played. In this case, I'm using Web Audio API analyserNode.fftSize to get all the points of the frequency in a audio file and create the spectrum with <canvas>
const audioCtx = new AudioContext();
//Create audio source
//Here, we use an audio file, but this could also be e.g. microphone input
const audioEle = new Audio();
audioEle.src = "good-day.mp3"; //insert file name here
audioEle.autoplay = true;
audioEle.preload = "auto";
const audioSourceNode = audioCtx.createMediaElementSource(audioEle);
//Create analyser node
const analyserNode = audioCtx.createAnalyser();
analyserNode.fftSize = 256;
const bufferLength = analyserNode.frequencyBinCount;
const dataArray = new Float32Array(bufferLength); // this is where all the frequencies are stored
Then, using this dataArray with all the frequencies of that moment, I run a condition that says: if that frequency is above X, change background to blue:
if (Math.max(...dataArray) + 100 > 70) { // +- 70 is a good number for that specific song
await changeBgColor();
}
The final results seems to work, but isn't perfect, since that if condition runs 60 times per second and sometimes doesn't catch the bass playing because of a voice or another sound in the audio file that mess with the frequency of the bass.
I don't know if there is something that really can be useful for this. I tried to use ToneJS and wadJS but I couldn't get anywhere with these libs.
I wonder what could be the best way to make these CSS iteration with sounds.
The song used for test this code was: Ice Cube - It Was A Good Day (good-day.mp3)
Full code:
index.html
<!DOCTYPE html>
<body id="bd">
<script>
const audioCtx = new AudioContext();
//Create audio source
//Here, we use an audio file, but this could also be e.g. microphone input
const audioEle = new Audio();
audioEle.src = "good-day.mp3"; //insert file name here
audioEle.autoplay = true;
audioEle.preload = "auto";
const audioSourceNode = audioCtx.createMediaElementSource(audioEle);
//Create analyser node
const analyserNode = audioCtx.createAnalyser();
analyserNode.fftSize = 256;
const bufferLength = analyserNode.frequencyBinCount;
const dataArray = new Float32Array(bufferLength);
//Set up audio node network
audioSourceNode.connect(analyserNode);
analyserNode.connect(audioCtx.destination);
//Create 2D canvas
const canvas = document.createElement("canvas");
canvas.style.position = "absolute";
canvas.style.top = 0;
canvas.style.left = 0;
canvas.width = window.innerWidth;
canvas.height = 300;
document.body.appendChild(canvas);
const canvasCtx = canvas.getContext("2d");
canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
async function draw() {
const changeBgColor = () => {
document.getElementById("bd").style["background-color"] = "blue";
};
const sleep = () => {
// setTimeout(() => null, 1000)
console.log("bass sound");
};
//Schedule next redraw
requestAnimationFrame(draw);
//Get spectrum data
analyserNode.getFloatFrequencyData(dataArray);
//Draw black background
canvasCtx.fillStyle = "rgb(0, 0, 0)";
canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
//Draw spectrum
const barWidth = (canvas.width / bufferLength) * 2.5;
let posX = 0;
document.getElementById("bd").style["background-color"] = "red";
if (Math.max(...dataArray) + 100 > 70) { // +- 70 is a good number for that specific song
await changeBgColor();
}
for (let i = 0; i < bufferLength; i++) {
const barHeight = (dataArray[i] + 100) * 2;
// console.log(barHeight)
canvasCtx.fillStyle = `rgb(${Math.floor(
barHeight + 100
)}, 255, ${Math.floor(barHeight + 200)})`;
canvasCtx.fillRect(
posX,
canvas.height - barHeight / 2,
barWidth,
barHeight / 2
);
posX += barWidth + 1;
}
}
draw();
</script>
</body>
My reference to this code was a doc in MDN about AnalyserNode

Here is quick demo on how to retrieve the music frequencies into an array and pass it to the CSS.
It's very basic, there is many problems, but maybe a good start for further proper animations.
const audio = document.getElementById('music');
audio.load();
audio.play();
const ctx = new AudioContext();
const audioSrc = ctx.createMediaElementSource(audio);
const analyser = ctx.createAnalyser();
audioSrc.connect(analyser);
analyser.connect(ctx.destination);
analyser.fftSize = 256;
const bufferLength = analyser.frequencyBinCount;
const frequencyData = new Uint8Array(bufferLength);
setInterval(async () => {
analyser.getByteFrequencyData(frequencyData);
let dataArray = Object.values(frequencyData);
//if (Math.max(...dataArray) + 100 > 70) { // +- 70 is a good number for that specific song
window.requestAnimationFrame(
await changeBgColor(dataArray)
)
//}
}, 100);
const changeBgColor = (dataArray) => {
console.log(dataArray)
let color1 = dataArray.slice(0,3).map((a) => a / 100) // Smaller numbers are darker
let color2 = dataArray.slice(3,6)
let color3 = dataArray.slice(6,9)
document.body.style.background = `linear-gradient(90deg, rgba(${color1}, 1) 0%, rgba(${color2},0.2) 35%, rgba(${color3},1) 100%)`
}
<audio id="music" src="https://cdn.glitch.com/02dcea11-9bd2-4462-ac38-eeb6a5ad9530%2F331_full_beautiful-minds_0171_preview.mp3?1522829295082" crossorigin="use-URL-credentials" controls="true"></audio>
<hr>
<span id="console"></span>

Related

Recording multiple canvas using MediaRecorder

I want to record the multiple layered canvas using MediaRecorder.
But, i don't know how to achieve it...
help me...
this is the my pseudo code
const RECORD_FRAME = 30;
const canvasVideoTrack = canvas.captureStream(RECORD_FRAME).getVideoTracks()[0];
const waterMarkCanvasTrack = waterMarkCanvas.captureStream(RECORD_FRAME).getVideoTracks()[0];
const stream= new MediaStream();
const mediaRecorder = new MediaRecorder(stream);
mediaRecorder.stream.addTrack(canvasVideoTrack)
mediaRecorder.stream.addTrack(waterMarkCanvasTrack)
// .... recording
You need to draw all your canvases on a single one.
Even if we could record multiple video streams (which we can't yet), what you need is to composite these video streams. And for this, you use a canvas:
const prepareCanvasAnim = (color, offset) => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
ctx.fillStyle = color;
let x = offset;
const anim = () => {
x = (x + 1) % canvas.width;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillRect(x, offset, 50, 50);
requestAnimationFrame(anim);
}
anim();
return canvas;
}
const canvas1 = prepareCanvasAnim("red", 20);
const canvas2 = prepareCanvasAnim("green", 80);
document.querySelector(".container").append(canvas1, canvas2);
const btn = document.querySelector("button");
const record = (evt) => {
btn.textContent = "Stop Recording";
btn.disabled = true;
setTimeout(() => btn.disabled = false, 5000); // at least 5s recording
// prepare our merger canvas
const canvas = canvas1.cloneNode();
const ctx = canvas.getContext("2d");
ctx.fillStyle = "#FFF";
const toMerge = [canvas1, canvas2];
const anim = () => {
ctx.fillRect(0, 0, canvas.width, canvas.height);
toMerge.forEach(layer => ctx.drawImage(layer, 0, 0));
requestAnimationFrame(anim);
};
anim();
const stream = canvas.captureStream();
const chunks = [];
const recorder = new MediaRecorder(stream);
recorder.ondataavailable = (evt) => chunks.push(evt.data);
recorder.onstop = (evt) => exportVid(chunks);
btn.onclick = (evt) => recorder.stop();
recorder.start();
};
function exportVid(chunks) {
const vid = document.createElement("video");
vid.controls = true;
vid.src = URL.createObjectURL(new Blob(chunks));
document.body.append(vid);
btn.onclick = record;
btn.textContent = "Start Recording";
}
btn.onclick = record;
canvas { border: 1px solid }
.container canvas { position: absolute }
.container:hover canvas { position: relative }
.container { height: 180px }
<div class="container">Hover here to "untangle" the canvases<br></div>
<button>Start Recording</button><br>
But if you're going this way anyway, you might just as well do the merging from the get-go and append a single canvas in the document, the browser's compositor will have less work to do.

Can't use a webRTC audio coming from a WSS:// server as a source for audioCtx.createMediaElementSource on client side

I am building a simple sound level indicator for a listener. Using MDN's guide for this (https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Visualizations_with_Web_Audio_API).
Unfortunately, I cannot access the audio from the browser, so that it could be sent over to a frequency analyser that I use for a visualization (the analyser returns a Uint8Array full of zeros, to be more specific).
Below is a code snippet.
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var audio = new Audio ("wss://demo...");
function analyseSound(){
audioCtx.resume().then(() => {
var analyser = audioCtx.createAnalyser();
var source = audioCtx.createMediaElementSource(audio);
source.connect(analyser);
analyser.fftSize = 2048;
var bufferLength = analyser.frequencyBinCount;
var dataArray = new Uint8Array(bufferLength);
console.log(dataArray); // returns Uint8Array(1024) [0, 0, 0, 0, ...]
var canvas = document.getElementById('indicator');
var canvasCtx = canvas.getContext("2d");
canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
function draw() {
drawVisual = requestAnimationFrame(draw);
analyser.getByteFrequencyData(dataArray);
canvasCtx.fillStyle = 'rgb(200, 200, 200)';
canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
var barWidth = (canvas.width / bufferLength) * 2.5;
var barHeight;
var x = 0;
for(var i = 0; i < bufferLength; i++) {
barHeight = dataArray[i]/2;
canvasCtx.fillStyle = 'rgb(' + (barHeight+100) + ',50,50)';
canvasCtx.fillRect(x,canvas.height-barHeight/2,barWidth,barHeight);
x += barWidth + 1;
}
};
draw();
});
}
audioCtx is called upon a page load, and, then, resumed on a button-click, as Chrome does not allow AudioContext before any user interaction with the page.
I tried setting up a new Websocket, and playing around with that, but, unfortunately, I have a very limited knowledge around this topic, so, there is no much luck with that either.
Any help is very much appreciated!

changing colour value of drawings after set amount of time with canvas and javascript

I've made an attempt at creating a simple audio visualizer for an mp3 track using canvas to draw/ animate circles in sync with the audio track using the web audio API.
What i've done so far:
What I want to do basically now is change the colours of the circles at a certain amount of time (eg. at a different part of the track - the drop etc). How would I go about doing this? setTimeout? I had a search but could not find anything (and I'm still quite new to JavaScript).
Here is the full code.
// Estabilish all variables tht analyser will use
var analyser, canvas, ctx, random = Math.random, circles = [];
// begins once all the page resources are loaded.
window.onload = function() {
canvas = document.createElement('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.body.appendChild(canvas);
ctx = canvas.getContext('2d', {alpha: false});
setupWebAudio(); //loads audio track
for (var i = 0; i < 20; i++) { // loop runs 50 times - creating 49 circles
circles[i] = new Circle();
circles[i].draw();
}
draw();
};
function setupWebAudio() {
var audio = document.createElement('audio');
audio.src = 'rustie.mp3';
audio.controls = true;
document.body.appendChild(audio);
var audioContext = new AudioContext(); // AudioContext object instance (WEB AUDIO API)
//contains an audio signal graph representing connections betweens AudioNodes
analyser = audioContext.createAnalyser(); // Analyser method
var source = audioContext.createMediaElementSource(audio);
// Re-route audio playback into the processing graph of the AudioContext
source.connect(analyser);
analyser.connect(audioContext.destination);
audio.play();
}
function draw() {
requestAnimationFrame(draw);
var freqByteData = new Uint8Array(analyser.frequencyBinCount); //Array of the frequency data from the audio track (representation of the sound frequency)
analyser.getByteFrequencyData(freqByteData); //take the analyser variable run it through the getByteFrequencyData method - passing through the array
ctx.fillStyle = "#ff00ed";
ctx.fillRect(0, 0, canvas.width, canvas.height); //fill the canvas with colour
for (var i = 1; i < circles.length; i++) {
circles[i].radius = freqByteData[i] * 1;
circles[i].y = circles[i].y > canvas.height ? 0 : circles[i].y + 1;
circles[i].draw();
}
}
function Circle() {
this.x = random() * canvas.width; // creates random placement of circle on canvas
this.y = random() * canvas.height;
this.radius = random() * 20 + 20; //creates random radius of circle
this.color = 'rgb(6,237,235)'; //colour of circles
}
Circle.prototype.draw = function() { //Drawing path
var that = this;
ctx.save();
ctx.beginPath();
ctx.globalAlpha = 0.75; //Transparency level
ctx.arc(that.x, that.y, that.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
ctx.restore();
}
Another thing to add also - where in the code is it setting the movement/path of the circles? As they are going from the top to the bottom of the canvas. Wanted to know if I could change this.
Question 1
Instead of hard coding the circle color to this.color = 'rgb(6,237,235)';, you can use a global variable to hold the hue var hue = 0; and then use that in your Circle.draw() like this: ctx.fillStyle = 'hsla(' + hue + ', 50%, 50%, 0.75)';
Note 1: by defining the alpha in the color, you no longer need to set ctx.globalAlpha.
Note 2: you don't need this.color any more.
As you say, you can use setTimeout to change the hue variable at a certain point in time. Or you can use the data in freqByteData to change the hue variable continuously.
More info about hsl color: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#hsl()_and_hsla()
Question 2
You are updating the y coordinate of each circle with this line:
circles[i].y = circles[i].y > canvas.height ? 0 : circles[i].y + 1;
Which means: if current y position is greater than the canvas height: set the new value to zero, otherwise increment by one.

Extract frames from video in JS

I'm trying to build a function that extracts frames from a video in JavaScript. Here the code I came up with. The function receives a source and a callback. From there, I create a video with the source and I want to draw frames of the video in the canvas with a set interval.
Unfortunately, the frames returned are all transparent images.
I tried a few different things, but I can't make it work. Can someone help?
Thanks.
const extractFramesFromVideo = function(src, callback) {
var video = document.createElement('video');
video.src = src;
video.addEventListener('loadeddata', function() {
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
canvas.setAttribute('width', video.videoWidth);
canvas.setAttribute('height', video.videoHeight);
var frames = [];
var fps = 1; // Frames per seconds to
var interval = 1 / fps; // Frame interval
var maxDuration = 10; // 10 seconds max duration
var currentTime = 0; // Start at 0
while (currentTime < maxDuration) {
video.currentTime = currentTime;
context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
var base64ImageData = canvas.toDataURL();
frames.push(base64ImageData);
currentTime += interval;
if (currentTime >= maxDuration) {
console.log(frames);
callback(frames);
}
}
});
}
export default extractFramesFromVideo;
After tweaking your code to wait for the seeked event, and fixing a few bits and pieces, it seems to work fine:
async function extractFramesFromVideo(videoUrl, fps=25) {
return new Promise(async (resolve) => {
// fully download it first (no buffering):
let videoBlob = await fetch(videoUrl).then(r => r.blob());
let videoObjectUrl = URL.createObjectURL(videoBlob);
let video = document.createElement("video");
let seekResolve;
video.addEventListener('seeked', async function() {
if(seekResolve) seekResolve();
});
video.addEventListener('loadeddata', async function() {
let canvas = document.createElement('canvas');
let context = canvas.getContext('2d');
let [w, h] = [video.videoWidth, video.videoHeight]
canvas.width = w;
canvas.height = h;
let frames = [];
let interval = 1 / fps;
let currentTime = 0;
let duration = video.duration;
while(currentTime < duration) {
video.currentTime = currentTime;
await new Promise(r => seekResolve=r);
context.drawImage(video, 0, 0, w, h);
let base64ImageData = canvas.toDataURL();
frames.push(base64ImageData);
currentTime += interval;
}
resolve(frames);
});
// set video src *after* listening to events in case it loads so fast
// that the events occur before we were listening.
video.src = videoObjectUrl;
});
}
Usage:
let frames = await extractFramesFromVideo("https://example.com/video.webm");
currentComponent.refs.videopreview is <video ref="videopreview" autoPlay></video> in HTML page
Below is the code to extract the frame from the video.
const getFrame = () => {
const video = currentComponent.refs.videopreview;
if(!video.srcObject) {
return null;
}
const canvas = document.createElement('canvas');
canvas.width = canvasWidth;
canvas.height = canvasHeight;
canvas.getContext('2d').drawImage(video, 0,0);
const data = canvas.toDataURL('image/jpeg');
return data; // frame data
}
getFrame function can be called at required interval.

Play a moving waveform for wav audio file in html

How to create a moving waveform for a audio file/tag in HTML?
When play button is clicked,the audio HTML element must be played and a corresponding moving waveform for the same should be generated....how to implement this?
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title id='title'>HTML Page setup Tutorial</title>
<script src='riffwave.js'></script>
<script type="text/javascript">
function myFunction()
{
var data = []; // just an array
for (var i=0; i<10000; i++) data[i] = /*Math.round(255 * Math.random())*/i; // fill data with random samples
var wave = new RIFFWAVE(data); // create the wave file
var audio = new Audio(wave.dataURI); // create the HTML5 audio element
audio.play();
}
</script>
</head>
<body>
<button type="button" onclick="myFunction()">Click Me!</button>
</body>
</html>
I want to create a waveform like this
Same as below but then with canvasjs:
Demo: http://seapip.com/canvas/visualizer4/
/*
Speed has to be bigger then refresh!!!
*/
//Speed to move from right to left, also the visible amount of time on the x axis (in milliseconds)
var speed = 10000;
//Time in milliseconds to redraw chart
var refresh = 30;
//Without var to make it a global variable accessable by the html onclick attribute
audioElement = document.getElementById('audioElement');
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var audioSrc = audioCtx.createMediaElementSource(audioElement);
var analyser = audioCtx.createAnalyser();
// Bind our analyser to the media element source.
audioSrc.connect(analyser);
audioSrc.connect(audioCtx.destination);
//Get frequency data
var frequencyData = new Uint8Array(analyser.frequencyBinCount);
//The animation reference
var animation;
//Create chart
var dps = []; // dataPoints
var chart = new CanvasJS.Chart("chart", {
interactivityEnabled: false,
width: 500,
height: 200,
axisX: {
title: "Time",
valueFormatString: "mm:ss"
},
axisY: {
title: "dB"
},
data: [{
type: "line",
dataPoints: dps
}]
});
chart.render();
//On play
audioElement.onplay = function() {
//Start drawing
animation = setInterval(function() {
drawWave();
}, refresh);
};
//On pause
audioElement.onpause = function() {
//Stop drawing
clearInterval(animation);
};
//On ended
audioElement.onended = function() {
//Stop drawing
clearInterval(animation);
//Reset time
time = 0;
//Reset dataPoints
dps = [];
//Prevent audio from looping (you can remove this if you want it to loop)
audioElement.pause();
};
//Max dB
var max = analyser.maxDecibels;
//Min dB
var min = analyser.minDecibels;
//Time
var time = 0;
//Our drawing method
function drawWave() {
// Copy frequency data to frequencyData array.
analyser.getByteFrequencyData(frequencyData);
//Total loudness of all frequencies in frequencyData
var totalLoudness = 0;
for(var i = 0; i < frequencyData.length; i++) {
totalLoudness += frequencyData[i];
}
//Average loudness of all frequencies in frequencyData on scale from 0 to 255
var averageLoudness = totalLoudness / frequencyData.length / 255;
//Decibels
var decibels = min + averageLoudness * Math.abs(min - max);
//Increase time
time += refresh;
//Add to chart
dps.push({
x: new Date(time),
y: decibels
});
//Maximum x values to draw based on speed ad refresh
if(dps.length > speed / refresh) {
dps.shift();
}
//Draw new chart
chart.render();
}
<audio id="audioElement" src="audio/Odesza - Above The Middle.mp3"></audio>
<div id="chart"></div>
<div>
<button onclick="audioElement.play()">Play the Audio</button>
<button onclick="audioElement.pause()">Pause the Audio</button>
<button onclick="audioElement.volume+=0.1">Increase Volume</button>
<button onclick="audioElement.volume-=0.1">Decrease Volume</button>
</div>
Keep in mind that #chart is a div instead of a canvas element, it took me a few minutes to find out why the chart wasn't showing at first :P
Same as below but with plotting from right to left. The stepSize variable sets both the animation speed and the size of the steps, if you want bigger steps to be drawn in then it needs to move faster and if you want smaller steps to be drawn it needs to move slower.
Demo: http://seapip.com/canvas/visualizer3
//Step size (pixels per 20ms)
var stepSize = 0.5;
//Without var to make it a global variable accessable by the html onclick attribute
audioElement = document.getElementById('audioElement');
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var audioSrc = audioCtx.createMediaElementSource(audioElement);
var analyser = audioCtx.createAnalyser();
// Bind our analyser to the media element source.
audioSrc.connect(analyser);
audioSrc.connect(audioCtx.destination);
//Get frequency data (800 = max frequency)
var frequencyData = new Uint8Array(400);
//Use below to show all frequencies
//var frequencyData = new Uint8Array(analyser.frequencyBinCount);
//Create canvas
var canvas = document.getElementById("wave");
canvas.style.width = "500px";
canvas.style.height = "100px";
//High dpi stuff
canvas.width = parseInt(canvas.style.width) * 2;
canvas.height = parseInt(canvas.style.height) * 2;
//Get canvas context
var ctx = canvas.getContext("2d");
//Stroke color
ctx.strokeStyle = "#ffff00";
//Draw thicker lines due to high dpi scaling
ctx.lineWidth = 2;
//Store y values
var drawY = [canvas.height];
//The animation reference
var animation;
//On play
audioElement.onplay = function() {
//Start drawing
animation = setInterval(function() {
drawWave();
}, 20);
};
//On pause
audioElement.onpause = function() {
//Stop drawing
clearInterval(animation);
};
//On ended
audioElement.onended = function() {
//Stop drawing
clearInterval(animation);
//Clear previous y values
drawY = [canvas.height];
//Prevent audio from looping (you can remove this if you want it to loop)
audioElement.pause();
};
//Our drawing method
function drawWave() {
// Copy frequency data to frequencyData array.
analyser.getByteFrequencyData(frequencyData);
//Total loudness of all frequencies in frequencyData
var totalLoudness = 0;
for(var i = 0; i < frequencyData.length; i++) {
totalLoudness += frequencyData[i];
}
//Average loudness of all frequencies in frequencyData
var averageLoudness = totalLoudness / frequencyData.length;
//Scale of average loudness from (0 to 1), frequency loudness scale is (0 to 255)
var y = averageLoudness / 255;
//Multiply with canvas height to get scale from (0 to canvas height)
y *= canvas.height;
//Since a canvas y axis is inverted from a normal y axis we have to flip it to get a normal y axis value
y = canvas.height - y;
//Store new y value
drawY.push(y);
//Clear previous drawing
ctx.clearRect(0, 0, canvas.width, canvas.height);
//Draw line
for(var i = drawY.length; i > 0; i--) {
//calculate x values
var x1 = canvas.width - (drawY.length - i - 1) * stepSize;
var x2 = canvas.width - (drawY.length - i) * stepSize;
//Stop drawing y values if the x value is outside the canvas
if(!x2) {
break;
}
ctx.beginPath();
ctx.moveTo(x1, drawY[i - 1]);
ctx.lineTo(x2, drawY[i]);
ctx.stroke();
}
}
<audio id="audioElement" src="audio/Odesza - Above The Middle.mp3"></audio>
<canvas id="wave"></canvas>
<div>
<button onclick="audioElement.play()">Play the Audio</button>
<button onclick="audioElement.pause()">Pause the Audio</button>
<button onclick="audioElement.volume+=0.1">Increase Volume</button>
<button onclick="audioElement.volume-=0.1">Decrease Volume</button>
</div>
Here's what I think you probably wanted, x axis is the time and y axis is the average loudness of all frequencies. Keep in mind that browsers like chrome don't draw the graph properly in a background tab because it limits the refresh interval and audio analyzer output.
Demo: http://seapip.com/canvas/visualizer2
//Without var to make it a global variable accessable by the html onclick attribute
audioElement = document.getElementById('audioElement');
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var audioSrc = audioCtx.createMediaElementSource(audioElement);
var analyser = audioCtx.createAnalyser();
// Bind our analyser to the media element source.
audioSrc.connect(analyser);
audioSrc.connect(audioCtx.destination);
//Get frequency data (800 = max frequency)
var frequencyData = new Uint8Array(400);
//Use below to show all frequencies
//var frequencyData = new Uint8Array(analyser.frequencyBinCount);
//Create canvas
var canvas = document.getElementById("wave");
canvas.style.width = "1000px";
canvas.style.height = "100px";
//High dpi stuff
canvas.width = parseInt(canvas.style.width) * 2;
canvas.height = parseInt(canvas.style.height) * 2;
//Get canvas context
var ctx = canvas.getContext("2d");
//Set stroke color to yellow
ctx.strokeStyle = "#ffff00";
//Draw twice as thick lines due to high dpi scaling
ctx.lineWidth = 2;
//Save x and y from the previous drawing
var drawX = 0;
var drawY = 0;
//Total duration (Seconds)
var duration;
//The animation reference
var animation;
//Audio is loaded
audioElement.oncanplaythrough = function() {
//Get duration
duration = audioElement.duration;
//On play
audioElement.onplay = function() {
//Start drawing
drawWave();
};
//On pause
audioElement.onpause = function() {
//Stop drawing
cancelAnimationFrame(animation);
};
//On ended
audioElement.onended = function() {
//Stop drawing
cancelAnimationFrame(animation);
//Clear previous drawing
ctx.clearRect(0, 0, canvas.width, canvas.height);
//Clear previous x and y values
drawX = 0;
drawY = 0;
//Prevent audio from looping (you can remove this if you want it to loop)
audioElement.pause();
};
};
//Our drawing method
function drawWave() {
//Current time (seconds)
var currentTime = audioElement.currentTime;
// Copy frequency data to frequencyData array.
analyser.getByteFrequencyData(frequencyData);
//Total loudness of all frequencies in frequencyData
var totalLoudness = 0;
for(var i = 0; i < frequencyData.length; i++) {
totalLoudness += frequencyData[i];
}
//Average loudness of all frequencies in frequencyData
var averageLoudness = totalLoudness / frequencyData.length;
//Get the previous x axis value
var previousDrawX = drawX;
//Scale of progress in song (from 0 to 1)
drawX = currentTime / duration;
//Multiply with canvas width to get x axis value
drawX *= canvas.width;
//Get the previous y axis value
var previousDrawY = drawY;
//Scale of average loudness from (0 to 1), frequency loudness scale is (0 to 255)
drawY = averageLoudness / 255;
//Multiply with canvas height to get scale from (0 to canvas height)
drawY *= canvas.height;
//Since a canvas y axis is inverted from a normal y axis we have to flip it to get a normal y axis value
drawY = canvas.height - drawY;
//Draw line
ctx.beginPath();
ctx.moveTo(previousDrawX, previousDrawY);
ctx.lineTo(drawX, drawY);
ctx.stroke();
//Animate
animation = requestAnimationFrame(drawWave);
}
<audio id="audioElement" src="audio/Odesza - Above The Middle.mp3"></audio>
<canvas id="wave"></canvas>
<div>
<button onclick="audioElement.play()">Play the Audio</button>
<button onclick="audioElement.pause()">Pause the Audio</button>
<button onclick="audioElement.volume+=0.1">Increase Volume</button>
<button onclick="audioElement.volume-=0.1">Decrease Volume</button>
</div>
Canvas visualizer example
Demo: http://seapip.com/canvas/visualizer/
//Without var to make it a global variable accessable by the html onclick attribute
audioElement = document.getElementById('audioElement');
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var audioSrc = audioCtx.createMediaElementSource(audioElement);
var analyser = audioCtx.createAnalyser();
// Bind our analyser to the media element source.
audioSrc.connect(analyser);
audioSrc.connect(audioCtx.destination);
//Get frequency data (400 = max frequency)
var frequencyData = new Uint8Array(400);
//Use below to show all frequencies
//var frequencyData = new Uint8Array(analyser.frequencyBinCount);
//Create canvas
var canvas = document.getElementById("wave");
canvas.style.width = "500px";
canvas.style.height = "100px";
//High dpi stuff
canvas.width = parseInt(canvas.style.width) * 2;
canvas.height = parseInt(canvas.style.height) * 2;
//Get canvas context
var ctx = canvas.getContext("2d");
//Set stroke color
ctx.strokeStyle = "#ffff00"
//Draw twice as thick lines due to high dpi scaling
ctx.lineWidth = 2;
//Animation reference
var animation;
//On play
audioElement.onplay = funtion() {
drawWave();
};
//On pause
audioElement.onpause = funtion() {
cancelAnimationFrame(animation);
};
//On ended
audioElement.onended = funtion() {
cancelAnimationFrame(animation);
};
//Our drawing method
function drawWave() {
// Copy frequency data to frequencyData array.
analyser.getByteFrequencyData(frequencyData);
//Draw the wave
ctx.clearRect(0, 0, canvas.width, canvas.height);
for(var i = 1; i < frequencyData.length; i++) {
var x1 = canvas.width / (frequencyData.length - 1) * (i - 1);
var x2 = canvas.width / (frequencyData.length - 1) * i;
var y1 = canvas.height - frequencyData[i - 1] / 255 * canvas.height;
var y2 = canvas.height - frequencyData[i] / 255 * canvas.height;
if(x1 && y1 && x2 && y2) {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
}
//Animate
animation = requestAnimationFrame(drawWave);
}
<audio id="audioElement" src="audio/Odesza - Above The Middle.mp3"></audio>
<canvas id="wave"></canvas>
<div>
<button onclick="document.getElementById('audioElement').play()">Play the Audio</button>
<button onclick="document.getElementById('audioElement').pause()">Pause the Audio</button>
<button onclick="document.getElementById('audioElement').volume+=0.1">Increase Volume</button>
<button onclick="document.getElementById('audioElement').volume-=0.1">Decrease Volume</button>
</div>
Plugins and tutorials about audio visualization:
https://wavesurfer-js.org/
http://waveformjs.org/#weird
https://www.bignerdranch.com/blog/music-visualization-with-d3-js/
https://github.com/wayou/HTML5_Audio_Visualizer
https://www.patrick-wied.at/blog/how-to-create-audio-visualizations-with-javascript-html
https://p5js.org/examples/examples/Sound_Frequency_Spectrum.php

Categories