Worklet Audio kini tersedia secara default

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.

Diagram cakupan Global Worklet dan Cakupan global utama
Fig.1

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.

Diagram node worklet dan prosesor audio
Fig.2

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.

Fig.2
Fig.2

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.