Громкость и воспринимаемая звучность

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

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

Громкость, усиление и воспринимаемая звучность


Давайте начнём с определений. Воспринимаемая звучность (loudness) — это субъективная оценка того, насколько интенсивно наши уши ощущают звук. Громкость (volume) — это измерение физической амплитуды звуковой волны. Усиление (gain) — это множитель, который влияет на амплитуду звука во время его обработки.

Другими словами, при усилении амплитуда звуковой волны масштабируется, а значение усиления используется в качестве множителя. Посмотрим на пример изменения формы звуковой волны при усилении амплитуды в два раза на Рисунке 3-1.

Рисунок 3-1. Слева: оригинальная форма звуковой волны, справа: усиленная в два раза
Рисунок 3-1. Слева: оригинальная форма звуковой волны, справа: усиленная в два раза

Мощность волны измеряется в децибелах (сокращенно dB) или в одной десятой бела — единице измерения, названной в честь Александра Грэхема Белла. Децибелы — относительная логарифмическая единица измерения, которая сравнивает уровень мощности волны с некоторой опорной точкой. Существует множество различных опорных точек для измерения dB, и тип опорной точки обозначается в виде дополнительного суффикса вместе с единицей измерения. Значение в dB бессмысленно без знания этой референсной точки. К примеру, dBV, dBu и dBm полезны для измерения электрических сигналов. Но так как мы имеем дело с цифровым аудио, обычно мы будем использовать два типа измерений: dBFS и dBSPL.

Первый тип — это dBFS (decibels full scale, децибелы относительно полной шкалы). Максимально возможный уровень звука, который может воспроизвести оборудование, равен 0 dBFS. Все остальные уровни выражаются отрицательными числами.

Математически dBFS вычисляются как:

dBFS = 20 * log( [sample level] / [max level] )

Максимальное значение dBFS в 16-битной аудиосистеме равно:

max = 20 * log(1111 1111 1111 1111 / 1111 1111 1111 1111) = log(1) = 0 dBFS

Обратите внимание, что максимальное значение dBFS равно 0 по определению, так как log(1) = 0. Минимальное значение dBFS в той же аудиосистеме равно:

min = 20 * log(0000 0000 0000 0001 / 1111 1111 1111 1111) = -96 dBFS

dBFS — величина усиления, а не громкости. Вы можете воспроизвести сигнал мощностью 0 dBFS через ваше стереоустройство с очень низким уровнем усиления и почти ничего не услышите. И наоборот, можно проиграть сигнал на уровне -30 dBFS с максимальным усилением и оглушить себя.

Тем не менее, вы, вероятно, слышали, как кто-то описывал громкость звука в децибелах. Технически речь шла о dBSPL — типе измерения мощности звука в децибелах относительно уровня звукового давления. Здесь опорой является точка в 0.000002 ньютона на кв. м. (что примерно соответствует звуку летящего комара на расстоянии 3 метров). У dBSPL нет верхнего предела, но на практике важно оставаться ниже уровня, при котором может повредиться слух (около 120 dBSPL) и значительно ниже уровня болевого порога (около 150 dBSPL). Web Audio API не использует dBSPL, поскольку итоговая громкость звука зависит от усиления на уровне ОС и самих колонок; API работает только с dBFS.

Логарифмическое определение децибелов в какой-то мере соответствует тому, как наши уши воспринимают звучность, но сама воспринимаемая звучность остаётся весьма субъективным понятием. Если сравнить значения в dB для звука и для него же с усилением в 2 раза, мы увидим прибавку примерно на 6 dB:

diff = 20 * log(2 / 2^16) - 20 * log(1 / 2^16) = 6.02 dB

Каждый раз, когда мы добавляем примерно по 6 dB, мы по сути удваиваем амплитуду сигнала. Сравнивая мощность звука на рок-концерте (~110 dBSPL) с мощностью звука будильника (~80 dBSPL), разница между ними будет в (110 - 80) / 6 dB, или примерно в пять раз — в этом случае множитель усиления будет равен 2^5 = 32x. Ручка громкости на стереоколонках также калибрует амплитуды экспоненциально. Другими словами, поворачивая ручку громкости на 3 пункта выше, вы умножаете амплитуду сигнала примерно в 8 раз (2^3). На практике, описанная здесь экспоненциальная модель является всего лишь приближением к тому, как наши уши воспринимают звучность, и производители аудиооборудования часто используют собственные кривые усиления, которые не являются ни линейными, ни экспоненциальными и пересекаются на более высоком уровне амплитуды.

Кроссфейд с равной мощностью

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

Применение прямого линейного затухания приведёт к графику на Рисунке 3-2, в котором звук может быть несбалансированным из-за провала громкости между двумя семплами.

Рисунок 3-2. Линейный кроссфейд между двумя треками
Рисунок 3-2. Линейный кроссфейд между двумя треками

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

Рисунок 3-3. Кроссфейд с равной мощностью звучит намного лучше
Рисунок 3-3. Кроссфейд с равной мощностью звучит намного лучше

График на Рисунке 3-3 может быть сгенерирован в коде с помощью не очень сложных вычислений:

function equalPowerCrossfade(percent) {
  // Используем кривую кроссфейда равной мощности
  var gain1 = Math.cos(percent * 0.5 * Math.PI);
  var gain2 = Math.cos((1.0 - percent) * 0.5 * Math.PI);

  this.ctl1.gainNode.gain.value = gain1;
  this.ctl2.gainNode.gain.value = gain2;
}

Клиппинг и измерение уровня сигнала

