Download Text + Code
-------------------------------------------------------------

**** advanced flash MX sound timing ****
      case study: xm2swf prototype

(c) 2002 by Frank P. Baumgartner

e-mail: baumgartner@active-web.cc || franky@scene.at
http  : www.active-web.cc

-------------------------------------------------------------


NEW:  you may find a KOREAN version at
      http://www.flashmx-coders.com/advancedtech/baumgartner/tech1.html

      thanks to Shinhee Kim (shinhee@shinstudio.com) for the translation!





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


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

	eg.:	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. :-(

facit: 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.



* case study: xm2swf
-------------------------------------------------------------
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
for example 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"