rumplestiltskin wrote:If the sound doesn't play, the app will be dead-on accurate. Its when the sound actually plays that the problem appears.
Right, but not playing a sound at all has no overhead, and trying to play one that won't load correctly still does. Anyway, try the script below. Replace the entire script of the card in the example stack with this:
Code: Select all
global startMilliseconds, bpmMilliseconds, theTimerIsEnabled, beatsElapsed
local sPlayerID
local sBeatMark -- time of last beat in ms
on openCard
setupPlayer
end openCard
on timerCheck
if not theTimerIsEnabled then exit timerCheck
if beatsElapsed = 0 then put startMilliseconds into sBeatMark
playBeat
put sBeatMark + bpmMilliseconds into tTargetTime
put tTargetTime - the milliseconds into tTimeLeft
send timerCheck to me in tTimeLeft milliseconds
put tTargetTime into sBeatMark
end timerCheck
on playBeat
mobileControlSet sPlayerID,"currenttime","0"
mobileControlDo sPlayerID, "play"
add 1 to beatsElapsed
set the text of field "lblBeatsElapsed" to beatsElapsed
end playBeat
on closeStack
put false into theTimerIsEnabled
if the environment = "mobile" and sPlayerID is in mobileControls() then
mobileControlDelete sPlayerID
end if
end closeStack
on setupPlayer
if the environment <> "mobile" then exit setupPlayer
mobileControlCreate "player"
put the result into sPlayerID
mobileControlSet sPlayerID, "visible", false
mobileControlSet sPlayerID, "filename", specialFolderPath("engine") & "/click.mp3"
end setupPlayer
This uses a native player, which has the advantage of preloading before any counting begins. That eliminates the overhead of retrieving a file from disk several times per second.
The timerCheck handler keeps track of the milliseconds when the last beat occurred, stored in sBeatMark. After it plays the sound, it calculates the target time for the next beat and subtracts the overhead from that to determine when the next "send" command should happen. The timerCheck calcs could be collapsed into a single line of code, but I left it this way for clarity.
The playBeat handler triggers sound playback immediately before doing any field updates, to prevent LC's slower field management from introducing any lag. Once playback begins, there's time to update the field text before the next beat is due.
Finally, I had to replace your click sound with one of my own that is 1 second long. The original audio file was too tiny to trigger any activity in the player. (Mine failed to play once too, but I quit the app and reopened it, and it worked.) The length of the sound file doesn't matter here because the script resets the starting time to zero before each playback, and the audio is already loaded so there's no overhead. You could add a couple of seconds of silence to the end of the sound to ensure it plays (which I would do,) which won't matter because playback will never get that far. I think Android is optimized for mp3, so try that format.
My physical clock runs a little slow so I'm not certain this script is entirely accurate, and once you get up to 3 beats per second it's hard to tell whether you're in synch or not by eyeballing it. But this sounded pretty regular to my ear.