2

I'm trying to play gapless loop using mp3 file. I've read some tutorials and learned there is something like encoder / decored delay and it is possible to fill that gap with proper music data using some tools. It even worked fine, but when I'm trying to play that music in flash (haxe nme actually, but I don't think it makes any difference), I discovered there is another delay, which is, I guess, made by flash.media.Sound (well, found some comments like this in the code in the internet actually).

Sooo, because I sometimes need to play sounds faster or slower, this is how my player works:

  • extraced bytes are stored in bytes var
  • speed is a float var, default = 1
  • there is position var indicating actual position in bytes (bytes are shared by many sound objects so I can't easily use bytes.position)
  • when I'm playing the sound, in the SampleDataEvent handler, I set bytes.position = position * 8, then read and play two floats and updating position += speed

If I'm doing something wrong already - please, point it out.

To get rid of that delay, I tried to:

  • add DELAY var
  • hardcode the length of sound read from audacity (it looks like flash.media.Sound object.length is not accurate, I'm not sure but I guess because of mp3 decoder/encoder delays)
  • make an assumption each sound is 128kbps, 44.1kHz
  • set DELAY so flash.media.Sound object bytesTotal - DELAY = hardcoded length * 128kbps

It playes, but:

  1. mp3 from audacity still has annoying gap.
  2. mp3 from the tool filling mp3 gap (from here http://www.compuphase.com/mp3/mp3loops.htm) is playing better, but sound at the beginning is not clear.
  3. I can hear annoying "click" noise after stopping sound.

Can you help me understanding why (1) happens and how to solve all 3 problems?

Douglas B. Staple
  • 10,510
  • 8
  • 31
  • 58
synek317
  • 749
  • 2
  • 7
  • 22

1 Answers1

0

Gapless loops are annoying to accomplish with Flash, but it is possible.

  • Trying to produce a gapless MP3 is difficult, so avoid the problem entirely! Import your loop as a WAV file, instead of an MP3. WAVs do not have any encoder delay, and can easily produce a seamless loop.

  • Use the loops parameter of Sound.play() to loop the sound. If the audio is truly gapless, this should produce a seamless loop. Do not try use the SOUND_COMPLETE event to loop the sound. This event is tied to the frame rate and fires shortly after the sound completes, and not when it actually does, so it will always cause a gap.

  • Sound.length is not usually accurate, as you have noticed. If you really need an accurate sample count, use Sound.extract() to extract the complete audio data. It returns the number of samples extracted.

  • If for some reason you are not able to embed a gapless WAV file, or you have to stream the MP3 from an external location, you can do this:

    1. Use Sound.extract() to extract the audio data.
    2. Chop off the silent sections at the beginning and end of the data. You could do this with some hard-coded values, or be smart and chop off by comparing the samples to some small epsilon.
    3. Reload the cropped data into a sound clip by using Sound.loadPCMFromByteArray().
    4. Now the cropped clip can be looped with Sound.play and the loops parameter.

Here's some sample code:

// load our original audio track and extract the samples
var music:Sound = new Music();
var data:ByteArray = new ByteArray();
music.extract(data,99999999);
data.position = 0;

// crop off silent samples from the beginning
// TODO: you should also do this for the end
var i:uint = 0;
const EPSILON:Number = 0.05;
var sample:Number = 0;
// step forward until the magnitude exceeds some threshold
while(data.bytesAvailable > 8 && sample < EPSILON) {
    sample = Math.abs(data.readFloat()) + Math.abs(data.readFloat());
}

// now, data.position is the location where the audio first kicks in
// let's crop it
var croppedData:ByteArray = new ByteArray();
croppedData.writeBytes(data, data.position);
croppedData.position = 0;

// load our newly cropped data and loop
music = new Sound();
music.loadPCMFromByteArray(croppedData, croppedData.length / 8);
music.play(0,99999);

This is much easier and less processor-intensive than trying to skip the gap in real-time with SampleDataEvent. If you really need dynamic playback using SampleDataEvent, then this method is still helpful to give you a nice chunk of audio data without the gaps.

Note that the above applies only to Flash. Since you are using NME, this adds an additional layer of wrinkles, because NME may fiddle with the sounds during packaging, and the above functionality is not available on all targets. However, if you are only publishing for the Flash target, these techniques should still work.

Mike Welsh
  • 1,549
  • 1
  • 9
  • 14