HTML5 PCM Waveform Visualisation

The oud riff was recorded at home through an 01X, visualised by some JavaScript code knocked-up in an hour one Monday morning.

One of the many wonderful features of the HTML5 Web Audio API is the ability to access the PCM data of an audio file. Combined with the speed of contemporary machines, this enables an HTML5 web page running in WAA-compatible browser to display the waveform of the playing file in real time – and it is really, really easy to use.

Access to the PCM data is via an ‘analyser’, which is inserted into the audio node chain, and provides an array of floats or unsigned integers for each audio ‘frame.’ The API allows the user to specify the number of samples to return in a frame, and for the purpose of this illustrative example, the default 2048 was replaced with 512, which is plenty for the non-professional eye, and allows for a relatively fast canvas refresh rate.

Access this data at regular intervals, render it to a canvas, and you’re done.

To make things a little more interesting, I thought to maintain a stack of the past n PCM waves, and render them disappearing into the distance. My maths skills are not advanced, and I spent some time messing around with scaling and translation factors applied directly to the stored co-ordinates, before smacking my head for my own stupidity when remembering that the canvas context supplies ‘scale’ and ‘translate’ functions to do this: for each generation in the frame stack, the origin is shifted up, and the scale is decreased, to create a perspective effect.

Update

Deep joy – requestAnimationFrame actually works, so finally setTimeout/.periodical is ditched. I found this shim, and slotted it into place within seconds:

window.requestAnimFrame = (function(){
  return  window.requestAnimationFrame       || 
		  window.webkitRequestAnimationFrame || 
		  window.mozRequestAnimationFrame    || 
		  window.oRequestAnimationFrame      || 
		  window.msRequestAnimationFrame     || 
		  function( callback ){
			window.setTimeout(callback, 1000 / 60);
		  };
})();

Frankly, I see no performance difference, but I am excited the possibilities for games programming.

Next

When dabbling with the scaling factors, I was tempted to shift to WebGL. I may try this next, using three.js. May also render an opaque polygon between generations, to create mountains. Or maybe I’ll just increase the number of generations – and hence their visual density – until the fan gets too loud.

Links