Я работаю над Javascript Music App, который включает в себя секвенсор. Для тех, кто не знаком, MIDI-секвенсоры работают примерно так: есть что-то, называемое PPQ: импульсы за четверть ноты. Каждый импульс называется "Tick". В нем показано, как могут быть "подразделения" на четверть ноты, например, разрешение. Итак, секвенсоры "играют" события, которые находятся в треках по одному тику за раз: играйте Tick1, ждите Tick Duration, Play tick2, Tick Duration и т.д.
Теперь скажем, что у нас есть BPM (Beats per Min) 120 с PPQ= 96 (стандарт). Это означает, что каждая продолжительность четвертной ноты составляет 500 мс, а каждая продолжительность тика - 5,20833 мс.
Какие альтернативы Timer у нас есть в Javascript?
1) У нас есть старый setTimeOut. У этого есть несколько проблем: мин. время ожидания - 4 мс. (https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Minimum_delay_and_timeout_nesting) Он также зависит от JITTER/time Variations. Он не является точным и требует, поскольку обратные вызовы сложены в четном цикле.
2) Существует альтернатива setTimeOut/setInterval, которая включает использование requestAnimationFrame(). Это ОЧЕНЬ точный и эффективный процессор. Однако минимальное время, которое может быть установлено, составляет около 16,7 мс (продолжительность кадра в типичном мониторе 60FPS)
Есть ли другая альтернатива? Чтобы точно спланировать событие каждые 2-5 мс?
Примечание: функция, выполняемая в конце цикла, playEventsAtTick() НЕ требует вообще, поэтому никогда не потребуется больше времени для выполнения, чем Tick Duration.
Спасибо! Дэнни Булло
Чтобы поддерживать какое-либо здравомыслие в этом, вы захотите сделать обработку звука на посвященный поток. А еще лучше, использовать Web Audio API, и пусть люди, которые думают об этих проблемах в течение длительного времени делать тяжелую работу образца-точность.
Также проверьте веб-MIDI (только хром).
Выйдите из моей лужайки: предложенный вами подход не полностью работает. Позвольте сказать, что я добавляю к веб-рабочему метод STOP Sequencer:
stop() {
this.run = false;
}
Проблема в том, что метод myWorker.onmessage = function (e) {...} никогда не запускается. Я подозреваю, что это связано с тем, что поток веб-рабочих "TOO BUSY" с бесконечным циклом. любой способ решить это?
Кроме того, во время игры он работает..... но процессор значительно повышается..... Единственным возможным решением может быть метод Sleep(), но Real SLEEP, который не существует в Javascript...
Спасибо
В отдельном потоке, таком как веб-рабочий, вы можете создать бесконечный цикл. В этом цикле все, что вам нужно сделать, это вычислить время между ударами. По истечении времени вы можете отправить сообщение в основной процесс, сделать некоторые визуальные эффекты, воспроизвести звук или что бы вы хотели сделать.
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>
bpm
и ppq
и отправляет это в основной поток. Работник работает довольно гладко, однако рендеринг браузера не всегда может идти в ногу, что является проблемой браузера, а не проблемой цикла.
Спасибо nvioli. Я знаю API веб-аудио. Однако я не думаю, что это может помочь здесь. Я напрямую не запускаю AUDIO: у меня есть MIDI-события (или пусть просто "СОБЫТИЯ"), хранящиеся в TRACKS. И эти события происходят в любом TICK. Таким образом, Sequencer должен зацикливать каждую Tick Duration для сканирования того, что нужно играть на этом конкретном тике.
С уважением, Дэнни Булло
while
цикла в процессе работы, и просто отправлять сообщения / от работника.