AS3 sound.extract() for waveform
Asked Answered
C

3

6

Good morning!

I'm trying to create a visual waveform for an MP3. The code I've included is called on successful load of the MP3. I intend to extract just a few important samples from the sound to create the waveform, rather than extract the entire sound into a bytearray. Even on a good machine, extracting an entire song can cause flash to freeze up for 3-5 seconds (or longer!). For my purposes, this isn't feasible.

Unfortunately, the code I've got below is failing to produce any numbers. If I extract the entire song it functions, but extraction of just the key points is giving me nothing. Does performing an extract make the remainder of the sound object invalid for future extracts? If so, is there some way around this that won't freeze flash for an extended period of time during the extract?

Some important variables from the rest of the code:

waveFormWidth: static width of the wave form sprite.
waveFormHeight: static height of the wave form sprite.
song: sound object that I'll be using to create the wave form.

    public function mapWaveForm(e:Event = null):void
    {

        // Clear the wave form sprite
        waveForm.graphics.clear();
        waveForm.graphics.lineStyle(0, 0x000000, 1);

        // Storage for the wave form bit data
        var byteOutput:ByteArray;
        var leftPeakSize:Number;
        var rightPeakSize:Number;
        var songTotalSamples:int;
        var thisSample:int;

        byteOutput = new ByteArray();

        // How many samples?
        songTotalSamples = (song.length * 44.1);

        // Loop for the wave form width
        for (var peakCount:int = 0; (peakCount < waveFormWidth); peakCount++)
        {

            // Get info at each peak.
            thisSample = Math.floor(songTotalSamples * (peakCount / waveFormWidth));
            song.extract(byteOutput, 1, thisSample);
            byteOutput.position = 0;
            trace(thisSample, byteOutput.readFloat());

            leftPeakSize = byteOutput.readFloat() / 1.27;
            rightPeakSize = byteOutput.readFloat() / 1.27;

            // Turn those peaks into something usable.
            leftPeakSize = leftPeakSize * (waveFormHeight * .5);
            rightPeakSize = rightPeakSize * (waveFormHeight * .5);

            // Make the left channel line
            waveForm.graphics.moveTo(peakCount, (waveFormHeight * .5));
            waveForm.graphics.lineTo(peakCount, (waveFormHeight * .5) - leftPeakSize);

            // Make the right channel line
            waveForm.graphics.moveTo(peakCount, (waveFormHeight * .5));
            waveForm.graphics.lineTo(peakCount, (waveFormHeight * .5) + rightPeakSize);


        }

    }

Thanks for your help guys!

Collodion answered 29/5, 2011 at 13:44 Comment(0)
R
3

For what it's worth, I was just working on the EXACT same thing and I just got it working after some research. I cannot seem to get it to work with WAV files, but this is compatable with MP3 files of all types. This is what I ended up with:

var IMAGE_SIZE:int = new int(600); // Final width of image
var IMAGE_DEPTH:int = new int(5); // How many passes per pixel of image
var RESOLUTION:int = new int(IMAGE_SIZE * IMAGE_DEPTH);
var DRAW_AMPLITUDE:int = new int(150); // pixel height of max volume line

var waveForm:Shape = new Shape();

var mp3File:Sound = new Sound();
mp3File.load(new URLRequest("audio.mp3"));
mp3File.addEventListener(Event.COMPLETE, parseSound);

function parseSound(e:Event):void {
    var soundBytes:ByteArray = new ByteArray();
    for (var i:int=0; i<RESOLUTION; i++) {
        mp3File.extract(soundBytes, 1, Math.floor((mp3File.length*44.1)*(i/RESOLUTION)));
        soundBytes.position = 0;
        while (soundBytes.bytesAvailable > 0) {
            var tmpNum:Number = new Number();
            tmpNum += soundBytes.readFloat();
        }
        drawWave(i, tmpNum);
        soundBytes.clear();
    }
    this.addChild(waveForm);
    trace("--DONE--");
}

function drawWave(i:Number, amp:Number):void {
    var pixX:Number = new Number(Math.floor(i/IMAGE_DEPTH));
    var pixY:Number = new Number((amp*DRAW_AMPLITUDE)/2);
    waveForm.graphics.lineStyle(1, 0x0, (1.2/IMAGE_DEPTH));
    waveForm.graphics.moveTo(pixX, ((DRAW_AMPLITUDE/2)-pixY));
    waveForm.graphics.lineTo(pixX, ((DRAW_AMPLITUDE/2)+pixY));
}
Revive answered 25/6, 2012 at 16:52 Comment(0)
O
2

This is an old post, and maybe I don't understand your question correctly, but the extract method allows you to specify the length of samples, and where in the start.

"public function extract(target:ByteArray, length:Number, startPosition:Number = -1):Number"

length is the number of samples to extract, and startPosition is at which position in the byteArray to start.

See http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/media/Sound.html#extract%28%29

Organza answered 13/4, 2012 at 7:16 Comment(0)
D
0

Use this class:

http://www.bytearray.org/?p=329

I've also incorporated this into a really crappy, half-baked experiment I started a while back at making a sound mixer. You can get the source code for that as well here:

http://code.google.com/p/ascensionsystems/downloads/list

Drolet answered 29/5, 2011 at 14:36 Comment(8)
Hi, thanks for the response, but in your example you're extracting the entire sound. That causes it to freeze for about 4 seconds on the song I'm using as an example, and isn't useful for what I'm doing. I had actually found your example before I posted here.Collodion
Is it a large audio file you're extracting?Drolet
It's a normal song. 10-15 minutes long? I need to accomodate for MP3s of up to podcast length (120 minutes+) which is why I'm trying to extract individual samples, rather than the entire thing.Collodion
Yeah that's gonna be an issue, you're having the lag because of the sheer size of the data. The only other way you could do this is to dynamically generate the wave form bit-by-bit as you play the song using the SampleDataEvent.Drolet
So there's no way to strategically acquire individual samples, rather than extracting the entire thing? The sound object is already fully loaded, it seems a bit strange that there's no way around a full bytearray extract.Collodion
Well you could load the sound file as a ByteArray. Just use a URLLoader and set the data type to BINARY to load the sound into a bytearray. This way you don't need to the VM to extract the binary data and output a bytearray. However, skimming through a 15 minute song and drawing a wave form from that data is probably going to freeze up the VM even still.Drolet
Interesting. Hmmm. That may have merit to it.Collodion
Ack, sadly no. When loaded as a binary and dumped directly into a bytearray, the MP3 is unencoded and the data is junk. It needs sound.extract to turn it into something usable.Collodion

© 2022 - 2024 — McMap. All rights reserved.