我正在尝试用Javascript编写一个简单的音乐序列器。
声音将由SoundManager2
播放我很快意识到setTimeout和setInterval对于这种时序没用。它们的准确性根本不够好。
所以我正在尝试的是:
创建一个声音事件队列(一个二维数组[音符,时间])
在while循环中处理队列
在伪代码中,它可能如下所示:
// The queue of time/note values (millisecs)
var q = [[0, C], [250, D], [500, E]]
var begin = (new Date).getTime()
while(q.length > 0 ){
var now = (new Date).getTime()
var eventTime = q[0][0] + begin
if( now >= eventTime){
playNote(q[0][1]) // Play the note
q.shift() // Now that the note has been played, discard it.
}
}
通过调试我发现这个方法看起来足够精确(playNote调用是在它应该的确切时间进行的。)
然而,当'序列'正在播放时,所有其他Javascript(包括实际发出声音的位)被暂停,这几乎不会让人感到惊讶。
这意味着我已经沉默了很长时间来运行序列,然后执行所有对playNote的调用。
我已经尝试在它自己的函数中隔离while循环,然后通过setTimeout调用它,希望这会创建一个后台线程来执行它自己的事情(即播放序列)而不会停止所有其他JS执行。这不起作用
我也尝试使用setTimeout调用playNote函数,希望尽管UI确实已经冻结,但至少序列正在正常播放,但这也不起作用。
我也试过一个组合,但你可能已经猜到了,这也不起作用。
所以我的问题是:
如何处理具有精确计时的事件队列,而不是在运行期间关闭所有其他代码执行
答案 0 :(得分:1)
我不知道是否有旧浏览器的解决方案,但web workers是用于在JavaScript中进行并行执行。
最新版本的Chrome和Firefox支持它们,我不了解其他浏览器。
答案 1 :(得分:0)
如果用递归函数替换while
循环(它不必自行执行),浏览器就不会冻结。
(function foo (q) {
var now = (new Date).getTime(),
eventTime = q[0][0] + begin;
if (q.length > 0) {
if (now >= eventTime) {
playNote(q[0][1]); // Play the note
q.shift(); // Discard played note
}
return foo(q); // Pass q into the next round
} else {
return; // End the "loop"
}
}(q));
修改:递归调用+定时器准确性问题应由setInterval
处理:
var timer = setInterval(function foo () {
var now = (new Date).getTime(),
eventTime = q[0][0] + begin;
if (q.length > 0) {
if (now >= eventTime) {
playNote(q[0][1]); // Play the note
q.shift(); // Discard played note
}
} else {
clearInterval(timer); // End the "loop"
}
}, 10); // This is in ms (10 is just a random number)