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