Подобно изображениям, выходящим за границы холста, звуки тоже могут быть обрезаны (клиппированы), если их волна превышает максимальный допустимый уровень. Возникающее при этом характерное искажение считается явно нежелательным. Поэтому аудиооборудование часто снабжено индикаторами, которые показывают величину уровней сигнала и помогают инженерам и слушателям избежать клиппинга. Эти индикаторы называются измерителями (см. Рисунок 3-4) и обычно имеют зелёную зону (норма, без клиппинга), жёлтую зону (близко к клиппингу) и красную зону (клиппинг).

Рисунок 3-4. Измеритель в типовом радиоприёмнике
Рисунок 3-4. Измеритель в типовом радиоприёмнике

Обрезанные звуки выглядят плохо на мониторе и так же плохо звучат. Очень важно обращать внимание на резкие искажения, или, наоборот, на слишком приглушённые миксы, которые заставляют слушателя повышать громкость.

Использование измерителей для обнаружения и предотвращения клиппинга

Из-за того, что множество звуков, проигрываемых одновременно, суммируются без снижения уровня, вы можете оказаться в ситуации, когда превысите порог возможностей вашего динамика. Максимальный уровень звука равен 0 dBFS или 2^16 для 16-битной аудиосистемы. В версии сигнала с плавающей точкой эти значения битов находятся в диапазоне [−1, 1]. Волновая форма звука, подвергшегося клиппингу, выглядит примерно так, как показано на Рисунке 3-5. В контексте Web Audio API, клиппинг происходит тогда, когда значения, отправленные на узел аудиовыхода, выходят за пределы допустимого диапазона. Хорошей практикой считается оставлять небольшой запас (так называемый headroom) в финальном миксе, чтобы не приближаться слишком близко к порогу клиппинга.

Рисунок 3-5. Волновая форма звука, подвергшегося клиппингу
Рисунок 3-5. Волновая форма звука, подвергшегося клиппингу

Помимо непосредственного прослушивания, вы можете определить наличие клиппинга и программно — добавив в свой аудиограф узел ScriptProcessorNode (прим. переводчика: этот узел устарел и заменён на AudioWorklet в новых версиях Web Audio API). Клиппинг может возникнуть, если какие-либо значения PCM-буфера выходят за пределы допустимого диапазона. В этом примере мы проверяем левый и правый каналы на наличие клиппинга и, если он обнаружен, сохраняем время обнаружения последнего клиппинга:

function onProcess(e) {
  var leftBuffer = e.inputBuffer.getChannelData(0);
  var rightBuffer = e.inputBuffer.getChannelData(1);

  checkClipping(leftBuffer);
  checkClipping(rightBuffer);
}

function checkClipping(buffer) {
  var isClipping = false;

  // Проходим по аудиобуферу в цикле, чтобы проверить,
  // вышло ли какое-либо значение из допустимого диапазона
  for (var i = 0; i < buffer.length; i++) {
    var absValue = Math.abs(buffer[i]);

    if (absValue >= 1.0) {
      isClipping = true;
      break;
    }
  }

  this.isClipping = isClipping;

  if (isClipping) {
    lastClipTime = new Date();
  }
}

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

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

Понятие динамического диапазона


В мире аудио динамический диапазон означает разницу между самыми громкими и самыми тихими участками звука. Объём динамического диапазона в музыкальных произведениях сильно варьируется в зависимости от жанра. Классическая музыка имеет большой динамический диапазон и часто содержит очень тихие фрагменты, за которыми следуют относительно громкие. Во множестве популярных жанров типа рока или электроники обычно задействован небольшой динамический диапазон. Музыка этих жанров обычно довольно громкая из-за конкуренции за слушателя (так называемая «Война Звучности») и для удовлетворения запросов потребителей. Такая равномерная звучность обычно достигается за счёт сжатия динамического диапазона.

Тем не менее, существует множество оправданных применений компрессии. Иногда записанная музыка имеет настолько широкий динамический диапазон, что некоторые фрагменты звучат так тихо или громко, что слушателю постоянно приходится держать палец на ручке громкости. Компрессия может сделать тише громкие участки и поднять громкость тихих участков так, чтобы они стали слышимы. На Рисунке 3-6 изображена исходная волна (сверху) и та же самая волна с применённой компрессией (внизу). Можно увидеть, что звук стал в целом громче, а в амплитуде стало меньше отличающихся участков.

Рисунок 3-6. Эффект от динамической компрессии
Рисунок 3-6. Эффект от динамической компрессии

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

Компрессоры могут быть смоделированы с помощью кривой компрессии с несколькими параметрами, каждый из которых можно настраивать в Web Audio API. Два основных параметра компрессии — это порог и коэффициент сжатия. Порог отвечает за значение самой низкой громкости, с которого компрессор начнёт уменьшать динамический диапазон. Коэффициент сжатия определяет степень снижения усиления, применяемую компрессором. На Рисунке 3-7 показано влияние порога и различных коэффициентов сжатия на кривую компрессии.

Рисунок 3-7. Пример кривой компрессии с базовыми параметрами
Рисунок 3-7. Пример кривой компрессии с базовыми параметрами

Динамическая компрессия

Компрессоры доступны в Web Audio API в виде узлов DynamicsCompressorNode. Использование умеренного количества динамической компрессии в вашем миксе является хорошей идеей, особенно в играх, в которых, как ранее упоминалось, вы не знаете заранее какие звуки будут проиграны и когда. Случай, в котором компрессия нежелательна, — это если вы имеете дело с кропотливо отмастеренными треками, которые уже настроены на правильное звучание и не микшируются с другими треками.

Реализация динамической компрессии в Web Audio API доступна через подключение узла динамической компрессии к вашему аудиографу, обычно в виде последнего узла перед аудиовыходом:

var compressor = context.createDynamicsCompressor();

mix.connect(compressor);
compressor.connect(context.destination);

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