Написать пост

Web Audio и объёмный звуковой ландшафт: введение

Аватар Иван Бирюков

Обложка поста Web Audio и объёмный звуковой ландшафт: введение

Рассказывает Si Robertson

В этом руководстве мы разберемся с основным элементами Web Audio, используемыми для создания объёмных звуковых ландшафтов в интерактивных приложениях с эффектом присутствия, например, 3D играх.

Web Audio API и терминология могут смутить, поэтому целью этого руководства является облегчить понимание принципов работы с Web Audio.

Демонстрация

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

Исходный код демки и используемые ресурсы даны в конце статьи.

AudioContext

Интерфейс AudioContext — это основа Web Audio, он предоставляет необходимые для создания различных элементов Web Audio функции и способ передачи звука «железу» и устройствам вывода звука.

			var audioContext = null
 
if (window.AudioContext !== undefined) {
    audioContext = new AudioContext()
}
		

Важно, чтобы интерфейс AudioContext был доступен, так как Web Audio — сравнительно новая технология, и она может не поддерживаться некоторыми браузерами.

Помимо предоставления необходимых функций, этот интерфейс обладает двумя важными свойствами: destination и listener, оба read-only. Свойство destination можно представить как средство связи с аудио-аппаратурой. Свойство listener представляет собой тот объект, который слушает весь звук в игре, например, персонаж или камера.

Буферы

Интерфейсы AudioBuffer и AudioBufferSourceNode позволяют проигрывать звуки. Объекты AudioBuffer содержат «сырые» звуковые файлы, которые всячески обрабатываются по пути к чьим-то колонкам или наушникам. Объекты AudioBufferSourceNode используются для запуска и остановки воспроизведения звуков, содержащихся в объектах AudioBuffer.

Стандартным способом загрузки аудио в объект AudioBuffer является использование объекта XMLHttpRequest с установленным в arraybuffer свойством responseType. После загрузки файла буфер отправляется объекту AudioContext для декодирования, и, в случае успеха, мы получаем объект AudioBuffer.

			var loader = new XMLHttpRequest()
 
loader.open("GET", "massive-explosion.ogg")
loader.responseType = "arraybuffer"
loader.onload = whenLoaded
loader.send()
 
function whenLoaded(event) {
    var data = loader.response
 
    if (data === null) {
        // There was a problem loading the file.
        return
    }
 
    // Decode the data.
    audioContext.decodeAudioData(data, whenDecoded)
}
 
function whenDecoded(audioBuffer) {
    // "audioBuffer" is an AudioBuffer object.
}
		

Функция decodeAudioData() имеет третий параметр, который принимает второй callback, который вызывается при невозможности декодирования загруженного аудиофайла.

			decodeAudioData(data, whenDecoded, whenFailed)
		

Не все браузеры поддерживают одни и те же форматы аудио (вот хорошая таблица), поэтому вы можете захотеть использовать второй callback для перехода к другому формату. Например, Internet Explorer не поддерживает OGG Vorbis, но поддерживает MP3. Единственной проблемой MP3 является отсутствие возможности бесшовно зацикливать аудио, которая есть в OGG Vorbis.

Когда нам доступен объект AudioBuffer, мы можем воспроизвести его, используя объект AudioBufferSourceNode.

			var source = audioContext.createBufferSource()
 
// Attach an AudioBuffer object.
source.buffer = audioBuffer
 
// Connect the "source" object to the "destination" object.
source.connect(audioContext.destination)
 
// Optionally, tell "source" to loop the audio continuously.
source.loop = false
 
// Start the audio.
source.start()
		

Важно помнить, что объекты AudioBufferSourceNode являются одноразовыми, т.е. вы можете вызвать функцию start() только один раз. Вам потребуется создать объект AudioBufferSourceNode и связать его с объектом destination, внедрённым в AudioContext, когда мы хотим воспроизвести из него аудиофайл.

Можно облегчить себе жизнь, написав небольшую функцию, которая создаёт, соединяет и запускает за нас объект AudioBufferSourceNode.

			function play(audioBuffer, audioContext) {
    var source = audioContext.createSourceBuffer()
    source.buffer = audioBuffer
    source.connect(audioContext.destination)
    source.start()
}
 
play(audioBuffer01, audioContext)
play(audioBuffer02, audioContext)
play(audioBuffer03, audioContext)
		

Когда объект AudioBufferSourceCode заканчивает воспроизведение, и если его ничто не использует, Web Audio автоматически его отключит. Это очень удобно, когда вам нужно запускать не требующие большого внимания короткие звуковые эффекты и т.п.

Если вы хотите зациклить аудио, используя свойство AudioBufferSourceNode loop, вам понадобится где-нибудь сохранить ссылку на объект AudioBufferSourceNode, чтобы можно было вызвать функцию stop() для остановки аудио.

			source.stop()
		

