Javascript:MIDI音序器应用程序中FAST Timer的setTimeOut的替代方法

时间:2018-10-26 16:21:11

标签: javascript timer settimeout midi requestanimationframe

我正在开发包含定序器的 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>

谢谢! 丹尼·布洛

4 个答案:

答案 0 :(得分:1)

要在执行此类操作时保持任何理智,您将需要在专用线程上进行音频处理。更好的是,使用Web Audio API并让长期考虑这些问题的人们完成样本准确性的艰苦工作。

还可以检出Web MIDI(仅适用于Chrome)。

答案 1 :(得分:0)

感谢 nvioli 。我知道Web Audio API。但是,我认为这没有帮助。 我不是直接触发AUDIO:我在TRACKS中存储了MIDI事件(或者说只是“ EVENTS”)。这些事件发生在任何时间。因此,音序器需要循环每个 tick持续时间,以扫描在特定tick中播放的内容。

关于, 丹尼·布洛

答案 2 :(得分:0)

在单独的线程(例如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()

接下来,我们可以创建索引页面,该页面将运行工作程序,并在每次从工作程序返回消息时闪烁一个方框。

<!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中不存在的实时睡眠 ...

谢谢