Портирование скриптового приложения на аудиоворклет

Поскольку старый скрипт-процессор Webaudio устарел с 2014 года, а Audioworklets появились в Chrome 64, я решил попробовать их. Однако у меня возникают трудности с переносом приложения. Я приведу 2 примера из хорошей статьи, чтобы показать мою точку зрения.

Сначала скрипт-обработчик:

var node = context.createScriptProcessor(1024, 1, 1);
node.onaudioprocess = function (e) {
  var output = e.outputBuffer.getChannelData(0);
  for (var i = 0; i < output.length; i++) {
    output[i] = Math.random();
  }
};
node.connect(context.destination);

Еще один, который заполняет буфер, а затем воспроизводит его:

var node = context.createBufferSource(), buffer = 
context.createBuffer(1, 4096, context.sampleRate), data = buffer.getChannelData(0);

for (var i = 0; i < 4096; i++) {
  data[i] = Math.random();
}

node.buffer = buffer;
node.loop = true;
node.connect(context.destination);
node.start(0);

Большая разница между ними заключается в том, что первый заполняет буфер новыми данными во время воспроизведения, а второй генерирует все данные заранее.

Поскольку я генерирую много данных, я не могу сделать это заранее. Есть множество примеров для Audioworklet, но все они используют другие узлы, на которых можно просто запустить.start(), подключить его, и он начнет генерировать аудио. Я не могу обернуть голову, как это сделать, когда у меня нет такого метода.

Поэтому мой вопрос в основном заключается в том, как сделать приведенный выше пример в Audioworklet, когда данные генерируются непрерывно в главном потоке в некотором массиве, и воспроизведение этих данных происходит в потоке Webaudio.

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

Мой текущий код на основе скриптового процессора находится на github, в частности, в vgmplay-js-glue.js.

Я добавил некоторый код в конструктор класса VGMPlay_WebAudio, переходя от примеров к реальному результату, но, как я уже сказал, я не знаю, в каком направлении двигаться сейчас.

constructor() {
            super();

            this.audioWorkletSupport = false;

            window.AudioContext = window.AudioContext||window.webkitAudioContext;
            this.context = new AudioContext();
            this.destination = this.destination || this.context.destination;
            this.sampleRate = this.context.sampleRate;

            if (this.context.audioWorklet && typeof this.context.audioWorklet.addModule === 'function') {
                    this.audioWorkletSupport = true;
                    console.log("Audioworklet support detected, don't use the old scriptprocessor...");
                    this.context.audioWorklet.addModule('bypass-processor.js').then(() => {
                            this.oscillator = new OscillatorNode(this.context);
                            this.bypasser = new AudioWorkletNode(this.context, 'bypass-processor');
                            this.oscillator.connect(this.bypasser).connect(this.context.destination);
                            this.oscillator.start();
                    });
            } else {
                    this.node = this.context.createScriptProcessor(16384, 2, 2);
            }
    }

1 ответ

Итак, мой вопрос в основном заключается в том, как сделать приведенный выше пример в Audioworklet,

Для вашего первого примера уже есть версия AudioWorklet для него: https://github.com/GoogleChromeLabs/web-audio-samples/blob/gh-pages/audio-worklet/basic/js/noise-generator.js

Я не рекомендую второй пример (он же сшивание буфера), потому что он создает много исходных узлов и буферов, таким образом, он может вызвать GC, который будет мешать другим задачам в основном потоке. Также разрыв может произойти на границе двух последовательных буферов, если запланированное время запуска не попадает в выборку. С учетом вышесказанного, вы не сможете услышать сбой в этом конкретном примере, потому что исходным материалом является шум.

когда данные генерируются непрерывно в главном потоке в некотором массиве, и воспроизведение этих данных происходит в потоке Webaudio.

Первое, что вы должны сделать, это отделить аудио генератор от основного потока. Аудио генератор должен работать на AudioWorkletGlobalScope, Вот и вся цель системы AudioWorklet - меньшая задержка и лучшая производительность воспроизведения звука.

В вашем кодеVGMPlay_WebAudio.generateBuffer() должен быть вызван в AudioWorkletProcessor.process() обратный вызов для заполнения выходного буфера процессора. Это примерно соответствует тому, что ваш onaudioprocess обратный вызов делает.

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

Я не думаю, что ваш вариант использования требует MessagePort, Я видел другие методы в коде, но они действительно ничего не делают, кроме запуска и остановки узла. Это можно сделать, подключив / отключив AudioWorkletNode в основном потоке. Нет необходимости в обмене сообщениями между потоками.

Пример кода в конце может быть настройкой для AudioWorklet. Я хорошо знаю, что разделение между настройкой и фактическим поколением аудио может быть сложным, но оно того стоит.

Несколько вопросов к вам:

  1. Как графический движок игры отправляет сообщения в генератор VGM?
  2. Может ли VGMPlay класс жить на рабочем потоке без какого-либо взаимодействия с основным потоком? Я не вижу никакого взаимодействия в коде, за исключением запуска и остановки.
  3. Является XMLHttpRequest важно для VGMPlay учебный класс? Или это можно сделать где-то еще?
Другие вопросы по тегам