Audio playback delay is not correct
I am writing a simple metronome component using Flex + AS3. I want it to play the "tick1" sound after, for example, every 500 milliseconds, and play a different "tick2" sound every fourth time. But in reality, the delay between sounds is not equivalent - sometimes less, sometimes more. I am testing it on the latest Chrome.
Here's my code:
//Somewhere here button bound to the 'toggle' function
import flash.utils.Timer;
import flash.events.TimerEvent;
import flash.media.SoundTransform;
import flash.media.SoundChannel;
private var bpm:Number = 120; //2 bit per second, delay=500ms
private var period:Number = 4;
private var timer:Timer = new Timer(bpm, period);
[Embed(source='sounds/1.mp3')]
private var tickSound1Class:Class;
private var tickSound1:Sound;
[Embed(source='sounds/2.mp3')]
private var tickSound2Class:Class;
private var tickSound2:Sound;
private var trans:SoundTransform = new SoundTransform(1);
private function init():void {
....
tickSound1 = new tickSound1Class() as Sound;
tickSound2 = new tickSound2Class() as Sound;
update();
timer.addEventListener(TimerEvent.TIMER, onTimerEvent);
....
}
private function update():void {
timer.delay = 1000 * 60/bpm;
timer.repeatCount = 0;
}
private function toggle():void {
if (timer.running) {
timer.reset();
startStopButton.label = "Start";
} else {
update();
timer.start();
startStopButton.label = "Stop";
}
}
private function onTimerEvent(event:TimerEvent):void {
var t:Timer = event.currentTarget as Timer;
if (t.currentCount % period == 0)
tickSound1.play(0, 0, trans);
else
tickSound2.play(0, 0, trans);
}
source to share
I think there are two main reasons:
- The object
Timer
in Flash Player is known to be imprecise, the delay between them fluctuates. -
Sound.play()
also introduces some delay before the sound starts playing, and in theory this delay can fluctuate. The lag is especially noticeable in the PPAPI version of Flash Player used in Chrome.
There are several solutions. I would suggest one of these:
- Use the pre-assembled sound of the entire metronome loop (tick1-pause1-tick2-pause2) and simply loop it using the second argument of the method
Sound.play()
; - use dynamic sound generation.
The second option is more flexible, but more difficult to implement. Basically you need to create a new instance of the object Sound
, subscribe to it SAMPLE_DATA
and call it play()
. In the handler you check event.position / 44.1
which will give you the current sound generation position in ms. Then, if you decide that the time to play tick1 or tick2 sounds, you name tickN.extract(event.data, ...)
, where tickN
is the tick1 or tick2 object, Sound
or else record the silence.
Learn more about dynamic sound generation here .
Also note that when called, Sound.play()
it returns an object SoundChannel
that has a property position
. This is the position in ms of the sound that is being played (not generated) at the moment, and that's for sure. So using this property you can come up with a third approach: create an object Sound
and set up a handler SAMPLE_DATA
like in the dynamic sound generation solution, but write silence (zeros) to the object event.data
inside the handler all the time. This is necessary to obtain an audio channel without sound reproduction. Then use the maximum frame rate (60 FPS) and Timer
the lowest possible latency (1ms). Every time it fires Timer
checksoundChannel.position
to determine if it's time to play the tick sound, and if so, just play as in your example. This approach will probably solve the inaccuracy problem Timer
, but it cannot handle the delay caused by the method tickSound.play()
.
source to share