Tutorial details:
Written by: Frank P. Baumgartner | Site
Time: 25 minutes
Difficulty Level: Advanced
Requirements: Flash MX
Topics Covered: Creating flawless sound timing in Flash MX.
Assumed knowledge: Plenty.

What you can expect:

Some nice technical informations about flash 6 sound internals, especially useful for skilled and experienced flashers.

What you cannot expect:

This is not a beginner's tutorial. This is nothing cool you can easily copy and put on your homepage.

Differences between flash 5 and flash MX sound engine:

As the experienced reader knows, the Sound() object already has been available since flash 5, which allows you to attach sounds from the library, set Volume and play the sound (with or without looping).

It was also possible to specify the sample start position in seconds, more or less sample-exact. (eg. snd.start( 0.001, 1 ); )

F lash 6 extends the existing sound object with further methods:

loadSound( "sound.mp3", streaming );
// streaming being a boolean, true or false

as well as onSoundComplete callback, which is triggered as soon as
the sound has finished playing. E.g.:

m = "sound1";
createEmptyMovieClip( m, 1 );
s = new Sound( eval(m) );
s.attachSound( "libsound1" );
s.onSoundComplete = function() {
        trace( "sound completed." );
}
s.start( 0, 1 ); // offset: 0, loops: 1

Now, this seemed very nice on first sight, so what if you try to play a hihat, and as soon as it's finished start it again. - will the two sounds be played seeminglessly after each other, that is, will the first sample of the second sound start immediatly after the last sample of the first sound played? The answer is: no, it won't. :-(

Fact: onSoundComplete alone cannot be used to do sample-exact timings!!!

What's the reason for this?

Well, internally flash uses a sound buffer for mixing several channels (up to 8). i guess it is approx. 2048 samples. in other words, when playing at 44100 kHz, all onSoundComplete events will be aligned to 2048*1000/44100 = 46.4399093 milliseconds! [this applies both to windows 2000 and windows XP which i've used for testing]

here is an ASCII graphics example:

111.. = sound1, a sound of 55 msec length
 2222... = sound2, a sound of 69 msec length


 0 46.4 92.9 139.3 185.8 msec
 | | | | |
 ----------------------------------------> time axis
 11111111111

    222222222222

   ^---- sound1.onSoundComplete will be triggered here!!!

as you can see, there will be a GAP between sound1 and sound2 because
of the 46.43.. msec alignment!!!!

How to overcome the gaps?

Well, as i've mentioned above, it's possible to trigger a sound with an offset, eg: start( offset, loopcount ) while offset is in seconds, it is a float number, ie: you can use the fractional part for gaining precision up to sample-precision. if you zero-prefix every sound with, say, 100 msec of silence, you can do sample-exact syncing!! anyways, you still have the problem that the first sound already stopped when you got the onSoundComplete event

Introducing a sync-sound:

As i've mentioned above, every onSoundComplete is aligned to 46.43... msec borders. Now, what if you play a sound consisting of one single sample? See here:

3 = sound3, a sound with just one sample of silence


 0 46.4 92.9 139.3 185.8 msec
 | | | | |
 ----------------------------------------> time axis
 3

  ^---- sound3.onSoundComplete will be triggered here!!!

As expected? well, if you continously play the sound over and over again, it will give you a constant timing mechanism running at 46.43.... Hertz! - this is great!

Every time you get a callback, you know that it is a multiple of 46.43... milliseconds! - now you can use this and calculate a proper sample offset for triggering sounds 1 and 2:

zzz111.. = sound1, a sound of 55 msec length,
    prefixed with 100msec of silence
 zzz2222... = sound2, a sound of 69 msec length,
    prefixed with 100msec of
 3  = sound3, a sync sound with just one sample


 0 46.4 92.9 139.3 185.8 msec
 | | | | |
 ----------------------------------------> time axis
 3 3 3 3 3
 11111111111

   zzz22222222222

  ^---- sound3.onSoundComplete will be triggered here:

   at this point you can exactly *calculate*
   which offset is needed for sound2 so that
   it will continously play after sound1 has
   finished.

At this point you can exactly calculate which offset is needed for sound2 so that it will continously play after sound1 has finished.

Case study: xm2swf (see example, download source)

For everybody not knowing what XM is, XM stands for extended module. Basically, the idea comes from amiga, which is you have several sound-samples which you can play on different notes [the trick is the same as you know it from playing a record faster or slower: the pitch changes!]

The original program for creating XMs was called "Fast Tracker 2", from Triton - a very famous demo-group.

The great thing about XMs (and modules in general) is, that you only needed few samples and could do rich sounds with small file-size, since storing a note is much smaller than storing the sample.

The problem with flash is, that you cannot specify the pitch for replaying a sample. - flash will only play the original tone, not higher nor lower.

So, what i've tried out is writing a small C++ program which reads an XM file, checks what tones are played, create all needed samples for all played tones, and convert all note-data to actionscript format. [sorry, the C++ program is NOT included in this package, it is really just a prototype at the moment!!]

You might say, this is crazy - because one single sample can be played 12 times or even more, BUT you have to remember that flash 6 has mp3/adpcm/zlib compression which do a good job so in the end the SWF will have approx. the same size as the original XM tune.

I have included a famous old 4-channel tune "house.xm" (you can play it with winamp) and the according flash FLA xm-player as well as the SWF.

As you might notice, it needs a fast PC so that flash does correct timing as well as it doesn't play really good, the problem is that flash only supports 8 channels, 1 is needed for the sync, and 2 flash channels are needed per input channel (remember the syncing, for syncing 2 sounds after each other you'll also waste 2 channels!)

Some wishes for the future:

Although this prototype is not really useable commercially, I am sure this can be improved in the future and maybe you got an idea about sound syncing.

I think it would be a good idea for macromedia to natively support XM (or any similar format) playback with flash 7. a nice proposal would be FMOD by firelight [hey firelight, am i getting money for this advertising? ;-) -> http://www.fmod.org/]. - such a player takes not more than 30k of plugin size, but you can do incredible sounds in minimal filesize: for demonstrating this i have included aryx.xm which is only 25kb !!! (please use winamp etc. for playing it!)

If you have found out other freaky flash 6 tricks, feel free to e-mail me!

kind regards,

franky

p.s.: you may always find latest experiments on my website,
www.active-web.cc under "research"