Caso de éxito: JAM con Chrome

Cómo logramos que el audio sea rock

Introducción

JAM con Chrome es un proyecto musical basado en la Web creado por Google. JAM con Chrome permite a personas de todo el mundo formar una banda y improvisar en tiempo real dentro del navegador. En DinahMoe, tuvimos el gran placer de formar parte de este proyecto. Nuestra función era producir música para la aplicación, y diseñar y desarrollar el componente musical. El desarrollo constaba de tres áreas principales: una "estación de trabajo de música" que incluye reproducción de MIDI, muestras de software, efectos de audio, enrutamiento y mezclas; un motor de lógica musical para controlar la música de forma interactiva en tiempo real y un componente de sincronización que garantiza que todos los jugadores de una sesión escuchen la música al mismo tiempo, un requisito previo para poder tocar juntos.

Para lograr el mayor nivel posible de autenticidad, precisión y calidad de audio, elegimos usar la API de Web Audio. En este caso práctico, analizaremos algunos de los desafíos que se nos presentaron y cómo los resolvimos. Ya hay una gran cantidad de excelentes artículos introductorios aquí en HTML5Rocks para que puedas comenzar a usar Web Audio, así que pasaremos directamente al extremo más profundo de la piscina.

Escritura de efectos de audio personalizados

La API de Web Audio tiene varios efectos útiles incluidos en la especificación, pero necesitábamos efectos más elaborados para nuestros instrumentos en JAM con Chrome. Por ejemplo, hay un nodo de retraso nativo en Web Audio, pero hay muchos tipos de retrasos: retraso estéreo, ping pong, retraso de slapback y la lista continúa. Afortunadamente, se pueden crear todos estos elementos en Web Audio utilizando los nodos de efectos nativos y algo de imaginación.

Como queríamos poder usar los nodos nativos y nuestros propios efectos personalizados de la manera más transparente posible, decidimos que necesitábamos crear un formato wrapper que pudiera lograr esto. Los nodos nativos en Web Audio usan su método de conexión para vincular nodos, por lo que tuvimos que emular este comportamiento. Así se ve la idea básica:

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

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

Con este patrón, estamos muy cerca de los nodos nativos. Veamos cómo se usaría esto.

//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);
Enrutamiento del nodo personalizado

La única diferencia entre nuestro nodo personalizado y uno nativo es que tenemos que conectarnos a la propiedad de entrada de nodos personalizados. Estoy seguro de que hay formas de eludir esto, pero esto fue lo suficientemente cercano para nuestros fines. Este patrón se puede desarrollar aún más para simular los métodos de desconexión de AudioNodes nativos, así como para adaptar las entradas y salidas definidas por el usuario cuando se conectan, y así sucesivamente. Consulta la especificación para saber qué pueden hacer los nodos nativos.

Ahora que ya teníamos nuestro patrón básico para crear efectos personalizados, el siguiente paso era darle al nodo personalizado un comportamiento personalizado. Veamos un nodo de retraso de slapback.

Baila sin parar

El slapback eco, a veces llamado eco slapback, es un efecto clásico que se usa en varios instrumentos, desde voces al estilo de los 50 hasta guitarras de surf. El efecto toma el sonido entrante y reproduce una copia del sonido con una leve demora de aproximadamente 75 a 250 milisegundos. Esto da una idea del sonido que se golpea, así como el nombre. Podemos crear el efecto de la siguiente manera:

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);
    };
};
Enrutamiento interno del nodo de slapback

Como algunos de ustedes ya se dieron cuenta, esta demora también se podría usar con tiempos de demora mayores y, por lo tanto, convertirse en un retraso mono normal con comentarios. A continuación, te mostramos un ejemplo en el que se usa este retraso para que escuches cómo suena.

Audio de ruta

Cuando trabajas con diferentes instrumentos y partes musicales en aplicaciones de audio profesionales, es esencial tener un sistema de enrutamiento flexible que te permita mezclar y modular los sonidos de manera eficaz. En JAM con Chrome, desarrollamos un sistema de bus de audio similar a los que se encuentran en las mezcladoras físicas. Esto nos permite conectar todos los instrumentos que necesiten un efecto de reverberación a un bus o canal común y luego agregar la reverberación a ese bus en lugar de agregar una a cada instrumento por separado. Esta es una optimización importante y se recomienda hacer algo similar no bien comiences a hacer aplicaciones más complejas.

Enrutamiento del AudioBus

Afortunadamente, esto es muy fácil de lograr en Web Audio. Básicamente, podemos usar el esqueleto que definimos para los efectos y usarlo de la misma manera.

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);
    };
};

Se usaría de la siguiente manera:

//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);

¡Listo! Aplicamos retraso, ecualización y reverberación (un efecto bastante costoso en términos de rendimiento) a la mitad del costo, como si hubiéramos aplicado los efectos a cada instrumento por separado. Si quisiéramos agregar un poco de emoción al bus, podríamos agregar dos nuevos nodos de ganancia, preGain y posGain, que nos permitirían desactivar o atenuar los sonidos en un bus de dos maneras diferentes. La ganancia previa se coloca antes que los efectos y postGain se coloca al final de la cadena. Si luego se atenúa la ganancia previa, los efectos seguirán resonando después de que la ganancia alcance el fondo, pero si lo atenuamos con posGain, todo el sonido se silenciará al mismo tiempo.

¿A dónde quieres ir desde aquí?

Estos métodos que describo aquí pueden, y deben, desarrollarse aún más. Elementos como la entrada y la salida de los nodos personalizados y los métodos de conexión podrían o deben implementarse usando la herencia basada en prototipos. Los autobuses deben poder crear efectos de forma dinámica al recibir una lista de efectos.

Para celebrar el lanzamiento de JAM con Chrome, decidimos que nuestro marco de trabajo de efectos sea de código abierto. Si esta breve introducción te deslumbró, echa un vistazo y no dudes en contribuir. Aquí se analiza cómo estandarizar un formato para entidades de audio web personalizadas. Participa