L'audio Worklet è ora disponibile per impostazione predefinita

Hongchan Choi

Chrome 64 include una nuova funzionalità molto attesa nell'API Web Audio: AudioWorklet. Imparerai i concetti di base e l'utilizzo per creare con un processore audio personalizzato con codice JavaScript. Dai un'occhiata dimostrazioni dal vivo. L'articolo successivo della serie, Audio Worklet Design Pattern, potrebbe essere una lettura interessante per sviluppare un'app audio avanzata.

Spiegazione: ScriptProcessorNode

L'elaborazione dell'audio nell'API Web Audio viene eseguita in un thread separato da quello principale Thread UI, per un funzionamento ottimale. Per attivare l'elaborazione audio personalizzata in JavaScript, l'API Web Audio ha proposto uno ScriptProcessorNode che utilizzava per richiamare lo script utente nel thread principale dell'interfaccia utente.

Questa progettazione presenta due problemi: la gestione degli eventi è asincrona e l'esecuzione del codice avviene sul thread principale. Il primo causa la latenza e quest'ultima fa pressione sul thread principale che è spesso affollate di varie attività relative all'interfaccia utente e al DOM che causano a "jank" o audio in "glitch". A causa di questo fondamentale difetto di progettazione, ScriptProcessorNode è deprecato dalla specifica e sostituito con AudioWorklet.

Concetti

Audio Worklet conserva il codice JavaScript fornito dall'utente all'interno thread di elaborazione audio. Ciò significa che non deve passare direttamente alla thread per elaborare l'audio. Ciò significa che il codice dello script fornito dall'utente viene eseguito sul thread del rendering audio (AudioWorkletGlobalScope) insieme ad altri AudioNodes integrato, che garantisce latenza sincrona e zero latenza aggiuntiva per il rendering delle immagini.

Diagramma dell'ambito globale principale e dell'ambito del worklet audio
. Fig.1

Registrazione e creazione di istanze

L'utilizzo del worklet audio è costituito da due parti: AudioWorkletProcessor e AudioWorkletNode. Questa operazione è più complessa dell'utilizzo di ScriptProcessorNode, ma è necessario per offrire agli sviluppatori la capacità di basso livello per e l'elaborazione dei dati. AudioWorkletProcessor rappresenta l'effettivo processore audio è scritto nel codice JavaScript e si trova nel file AudioWorkletGlobalScope. AudioWorkletNode è la controparte di AudioWorkletProcessor e richiede della connessione da e verso altri AudioNodes nel thread principale. it è esposto nell'ambito globale principale e funziona come un normale AudioNode.

Ecco una coppia di snippet di codice che dimostrano la registrazione e e creazione di un'istanza.

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

Per creare un AudioWorkletNode, devi aggiungere un AudioContext e il nome del processore come stringa. Una definizione di processore può essere caricati e registrati dalla chiamata addModule() del nuovo oggetto Audio Worklet. Le API Worklet, incluso il Worklet audio, sono disponibili solo in un contesto sicuro, la pagina che le utilizza deve essere pubblicata tramite HTTPS, sebbene http://localhost sia considerata sicura per i test locali.

Puoi definire la sottoclasse AudioWorkletNode un nodo personalizzato supportato dal processore in esecuzione sul 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);

Il metodo registerProcessor() in AudioWorkletGlobalScope utilizza un stringa per il nome del processore da registrare e la definizione della classe. Dopo il completamento della valutazione del codice dello script in ambito globale, la promessa di AudioWorklet.addModule() verrà risolta quando invii una notifica agli utenti che la definizione della classe sia pronta per essere usata nell'ambito globale principale.

Parametri audio personalizzati

Uno degli aspetti utili di AudioNodes è il parametro pianificabile automatica con AudioParam. AudioWorkletNodes può usarli per ottenere parametri esposti che possono essere controllati automaticamente alla velocità audio.

Diagramma del nodo e del processore del worklet audio
. Fig.2

I parametri audio definiti dall'utente possono essere dichiarati in un AudioWorkletProcessor definizione della classe mediante un insieme di AudioParamDescriptor. La il motore WebAudio sottostante raccoglie queste informazioni durante di un AudioWorkletNode, quindi crea e collega AudioParam di oggetti nel nodo di conseguenza.

/* 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.
    }
  }
}

Metodo AudioWorkletProcessor.process()

L'elaborazione effettiva dell'audio avviene nel metodo di callback process() nella sezione AudioWorkletProcessor. Deve essere implementato da un utente del corso definizione di Kubernetes. Il motore WebAudio richiama questa funzione in modo sincronizzato per alimentare input e parametri e recuperare gli output.

/* 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;
}

Inoltre, il valore restituito del metodo process() può essere utilizzato per controllare l'intera durata di AudioWorkletNode per consentire agli sviluppatori l'impronta di memoria. Restituzione di false da process() segni di metodo processore non attivo e il motore WebAudio non richiama più . Per mantenere attivo il processore, il metodo deve restituire true. In caso contrario, la coppia di nodo e processore viene garbage collection dal sistema. e alla fine.

Comunicazione bidirezionale con MessagePort

A volte, un AudioWorkletNode personalizzato vuole esporre controlli che non viene mappato a AudioParam, ad esempio un attributo type basato su stringa utilizzata per controllare un filtro personalizzato. A questo scopo e non solo, AudioWorkletNode e AudioWorkletProcessor dispongono di un MessagePort per la comunicazione bidirezionale. Qualsiasi tipo di dati personalizzati possono essere scambiati tramite questo canale.

Fig.2
. Fig.2

È possibile accedere a MessagePort con l'attributo .port sia sul nodo che per il processore. Il metodo port.postMessage() del nodo invia un messaggio a al gestore port.onmessage del processore associato e 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 supporta il trasferimento, che ti consente dell'archiviazione dei dati o di un modulo WASM al limite del thread. Si apre e offre innumerevoli possibilità di utilizzo del sistema Audio Worklet.

Procedura dettagliata: creazione di un GainNode

Ecco un esempio completo di GainNode basato su AudioWorkletNode e AudioWorkletProcessor.

Il file index.html:

<!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>

Il file gain-processor.js:

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

Questa lezione tratterà le nozioni fondamentali del sistema Audio Worklet. Sono disponibili demo dal vivo nel repository GitHub del team Chrome WebAudio.

Transizione delle funzionalità: da sperimentale a stabile

Audio Worklet è abilitato per impostazione predefinita per Chrome 66 o versioni successive. Nelle versioni 64 e 65 di Chrome, la funzionalità era dietro il flag sperimentale.