13

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 enter image description here

Prateek Ratnaker
  • 817
  • 11
  • 35

1 Answers1

12

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

seahorsepip
  • 4,519
  • 1
  • 19
  • 30
  • your demo isn't working ....could you please give a fiddle for the same? – Prateek Ratnaker Aug 19 '16 at 02:57
  • I can't give a jsfiddle/codepen/etc since I can't use ``createMediaElementSource`` if the media isn't on the same domain as the html and js. – seahorsepip Aug 19 '16 at 03:10
  • Seems to work fine here: http://i.imgur.com/BIpz1xI.gif , can you comment with whatever shows in the browser console after loading the page and pressing play? Also does it play music? Do you hear anything? – seahorsepip Aug 19 '16 at 03:15
  • what's jslogo.js in your code?.....what's requestanimationframe?...could you please explain your code in detail...like how visualization is drawn when play button is clicked... i am not able to understand the flow – Prateek Ratnaker Aug 19 '16 at 03:36
  • jslogo is a drawing library that uses the logo/turtle language to draw on a svg element: http://seapip.com/jslogo/ . I suggest using the canvas method instead since that doesn't require any dependencies, runs smoother and drawing on canvas has documentation. – seahorsepip Aug 19 '16 at 04:02
  • ``requestanimationframe()`` is a function in modern browsers that attaches the function supplied as argument on the next redraw, it calls the draw function around 60 timer per second if possible, I limited that using settimeout since such a high fps isn't needed in this case. You can replace ``requestanimationframe(drawWave)`` with ``drawWave()`` since the redrawing is already handled by setTimeout. – seahorsepip Aug 19 '16 at 04:03
  • The code below ``//Draw the wave`` does the actual drawing on the canvas, first it clears the canvas of any existing drawings and then it loops trough all the frequencies and draw them as lines from left to right on the canvas. The points used to draw the lines are calculated in x1, y1, x2, y2. More info on drawing lines on canvas elements: http://www.w3schools.com/tags/canvas_lineto.asp – seahorsepip Aug 19 '16 at 04:07
  • Also the demo does NOT work in IE and also not as local file (url starting with file://) due to cors limitations in modern browsers. (Cors means that the audio file loaded should be on the same domain as the script, sadly file:// url's aren't seen as a domain). You can use a local dev server or web server for testing. – seahorsepip Aug 19 '16 at 04:11
  • var frequencyData = new Uint8Array(analyser.frequencyBinCount);...how is the frequency bin count calculated?...it gives an array of length 1024 in my case...is this based on the size/duration of audio file and i want the plotting based on DB/frequency value vs Time which is the current time of the music being played...so there must be a sync between the music current time vs corresponding DB/frequency value at that particular moment...so waveform/visualization being displayed must be in accordance to current time of the audio being played ...how to do this? – Prateek Ratnaker Aug 19 '16 at 07:37
  • Db frequency vs time is possible but if the x-axis is time and y-axis is the loudness(dB) of a frequency then there is no axis left to differentiate between frequencies, since the graph shown in your post is a 2d graph with only an x and y axis. – seahorsepip Aug 20 '16 at 08:33
  • frequencyBinCount.length is half the fftsize, see wikipedia for fftsize. The following link shows how to calculate the amount of hz per step in the frequencybinCount array http://stackoverflow.com/questions/14789283/what-does-the-fft-data-in-the-web-audio-api-correspond-to – seahorsepip Aug 20 '16 at 08:35
  • The samplerate is ``audioCtx.sampleRate`` – seahorsepip Aug 20 '16 at 08:40
  • But since we're limited by the fact we've only got 2 axis we just take the average loudness of all frequencies. So ``y = canvas.height-Math.sum(frequencyData)/frequencyData.length/255*canvas.height;`` and ``x = totalTime / currentTime * canvas.width;`` I'll update the answer with this added as another demo. – seahorsepip Aug 20 '16 at 08:47
  • in that case,we wouldn't need to call drawwave function using settimeout?....wouldn't it be called on click of play button such that ...it takes current time into consideration and then plots the wave.......can't we plot a moving wave which moves from right to left like in this chart http://canvasjs.com/editor/?id=http://canvasjs.com/example/gallery/dynamic/realtime_line/ – Prateek Ratnaker Aug 20 '16 at 10:33
  • 3
    I added code on top of my answer that shows code how to draw a wave from left to right. I used animationframe and removed settimeout since it wasn't needed. To plot a wave from right to left we need to store the previous drawing steps and redraw the whole wave on every movement, it's a bit more complex but I can add it to the answer if you want. If you have any questions about the code on top of my answer I just added let me know. – seahorsepip Aug 20 '16 at 10:39
  • please have a look at this link http://canvasjs.com/editor/?id=http://canvasjs.com/example/gallery/dynamic/realtime_line/ .................can't we do something like this with normal canvas without any dependency like jslogo? – Prateek Ratnaker Aug 20 '16 at 10:50
  • 3
    I updated the answer to only show the canvas examples that have no dependencies. I'll add another example that shows how the left to right plotting canvas can be modified to draw from right to left like in your canvasjs example. – seahorsepip Aug 20 '16 at 10:58
  • just a suggestion can't we just precalculate all the loudness at different time of our audio and then plot those values using canvasJS and play the audio simulataneously so that it gives a feel that in a limited frame the audio wave is moving through the y-axis just as in canvas JS?.....PS: Thank you so much for helping this much – Prateek Ratnaker Aug 20 '16 at 11:19
  • I just wrote the same plotting animationwithout canvas js. And there is no need to precalculate, besides precalculating loudness of the audio is a sadly a bit more complex in js :/ I'll update the answer with the new plotting animation added. – seahorsepip Aug 20 '16 at 11:38
  • If you want I can write an example that uses canvasjs in the link you posted. – seahorsepip Aug 20 '16 at 11:52
  • Thank you so much....you are really a genius....please show me how to do this with canvasJS?...please show the DB vs time value on axes as well...PS:Thank you so much – Prateek Ratnaker Aug 20 '16 at 11:54
  • 3
    No problem. The stepSize variable is the amount of pixels in width is drawn for 20ms of music if you wondered what it meant. – seahorsepip Aug 20 '16 at 11:59
  • 3
    Almost done, haven't used canvasjs before so it took me some time to figure out. – seahorsepip Aug 20 '16 at 13:25
  • 3
    Added an example with canvasjs. – seahorsepip Aug 20 '16 at 13:54