我正在开发一个包含序列器的Javascript音乐应用程序。对于那些不熟悉的人,MIDI sequencers的工作原理是这样的:有一种叫做PPQ的东西:pulses per quarter note。每个脉冲被称为“滴答”脉冲()。它描绘了每个季度音符的“细分”,比如分辨率。因此,Sequencer一次一个节拍地“播放”音轨中的事件: Play Tick1、wait Tick Duration、Play tick2、Tick Duration等等。
现在,假设我们有一个带有PPQ=96 (标准)的120的BPM(每分钟跳数)。这意味着每个季度音符持续时间为500ms,每个滴答持续时间为5.20833ms。
我们在Javascript中有哪些计时器替代方案?
1)我们有旧的setTimeOut。它有几个问题:等待时间为4ms。(https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Minimum_delay_and_timeout_nesting)它也会受到抖动/时间变化的影响。它并不精确,而且要求很高,因为回调堆叠在偶数循环中。
2)有一个涉及使用requestAnimationFrame().的setTimeOut/setInterval替代方案它非常精确,CPU效率也很高。但是,可以设置的最小时间约为16.7ms (典型的60FPS监视器中一帧的持续时间)
还有没有别的选择?要精确地每2-5ms安排一次事件?
注意:在循环中完成的函数,playEventsAtTick()一点也不苛刻,所以它不会比Tick Duration花更多的时间来执行。
谢谢!丹尼·布洛
发布于 2018-10-27 00:28:49
为了在做这类事情时保持理智,你需要在一个专门的线程上做音频处理。更好的是,使用Web Audio API,让那些长期考虑这些问题的人来做样本准确性的艰苦工作。
也可以查看Web MIDI (仅限chrome)。
发布于 2018-10-27 00:56:57
感谢nvioli。我知道Web Audio API。然而,我不认为这能在这里有所帮助。我没有直接触发音频:我在曲目中存储了MIDI事件(或者说只是“事件”)。这些事件在任何时刻都会发生。因此,Sequencer需要循环每个节拍持续时间,以扫描在特定节拍播放的内容。
致敬,丹尼·布洛
发布于 2018-10-27 02:15:16
在单独的线程中,例如web worker,您可以创建无限循环。在这个循环中,你需要做的就是计算两个节拍之间的时间。在时间有效后,您可以向主进程发送一条消息,以执行一些视觉效果、播放声音或任何您想要执行的操作。
这是一个Working example
class MyWorker {
constructor() {
// Keeps the loop running
this.run = true
// Beats per minute
this.bpm = 120
// Time last beat was called
this.lastLoopTime = this.milliseconds
}
get milliseconds() {
return new Date().getTime()
}
start() {
while (this.run) {
// Get the current time
let now = this.milliseconds
// Get the elapsed time between now and the last beat
let updateLength = now - this.lastLoopTime
// If not enough time has passed restart from the beginning of the loop
if (updateLength < (1000 * 60) / this.bpm) continue;
// Enough time has passed update the last time
this.lastLoopTime = now
// Do any processing that you would like here
// Send a message back to the main thread
postMessage({ msg: 'beat', time: now })
}
}
}
new MyWorker().start()
接下来,我们可以创建索引页面,它将运行worker,并在每次从worker返回消息时闪现一个正方形。
<!DOCTYPE html>
<html lang="en">
<head>
<script>
// Start the worker
var myWorker = new Worker('worker.js')
// Listen for messages from the worker
myWorker.onmessage = function (e) {
var msg = e.data
switch (msg.msg) {
// If the message is a `beat` message, flash the square
case 'beat':
let div = document.querySelector('div')
div.classList.add('red')
setTimeout(() => div.classList.remove('red'), 100)
break;
}
}
</script>
<style>
div { width: 100px; height: 100px; border: solid 1px; }
.red { background: red; }
</style>
</head>
<body>
<div></div>
</body>
</html>
https://stackoverflow.com/questions/53012768
复制相似问题