Actualizaciones de audio web en Chrome 49

Cristian Rodríguez
Chris Wilson

Chrome ha mejorado de forma constante y silenciosa su compatibilidad con la API Web Audio. En Chrome 49 (versión beta a partir de febrero de 2016 y esperamos que sean estables en marzo de 2016), actualizamos varias funciones para realizar un seguimiento de la especificación y, además, 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 basado en promesas. El método decodeAudioData() siempre ha tomado las funciones de devolución de llamada de éxito y error como parámetros:

context.decodeAudioData( arraybufferData, onSuccess, onError);

Sin embargo, ahora puedes usar el método de promesa 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 parece más detallado, las promesas hacen que la programación asíncrona sea más fácil y coherente. Para la compatibilidad, las funciones de devolución de llamada Success y Error aún son compatibles, según la especificación.

Sin conexiónAudioContext ahora es compatible con suspend() y reanudación()

A primera vista, puede parecer extraño tener suspend() en un elemento OfflineAudioContext. Después de todo, se agregó suspend() a AudioContext para habilitar la puesta en modo de espera del hardware de audio, lo que no tiene sentido en situaciones en las que realizas renderizaciones en un búfer (que es para qué 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.

Por ejemplo, la Sonata de luz de luna de Beethoven contiene alrededor de 6,500 notas. Es probable que cada "nota" se deconstruya 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 completos en un búfer con OfflineAudioContext, es probable que no quieras crear todos esos nodos a la vez. En cambio, 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 deben crearse previamente al comienzo del procesamiento y disminuir las demandas de memoria.

IIRFilterNode

En la especificación, se agregó un nodo para los audiófilos que desean crear su propia respuesta de impulso infinito especificada de forma precisa: IIRFilterNode. Este filtro complementa el 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 similares). IIRFilterNode permite la especificación precisa de filtros que no se podían crear antes, como los de un solo orden. Sin embargo, usar IIRFilterNode requiere un conocimiento profundo de cómo funcionan los filtros de IIR y tampoco son programables como los BiquadFilterNodes.

Cambios anteriores

También quiero mencionar un par de mejoras que se implementaron anteriormente: en Chrome 48, la automatización de nodos BiquadFilter comenzó a ejecutarse a velocidad de audio. La API no cambió en absoluto, pero esto significa que tus 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 estamos conectando. 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 bailando!