Audio Visualiser Using D3

D3 isn't just for squares with their pie charts and bar graphs anymore. D3 can be totally Rock 'n Roll, man! And to prove that to all you naysayers, we'll go ahead and build a sweet rockin' audio visualiser right in our browser.

Or, if you prefer, cut to the chase and check out the full code on Github here.

Set Up index.html

To get at some tunes, I'm going to use an HTML5 audio tag with id=player right in index.html and just source the audio from a local file. We can also tack on a div with and id="visualiser" for housing our visualiser. Make sure to load up d3 and jQuery while you're at it too!

If you're using a local audio file, you need to set up a localhost server and not just open the html file directly from your browser. Without a local server, you'll get a CORS Access Restriction error when you try and run the visualiser!

Grabbing the AudioContext()

Next, we're going create an AudioContext object, context, so we can get at all those sweet frequencies.

$(function(){
    var context = new AudioContext();
});

Create an AnalyserNode

We'll use the createAnalyser method on the context object to create an AnalyserNode, analyser, that will provide us with the real time frequency data we're looking for.

Then we'll set what essentially amounts to the resolution of our analyser object by setting its fftSize property. The fftSize must be a power of 2 for some mathematically magical reason. The higher the resolution, the sharper the visualiser, but we'll end up animating animating exponentially more elements which can take a toll. I've found that 512 to 1024 is a good balance between resolution and performance, but feel free to play around with it and see what you can do.

Finally, we'll create a frequencyData array which is what we'll ultimately pass as data to d3 to power our visualisation.

$(function() {
    var context = new AudioContext();
    var analyser = context.createAnalyser();
    analyser.fftSize = 64;
    var frequencyData = new Uint8Array(analyser.frequencyBinCount);
});

Connect Our Analyser

Next up, we have to route our tunes through our analyser. Using jQuery, we're going to grab the audio HTML tag we're listening to. We'll pass the audio tag element to the context.createMediaElementSource method and set it as our source. That will allow us to get the frequency data. We'll connect our analyser to the audio source, and then finally, connect our analyser to context.destination, in normal parlance, you speakers, so that we can once again hear the music. Check out the AnaylserNode and AudioContext docs for more info on what ever the hell's going on here.

$("#player").bind('canplay', function() {
        var source = context.createMediaElementSource(this);
        source.connect(analyser);
        analyser.connect(context.destination);
    });

Our analyser is all wired up to report back to us with real time info on what's playing. To get an updated array of the frequency values, all we need to do is run analyser.getByteFrequencyData(frequencyData) and the frequencyData array we declared earlier, will get updated.

To check it out, run a little console.log action.

setInterval(function(){  
  analyser.getByteFrequencyData(frequencyData);
  console.log(frequencyData)
}, 500);

Once the music starts playing, you should see all kinds of numbers dancing around in the console. Unfortunately, that's only exciting for us geeky dev types. We'll have to up our game to get the groupies.

Visualise It

A constantly changing array of numbers! D3 is all over that! Let's set up an svg element to hold our sweet visuals.

var width = 800,  
        height = 600,
        barPadding = 0;

    var svg = d3.select('#visualiser')
        .append('svg')
        .attr('width', width)
        .attr('height', height);

We'll create an ever so basic bar graph in d3 bound to the frequency information. This visualiser is as basic as they get, but d3 has so much awesomeness, your imagination is the limit.

Since we're going to be continuously updating our graph with the real time frequency data, we'll wrap drawing the graph in an update function.

    var update = function(data) {

        rect = svg.selectAll('rect')
            .data(data)

        rect.enter().append('rect');

        rect.attr('width', function() {
                return width / data.length - barPadding;
            })
            .attr('height', function(d) {
                return d * 1000;
            })
            .attr('x', function(d, i) {
                return i * width / data.length;
            })
            .attr('y', function(d) {
                return height - d;
            })
            .attr('fill', function(d) {
                return "rgb(0, 0, " + (d * 10) + ")";
            });
    };

update(frequencyData);  

Lastly, we'll make use of d3.timer() to run continuously run our animation. We also have to make sure and call analyser.getByteFrequencyData(frequencyData) so that we get real time audio data.

d3.timer(function() {  
        analyser.getByteFrequencyData(frequencyData);
        update(frequencyData);
    });

And now, you only have one more question to answer. Are you ready to Rock?

I'm such a nerd...

Full code up on Git.