Wednesday, January 12, 2011

Project Jumper Part 8: This isn't stomp at all! Fixing the delay in embedded sounds

I fully intended to spend this update on giving helmutguy a more robust set of physics, including the ability to stomp on Skel-Monsta's head. Instead, that stupid delay at the beginning of embedded mp3s has been driving me crazy, so I'm going to fix it once and for all. While we're at it, we'll get our first taste of editing the actual flixel files to suit our needs.

The key to this problem was actually embarrassingly simple. First, I looked at the FlxG.play() function. In FlashDevelop, you can put the cursor on a function and hit F4, and it will take you that function's code, opening up its class if needed.

So that took me to FlxSound.play(). It turns out that all FlxG.play does is create an FlxSound object and play it. Simple enough. Looking more closely at FlxSound.play(), the important that it does is call Sound.play(), the actual Flash command. No problem, F4 will show us that, too. And here's where it gets interesting: Even though it only displays the function header (since the actual code isn't in actionscript), we can figure out what we need to do. Take a look:

/**
         Generates a new SoundChannel object to play back the sound. This method
     returns a SoundChannel object, which you access to stop the sound and to monitor volume. 
     (To control the volume, panning, and balance, access the SoundTransform object assigned
     to the sound channel.)
     @param    startTime    The initial position in milliseconds at which playback should 
     start.
     
     @param    loops    Defines the number of times a sound loops back to the startTime value
     before the sound channel stops playback. 
     
     @param    sndTransform    The initial SoundTransform object assigned to the sound channel.
     
     
     @returns    A SoundChannel object, which you use to control the sound. 
     This method returns null if you have no sound card 
     or if you run out of available sound channels. The maximum number of 
     sound channels available at once is 32.
         */
        public function play (startTime:Number = 0, loops:int = 0, sndTransform:SoundTransform = null) : SoundChannel;

See that first parameter? startTime is how far to skip ahead in the file when playing it. That's exactly what we want to do.

Now it's just a matter of letting flash know we want to skip ahead. Unfortunately, there are no parameters in the Flixel code to change that value, so we're going to have to change Flixel. Let's work backwards.

How about we change FlxSound.play() so that instead of not taking any parameters at all, it will accept a parameter we'll call Skip, and it will send that parameter on to Flash. We want this new code to change as little as possible, so we'll allow you to call play() without a parameter, and it will just assume 0. That way, if you don't change your program it will still work the same way it used to.

For comparison, this is what FlxSound.play() looks like before:

/**
* Call this function to play the sound.
*/
public function play():void
{
if(_position < 0)
return;
if(_looped)
{
if(_position == 0)
{
if(_channel == null)
_channel = _sound.play(0,9999,_transform);
if(_channel == null)
active = false;
}
else
{
_channel = _sound.play(_position,0,_transform);
if(_channel == null)
active = false;
else
_channel.addEventListener(Event.SOUND_COMPLETE, looped);
}
}
else
{
if(_position == 0)
{
if(_channel == null)
{
_channel = _sound.play(0,0,_transform);
if(_channel == null)
active = false;
else
_channel.addEventListener(Event.SOUND_COMPLETE, stopped);
}
}
else
{
_channel = _sound.play(_position,0,_transform);
if(_channel == null)
active = false;
}
}
playing = (_channel != null);
_position = 0;
}

And here's what it looks like after I muck around with it:


        /**
         * Creates a new sound object from an embedded Class object.
         * 
         * @param    EmbeddedSound    The sound you want to play.
         * @param    Volume           How loud to play it (0 to 1).
         * @param    Looped           Whether or not to loop this sound.
         * @param    Skip             How far, in ms, from the beginning of the file to start playing
         * 
         * @return    A FlxSound object.
         */
        static public function play(EmbeddedSound:Class,Volume:Number=1.0,Looped:Boolean=false,Skip:Number=0):FlxSound //added Skip parameter
        {
            var i:uint = 0;
            var sl:uint = sounds.length;
            while(i < sl)
            {
                if(!(sounds[i] as FlxSound).active)
                    break;
                i++;
            }
            if(sounds[i] == null)
                sounds[i] = new FlxSound();
            var s:FlxSound = sounds[i];
            s.loadEmbedded(EmbeddedSound,Looped);
            s.volume = Volume;
            s.play(Skip);  // Send the Skip parameter on to FlxSound
            return s;
        }
Putting It to Work
Ah, silence, my old nemesis. We meet again.






How do we actually use this change? It won't automagically detect how much silence is at the start of the file, we're going to have to check that manually. Crack open the offending sound file in whatever your sound editor of choice is. I'm using Audacity. Figure out how much silence got put in the file. In my experience, a sound effect made with sfxr and converted to mp3 with Audacity will always have 50ms of silence, but you should always check. Once you know how much you need to cut out, you just have to include that information in the function call. That squiggle over on the left is my jump.mp3 that drove me crazy for so long. With my fancy new function call, I can change the line to:


FlxG.play(sndJump, 1, false,50); // That last 50 is how far to skip ahead.

Since I've started changing the actual flixel files, I'm going to start including the flixel library in the zip files. I was leaving them out to save space, but I'm too lazy to pick and choose only the ones I've actually changed. So now they're all going into the pile.

BTW: This may or may not help with looping MP3s. I haven't tested it yet. At the very least, it shouldn't break anything, but it may not fix the stutter you get at the loop point. I'll give it a try sooner or later, if a reader doesn't beat me to it.

Source here. Not bothering posting a compile this time, since this is all behind the scenes stuff with no real visible change.

2 comments:

  1. super useful ! thanks a bunch.

    ReplyDelete
  2. I thing this issue was solved in the past, because I never had that sound problem....or at least I never noticed :)

    ReplyDelete