Playback MP3-Loop (gapless)

This is one of the most frequently asked question in my inbox.

Is it possible to loop MP3 without gaps?
Yes, but to answer the question I like to explain, why gap-less MP3 looping was actually not possible before Flash10 (Except for Flash-IDE encoded MP3 or ugly hacks).

Encoding Audio
Every audio encoder works by subdivided the audio stream into frames. Each time-frame contains information about the current frequency bands in use. You can easily follow, why an algorithm needs at least a couple of amplitudes (called a window) to analyze the spectrum. A single amplitude does not have any information about frequencies. Imagine a snapshot of a speakers membrane. There is no way to say anything about its velocity, not even its sign.

MP3 loss-less
MP3 also encodes audio with a certain amount of samples each frame. The length of a frame however is not free to choose. That is why encoder add more data (encoder delay) to the audio stream then actually necessary to ensure minimal frame length (depending on algorithm). Unfortunately the engineers of MP3 back then forgot to add the information how much silence they added to the encoded file. To make it absolutely clear: This information is lost for all times. You may write algorithms to estimate the delay, but it is not possible to recover exactly.

Btw: FlashIDE encoded MP3 loop gap-less, cause the SWF contains the original amount.

Does Lame help?
Lame offers embedding this missing information in a hidden MP3 frame. But to get this information you have to load the MP3 as a flash.utils.ByteArray, parse all MP3 frame-headers (no decoding necessary) and do some calculations. But afterward you need to load it again into a flash.media.Sound object to make use of the extract method. So better store this information of each loop in your database or simply somewhere in the Actionscript.

Solution
Once you have access to the original amount of samples you can use the Flash10 Playback API to playback your loop and wrap it seamlessly on low-level. You only have to take the encoder delay into account. If you extract from the very beginning you read nothing else than zero amplitudes. Check out the magic-number provided in the source code. This number works perfectly with Lame encoded MP3. This number is not Magic of course. It simply combines the amount of samples LAME added in front of the MP3 – plus – the amount of samples the Flash decoder thinks it were added. You can actually loop waveforms now.

Example

Get Adobe Flash player

Track Void Panic | wav | mp3 | 124417 samples | Sourcecode

Still to complicated?
Vote for native Vorbis support!

