Playback MP3-Loop (gapless)

April 9th, 2010

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!

Filed under: +

61 Responses to “Playback MP3-Loop (gapless)”

  1. Jeff Says:
    April 9th, 2010 at 8:22 pm

    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. phihochzwei Says:
    April 9th, 2010 at 10:36 pm

    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. Andre Michelle Says:
    April 9th, 2010 at 10:39 pm

    @phihochzwei Any chance to recall, what it was? Are you sure it exported MP3, not SWF?

  4. phihochzwei Says:
    April 9th, 2010 at 10:42 pm

    Sure, still got it at home. Will give it to you next week in cologne :)

  5. Andre Michelle Says:
    April 9th, 2010 at 10:44 pm

    @phihochzwei Alright. Sounds perfect ;)

  6. shawn Says:
    April 9th, 2010 at 10:59 pm

    I’ve been waiting for someone to go over this for a looooooong time. thank you so much!

  7. David Wilhelm Says:
    April 10th, 2010 at 5:26 am

    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.

  8. Bart Wttewaall Says:
    April 10th, 2010 at 12:27 pm

    Here’s a tool I used for looping mp3’s seamlessly in a track mixer project. It works great, and there’s no need for extracting the sound or anything, just load and play (repeatedly).

    http://www.compuphase.com/mp3/mp3loops.htm

  9. Andre Michelle Says:
    April 10th, 2010 at 12:34 pm

    @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 ;)

  10. mc Says:
    April 11th, 2010 at 12:18 pm

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

  11. Weekly Digest for April 11th — Hello. My name is Václav Vančura. Says:
    April 11th, 2010 at 7:36 pm

    [...] Shared Playback MP3-Loop (gapless). [...]

  12. Tony Says:
    April 15th, 2010 at 9:02 pm

    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!

  13. Tony Says:
    April 16th, 2010 at 2:39 am

    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!

  14. mc Says:
    April 16th, 2010 at 8:19 am

    Yes ! Groovy !
    Works wonderfully !

    Thank you Tony !

  15. Andre Michelle Says:
    April 16th, 2010 at 8:59 am

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

  16. Timewave Games Says:
    April 22nd, 2010 at 3:10 am

    [...] is pretty effective. There is one loop that is impossible for me to achieve though. I’ll try Andre Michelle’s trick on that one (the most important one, the ingame music. All the other files were not even loops. I [...]

  17. Weekly Digest for May 2nd — Hello. My name is Václav Vančura. Says:
    May 2nd, 2010 at 7:29 pm

    [...] Andre Michelle » Blog Archive » Playback MP3-Loop (gapless) [...]

  18. Kenny Says:
    May 19th, 2010 at 5:54 pm

    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

  19. Matt Says:
    June 4th, 2010 at 4:45 pm

    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!

  20. swinburn Says:
    June 14th, 2010 at 3:30 pm

    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

  21. mp3-pitch und loop - Flashforum Says:
    June 16th, 2010 at 3:11 pm

    [...] [...]

  22. Andre Michelle Says:
    June 23rd, 2010 at 4:56 pm

    @Matt
    “I understand the logic, but Tony and mc appear to be correct!”

    That doesn’t make sense at all ;)

  23. Aaron Says:
    July 20th, 2010 at 6:53 pm

    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

  24. yueveron Says:
    July 26th, 2010 at 9:02 am

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

  25. Alex Says:
    September 6th, 2010 at 5:03 am

    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?

  26. zefie Says:
    September 12th, 2010 at 8:09 pm

    Works great :) I modified it quite heavily. It works well for short loops like the one you demonstrate here and some of my local tests, however for longer loops i still get a tiny pop. Barely noticeable but not gapless like the short files. Any idea why that is?

    See what I mean here:
    http://blog.midnightchannel.net/2010/09/kgindom-hearts-birth-by-sleep.html

  27. zefie Says:
    September 12th, 2010 at 9:09 pm

    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…

  28. zefie Says:
    September 12th, 2010 at 9:19 pm

    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)

  29. zefie Says:
    September 14th, 2010 at 10:19 am

    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);

  30. Michael Bartnett Says:
    October 4th, 2010 at 5:51 am

    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!

  31. Matt Says:
    October 27th, 2010 at 11:34 am

    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!

  32. Andre Michelle Says:
    October 27th, 2010 at 11:41 am

    @Matt: No problem. Thanks for the correction.

  33. Matt Says:
    October 27th, 2010 at 12:05 pm

    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?

  34. Andre Michelle Says:
    October 27th, 2010 at 12:57 pm

    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

  35. Matt Says:
    November 2nd, 2010 at 11:02 am

    Yeah, I see the problem with that!

    Thanks Andre!

  36. 【見た聞いた】FITC Tokyo 2010, Andre Michelle「拍動性クラックル」Pulsatile Crackle | イナヅマtvログ Says:
    December 6th, 2010 at 9:12 am

    [...] MP3-Loop (gapless) : http://blog.andre-michelle.com/2010/playback-mp3-loop-gapless/ [...]

  37. Seamless looping of dynamically loaded mp3 files Says:
    December 7th, 2010 at 10:48 pm

    [...] http://blog.andre-michelle.com/2010/playback-mp3-loop-gapless/ [...]

  38. Sound.length genau 1 ms lang? - Flashforum Says:
    December 22nd, 2010 at 2:31 pm

    [...] [...]

  39. Cardin Says:
    December 24th, 2010 at 10:07 am

    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)

  40. zefie Says:
    February 3rd, 2011 at 1:25 pm

    Here is my final version of my highly modified version of your loop script :)

    Highly appreciated, I have now released the source code as well :)

    Hope my tweaks come in handy to someone.

    http://files.mysticmidnight.net/zefie/media/Audio/Loops/MP3/

  41. Zefie Says:
    February 21st, 2011 at 1:50 am

    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.

  42. Alex Says:
    February 22nd, 2011 at 2:12 am

    Why the heck did my above post say Zefie? I don’t remember entering that name.

  43. Daniel Iregui Says:
    March 7th, 2011 at 2:07 am

    Hi,

    Can someone tell me why do you use the enabled property instead of playing and stopping the ‘out’ sound?

    Thanks!
    Daniel.

  44. zefie Says:
    March 16th, 2011 at 3:34 am

    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.

  45. Wave Datei apsielen - Flashforum Says:
    March 30th, 2011 at 9:56 pm

    [...] [...]

  46. Jamie Says:
    May 12th, 2011 at 6:39 pm

    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.

  47. Jamie Says:
    May 14th, 2011 at 4:39 am

    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.

  48. Sound totalLength - Flashforum Says:
    May 25th, 2011 at 11:23 am

    [...] [...]

  49. Jimmy Says:
    June 12th, 2011 at 9:44 pm

    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!

  50. [tonfall] MP3 loopen - Flashforum Says:
    June 27th, 2011 at 4:29 pm

    [...] [...]

  51. ariel sommeria (ariel) | Pearltrees Says:
    January 17th, 2012 at 1:00 am

    [...] Btw: FlashIDE encoded MP3 loop gap-less, cause the SWF contains the original amount. Does Lame help? 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. Andre Michelle » Blog Archive » Playback MP3-Loop (gapless) [...]

  52. Looping Audio in Flash « Ariel Sommeria .com Says:
    January 18th, 2012 at 5:13 pm

    [...] you need perfect, then this post has a pretty good method for it. http://blog.andre-michelle.com/2010/playback-mp3-loop-gapless/ It however does not tell you how to get one necessary information: the number of samples in your [...]

  53. Looping Audio in Flash, part 2 « Ariel Sommeria .com Says:
    February 1st, 2012 at 5:17 pm

    [...] continued building on http://blog.andre-michelle.com/2010/playback-mp3-loop-gapless/ . And I’ve come up with some [...]

  54. ariel sommeria Says:
    February 1st, 2012 at 5:31 pm

    Hi, I found a bug that was harmless in a browser but that failed on an iphone:
    Your extract method sometimes reads beyond the buffer because it doesn’t properly take MAGIC_DELAY into account. Code here:
    http://arielsommeria.com/blog/2012/02/01/looping-audio-in-flash-part-2/
    And thanks!

  55. Andre Michelle Says:
    February 2nd, 2012 at 10:42 am

    @ariel: This is not a bug.
    MAGIC_DELAY is found by comparison of the original WAV and the Flashplayer(!) decoded audio.
    However the Flash Sound API on other platforms has different properties since the native environment and most likely the underlying code has nothing in common. You have to find another “MAGIC_DELAY”.

  56. Michael Bartnett · Game audio: programming, music, and sound design Says:
    February 26th, 2012 at 7:15 pm

    [...] mp3 files and loops them using sampleDataCallback (and the magic LAME mp3 encoder delay number that Andre Michelle figured out). Thanks to this, I can guarantee the position of a music loop within 2048 frames of audio. it [...]

  57. judah Says:
    March 30th, 2012 at 10:59 pm

    Is it possible to get the total sample length at runtime?

  58. judah Says:
    March 30th, 2012 at 11:12 pm

    Another question, if MP3 files pose a problem, then can we extract out the raw sound data (at runtime) and play and loop that? Could we then save that raw data to file and load it in in the future instead of the MP3?

  59. judah Says:
    April 4th, 2012 at 8:24 am

    I think AS3SWF can get the samples from an MP3 via the tags meta data.

    tagDefineSound = TagDefineSound.createWithMP3(1, mp3);

    trace(tagDefineSound.toString())

    [14:DefineSound] SoundID: 1, Format: MP3, Rate: 44kHz, Size: 16bit, Type: stereo, Samples: 428544

    It also seems it can show the type (stereo or mono), rate, size and format. Thank you Claus.

    AS3SWF
    https://github.com/claus/as3swf/wiki/Play-MP3-directly-from-ByteArray

    PS I’ve read it may be possible to do correctly looping when the cue points are embedded in SWF (via Flash IDE). If AS3SWF is able to embed a mp3 at runtime it may be possible to embed the cue times with it (and thus have gapless loops with standard Sound class???).

  60. Chad Upton Says:
    April 9th, 2012 at 3:58 am

    IT WORKED!!! Bart’s solution worked perfectly for me. I used the tool he suggested to convert my WAV files into MP3s and then it worked. The MP3s I generated from garageband did not work, even though I exported a clip that did not have any gaps. So, use this tool (as Bart suggested) and make sure you install LAME and add lame’s install folder to your windows PATH variable list for the easiest time with the GUI version. Here are all the URLs you’ll need to do these things:

    http://www.compuphase.com/mp3/mp3loops.htm

    http://lame1.buanzo.com.ar/#lamewindl

    http://geekswithblogs.net/renso/archive/2009/10/21/how-to-set-the-windows-path-in-windows-7.aspx

  61. mike gieson Says:
    June 16th, 2012 at 1:43 am

    Another cheesy solution is to “trim” the bytearray. (similar to classic string “trim” a-la PHP).

    The trick is to check if target.readFloat() is > +/-0.0099

    I’m guessing that when you read the bytes for the id3v1 + id3v2 and any other non-sample data, the “readFloat” value is way out of range.

    From purely analyzing the readFloat values, it looks like a lot of 0’s and values like +/-0.000091552734375

    But sample data is generally +/-0.0900.

    Again, this is rather cheesy, but seems to work ok without having to load WAVs or embed in SWFs, external MP3s are nice.

    Here’s some psuedo code to “trim”:

    // collect = a vector containing readFloat()’s from the mp3 extracted to bytearray
    var b:int = 0;
    var f:int = 0;
    var runaway:int = 0;
    var runawayLen:int = collect.length;

    while (!found){

    if( inrange(collect[f]) ){
    foundFront = true;
    }
    if( inrange(collect[b]) ){
    foundBack = true;
    }

    if(foundFront == false){
    countFront++;
    }
    if(foundBack == false){
    countBack++;
    }

    if((foundFront && foundBack) || runaway > runawayLen){
    found = true;
    }
    b–;
    f++;
    runaway++;
    }

    function inrange(theVal):Boolean{
    if( theVal > 0){
    if(theVal > 0.0099){
    return true;
    } else {
    return false;
    }
    } else if( theVal < 0){
    if(theVal < -0.0099){
    return true;
    } else {
    return false;
    }
    } else {
    return false;
    }
    }

    var rawSampleData:Array = []; // probably want to use a vector.
    var count:int = 0;
    for(var i=countFront;i<collected.length-countBack; i++){
    rawSampleData[count] = collected[i];
    count++
    }