Разработка игрового звука с помощью Web Audio API

Введение

Аудио — это огромная часть того, что делает мультимедийные возможности такими привлекательными. Если вы когда-нибудь пробовали смотреть фильм с выключенным звуком, вы наверняка это замечали.

Игры не исключение! Мои самые теплые воспоминания о видеоиграх связаны с музыкой и звуковыми эффектами. Сейчас, спустя почти два десятилетия после того, как я поиграл в свои любимые игры, я до сих пор не могу выкинуть из головы композиции Кодзи Кондо для Zelda и атмосферный саундтрек Мэтта Ульмена к Diablo. Та же запоминаемость применима и к звуковым эффектам, таким как мгновенно узнаваемые отклики на щелчки юнитов из Warcraft и сэмплы из классических игр Nintendo.

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

Звук игры в Интернете

Для простых игр может быть достаточно использования тега <audio> . Однако многие браузеры имеют плохую реализацию, что приводит к сбоям в работе звука и высокой задержке. Надеемся, что это временная проблема, поскольку поставщики усердно работают над улучшением своих реализаций. Чтобы получить представление о состоянии тега <audio> , на сайте areweplayingyet.org есть хороший набор тестов.

Однако если глубже изучить спецификацию тега <audio> , становится ясно, что есть много вещей, которые с его помощью просто невозможно сделать, что неудивительно, поскольку он был разработан для воспроизведения мультимедиа. Некоторые ограничения включают в себя:

  • Нет возможности применять фильтры к звуковому сигналу
  • Нет возможности получить доступ к необработанным данным PCM.
  • Нет понятия о положении и направлении источников и слушателей.
  • Никакого точного тайминга.

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

Фоновая музыка

В играх часто фоновая музыка играет постоянно.

Это может очень раздражать, если ваш цикл короткий и предсказуемый. Если игрок застрял в какой-то области или уровне, а в фоновом режиме постоянно воспроизводится один и тот же сэмпл, возможно, стоит постепенно затухать дорожку, чтобы избежать дальнейшего разочарования. Другая стратегия — использовать миксы разной интенсивности, которые постепенно переходят друг в друга, в зависимости от контекста игры.

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

Гаражная группа