61 thoughts on “Playback MP3-Loop (gapless)”

  1. I prefer to import wav’s in IDE fla’s, for the looping and also because if you start with good quality wav’s you can easily tune the audio compression (in bulk).

  2. About a year ago, I found a little command-line tool to convert wav and mp3 into seamless mp3. Worked fine for loading from outside the swf.

    So I am a little bit confused because you said that gapless looping only works since FP10

  3. Thanks for sharing this. It is helpful for something I’m working on. So that explains the 0’s at the start of the mp3 data…funny that they put the gap at the start of the track, not the end.

  4. @Bart:

    Can you show us your project? I am positive, that it won’t work as described in the article. A Flash timer won’t produce sample-exact results.

    However the mass of websites with badly looped MP3s show me, that it might not be a problem for everyone. No offense ;)

  5. Hi and thanks for sharing this !
    I am trying to apply it to my own loops but I don’t know how to find the samplesTotal information.
    You say you have to load an mp3 as a ByteArray, parse all MP3 frame-headers and do some calculations. Could you precise what those calculations are ? (or could you precise where this information is to be found in the lame encoded mp3 data ?)

  6. Having trouble with this. If I let Flash handle wav to mp3 conversion on compile, then I’m left with no idea what the values should be for totalSamples and MAGIC_DELAY.

    Other than that, works like a charm! Almost!

  7. Ceased to have trouble with this! If Flash is handling wav to mp3 conversion for you, then the totalSamples will be 44100 * mp3.length / 1000. (44100 samples per second, and a sound length is measured in milliseconds).

    MAGIC_DELAY = 0!

  8. @Tony

    Sorry to disappoint you, but you are totally wrong. You formula returns the number of samples of all MP3 frames, which is NOT the exact number of samples of the original audio. This information is still lost.

  9. Pingback: Timewave Games
  10. Hey Andre,

    Great write up. I have two questions:

    1. Is the MAGIC_DELAY supposed to work for all LAME encoded mp3s? Or does it require some understanding or trial and error to get the MAGIC_DELAY if you do not know how it was encoded?

    2. I tried this with different loops with different lengths, such as in the case of a mixer, the loops fall out of sync over time. Is this normal?

    Thanks,
    Kenny

  11. Andre,

    I understand the logic, but Tony and mc appear to be correct! I figured this out a few months ago using the same formula as Tony and it works perfectly. When I run a trace statement on the result, and compare it to the total samples in my audio editor, it gives a figure accurate down to the sample! And it works with mp3s of different length too. Occasionally it might be off by a few samples (1-4), but I think this is due to internal rounding error in Flash than anything else! Any inaccuracies are so small they are virtually inaudible!

    I don`t know what to make of it, but it does work!

  12. im currently working on a project that takes an mp3 encrypted stream decrypts it and plays. The problem i face is that i break up the stream into 200kb chunks.The chunks play fine only problem is that when i jump from one to the next there a small delay of a 100msec which i cant solve. Any ideas would be appreciated
    thanks man

  13. Hey Andre,

    Does lame need any special options to embed the hidden MP3 frame? I gave it a test and the mp3 that lame generated isn’t playing gapless for me, rather it tries to loop early.

    I didnt update samplesTotal yet, I’m not sure where to get that value.
    How do I find out samplesTotal for a given track?

    Here’s what I see with lame: http://pastebin.com/raw.php?i=fxYSAZKe

    Thanks for this solution and your time!

    -A

  14. Hi Ahdre,
    Can i combine two mp3’s ByteArray in one mp3, i want to combine two mp3 as one.
    Thank you very much!

  15. This works great except for one thing. It has to load the file completely before it starts to play. When I tried to make it play before the file was completely downloaded, it was just silent. Is there any way to make it start playing the mp3 as soon as it starts to download?

  16. Interestingly enough the tiny gap is removed if the file is encoded in 320kbit, so it has to have something to do with the bitrate…

  17. 160kbit has a ultra tiny gap that you probably wont notice unless you are an audiophile. 192kbit and higher seems perfect. (my loop is 53 seconds)

  18. also for those interested, Tony is not far off (Sorry Andre)..

    Try this, I find it works for all my 192kbps LAME 3.98.4 encoded files…

    samplesTotal = ((44100 * mp3.length / 1000) – MAGIC_DELAY – 119);

  19. Hi Andre,

    First I wanted to let you know your blog and utilities have helped me learn a tremendous amount about audio in Flash, so thank you for sharing your knowledge.

    The utility that Bart Wttewaall mentioned in comment #8 seemed to work well for our Global Game Jam project. I was the composer/audio programmer on the team so I was looking for a way to make the music interactive but also space efficient.

    My question for you is: have you come across any serious limits using the extract() method when looping audio in Flash 10? I want to write my own little adaptive music system for future games, but I’m concerned about hogging a bunch of memory or pushing Flash’s audio engine too far.

    Here’s our game with looped music stems that swap in/out as the level changes (but not without it’s quirks . . .):
    http://www.bartnett.com/games/reverser

    Thanks again!

  20. Andre,

    In response to my post above – I overlooked this part of your post: “FlashIDE encoded MP3 loop gap-less, cause the SWF contains the original amount.” All of my mp3 files are embedded in an SWF file, so that is why I`m getting accurate results on their length.

    Sorry about that!

  21. Also, I`m trying to streamline my audio application.

    Isn`t there a way to extract the entire mp3 file into a byteArray when it is first loaded into the SWF at runtime? Maybe I`m wrong, but it just seems to me that using the extract() method to decode the mp3 file each time you loop through it would waste valuable processor cycles. Wouldn`t it be more efficient if it was already converted into raw PCM data (with leading/trailing silence trimmed) which is then stored into one long byteArray. Then you just need to loop through the byteArray with readFloat() each time to fill the sound buffer. It would surely take up more memory, but wouldn`t it also be faster?

  22. Sure, that would be much faster, but considering memory brings you back to extract block-wise.

    Say 1 Minute of Audio will be encoded to:
    60*44100*2*4 (seconds*sampleRate*stereo*float)
    =
    21168000 bytes ~= 20671k ~= 20m

  23. Hi Andre,

    Can you go into a little more detail on how you obtained the Magic number? The current magic number gave an almost perfect loop, but still discernible if the user happened to turn up his speakers.

    Would the magic number change if I used a newer lame encoder? (3.98.3 to be exact)

  24. That’s a nice player. I’m still having the same issue that I commented about earlier. I can’t seem to figure out how to make the song start playing before it finishes loading. When you’ve got a 5-8 MB song, that’s something that’s important.

  25. Hi Alex, that could be a simple fix, but knowing flash it won’t be. Next time I install Flash 10 I will play around with it, dunno when that will be though.

    Ifyou are using my source check out the parts that check if the file is completely loaded or not, right now by design it doesn’t play until its finished loading intentionally, but you should be able to let it play when it loads say .. 1 megabyte.

  26. Thanks so much! This article helped me a great deal. I altered the code so that you don’t need to predetermine the total samples and also to use writeBytes instead of a looping writeFloat.

    Although I find the front offset does vary, depending on bitrate and sampling rate, once I get a properly encoded mp3, it works fantastic… on my PC.

    However, the thing is, even though a compiled swf works well when testing on my laptop, I cannot get the same gapless loop to work on my PlayBook! I have yet to figure it out, but needless to say, it is causing me a bit of a headache.

    I suspect that for some reason there are some extra 0’s at the end of the sample when loaded. Could be wrong, but adjusting the initial offset isn’t working on the PlayBook.

  27. Thought I’d follow up in case there are any other devs working with sound on the PlayBook Air SDK who stumble across this post.

    It took me way too long to figure out, but on the PlayBook (which seems to be running Flash 10.2.132.0), sound.extract doesn’t work totally as it should.

    According to the docs, the 3rd parm on the extract method specifies the sample location from where to start the extract. This works nicely on Flash on my laptop, but on the PlayBook, a third parameter of any value seems to sometimes read from the beginning of the file :/

    Could be more that I’m missing, but for now I’m satisfied with avoiding using the location parameter and instead doing some extra extract calls to skip unwanted samples.

  28. Hi There, I’m pretty bad at Flash, so I’m probably having a really obvious problem, but I can’t get this to work.

    I get this,

    TypeError: Error #1009: Cannot access a property or method of a null object reference.
    at MP3Loop/initUI()
    at MP3Loop()
    at loop_test_fla::MainTimeline/frame1()

    and the code in my main file looks like this:

    var looper = new MP3Loop();

    Any help would be much appreciated!

Comments are closed.