Я пытаюсь добиться управляемого панорамирования для любого количества одновременных веб-источников звука. Сами источники моно. Я работаю в Javascript с веб-аудио API (https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API).
В настоящее время проблема, с которой я сталкиваюсь, заключается в том, что я пытаюсь использовать многоканальный выход (по одному для каждого источника), но интерпретация канала переопределяет мои попытки панорамирования (см. https://developer.mozilla.org/en-US/docs/Web/API/AudioNode/channelInterpretation), заставляет меня думать, что я делаю что-то неправильно архитектурно.
Я бы хотел оставить здесь вещи в основном на концептуальном уровне, поскольку я считаю, что именно в этом заключается моя проблема.
Текущая настройка
Мой подход к этому состоит в том, чтобы один узел обрабатывал всю обработку для каждого источника, называемого здесь «scriptNode». Создается количество каналов, равное количеству аудиоисточников, и также создается такое же равное количество узлов панорамирования. График выглядит так:
The bundle size (the '=' segments) is the number of channels, set to be equal to the number of sources.
scriptNode == splitter =+-- panner1 --+= merger == destination
\-- panner. --/
\-- panner. --/
\-- pannerN --/
Некоторые ошибки, я вызываю эту функцию для установки scriptNode:
scriptNode = firstPart.audioCtx.createScriptProcessor(2048, 0, numParts);
Где numParts - количество источников. Я также устанавливаю scriptCode channelCountMode в «явный» и channelInterpretation в «ораторы». Одна из этих настроек может оказаться важной, но я не смог ничего найти, пытаясь поиграть с настройками.
Проблема
Когда я на самом деле тестирую свой код с этой архитектурой, я получу следующее поведение в зависимости от количества выбранных частей. Ползунки панорамирования привязаны к значениям «панорамирования» узла панорамирования для каждого соответствующего источника.
- numParts = 1: монофонический выход, панорамирование с помощью ползунка не влияет на громкость выходного сигнала (сильнее к середине). Я полагаю, что это побочный продукт микширования моно из паннера.
- numParts = 2: стереофонический выход, один жесткий левый, один жесткий правый. Панорамирование обоих каналов с помощью ползунка ничего не делает.
- numParts = 3: То же, что и = 2, но третий канал молчит.
- numParts = 4: Аналогично = 2, теперь все каналы снова работают, они панорамируются в порядке L / R / L / R. Повторное панорамирование с помощью ползунка ничего не делает.
Такое поведение, кажется, соответствует описанию channelInterpretation, но я хочу, чтобы панорамирование работало для каждого источника отдельно, независимо от количества каналов, которые я использую. И я все еще хотел бы использовать каналы, потому что каждый из моих источников ожидает записи в монобуфер.
Могу ли я сделать архитектурную настройку, чтобы сохранить эту многоканальную стратегию и добиться того, что я ищу?
Фрагменты кода
Соответствующие части текущего кода, основанные на моих утверждениях выше, наряду с попытками исправить проблему.
Изменить: Благодаря комментариям ниже, мне удалось найти проблему. Я вызвал однострочное исправление, чтобы этот код можно было использовать позже.
Функция обработки звука. Только первый синтезатор (источник) устанавливает этот обратный вызов:
function customAudioProcessCallback( audioProcessingEvent )
{
// First synth - process audio for all synths!
const outputBuffer = audioProcessingEvent.outputBuffer;
for ( var i = 0; i < numParts; i++ ) {
// Each part writes to one channel.
synthParts[ i ].synthesize(outputBuffer.getChannelData( i ), outputBuffer.length);
}
}
Соответствующий фрагмент функции воспроизведения:
function play()
{
const contextClass = (window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.oAudioContext || window.msAudioContext);
synthParts[ 0 ].audioCtx = new contextClass();
synthParts[ 0 ].scriptNode = synthParts[ 0 ].audioCtx.createScriptProcessor ? synthParts[ 0 ].audioCtx.createScriptProcessor(2048, 0, numParts+1) : synthParts[ 0 ].audioCtx.createJavaScriptNode(2048, 0, numParts+1); // 2048, 0 input channels, ? outputs
synthParts[ 0 ].scriptNode.onaudioprocess = customAudioProcessCallback;
synthParts[ 0 ].scriptNode.channelCountMode = 'explicit';
synthParts[ 0 ].scriptNode.channelInterpretation = 'speakers';
// Set up splitter and panners for all channels
var splitter = synthParts[ 0 ].audioCtx.createChannelSplitter( numParts+1 );
for ( var i = 0; i < numParts; i++ ) {
panList[ i ] = synthParts[ 0 ].audioCtx.createStereoPanner();
panList[ i ].pan = panValues[ i ];
}
// Connection:
// scriptNode -- splitter -+-- panner1 --+- destination
// \-- panner. --/
// \-- pannerN --/
synthParts[ 0 ].scriptNode.connect(splitter);
for ( var i = 0; i < numParts; i++ ) {
splitter.connect( panList[ i ], i);
// This line used to read:
// panList[ i ].connect( synthParts[ 0 ].audioCtx.destination, 0, i );
// However, this was connecting multiple parts to the input of the audio context destination, which is limited to 1 input. The correct call is below.
panList[ i ].connect( synthParts[ 0 ].audioCtx.destination );
}
}