Chrome 64 dilengkapi dengan fitur baru yang sangat dinantikan di Web Audio API - AudioWorklet. Di sini, Anda akan mempelajari konsep dan penggunaan untuk membuat pemroses audio kustom dengan kode JavaScript. Lihat demo live. Artikel berikutnya dalam seri ini, Pola Desain Worklet Audio, mungkin merupakan bacaan yang menarik untuk membuat aplikasi audio lanjutan.
Latar belakang: ScriptProcessorNode
Pemrosesan audio di Web Audio API berjalan dalam thread terpisah dari thread UI utama, sehingga berjalan dengan lancar. Untuk mengaktifkan pemrosesan audio kustom di JavaScript, Web Audio API mengusulkan ScriptProcessorNode yang menggunakan pengendali peristiwa untuk memanggil skrip pengguna di thread UI utama.
Ada dua masalah dalam desain ini: penanganan peristiwa bersifat asinkron
karena desain, dan eksekusi kode terjadi di thread utama. Yang pertama
menyebabkan latensi, dan yang kedua menekan thread utama yang
biasanya penuh dengan berbagai tugas terkait UI dan DOM yang menyebabkan UI "terganggu" atau audio "bermasalah". Karena kekurangan desain mendasar ini,
ScriptProcessorNode
tidak digunakan lagi dari spesifikasi dan
diganti dengan AudioWorklet.
Konsep
Worklet Audio menyimpan kode JavaScript yang disediakan pengguna dalam
thread pemrosesan audio. Artinya, tidak perlu beralih ke thread
utama untuk memproses audio. Ini berarti kode skrip yang disediakan pengguna akan dijalankan
di thread rendering audio (AudioWorkletGlobalScope
) bersama dengan
AudioNodes
bawaan lainnya, yang memastikan latensi tambahan nol dan rendering
sinkron.
Pendaftaran dan pembuatan instance
Menggunakan Audio Worklet terdiri dari dua bagian: AudioWorkletProcessor
dan
AudioWorkletNode
. Hal ini lebih rumit daripada menggunakan ScriptProcessorNode,
tetapi diperlukan untuk memberi developer kemampuan tingkat rendah untuk pemrosesan
audio kustom. AudioWorkletProcessor
mewakili pemroses audio sebenarnya
yang ditulis dalam kode JavaScript, dan berada di AudioWorkletGlobalScope
.
AudioWorkletNode
adalah pasangan dari AudioWorkletProcessor
dan menangani
koneksi ke dan dari AudioNodes
lain di thread utama. Fungsi ini
diekspos dalam cakupan global utama dan berfungsi seperti AudioNode
reguler.
Berikut adalah sepasang cuplikan kode yang menunjukkan pendaftaran dan pembuatan instance.
// 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);
});
Untuk membuat AudioWorkletNode
, Anda harus menambahkan objek
AudioContext dan nama pemroses sebagai string. Definisi prosesor dapat
dimuat dan didaftarkan oleh panggilan addModule()
objek Audio Worklet baru.
Worklet API termasuk Audio Worklet hanya tersedia dalam
konteks aman, sehingga
halaman yang menggunakannya harus ditayangkan melalui HTTPS, meskipun http://localhost
dianggap aman untuk pengujian lokal.
Anda dapat membuat subclass AudioWorkletNode
untuk menentukan
node kustom yang didukung oleh prosesor yang berjalan di 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);
Metode registerProcessor()
di AudioWorkletGlobalScope
menggunakan
string untuk nama pemroses yang akan didaftarkan dan definisi class.
Setelah evaluasi kode skrip selesai dalam cakupan global, promise dari AudioWorklet.addModule()
akan di-resolve untuk memberi tahu pengguna bahwa definisi class siap digunakan dalam cakupan global utama.
Parameter audio kustom
Salah satu hal yang berguna tentang AudioNodes adalah otomatisasi parameter
yang dapat dijadwalkan dengan AudioParam
. AudioWorkletNodes dapat menggunakannya untuk mendapatkan
parameter yang diekspos yang dapat dikontrol pada kecepatan audio secara otomatis.
Parameter audio yang ditentukan pengguna dapat dideklarasikan dalam definisi class
AudioWorkletProcessor
dengan menyiapkan kumpulan AudioParamDescriptor
. Mesin
WebAudio yang mendasarinya mengambil informasi ini selama
pembuatan AudioWorkletNode, lalu membuat dan menautkan
objek AudioParam
ke node yang sesuai.
/* 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.
}
}
}
Metode AudioWorkletProcessor.process()
Pemrosesan audio yang sebenarnya terjadi di metode callback process()
di
AudioWorkletProcessor
. Ini harus diterapkan oleh pengguna dalam
definisi class. Mesin WebAudio memanggil fungsi ini dengan cara isochronous
untuk memasukkan input dan parameter serta mengambil 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;
}
Selain itu, nilai yang ditampilkan dari metode process()
dapat digunakan untuk
mengontrol masa aktif AudioWorkletNode
sehingga developer dapat mengelola
jejak memori. Menampilkan false
dari metode process()
akan menandai
prosesor tidak aktif, dan mesin WebAudio
tidak lagi memanggil
metode. Agar prosesor tetap aktif, metode harus menampilkan true
.
Jika tidak, pasangan node dan prosesor akan dihapus oleh sistem
pada akhirnya.
Komunikasi dua arah dengan MessagePort
Terkadang, AudioWorkletNode
kustom ingin mengekspos kontrol yang tidak
dipetakan ke AudioParam
, seperti atribut type
berbasis string
yang digunakan untuk mengontrol filter kustom. Untuk tujuan ini dan lainnya,
AudioWorkletNode
dan AudioWorkletProcessor
dilengkapi dengan
MessagePort
untuk komunikasi dua arah. Semua jenis data kustom
dapat dipertukarkan melalui saluran ini.
MessagePort dapat diakses dengan atribut .port
di node dan
prosesor. Metode port.postMessage()
node mengirim pesan ke
pengendali port.onmessage
prosesor terkait dan sebaliknya.
/* 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
mendukung transfer, yang memungkinkan Anda
mentransfer penyimpanan data atau modul WASM melalui batas thread. Hal ini membuka
kemungkinan yang tak terhitung tentang cara sistem Worklet Audio dapat digunakan.
Panduan: Membuat GainNode
Berikut adalah contoh lengkap GainNode yang dibuat di atas
AudioWorkletNode
dan AudioWorkletProcessor
.
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>
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);
Bagian ini membahas dasar-dasar sistem Audio Worklet. Demo langsung tersedia di repositori GitHub tim WebAudio Chrome.
Transisi fitur: Eksperimental ke Stabil
Worklet Audio diaktifkan secara default untuk Chrome 66 atau yang lebih baru. Di Chrome 64 dan 65, fitur ini berada di balik flag eksperimental.