我正在开发包含定序器的 Javascript音乐应用。对于不熟悉的人来说, MIDI音序器的工作原理非常像这样:有一种叫做 PPQ 的东西: 每四分音符脉冲 < / strong>。每个脉冲称为“刻度” 。它描述了每四分音符可能有多少“细分”,如分辨率。因此,音序器一次“播放”曲目中的事件:播放Tick1,等待Tick持续时间,Tick2,Tick持续时间,等等。
现在,假设我们的 BPM (每分钟跳数)为 120 ,具有 PPQ = 96 (标准)。这意味着每个四分音符持续时间为500毫秒,每个T音持续时间为5.20833毫秒。
我们在Javascript中有哪些计时器替代方案?
1)我们有旧的 setTimeOut 。它有几个问题:分钟。等待时间是4ms。 (https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Minimum_delay_and_timeout_nesting) 它也受抖动/时间变化的影响。它不精确且要求很高,因为回叫被堆叠在偶数循环中。
2) setTimeOut / setInterval 有另一种选择,其中涉及使用 requestAnimationFrame()。它非常精确且CPU效率很高。但是,可以设置的最短时间约为16.7ms(典型的60FPS显示器中帧的持续时间)
还有其他选择吗?要每2-5毫秒精确地安排一个事件?
注意:在循环中完成的功能 playEventsAtTick()完全没有要求,因此执行时间不会比 Tick持续时间更多。< / p>
谢谢! 丹尼·布洛
答案 0 :(得分:1)
要在执行此类操作时保持任何理智,您将需要在专用线程上进行音频处理。更好的是,使用Web Audio API并让长期考虑这些问题的人们完成样本准确性的艰苦工作。
还可以检出Web MIDI(仅适用于Chrome)。
答案 1 :(得分:0)
感谢 nvioli 。我知道Web Audio API。但是,我认为这没有帮助。
我不是直接触发AUDIO:我在TRACKS中存储了MIDI事件(或者说只是“ EVENTS”)。这些事件发生在任何时间。因此,音序器需要循环每个
关于, 丹尼·布洛
答案 2 :(得分:0)
在单独的线程(例如web worker)中,您可以创建一个无限循环。在此循环中,您所需要做的就是计算节拍之间的时间。时间有效后,您可以向主流程发送消息,以进行一些视觉效果,播放声音或执行任何操作。
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()
接下来,我们可以创建索引页面,该页面将运行工作程序,并在每次从工作程序返回消息时闪烁一个方框。
<!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>
答案 3 :(得分:0)
下车草坪:您建议的方法不能完全起作用。假设我向网络工作者添加了一种方法,以停止音序器:
stop() {
this.run = false;
}
问题在于方法 myWorker.onmessage =函数(e){...} 永远不会被触发。我怀疑这是因为Web Worker线程无休止地循环了“太忙”。有什么办法解决吗?
另外,在播放时,它也可以工作.....但是CPU会大大上升.....唯一可能的解决方案是 Sleep()方法,但是 < em> Java中不存在的实时睡眠 ...
谢谢