Reputation: 2428
I'm using MIDI.js to play a MIDI file with several musical instruments.
The following things execute too late, how can I fix that?
start()
of an AudioBufferSourceNode
here.setTimeout
here. Their "lateness" is even worse than that of the first notes.When I stop the song and start it again, there are no problems anymore, but the delay
values are very similar. So the delay
values are probably not the cause of the problem.
(I use the latest official branch (named "abcjs") because the "master" branch is older and has more problems with such MIDI files.)
Upvotes: 1
Views: 107
Reputation: 4730
That is how JavaScript Event Loop works.
Calling
setTimeout
... doesn't execute the callback function after the given interval.The execution depends on the number of waiting tasks in the queue.
... the delay is the minimum time required for the runtime to process the request (not a guaranteed time).
https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#zero_delays
Instead of setTimeout()
you can use window.requestAnimationFrame()
and calculate elapsed time for delay by yourself.
Window.requestAnimationFrame() - Web APIs | MDN
The
window.requestAnimationFrame()
method tells the browser that you wish to perform an animation and requests that the browser calls a specified function to update an animation before the next repaint. The method takes a callback as an argument to be invoked before the repaint.... will request that your animation function be called before the browser performs the next repaint. The number of callbacks is usually 60 times per second, but will generally match the display refresh rate in most web browsers
https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
performance.now() - Web APIs | MDN https://developer.mozilla.org/en-US/docs/Web/API/Performance/now
In our situation, we don't want to do any animation but want to just use it for a better-precision timeout.
const delayMs = 1000;
const startTime = performance.now();
function delay(func) {
const delayStartTime = performance.now();
function delayStep() {
// Run again if still elapsed time is less than a delay
if (performance.now() - delayStartTime <= delayMs) {
window.requestAnimationFrame(delayStep);
}
else
{
// Run the delayed function
func();
}
}
// Run first time
window.requestAnimationFrame(delayStep);
}
// Trying `setTimeout()`
setTimeout(() => doSomeJob('setTimeout()'), delayMs);
// Trying `delay()`
delay(() => doSomeJob('delay()'));
// Function that we'd like to run with a delay
function doSomeJob(marker)
{
const elapsedTime = performance.now() - startTime;
console.log(`${marker}: Ran after ${elapsedTime / 1000} seconds`);
}
If you run it many times, you'll see that delay()
is pretty much all the time better than setTimeout()
. The difference is very small because there is nothing else happens on the page. If there will be something intensive running, setTimeout()
should demonstrate worse "precision".
Upvotes: 0