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.