К этому моменту мы используем буферы для воспроизведения аудио, но это происходит без панорамирования и ориентации в пространстве. Вот где в игру вступают объекты PannerNode.

Паннеры

Объекты PannerNode позволяют задать позицию звука в 3D-пространстве, используя прямоугольную систему координат. Именно здесь творится большая часть 3D-магии.

У объекта PannerNode есть несколько свойств, позволяющих тонко настроить поведение аудио, но сейчас нам интересны лишь два: maxDistance и panningModel. Свойство maxDistance — это то расстояние до слушателя, на котором громкость звука будет равна нулю. Это — произвольное значение, имеющее значение 10000 по умолчанию. panningModel сообщает Web Audio, как обрабатывать аудио, проходящее через объект PannerNode. Для объёмных звуковых ландшафтов вам нужно будет установить значение HRTF (head-related transfer function).

Для установки положения AudioBufferSourceNode мы используем функцию setPosition(), внедрённую в объект PannerNode.

			var panner = audioContext.createPanner()
panner.panningModel = "HRTF"
 
// Set the 3D position (x, y, z).
panner.setPosition(1, 2, 3)
 
// Connect the "source" object to the "panner" object.
source.connect(panner)
 
// Connect the "panner" object to the "destination" object.
panner.connect(audioContext.destination)
 
// Start the audio.
source.start()
		

Для понимания обновим ту маленькую функцию, созданную ранее:

			function play(audioBuffer, x, y, z, audioContext) {
    var source = audioContext.createSourceBuffer()
    source.buffer = audioBuffer
 
    var panner = audioContext.createPanner()
    panner.panningModel = "HRTF"
    panner.setPosition(x, y, z)
 
    source.connect(panner)
    panner.connect(audioContext.destination)
 
    source.start()
}
 
play(audioBuffer01, 1, 2, 3, audioContext)
play(audioBuffer02, 4, 5, 6, audioContext)
play(audioBuffer03, 7, 8, 9, audioContext)
		

Сейчас мы воспроизводим звуки, позиционируя их в пространстве, но остался ещё один важный элемент: аудио-слушатель.

Аудиослушатель

Каждому объекту AudioContext соответствует объект listener, который содержит положение и ориентацию в пространстве чего-то, что слушает звук. Обычно этим чем-то является виртуальная камера, закрепленная на голове персонажа, бампер машины, хвост самолёта или что-то другое — главное, чтобы это имело смысл с точки зрения пользователя.

Объект listener имеет функции setPosition() и setOrientation(). Функция setPosition()помещает слушателя где-то в пространстве, а функция setOrientation() его вращает.

Функция setPosition() работает так же, как и аналогичная функция PannerNode, и принимает три координаты.

			audioContext.listener.setPosition(x, y, z)
		

Функция setOrientation() чуть более сложная, она принимает два единичных вектора. Первый вектор отражает вращение слушателя, а второй — вертикальное направление относительно слушателя.

			audioContext.listener.setOrientation(x1, y1, z1, x2, y2, z2)
		

Если вам нужно вращать слушателя лишь по одной оси, вычисления достаточно просты. Например, если вы используете ту же систему координат, что и WebGL, в ней положительное направление x указывает на правый край экрана, положительное направление y — на верхний, а  z — из экрана, вы можете вращать слушателя вокруг оси y , используя один вызов функции cos() и один вызов функции sin().

			// The listener's position (could be anything).
var x = 50
var y = 100
var z = 0
 
audioContext.listener.setPosition(x, y, z)
 
// Calculate the rotation vector.
// rad = rotation, in radians
var rad = 0.10
var v1 = Math.cos(rad) // x
var v2 = 0             // y
var v3 = Math.sin(rad) // z
 
// The "up" vector
var v4 = 0 // x
var v5 = 1 // y
var v6 = 0 // z
 
audioContext.listener.setOrientation(v1, v2, v3, v4, v5, v6)
		

Демонстрация к этому руководству делает похожую вещь и вращает объекты PannerNode вокруг одной оси.

Заключение

В этом руководстве мы взглянули на основные элементы Web Audio, используемые для создания объёмных звуковых ландшафтов для интерактивных приложений с эффектом погружения, таких как 3D игры. Надеюсь, эта статья была полезной и предоставила достаточно информации, чтобы понять принципы совместной работы аудиобуферов, паннеров и слушателей.

Ресурсы

Следующий шаг: реализация

В следующей части руководства мы используем всё написанное выше, добавим ещё немного и создадим простой API. В ней мы сосредоточимся на 3D играх, но API будет достаточно обобщённым, чтобы его можно было использовать и в других интерактивных приложениях с эффектом погружения.

Следите за новыми постами
Следите за новыми постами по любимым темам
6К открытий6К показов