Timing Trouble

I’m having an issue timing a function call with the beat on a music track. I’ve calculated the milliseconds per beat to run the function using the following code;

local function beat(event)
print(event.time)
local beatTimer = timer.performWithDelay(mspb, beat)
end

bpm = 105 – beats per minute of the track
mspb = (60000/bpm) – milliseconds per beat

beat()
The music and the function line up nicely at first but slowly lose sync as the program executes. I’ve double and triple checked my audio file and it is a nice steady 105 bpm the whole song and the calculations are correct in theory but the output in the terminal is slightly slower than the music. I was wondering if maybe there is some issue where processor load could slow down the timer built into corona and cause this.

Any help is appreciated on this one. [import]uid: 12747 topic_id: 4629 reply_id: 304629[/import]

Sorry, none of the audio engines in Corona are designed for this kind of timing synchronization. (FYI, Android itself really isn’t setup for high performance audio.) In general, audio plays on another background thread while your Lua loop works mostly on the main thread so there will be drift when switching between threads. Also, the timers that run performWithDelay are not high precision timers to begin with.
[import]uid: 7563 topic_id: 4629 reply_id: 14630[/import]

computer timers are never completely accurate which is why most music programs use an audio buffer that presumably talks to the soundcard on a hardware interrupt.

Flash 10 does this now
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/events/SampleDataEvent.html

writing audio bytes directly into the Sound buffer…

[as3]
//The following example plays a simple sine wave.

var BUFFER_SIZE:uint = 8192;
var mySound:Sound = new Sound();
function sineWaveGenerator(event:SampleDataEvent):void {
for ( var c:int=0; c<buffer_size c> event.data.writeFloat(Math.sin((Number(c+event.position)/Math.PI/2))*0.25);
event.data.writeFloat(Math.sin((Number(c+event.position)/Math.PI/2))*0.25);
}
}

mySound.addEventListener(SampleDataEvent.SAMPLE_DATA,sineWaveGenerator);
mySound.play();
[/as3]

This principle is used to sequence/mix audio tracks together into a single continual buffer stream.

The event is not directly synced to a timer so you have to do some calculations to adjust the visuals against the latency. Also whilst the audio is mostly solid you can get some CPU glitches, so you have to re-sync your visuals accordingly. (Essentially the event only fires every time the buffer has been through the sound device and is ready to take more bytes… you can’t guarantee the specific timing of this but theoretically every tick would be approximately SAMPLE_RATE / BUFFER_SIZE in seconds I think)

I think this principle is also used in most iOS audio apps but I think it will be a long time until we see this in Corona!

I suggest if you want to make an audio app you get down and dirty with XCode and the NUI libraries (as used by Intua Beatmaker etc) but it doesn’t come for free

http://www.libnui.net/

(note I have no links with NUI/Intua and can’t really give you any expert advice on the topic, sorry)

regards
J [import]uid: 6645 topic_id: 4629 reply_id: 14811[/import] </buffer_size>

Oh. Well that answers my thread then: http://developer.anscamobile.com/forum/2011/02/22/syncing-music-and-animation

Where did I put the drawing board? [import]uid: 26769 topic_id: 4629 reply_id: 24507[/import]

i’d avoid drawing applications too for now :wink: [import]uid: 6645 topic_id: 4629 reply_id: 24533[/import]

That’s enough out of you!

:slight_smile: [import]uid: 26769 topic_id: 4629 reply_id: 24537[/import]

You are just going about this the wrong way.

You can still accomplish pretty much what you want and be off no more than 16ms of the mark for any given beat–BUT–the most important thing is that the target to hit will not drift so over time, every beat will be triggered when it should, with an error of a maximum of 16 milliseconds from the ideal target-- some desktop soundcards have way higher latency than this… so really-- don’t give up at all!

What you need to do is store the original system ticks at the music start time, then keep polling on each enterframe event (at 60fps of course) to see if you passed the next calcuated beat. if you have, then calculate the system time of the next beat and keep going.

And secondly, a hypothetical-

even if the performWithDelay timer was perfect, your recursive call within beat() is confusing. Even if it was perfect you should just have am infinitely looping performwithdelay outside of the beat function (e.g. timer.performWithDelay(yourBPM, beat, 0)) that calls beat. The reason is that there would still be a very small amount of processing time inserted before the timer is called again while the code in the function is being processed… the insertion of this time on each iteration would keep pushing the timing error farther and farther out. As it is, there is probably processing time insertion issues between loops with the looping mode of performWithDelay, so just use the method of checking the system ticks against the system ticks value for the next calculated beat, OK?? OK!!

As a test a while back I had a perfectly-spliced wave file (e.g. setting it to loop would produce the exact number of samples to maintain even tempo) audio loop going going, and using the method of checking the system ticks against the system ticks value for the next calculated beat, I had a bouncing character (using ease quad transition to) going in perfect sync with the music for a long long time and it never went out of sync for as long as I was patient enough to watch.

See my reply on this thread for more:
https://developer.anscamobile.com/forum/2011/08/15/drum-toy-0
Cheers.

Gary [import]uid: 106887 topic_id: 4629 reply_id: 84051[/import]