Пример использования: JAM с Chrome

Как мы сделали звук потрясающим

Введение

JAM with Chrome — это музыкальный веб-проект, созданный Google. JAM с Chrome позволяет людям со всего мира создавать группы и играть в реальном времени прямо в браузере. Мы в DinahMoe имели огромное удовольствие принять участие в этом проекте. Наша роль заключалась в создании музыки для приложения, а также в проектировании и разработке музыкальной составляющей. Разработка состояла из трех основных областей: « музыкальная рабочая станция », включающая воспроизведение MIDI, программные сэмплеры, аудиоэффекты, маршрутизацию и микширование; музыкальный логический движок для интерактивного управления музыкой в ​​реальном времени; и компонент синхронизации, который гарантирует, что все игроки в сеансе слышат музыку точно в одно и то же время, что является необходимым условием для совместной игры.

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

Написание собственных звуковых эффектов

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

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

var MyCustomNode = function(){
    this.input = audioContext.createGain();
    var output = audioContext.createGain();

    this.connect = function(target){
       output.connect(target);
    };
};

С этим шаблоном мы очень близки к собственным узлам. Давайте посмотрим, как это будет использоваться.

//create a couple of native nodes and our custom node
var gain = audioContext.createGain(),
    customNode = new MyCustomNode(),
    anotherGain = audioContext.createGain();

//connect our custom node to the native nodes and send to the output
gain.connect(customNode.input);
customNode.connect(anotherGain);
anotherGain.connect(audioContext.destination);
Маршрутизация пользовательского узла

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

Теперь, когда у нас есть базовый шаблон для создания пользовательских эффектов, следующим шагом было придание пользовательскому узлу некоторого пользовательского поведения. Давайте посмотрим на узел задержки Slapback.

Пощечина, как ты это имеешь в виду

Задержка slapback, иногда называемая slapback echo, представляет собой классический эффект, используемый на ряде инструментов, от вокала в стиле 50-х до гитар для серфинга. Эффект принимает входящий звук и воспроизводит его копию с небольшой задержкой примерно 75-250 миллисекунд. Это создает ощущение, что звук возвращается обратно, отсюда и название. Мы можем создать такой эффект:

var SlapbackDelayNode = function(){
    //create the nodes we'll use
    this.input = audioContext.createGain();
    var output = audioContext.createGain(),
        delay = audioContext.createDelay(),
        feedback = audioContext.createGain(),
        wetLevel = audioContext.createGain();

    //set some decent values
    delay.delayTime.value = 0.15; //150 ms delay
    feedback.gain.value = 0.25;
    wetLevel.gain.value = 0.25;

    //set up the routing
    this.input.connect(delay);
    this.input.connect(output);
    delay.connect(feedback);
    delay.connect(wetLevel);
    feedback.connect(delay);
    wetLevel.connect(output);

    this.connect = function(target){
       output.connect(target);
    };
};
Внутренняя маршрутизация узла slapback

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

Маршрутизация звука

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

Маршрутизация AudioBus

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

var AudioBus = function(){
    this.input = audioContext.createGain();
    var output = audioContext.createGain();

    //create effect nodes (Convolver and Equalizer are other custom effects from the library presented at the end of the article)
    var delay = new SlapbackDelayNode(),
        convolver = new tuna.Convolver(),
        equalizer = new tuna.Equalizer();

    //route 'em
    //equalizer -> delay -> convolver
    this.input.connect(equalizer);
    equalizer.connect(delay.input);
    delay.connect(convolver);
    convolver.connect(output);

    this.connect = function(target){
       output.connect(target);
    };
};

Это будет использоваться следующим образом:

//create some native oscillators and our custom audio bus
var bus = new AudioBus(),
    instrument1 = audioContext.createOscillator(),
    instrument2 = audioContext.createOscillator(),
    instrument3 = audioContext.createOscillator();

//connect our instruments to the same bus
instrument1.connect(bus.input);
instrument2.connect(bus.input);
instrument3.connect(bus.input);
bus.connect(audioContext.destination);

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

Куда отсюда?

Эти методы, которые я здесь описал, могут и должны развиваться дальше. Такие вещи, как ввод и вывод пользовательских узлов и методы подключения, могут/должны быть реализованы с использованием наследования на основе прототипов. Автобусы должны иметь возможность динамически создавать эффекты, передавая им список эффектов.

Чтобы отпраздновать выпуск JAM с Chrome, мы решили сделать нашу среду эффектов открытым исходным кодом . Если это краткое введение заинтересовало вас, пожалуйста, посмотрите и не стесняйтесь внести свой вклад. Здесь идет дискуссия относительно стандартизации формата для пользовательских объектов веб-аудио. Принимайте участие!