O objeto de áudio agora está disponível por padrão

O Chrome 64 vem com um novo recurso muito esperado da API Web Audio: AudioWorklet (em inglês). Aqui, você vai aprender conceitos e usos para criar um processador de áudio personalizado com código JavaScript. Dê uma olhada no demonstrações ao vivo. O próximo artigo da série, Padrão de design de worklet de áudio, pode ser uma leitura interessante para criar um app de áudio avançado.

Segundo plano: ScriptProcessorNode

O processamento de áudio na API Web Audio é executado em uma linha de execução separada do principal linha de execução de IU, para que funcione sem problemas. Para ativar o processamento de áudio personalizado no JavaScript, a API Web Audio propôs um ScriptProcessorNode que usava manipuladores de eventos para invocar o script de usuário na linha de execução de interface principal.

Há dois problemas nesse design: o tratamento de eventos é assíncrono por design, e a execução do código acontece na linha de execução principal. O primeiro induz a latência, e esta última pressiona a linha de execução principal muito ocupados com várias tarefas relacionadas à interface e ao DOM, o que causa para "instabilidade" ou o áudio para "falha". Devido a essa falha fundamental no design, O uso de ScriptProcessorNode foi descontinuado na especificação e substituído por AudioWorklet.

Conceitos

O Worklet de áudio mantém o código JavaScript fornecido pelo usuário linha de execução de processamento de áudio. Isso significa que ele não precisa acessar a página principal para processar o áudio. Isso significa que o código do script fornecido pelo usuário na linha de execução de renderização de áudio (AudioWorkletGlobalScope) junto a outros AudioNodes integrado, o que garante zero latência adicional e controles renderização.

Escopo global principal e diagrama do escopo de Worklet de áudio
Fig.1

Registro e instanciação

O uso do Worklet de áudio consiste em duas partes: AudioWorkletProcessor e AudioWorkletNode. Isso é mais complexo do que usar ScriptProcessorNode, mas é necessário para que os desenvolvedores tenham a capacidade de baixo nível de recursos de áudio processamento. AudioWorkletProcessor representa o processador de áudio real. escritos em código JavaScript e residem no AudioWorkletGlobalScope. AudioWorkletNode é a contraparte de AudioWorkletProcessor e usa cuida da conexão de e para outros AudioNodes na linha de execução principal. Ela é exposto no escopo global principal e funciona como um AudioNode normal.

Aqui está um par de snippets de código que demonstram o registro e os instanciação.

// 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 criar um AudioWorkletNode, adicione um AudioContext. objeto e o nome do processador como uma string. Uma definição de processador pode ser carregado e registrado pela chamada addModule() do novo objeto "AudioWorklet". As APIs de Worklet, incluindo a Audio Worklet, só estão disponíveis em contexto seguro, portanto, que os usa deve ser exibido por HTTPS, embora http://localhost seja considerados seguros para testes locais.

Você pode criar uma subclasse AudioWorkletNode para definir um nó personalizado apoiado pelo processador em execução no 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);

O método registerProcessor() no AudioWorkletGlobalScope usa uma string para o nome do processador a ser registrado e a definição da classe. Após a conclusão da avaliação do código de script no escopo global, promessa de AudioWorklet.addModule() será resolvida notificando os usuários que a definição da classe está pronta para ser usada no escopo global principal.

Parâmetros de áudio personalizados

Uma das coisas úteis sobre os AudioNodes é o parâmetro programável automação com AudioParam. Os AudioWorkletNodes podem usá-los para parâmetros expostos que podem ser controlados automaticamente pela taxa de áudio.

Diagrama do processador e do nó de worklet de áudio
Fig.2

Os parâmetros de áudio definidos pelo usuário podem ser declarados em um AudioWorkletProcessor. a definição da classe configurando um conjunto de AudioParamDescriptor. A o mecanismo WebAudio subjacente coleta essas informações durante a construção de um AudioWorkletNode e, em seguida, cria e vincula AudioParam ao nó adequadamente.

/* 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()

O processamento de áudio acontece no método de callback process() na AudioWorkletProcessor. Ele precisa ser implementado por um usuário da classe. definição. O mecanismo WebAudio invoca essa função em uma para alimentar entradas e parâmetros, e buscar saídas.

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

Além disso, o valor de retorno do método process() pode ser usado para controlar o ciclo de vida de AudioWorkletNode para que os desenvolvedores possam gerenciar o consumo de memória. Como retornar false de marcas de método process() o processador está inativo e o mecanismo WebAudio não invoca mais o . Para manter o processador ativo, o método precisa retornar true. Caso contrário, o par de nó e processador vai ser coletado pelo sistema. mais tarde.

Comunicação bidirecional com o MessagePort

Às vezes, uma AudioWorkletNode personalizada quer expor controles que não mapear para AudioParam, como um atributo type baseado em string usada para controlar um filtro personalizado. Para esse propósito e além, AudioWorkletNode e AudioWorkletProcessor estão equipados com um MessagePort para comunicação bidirecional. Qualquer tipo de dados personalizados podem ser trocadas por este canal.

Fig.2
Fig.2

O MessagePort pode ser acessado com o atributo .port no nó e do processador. O método port.postMessage() do nó envia uma mensagem para no gerenciador port.onmessage do processador associado e vice-versa.

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

O MessagePort oferece suporte a transferíveis, o que permite armazenamento de dados transferidos por transferência ou um módulo WASM acima do limite do encadeamento. Isso abre inúmeras possibilidades de uso do sistema Worklet de áudio.

Tutorial: criar um GainNode

Aqui está um exemplo completo do GainNode criado com base AudioWorkletNode e AudioWorkletProcessor.

O arquivo 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>

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

Isso aborda os fundamentos do sistema de Objetos de Áudio. Há demonstrações ao vivo disponíveis no repositório GitHub da equipe do Chrome WebAudio.

Transição de recursos: de experimental para estável

O Worklet de áudio é ativado por padrão no Chrome 66 ou posterior. No Chrome 64 e 65, o recurso estava por trás da sinalização experimental.