JavaScript: альтернатива setTimeOut для таймера FAST в приложении MIDI Sequencer - PullRequest
0 голосов
/ 26 октября 2018

Я работаю над музыкальным приложением Javascript, которое включает в себя секвенсор . Для тех, кто не знаком, MIDI-секвенсоры работают примерно так: есть нечто, называемое PPQ : импульсов на четвертную ноту . Каждый импульс называется «Тик» . Он показывает, как могут быть «подразделения» на квартальную ноту, например разрешение. Таким образом, секвенсоры «воспроизводят» события, которые находятся в треках, по одному тику за раз: Play Tick1, Wait Tick Duration, Play tick2, Tick Duration и т. Д.

Теперь, скажем, у нас есть BPM (Ударов в минуту) 120 с PPQ = 96 (стандарт). Это означает, что каждая длительность квартальной ноты равна 500 мс, а каждая длительность тика - 5,20833 мс.

Какие альтернативы таймера у нас есть в Javascript?

1) У нас есть старый setTimeOut . У него есть несколько проблем: мин. Время ожидания составляет 4 мс. (https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Minimum_delay_and_timeout_nesting) Это также зависит от изменения времени / времени. Это не является точным и требовательным, так как обратные вызовы укладываются в четный цикл.

2) Существует альтернатива setTimeOut / setInterval , которая включает использование requestAnimationFrame () . Это ОЧЕНЬ точное и эффективное использование процессора. Однако минимальное время, которое оно может быть установлено, составляет около 16,7 мс (продолжительность кадра в типичном мониторе с частотой 60 кадров в секунду)

Есть ли другая альтернатива? Чтобы точно планировать событие каждые 2-5 мс?

Примечание: функция, выполняемая в стороне от цикла, playEventsAtTick () НЕ требует вообще, поэтому выполнение никогда не займет больше времени, чем Tick Duration .

Спасибо! Дэнни Булло

Ответы [ 4 ]

0 голосов
/ 27 октября 2018

Get Off My Lawn: Подход, который вы предложили, не работает полностью.Допустим, я добавляю метод к веб-работнику в STOP the Sequencer :

stop() {
    this.run = false;
}

Проблема в том, что метод myWorker.onmessage = function (e) {...} никогда не срабатывает.Я подозреваю, что это потому, что поток Web Worker "СЛИШКОМ ЗАНЯТ" с бесконечным циклом.Есть ли способ решить эту проблему?

Кроме того, во время игры это работает ..... но процессор значительно возрастает ..... Единственное возможное решение будет Sleep () метод , но Реальный СОН , которого нет в Javascript ...

Спасибо

0 голосов
/ 26 октября 2018

Спасибо nvioli .Я в курсе API Web Audio.Тем не менее, я не думаю, что это может помочь здесь.Я не запускаю AUDIO напрямую: у меня есть треки MIDI (или, скажем, просто «СОБЫТИЯ»).И эти события происходят в любой момент.Таким образом, секвенсор должен зацикливаться на каждой длительности тика , чтобы отсканировать, что играть на этом конкретном тике.

С уважением, Дэнни Булло

0 голосов
/ 26 октября 2018

В отдельном потоке, таком как веб-работник , вы можете создать бесконечный цикл. В этом цикле все, что вам нужно сделать, это рассчитать время между ударами. После истечения времени вы можете отправить сообщение основному процессу, сделать некоторые визуальные эффекты, воспроизвести звук или сделать то, что вы хотели бы сделать.

Вот Рабочий пример

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>
0 голосов
/ 26 октября 2018

Для поддержания здравого смысла в подобных вещах вам понадобится обработка звука в выделенном потоке.Более того, используйте Web Audio API и позвольте людям, которые давно думают об этих проблемах, выполнить тяжелую работу по точности выборки.

Также ознакомьтесь с WebMIDI (только хром).

...