When writing applications that work with audio, it's often useful to be able to draw a waveform of the audio. (That is, a static amplitude-vs-time graph, rather than a dynamic graph of frequencies while the audio plays)
I've written a component that takes an MP3 file and draws its waveform.
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml"
width="100%" height="100%">
<mx:Script>
<![CDATA[
private var sound:Sound;
private var _soundLength:Number; //in ms
private var sampleArray:Array;
public function getXByTime(milliseconds:Number):Number
{
if(!_soundLength) return 0;
return
1.0*milliseconds/_soundLength*slate.width + slate.x;
}
public function getTimeByX(xVal:Number):Number
{
if(!_soundLength) return 0;
return 1.0*(xVal -
slate.x)/slate.width*_soundLength;//returns milliseconds
}
public function loadSound(soundLoc:String):void
{
try
{
sound = new Sound();
sampleArray = new
Array();
sound.addEventListener(IOErrorEvent.IO_ERROR, onSoundError);
sound.addEventListener(Event.COMPLETE, onSoundLoad);
sound.load(new
URLRequest(soundLoc));
}
catch(e:Error)
{
trace(e.message);
}
}
private function onSoundError(evt:IOErrorEvent):void
{
trace(evt.text + '; error loading
sound!');
}
private function onSoundLoad(evt:Event):void
{
_soundLength = sound.length;
var numTotalSamples:Number =
int(_soundLength*44.1); //assume 44.1kHz sample rate
var soundBytes:ByteArray = new
ByteArray();
/*
* sound.extract extracts the sound byte
data into the soundBytes array, returning the actual number of
samples
* extracted.
* The second parameter passed to
sound.extract is the number of samples to extract - adding
100 to the number
* calculated from the sample rate is
insurance against overflows, while capping it at 440000 samples is
to
* avoid crashes from too much data.
* By first reading the byte data with an
excessive number of samples (extras are left as zero) and then
cutting off
* the ByteArray at the exact number of
samples read by setting the length to eight times the number read,
the code
* makes sure the resulting array has all
of the samples from the audio (if not cut off by the 440000 cap)
but no blank
* ones at the end.
*/
numTotalSamples =
sound.extract(soundBytes,Math.min(numTotalSamples+100,440000));
soundBytes.length = 8*numTotalSamples;
soundBytes.position = 0;
while(soundBytes.bytesAvailable > 0)
{
/*
* Alternate floats are
the left and then the right channel - this code is written for mono
audio,
* but for stereo, just
read both channels and average them before pushing the value to the
array
* of samples. Or, to
draw both channels, use two arrays and drawData() for both.
*/
sampleArray.push(soundBytes.readFloat());//save the left
channel
soundBytes.readFloat();//read and ignore the (identical;
sound is assumed mono) right channel
}
drawData();
}
private function drawData():void
{
slate.graphics.clear();
var vC:Number = slate.height/2; //Vertical
center
var w:Number = slate.width;
var h:Number = slate.height;
//Thin black line
slate.graphics.lineStyle(1,0x000000);
slate.graphics.moveTo(0,vC);
slate.graphics.lineTo(w,vC); //Draw the
horizontal axis
var l:Number = sampleArray.length;
slate.graphics.moveTo(0,vC);//Move back to
the beginning
//Draw all but the last couple of points -
they tend to cause jags in the graph
for(var i:Number = 0; i < l - 3; i++)
{slate.graphics.lineTo(i*w/l,vC - sampleArray[i]*h/2);}//Draw
a line to the next point
}
public function clearDrawn():void
{
slate.graphics.clear();
}
]]>
</mx:Script>
<mx:Canvas id="graph"
backgroundColor="0xFFFFFF" backgroundDisabledColor="0xFFFFFF"
width="100%"
height="100%"
backgroundAlpha="0">
<mx:UIComponent
id="slate" height="100%" width="100%" />
</mx:Canvas>
</mx:Canvas>
----------------------------------------------------------------------------------------------------------------------------------
By calling the loadSound() method of an instance of the AudioVisualizer component, the audio is loaded in and the waveform is drawn.
The code here is wired to work with 44.1kHz mono sound but is easily adjusted to any other parameteers.
The default is to draw a black waveform on a white background; by changing the colors of the line and the canvas background, that's adjustable.
Also included are accessor methods that may prove useful for working with the graph.
+