chamberlainpi
chamberlainpi

Reputation: 5241

How to play a seamless loop from an AudioSprite in AS3, without SAMPLE_DATA

I created a batch of sounds assembled with this tool:

AudioSprite

https://github.com/tonistiigi/audiosprite

The output is generally used for JS libraries, such as Howler, Zynga Jukebox, or SoundJS - but I wanted to see if it's possible to implement in AS3.

I started creating a Sound player that can load, parse and play the sounds based on the JSON and MP3 file this tool generates.

So far so good! ... except for loops.

Now, the big question is - is there a way to play a Sound-loop seamlessly given that all music & sounds coexist in the same MP3 file, and it has a start & end range to play and stop it?

Example of how the sounds are placed in the file:

mygame_sounds.mp3 = [BUZZ + LASER + BOING ... + TRACKLOOP]

I'm looking for a solution that does not involve using the SAMPLE_DATA Event (given it eats up a lot of CPU usage). If there's no way around it, please explain why.

So far I've had mild success using flash.utils.Timer objects triggered after a given AudioSprite's duration, but it's not consistent.

To stop / dispose of a non-looping sound, I rely on a Master Timer (running at very short intervals) and that seems to "cut" the sample appropriately. But I already tried using this Master Timer to play a looped-sound over and over - same latency issues.

Is there any method to predict / measure how much latency is to be expected by the time the sound completes one pass?

Upvotes: 1

Views: 771

Answers (3)

chamberlainpi
chamberlainpi

Reputation: 5241

Although I'm still working on the perfect solution, this is the best I could come up with:

  1. Load the JSON file / ByteArray.
  2. Parse the JSON file to obtain each sprites' ID, start and end times.
  3. Load the MP3 file / ByteArray (requires loadCompressedDataFromByteArray()) into a master Sound object.
  4. Once loaded, check if any sprites are marked as "loops".
  5. Create separate Sound objects for the above loops, and extract the portion from the master Sound via loadPCMFromByteArray() with some "magic-numbers" (details below).
  6. To play a one-shot sound, call the master Sound's play(sprite.start * 1000) (depending on the format, usually the JSON's start values are in seconds, needs to be in milliseconds).
  7. To play a seamless-loop sound, call the individual Sound object's (created in step #5) play(0, 9999) method.

I won't go too deep in details on how to stop the sounds (SoundChannel.stop(), bam!), but I'll explain the "magic-numbers" mentioned above. See this snippet:

    var goldenOffset:UInt = (64 << 5);
    var goldenDuration:UInt = (64 << 2);
    var sampleRate:UInt = 44100;
    
    for (id in loops) {
        var sprite:AudioSpriteItem = _mapSprites.get(id);
        var loop:Sound = _mapLoops.get(id);
        var sampleBytes = new ByteArray();
        var samplesTotal:UInt = cast(sprite.duration * sampleRate + goldenDuration);
        var samplesStart:UInt = cast(sprite.start * sampleRate + goldenOffset);
        
        _sound.extract(sampleBytes, samplesTotal, samplesStart);
        
        sampleBytes.position = 0;
        loop.loadPCMFromByteArray(sampleBytes, samplesTotal, "float", true);
    }

Quite honestly, these magic goldenOffset and goldenDuration values were just found via Trial-and-Error. I could get close to a seamless loop without them by just calculating the start and duration with the sampleRate (assuming 44100 by default), but each endings had a bit of a hiccup to it.

After several adjustments, those couple "64 left bit-shifted" values made the loops sound smoother.

I posted the Haxe project on github (compiled SWC also available in /bin folder) if you wish to try it / read through the code.

FLAudioSprite

Github page: https://github.com/bigp/FLAudioSprite

SWF Demo (Download): bit.ly/FLAudioSpriteSWFDemo

Upvotes: 0

Vesper
Vesper

Reputation: 18747

The reason of why you can't get smooth loops of a track retrieved from a larger audio file is that you cannot check sound position faster than once per SWF frame, which length depends on stage.frameRate and total processing time of your application and is generally varied. So, if your looping sounds lasts say 5.123 seconds (I don't care how many samples, just that its length does not make a full number of frames regardless of stage.frameRate), your sound will attempt to play for either 5.125 seconds (205 frames at 40 fps, IMO best bet for this particular sound), 5.133 seconds (154 frames at 30 fps) or some weird number of frames if the SWF would experience lag. The excess milliseconds cannot be totally controlled by any means due to AS3/Flash engine optimization. So, consider not using audio sprites and shift into audio packs (several audio files in an SWF, or one sound in an MP3).

Upvotes: 1

OJay
OJay

Reputation: 1239

In SoundJS we could not find a way to allow smooth looping of audio sprites in AS3 and went with a timer. We found Web Audio was the only api that allowed smooth looping, and therefore recommended staying away from audiosprites for sounds that needed to loop smoothly if any other plugin might be used.

Hope that helps.

Upvotes: 1

Related Questions