Avg. Rating 4.0

Problem

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)

Solution

I've written a component that takes an MP3 file and draws its waveform.

Detailed explanation

 <?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.


+
This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License. Permissions beyond the scope of this license, pertaining to the examples of code included within this work are available at Adobe.

Report abuse

Related recipes