Worklet de audio ahora está disponible de forma predeterminada

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.

Diagrama del alcance global principal y del Worklet de audio
Fig.1
.

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.

Diagrama del procesador y el nodo del worklet de audio
Fig.2
.

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.

Fig.2
Fig.2
.

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.