Затем, используя Web Audio API, вы можете импортировать все эти семплы, используя что-то вроде класса BufferLoader через XHR (подробно это описано во вводной статье Web Audio API . Загрузка звуков требует времени, поэтому активы, которые используются в игре должен загружаться при загрузке страницы, в начале уровня или, возможно, постепенно, пока игрок играет.

Затем вы создаете источник для каждого узла и узел усиления для каждого источника и подключаете граф.

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

// Assume gains is an array of AudioGainNode, normVal is the intensity
// between 0 and 1.
var value = normVal - (gains.length - 1);
// First reset gains on all nodes.
for (var i = 0; i < gains.length; i++) {
    gains[i].gain.value = 0;
}
// Decide which two nodes we are currently between, and do an equal
// power crossfade between them.
var leftNode = Math.floor(value);
// Normalize the value between 0 and 1.
var x = value - leftNode;
var gain1 = Math.cos(x - 0.5*Math.PI);
var gain2 = Math.cos((1.0 - x) - 0.5*Math.PI);
// Set the two gains accordingly.
gains[leftNode].gain.value = gain1;
// Check to make sure that there's a right node.
if (leftNode < gains.length - 1) {
    // If there is, adjust its gain.
    gains[leftNode + 1].gain.value = gain2;
}

В приведенном выше подходе два источника воспроизводятся одновременно, и мы плавно переходим между ними, используя кривые одинаковой мощности (как описано во введении ).

Многие разработчики игр сегодня используют тег <audio> для фоновой музыки, поскольку он хорошо подходит для потоковой передачи контента. Теперь вы можете переносить контент из тега <audio> в контекст веб-аудио.

Этот метод может быть полезен, поскольку тег <audio> может работать с потоковым контентом, что позволяет сразу воспроизводить фоновую музыку, не дожидаясь ее загрузки. Перенеся поток в API веб-аудио, вы можете манипулировать потоком или анализировать его. В следующем примере к музыке, воспроизводимой через тег <audio> , применяется фильтр нижних частот:

var audioElement = document.querySelector('audio');
var mediaSourceNode = context.createMediaElementSource(audioElement);
// Create the filter
var filter = context.createBiquadFilter();
// Create the audio graph.
mediaSourceNode.connect(filter);
filter.connect(context.destination);

Более полное обсуждение интеграции тега <audio> с API веб-аудио см. в этой короткой статье .

Звуковые эффекты

Игры часто воспроизводят звуковые эффекты в ответ на действия пользователя или изменения состояния игры. Однако, как и фоновая музыка, звуковые эффекты могут очень быстро надоесть. Чтобы избежать этого, часто полезно иметь набор похожих, но разных звуков для воспроизведения. Это может варьироваться от умеренных вариаций образцов шагов до резких вариаций, как это видно в серии Warcraft в ответ на нажатие на юнитов.

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

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

var time = context.currentTime;
for (var i = 0; i < rounds; i++) {
    var source = this.makeSource(this.buffers[M4A1]);
    source.noteOn(time + i - interval);
}

Вот если бы все пулеметы в вашей игре звучали именно так, это было бы довольно скучно. Конечно, они будут различаться по звуку в зависимости от расстояния до цели и относительного положения (подробнее об этом позже), но даже этого может быть недостаточно. К счастью, API веб-аудио позволяет легко настроить приведенный выше пример двумя способами:

  1. С тонким сдвигом во времени между выстрелами пуль.
  2. Изменяя скорость воспроизведения каждого сэмпла (а также изменяя высоту тона), чтобы лучше имитировать случайность реального мира.

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

3D позиционный звук

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

В приведенном выше примере в центре холста находится слушатель (значок человека), а мышь влияет на положение источника (значок динамика). Выше приведен простой пример использования AudioPannerNode для достижения такого рода эффекта. Основная идея приведенного выше примера — реагировать на движение мыши, устанавливая положение источника звука следующим образом:

PositionSample.prototype.changePosition = function(position) {
    // Position coordinates are in normalized canvas coordinates
    // with -0.5 < x, y < 0.5
    if (position) {
    if (!this.isPlaying) {
        this.play();
    }
    var mul = 2;
    var x = position.x / this.size.width;
    var y = -position.y / this.size.height;
    this.panner.setPosition(x - mul, y - mul, -0.5);
    } else {
    this.stop();
    }
};

Что нужно знать о пространственной обработке Web Audio:

  • По умолчанию слушатель находится в начале координат (0, 0, 0).
  • Позиционные API веб-аудио не имеют единиц измерения, поэтому я ввел множитель, чтобы демо звучало лучше.
  • Веб-аудио использует декартову координату y-is-up (противоположность большинству компьютерных графических систем). Вот почему я меняю ось Y во фрагменте выше.

Дополнительно: звуковые конусы

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

Модель позиции

К контексту API веб-аудио прикреплен один AudioListener, который можно настроить в пространстве с помощью положения и ориентации. Каждый источник может быть передан через AudioPannerNode, который распределяет входной звук по пространству. Узел панорамирования имеет положение и ориентацию, а также модель расстояния и направления.

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

var panner = context.createPanner();
panner.coneOuterGain = 0.5;
panner.coneOuterAngle = 180;
panner.coneInnerAngle = 0;

Хотя мой пример представлен в 2D, эта модель легко обобщается на третье измерение. Пример звука, преобразованного в 3D, см. в этом позиционном образце . Помимо положения, звуковая модель Web Audio также дополнительно включает в себя скорость для доплеровских сдвигов. В этом примере эффект Доплера показан более подробно.

Для получения дополнительной информации по этой теме прочитайте это подробное руководство по [смешиванию позиционного звука и WebGL][webgl].

Эффекты и фильтры комнаты

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

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

Для получения дополнительной информации о том, как импульсные характеристики могут быть созданы в конкретной среде, прочтите раздел «Настройка записи» в разделе « Свертка » спецификации Web Audio API.

Что еще более важно для наших целей, API веб-аудио предоставляет простой способ применить эти импульсные характеристики к нашим звукам с помощью ConvolverNode .

// Make a source node for the sample.
var source = context.createBufferSource();
source.buffer = this.buffer;
// Make a convolver node for the impulse response.
var convolver = context.createConvolver();
convolver.buffer = this.impulseResponseBuffer;
// Connect the graph.
source.connect(convolver);
convolver.connect(context.destination);

Также см. эту демонстрацию эффектов комнаты на странице спецификаций Web Audio API, а также этот пример , который дает вам контроль над сухим (необработанным) и обработанным (обработанным с помощью конволвера) микшированием великолепного джазового стандарта.

Последний отсчет

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

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

Обрезка

Вот реальный пример обрезки в действии. Форма волны выглядит плохо:

Обрезка

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

Обнаружение обрезки

С технической точки зрения ограничение происходит, когда значение сигнала в любом канале превышает допустимый диапазон, а именно между -1 и 1. Как только это обнаружено, полезно дать визуальную обратную связь о том, что это происходит. Чтобы сделать это надежно, поместите JavaScriptAudioNode в свой график. Аудиограф будет настроен следующим образом:

// Assume entire sound output is being piped through the mix node.
var meter = context.createJavaScriptNode(2048, 1, 1);
meter.onaudioprocess = processAudio;
mix.connect(meter);
meter.connect(context.destination);

А обрезку можно обнаружить в следующем обработчике processAudio :

function processAudio(e) {
    var buffer = e.inputBuffer.getChannelData(0);

    var isClipping = false;
    // Iterate through buffer to check if any of the |values| exceeds 1.
    for (var i = 0; i < buffer.length; i++) {
    var absValue = Math.abs(buffer[i]);
    if (absValue >= 1) {
        isClipping = true;
        break;
    }
    }
}

В общем, будьте осторожны и не злоупотребляйте JavaScriptAudioNode из соображений производительности. В этом случае альтернативная реализация измерения может опросить RealtimeAnalyserNode в аудиографе на предмет getByteFrequencyData во время рендеринга, как определено requestAnimationFrame . Этот подход более эффективен, но пропускает большую часть сигнала (включая места, где он потенциально обрезается), поскольку рендеринг происходит не более 60 раз в секунду, тогда как аудиосигнал меняется гораздо быстрее.

Поскольку обнаружение клипов очень важно, вполне вероятно, что в будущем мы увидим встроенный узел MeterNode Web Audio API.

Предотвратить обрезку

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

Добавьте немного сахара

Компрессоры обычно используются в производстве музыки и игр для сглаживания сигнала и контроля всплесков общего сигнала. Эта функциональность доступна в мире веб-аудио через DynamicsCompressorNode , который можно вставить в аудиограф, чтобы сделать звук более громким, богатым и полным, а также помочь с обрезкой. Непосредственно цитируя спецификацию, этот узел

Использование динамического сжатия, как правило, является хорошей идеей, особенно в игровой обстановке, где, как обсуждалось ранее, вы не знаете точно, какие звуки будут воспроизводиться и когда. Plink от DinahMoe labs — отличный тому пример, поскольку воспроизводимые звуки полностью зависят от вас и других участников. Компрессор полезен в большинстве случаев, за исключением некоторых редких, когда вы имеете дело с тщательно сведенными треками, которые уже настроены так, чтобы звучать «в самый раз».

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

// Assume the output is all going through the mix node.
var compressor = context.createDynamicsCompressor();
mix.connect(compressor);
compressor.connect(context.destination);

Более подробную информацию о динамическом сжатии можно найти в этой очень информативной статье в Википедии .

Подводя итог, внимательно прислушивайтесь к клиппированию и предотвращайте его, вставив узел главного усиления. Затем уплотняем весь микс с помощью узла динамического компрессора. Ваш аудиографик может выглядеть примерно так:

Конечный результат

Заключение

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

Дополнительную информацию о веб-аудио можно найти в вводной статье о начале работы . Если у вас есть вопросы, посмотрите, есть ли на них ответ в разделе часто задаваемых вопросов о веб-аудио . Наконец, если у вас есть дополнительные вопросы, задайте их в Stack Overflow , используя тег веб-аудио .

Прежде чем закончить, позвольте мне сегодня рассказать вам о некоторых замечательных способах использования API веб-аудио в реальных играх: