Actualizaciones de audio web en Chrome 49

Chris Wilson
Chris Wilson

Chrome ha mejorado de forma constante y silenciosa su compatibilidad con la API de Web Audio. En Chrome 49 (beta a partir de febrero de 2016 y se espera que sea estable en marzo de 2016), actualizamos varias funciones para hacer un seguimiento de las especificaciones y también agregamos un nodo nuevo.

decodeAudioData() ahora muestra una promesa.

El método decodeAudioData() en AudioContext ahora muestra un Promise, lo que habilita el control de patrones asíncronos basados en promesas. El método decodeAudioData() siempre tomó las funciones de devolución de llamada de éxito y error como parámetros:

context.decodeAudioData( arraybufferData, onSuccess, onError);

Pero ahora puedes usar el método Promise estándar para controlar la naturaleza asíncrona de la decodificación de datos de audio:

context.decodeAudioData( arraybufferData ).then(
        (buffer) => { /* store the buffer */ },
        (reason) => { console.log("decode failed! " + reason) });

Aunque en un solo ejemplo esto se vea más detallado, las promesas hacen que la programación asíncrona sea más fácil y coherente. Para garantizar la compatibilidad, las funciones de devolución de llamada de éxito y error aún son compatibles, según la especificación.

OfflineAudioContext ahora admite suspend() y resume().

A primera vista, puede parecer extraño tener suspend() en un OfflineAudioContext. Después de todo, suspend() se agregó a AudioContext para habilitar el modo de espera del hardware de audio, lo que parece inútil en situaciones en las que se renderiza a un búfer (para lo que sirve OfflineAudioContext, por supuesto). Sin embargo, el objetivo de esta función es poder construir solo una parte de una “puntuación” a la vez para minimizar el uso de memoria. Puedes crear más nodos mientras están suspendidos en medio de una renderización.

A modo de ejemplo, la Sonata Claro de Luna de Beethoven contiene alrededor de 6,500 notas. Es probable que cada "nota" se deconstruya en al menos un par de nodos de gráfico de audio (p.ej., un AudioBuffer y un nodo Gain). Si deseas renderizar los siete minutos y medio en un búfer con OfflineAudioContext, es probable que no quieras crear todos esos nodos a la vez. En su lugar, puedes crearlas en fragmentos de tiempo:

var context = new OfflineAudioContext(2, length, sampleRate);
scheduleNextBlock();
context.startRendering().then( (buffer) => { /* store the buffer */ } );

function scheduleNextBlock() {
    // create any notes for the next blockSize number of seconds here
    // ...

    // make sure to tell the context to suspend again after this block;
    context.suspend(context.currentTime + blockSize).then( scheduleNextBlock );

    context.resume();
}

Esto te permitirá minimizar la cantidad de nodos que se deben crear previamente al comienzo de la renderización y disminuir las demandas de memoria.

IIRFilterNode

La especificación agregó un nodo para los audiófilos que quieran crear su propia respuesta de impulso infinito especificada con precisión: IIRFilterNode. Este filtro complementa BiquadFilterNode, pero permite la especificación completa de los parámetros de respuesta del filtro (en lugar del AudioParams fácil de usar de BiquadFilterNode para el tipo, la frecuencia, Q y otros). IIRFilterNode permite la especificación precisa de filtros que no se podían crear antes, como los filtros de un solo orden. Sin embargo, el uso de IIRFilterNode requiere un conocimiento profundo de cómo funcionan los filtros IIR y tampoco se pueden programar como BiquadFilterNodes.

Cambios anteriores

También quiero mencionar algunas mejoras que se implementaron anteriormente: en Chrome 48, la automatización de nodos BiquadFilter comenzó a ejecutarse a la velocidad de audio. La API no cambió en absoluto para hacer esto, pero esto significa que los barridos de filtros sonarán aún más fluidos. También en Chrome 48, agregamos el encadenamiento al método AudioNode.connect() mostrando el nodo al que nos conectamos. Esto facilita la creación de cadenas de nodos, como en este ejemplo:

sourceNode.connect(gainNode).connect(filterNode).connect(context.destination);

Eso es todo por ahora. ¡Sigue rockeando!