Chrome 64 incluye una nueva función muy esperada en la API de Web Audio: AudioWorklet. Aquí, aprenderás conceptos y usos para crear una procesador de audio personalizado con código JavaScript. Consulta la demostraciones en vivo. En el siguiente artículo de la serie, Audio Worklet Design Pattern, puede ser una lectura interesante para compilar una app de audio avanzada.
Segundo plano: ScriptProcessorNode
El procesamiento de audio en la API de Web Audio se ejecuta en un subproceso independiente del principal subproceso de IU, para que se ejecute sin problemas. Para habilitar el procesamiento de audio personalizado en JavaScript, la API de Web Audio propuso un ScriptProcessorNode que usaba para invocar la secuencia de comandos del usuario en el subproceso de IU principal.
Este diseño presenta dos problemas: el manejo de eventos es asíncrono.
y la ejecución del código ocurre en el subproceso principal. El anterior
induce la latencia y esta última presiona el subproceso principal que se
Se suelen llenar con varias tareas relacionadas con la IU y el DOM, lo que provoca que la IU
a "bloqueo" o el audio a una “falla”. Debido a esta falla de diseño fundamental,
ScriptProcessorNode
dejó de estar disponible en la especificación.
y se reemplazó por AudioWorklet.
Conceptos
Audio Worklet mantiene el código JavaScript proporcionado por el usuario dentro de
subproceso de procesamiento de audio. Eso significa que no tiene que saltar a la parte principal
subproceso para procesar el audio. Esto significa que el código de secuencia de comandos
proporcionado por el usuario se ejecuta
en el subproceso de renderización de audio (AudioWorkletGlobalScope
) junto con otro
AudioNodes
integrado, que garantiza cero latencia adicional y generación
y procesamiento.
Registro y creación de instancias
El uso de Worklet de audio consta de dos partes: AudioWorkletProcessor
y
AudioWorkletNode
Esto es más involucrado que usar ScriptProcessorNode,
pero es necesario para brindarles a los desarrolladores la capacidad de bajo nivel de audio personalizado
el procesamiento de datos. AudioWorkletProcessor
representa el procesador de audio real.
escrita en código JavaScript, y se encuentra en AudioWorkletGlobalScope
.
AudioWorkletNode
es la contraparte de AudioWorkletProcessor
y toma
se encarga de la conexión hacia y desde otro AudioNodes
en el subproceso principal. Integra
se expone en el alcance global principal y funciona como un AudioNode
normal.
Este es un par de fragmentos de código que demuestran el registro y el la creación de instancias.
// The code in the main global scope.
class MyWorkletNode extends AudioWorkletNode {
constructor(context) {
super(context, 'my-worklet-processor');
}
}
let context = new AudioContext();
context.audioWorklet.addModule('processors.js').then(() => {
let node = new MyWorkletNode(context);
});
Para crear un AudioWorkletNode
, debes agregar un objeto AudioContext.
y el nombre del procesador como una cadena. Una definición de procesador puede ser
cargado y registrado por la llamada addModule()
del nuevo objeto Audio Worklet.
Las APIs de Worklet, incluido el Worklet de audio, solo están disponibles en un
contexto seguro y, de esta forma,
la página que las utiliza debe entregarse a través de HTTPS, aunque http://localhost
es
considerado como seguro para las pruebas locales.
Puedes crear la subclase AudioWorkletNode
para definir
un nodo personalizado respaldado por el procesador que se ejecuta en el worklet.
// This is the "processors.js" file, evaluated in AudioWorkletGlobalScope
// upon audioWorklet.addModule() call in the main global scope.
class MyWorkletProcessor extends AudioWorkletProcessor {
constructor() {
super();
}
process(inputs, outputs, parameters) {
// audio processing code here.
}
}
registerProcessor('my-worklet-processor', MyWorkletProcessor);
El método registerProcessor()
en AudioWorkletGlobalScope
toma un
para el nombre del procesador que se registrará y la definición de la clase.
Después de que finaliza la evaluación del código de secuencia de comandos en el alcance global, el
se resolverá la promesa de AudioWorklet.addModule()
y se notificará a los usuarios
que la definición de clase está lista para usarse en el alcance global principal.
Parámetros de audio personalizados
Uno de los aspectos útiles de AudioNodes es el parámetro programable
automatización con AudioParam
. AudioWorkletNodes puede usarlos para obtener
parámetros expuestos que se pueden controlar automáticamente a la velocidad del audio.
Los parámetros de audio definidos por el usuario se pueden declarar en un AudioWorkletProcessor
.
definición de la clase mediante la configuración de un conjunto de AudioParamDescriptor
. El
el motor de WebAudio subyacente recoge esta información durante la
de un objeto AudioWorkletNode y, luego, crea y vincula
AudioParam
al nodo según corresponda.
/* A separate script file, like "my-worklet-processor.js" */
class MyWorkletProcessor extends AudioWorkletProcessor {
// Static getter to define AudioParam objects in this custom processor.
static get parameterDescriptors() {
return [{
name: 'myParam',
defaultValue: 0.707
}];
}
constructor() { super(); }
process(inputs, outputs, parameters) {
// |myParamValues| is a Float32Array of either 1 or 128 audio samples
// calculated by WebAudio engine from regular AudioParam operations.
// (automation methods, setter) Without any AudioParam change, this array
// would be a single value of 0.707.
const myParamValues = parameters.myParam;
if (myParamValues.length === 1) {
// |myParam| has been a constant value for the current render quantum,
// which can be accessed by |myParamValues[0]|.
} else {
// |myParam| has been changed and |myParamValues| has 128 values.
}
}
}
Método AudioWorkletProcessor.process()
El procesamiento de audio real ocurre en el método de devolución de llamada process()
de la
AudioWorkletProcessor
La debe implementar un usuario de la clase.
definición. El motor de WebAudio invoca esta función en un formato
para proporcionar entradas y parámetros, y recuperar resultados.
/* AudioWorkletProcessor.process() method */
process(inputs, outputs, parameters) {
// The processor may have multiple inputs and outputs. Get the first input and
// output.
const input = inputs[0];
const output = outputs[0];
// Each input or output may have multiple channels. Get the first channel.
const inputChannel0 = input[0];
const outputChannel0 = output[0];
// Get the parameter value array.
const myParamValues = parameters.myParam;
// if |myParam| has been a constant value during this render quantum, the
// length of the array would be 1.
if (myParamValues.length === 1) {
// Simple gain (multiplication) processing over a render quantum
// (128 samples). This processor only supports the mono channel.
for (let i = 0; i < inputChannel0.length; ++i) {
outputChannel0[i] = inputChannel0[i] * myParamValues[0];
}
} else {
for (let i = 0; i < inputChannel0.length; ++i) {
outputChannel0[i] = inputChannel0[i] * myParamValues[i];
}
}
// To keep this processor alive.
return true;
}
Además, el valor que se muestra del método process()
se puede usar para lo siguiente:
controlar la vida útil de AudioWorkletNode
para que los desarrolladores puedan administrar
el uso de memoria. Cómo mostrar false
de marcas de método process()
el procesador está inactivo y el motor WebAudio
ya no invoca al
. Para mantener el procesador activo, el método debe mostrar true
.
De lo contrario, el sistema recolecta el par de nodos y procesadores como elementos no utilizados.
con el tiempo.
Comunicación bidireccional con MessagePort
A veces, un AudioWorkletNode
personalizado quiere exponer los controles que no lo hacen.
asignar a AudioParam
, como un atributo type
basado en cadenas
usarse para controlar un filtro personalizado. Para este fin y más allá,
AudioWorkletNode
y AudioWorkletProcessor
están equipados con un
MessagePort
para la comunicación bidireccional Cualquier tipo de datos personalizados
pueden intercambiarse a través de este canal.
Se puede acceder a MessagePort con el atributo .port
en el nodo y
el encargado del tratamiento de datos. El método port.postMessage()
del nodo envía un mensaje a
el controlador port.onmessage
del procesador asociado y viceversa.
/* The code in the main global scope. */
context.audioWorklet.addModule('processors.js').then(() => {
let node = new AudioWorkletNode(context, 'port-processor');
node.port.onmessage = (event) => {
// Handling data from the processor.
console.log(event.data);
};
node.port.postMessage('Hello!');
});
/* "processors.js" file. */
class PortProcessor extends AudioWorkletProcessor {
constructor() {
super();
this.port.onmessage = (event) => {
// Handling data from the node.
console.log(event.data);
};
this.port.postMessage('Hi!');
}
process(inputs, outputs, parameters) {
// Do nothing, producing silent output.
return true;
}
}
registerProcessor('port-processor', PortProcessor);
MessagePort
admite transferibles, lo que te permite
de transferencia de datos
o un módulo WASM sobre el límite del subproceso. Se abrirá
las innumerables posibilidades de cómo se puede utilizar el sistema Audio Worklet.
Explicación: Compila un GainNode
Este es un ejemplo completo de GainNode a partir de
AudioWorkletNode
y AudioWorkletProcessor
.
El archivo index.html
tiene las siguientes características:
<!doctype html>
<html>
<script>
const context = new AudioContext();
// Loads module script with AudioWorklet.
context.audioWorklet.addModule('gain-processor.js').then(() => {
let oscillator = new OscillatorNode(context);
// After the resolution of module loading, an AudioWorkletNode can be
// constructed.
let gainWorkletNode = new AudioWorkletNode(context, 'gain-processor');
// AudioWorkletNode can be interoperable with other native AudioNodes.
oscillator.connect(gainWorkletNode).connect(context.destination);
oscillator.start();
});
</script>
</html>
El archivo gain-processor.js
tiene las siguientes características:
class GainProcessor extends AudioWorkletProcessor {
// Custom AudioParams can be defined with this static getter.
static get parameterDescriptors() {
return [{ name: 'gain', defaultValue: 1 }];
}
constructor() {
// The super constructor call is required.
super();
}
process(inputs, outputs, parameters) {
const input = inputs[0];
const output = outputs[0];
const gain = parameters.gain;
for (let channel = 0; channel < input.length; ++channel) {
const inputChannel = input[channel];
const outputChannel = output[channel];
if (gain.length === 1) {
for (let i = 0; i < inputChannel.length; ++i)
outputChannel[i] = inputChannel[i] * gain[0];
} else {
for (let i = 0; i < inputChannel.length; ++i)
outputChannel[i] = inputChannel[i] * gain[i];
}
}
return true;
}
}
registerProcessor('gain-processor', GainProcessor);
Esto abarca los conceptos básicos del sistema Audio Worklet. Hay demostraciones en vivo disponibles en el repositorio de GitHub del equipo de Chrome WebAudio.
Transición de funciones: de experimento a estable
Worklet de audio está habilitado de forma predeterminada en Chrome 66 o versiones posteriores. En Chrome 64 y 65, la función estaba protegida por el indicador experimental.