Chrome 64 hadir 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 langsung. Artikel berikutnya dalam seri ini, Audio Worklet Design Pattern, dapat menjadi bacaan yang menarik untuk membangun aplikasi audio tingkat lanjut.
Latar belakang: ScriptProcessorNode
Pemrosesan audio di Web Audio API berjalan di thread terpisah dari thread utama UI thread, sehingga akan berjalan lancar. Untuk mengaktifkan pemrosesan audio kustom di JavaScript, Web Audio API mengusulkan ScriptProcessorNode yang menggunakan untuk memanggil skrip pengguna di thread UI utama.
Ada dua masalah dalam desain ini: penanganan peristiwa bersifat asinkron
sesuai desain, dan eksekusi kode
terjadi di thread utama. Mantan
memicu latensi, dan yang kedua menekan thread utama yang
biasanya dipenuhi dengan berbagai tugas terkait UI dan DOM yang menyebabkan UI
ke "jank" atau audio ke "glitch". Karena cacat desain
yang mendasar ini,
ScriptProcessorNode
tidak digunakan lagi dalam spesifikasi dan
diganti dengan AudioWorklet.
Konsep
Audio Worklet menyimpan semua kode JavaScript yang disediakan pengguna dalam
thread pemrosesan audio. Artinya, {i>wireframe<i} tidak perlu
melompat ke bagian utama
untuk memproses audio. Ini berarti kode skrip yang
disediakan pengguna akan dijalankan
di thread rendering audio (AudioWorkletGlobalScope
) bersama dengan
AudioNodes
bawaan, yang memastikan nol latensi tambahan dan
proses rendering.
Pendaftaran dan pembuatan instance
Penggunaan Worklet Audio terdiri dari dua bagian: AudioWorkletProcessor
dan
AudioWorkletNode
. Ini lebih rumit dibandingkan
menggunakan ScriptProcessorNode,
tetapi hal itu harus memberi developer
kemampuan tingkat rendah untuk audio kustom
diproses. AudioWorkletProcessor
merepresentasikan prosesor audio sebenarnya
ditulis dalam kode JavaScript, dan berada di AudioWorkletGlobalScope
.
AudioWorkletNode
adalah pasangan dari AudioWorkletProcessor
dan menggunakan
koneksi ke dan dari AudioNodes
lain di thread utama. Ini
diekspos dalam cakupan dan fungsi global utama seperti AudioNode
reguler.
Berikut ini sepasang cuplikan kode yang menunjukkan proses pendaftaran dan untuk 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 AudioContext
dan nama prosesor sebagai string. Definisi prosesor dapat
dimuat dan didaftarkan oleh panggilan addModule()
objek Audio Worklet baru.
API Worklet yang mencakup Audio Worklet hanya tersedia di
konteks yang aman, sehingga
halaman yang menggunakannya harus ditayangkan melalui HTTPS, meskipun http://localhost
dianggap aman untuk pengujian lokal.
Anda dapat membuat subclass AudioWorkletNode
untuk menentukan
{i>node<i} khusus 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
mengambil
untuk nama prosesor yang akan didaftarkan dan definisi class.
Setelah selesainya evaluasi kode skrip dalam cakupan global,
janji dari AudioWorklet.addModule()
akan diselesaikan dengan memberi tahu pengguna
bahwa definisi class tersebut siap digunakan dalam cakupan global utama.
Parameter audio kustom
Salah satu hal yang berguna tentang AudioNodes adalah parameter yang dapat dijadwalkan
otomatisasi dengan AudioParam
. AudioWorkletNodes dapat menggunakannya untuk mendapatkan
terbuka yang dapat dikontrol dengan kecepatan audio secara otomatis.
Parameter audio yang ditentukan pengguna dapat dideklarasikan di AudioWorkletProcessor
dengan menyiapkan kumpulan AudioParamDescriptor
. Tujuan
mesin WebAudio dasar mengambil informasi ini selama
pembuatan AudioWorkletNode, lalu membuat dan menautkan
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
. Aplikasi ini harus diimplementasikan oleh pengguna di class
definisi. Mesin WebAudio mengaktifkan fungsi ini dalam isokronos
untuk memberikan feed 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 tanda metode process()
prosesor tidak aktif, dan mesin WebAudio
tidak lagi memanggil
. Agar prosesor tetap aktif, metode ini harus menampilkan true
.
Jika tidak, pasangan node dan prosesor akan dibersihkan sampah memorinya oleh sistem
pada akhirnya.
Komunikasi dua arah dengan MessagePort
Terkadang, AudioWorkletNode
kustom ingin memperlihatkan kontrol yang tidak
dipetakan ke AudioParam
, seperti atribut type
berbasis string
yang digunakan untuk mengontrol filter kustom. Untuk tujuan ini dan seterusnya,
AudioWorkletNode
dan AudioWorkletProcessor
dilengkapi dengan
MessagePort
untuk komunikasi dua arah. Semua jenis data khusus
dapat ditukarkan melalui saluran ini.
MessagePort dapat diakses dengan atribut .port
pada node dan
prosesor. Metode port.postMessage()
node mengirimkan pesan ke
pengendali port.onmessage
prosesor terkait dan secara terbalik.
/* 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 transferable, yang memungkinkan Anda
penyimpanan data transfer data, atau modul
WASM melalui batas thread. Tindakan ini akan membuka
banyak kemungkinan cara penggunaan sistem Worklet Audio.
Panduan: Membangun 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);
Pembahasan ini membahas dasar-dasar sistem Worklet Audio. Demo live tersedia di repositori GitHub tim Chrome WebAudio.
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.