Audio Worklet พร้อมให้ใช้งานตามค่าเริ่มต้นแล้ว

Chrome 64 มาพร้อมฟีเจอร์ใหม่ที่ทุกคนตั้งตารอใน Web Audio API - AudioWorklet คุณจะได้เรียนรู้แนวคิดและการใช้งานเพื่อสร้าง ตัวประมวลผลเสียงแบบกำหนดเองพร้อมโค้ด JavaScript ดูที่ การสาธิตแบบสด บทความถัดไปในซีรีส์ รูปแบบการออกแบบของ Audio Worklet ก็น่าจะอ่านข้อมูลเกี่ยวกับการสร้างแอปเสียงขั้นสูงได้นะ

พื้นหลัง: ScriptProcessorNode

การประมวลผลเสียงใน Web Audio API จะทำงานในเทรดที่แยกต่างหากจากเทรดหลัก เทรด UI เพื่อให้ทำงานได้อย่างราบรื่น วิธีเปิดใช้การประมวลผลเสียงที่กำหนดเองใน JavaScript, Web Audio API ได้เสนอ ScriptProcessorNode ซึ่งใช้ เครื่องจัดการเหตุการณ์เพื่อเรียกใช้สคริปต์ของผู้ใช้ในเทรด UI หลัก

การออกแบบนี้มีปัญหา 2 ข้อ คือ การจัดการเหตุการณ์เป็นแบบไม่พร้อมกัน และการเรียกใช้โค้ดจะเกิดขึ้นบนเทรดหลัก โปรไฟล์เก่า ทำให้เกิดเวลาในการตอบสนอง และแรงกดดันต่อเทรดหลัก บ่อยครั้งที่แออัดด้วยงานที่เกี่ยวกับ UI และ DOM ต่างๆ ซึ่งทำให้เกิด UI "กระตุก" หรือเสียงเป็น "ข้อบกพร่อง" และข้อบกพร่องในการออกแบบ พื้นฐานนี้ ScriptProcessorNode เลิกใช้งานแล้วจากข้อกำหนดและ แทนที่ด้วย AudioWorklet

แนวคิด

Audio Worklet จะเก็บโค้ด JavaScript ที่ผู้ใช้เป็นผู้ระบุทั้งหมดไว้ภายใน เทรดประมวลผลเสียง ซึ่งหมายความว่าจะไม่ต้องข้ามไปยังหน้าหลัก เทรดเพื่อประมวลผลเสียง ซึ่งหมายความว่าโค้ดสคริปต์ที่ผู้ใช้เป็นผู้ระบุจะทำงาน ในเธรดการแสดงผลเสียง (AudioWorkletGlobalScope) และอีกหลายรายการ AudioNodes ในตัวซึ่งทำให้เวลาในการตอบสนองและซิงโครนัสเพิ่มขึ้นเป็นศูนย์ ในการแสดงภาพ

วันที่ แผนภาพขอบเขตหลักส่วนกลางและแผนภาพขอบเขตของ Audio Worklet
Fig.1

การลงทะเบียนและการสร้างอินสแตนซ์

การใช้ Worklet เสียงประกอบด้วย 2 ส่วนคือ AudioWorkletProcessor และ AudioWorkletNode ซึ่งจะเกี่ยวข้องมากกว่าการใช้ ScriptProcessorNode แต่ก็จำเป็นที่จะต้องให้นักพัฒนาซอฟต์แวร์มีความสามารถ ในระดับต่ำสำหรับเสียงที่กำหนดเอง การประมวลผล AudioWorkletProcessor คือตัวประมวลผลเสียงจริง ซึ่งเขียนด้วยโค้ด JavaScript และอยู่ใน AudioWorkletGlobalScope AudioWorkletNode เป็นคู่ของ AudioWorkletProcessor และใช้เวลา ดูแลการเชื่อมต่อกับและจาก AudioNodes อื่นๆ ในเทรดหลัก ทั้งนี้ แสดงอยู่ในขอบเขตทั่วโลกหลัก และมีหน้าที่เหมือนกับ AudioNode ปกติ

นี่คือข้อมูลโค้ดคู่ที่แสดงการลงทะเบียนและ การสร้างอินสแตนซ์

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

หากต้องการสร้างAudioWorkletNode คุณต้องเพิ่ม AudioContext และชื่อตัวประมวลผลเป็นสตริง คําจํากัดความของผู้ประมวลผลข้อมูลอาจเป็น โหลดและลงทะเบียนแล้วโดยการเรียกใช้ addModule() ของออบเจ็กต์ Audio Worklet ใหม่ Worklet API ซึ่งรวมถึง Audio Worklet จะใช้ได้เฉพาะใน บริบทที่ปลอดภัย ดังนั้นจึง หน้าเว็บที่ใช้รหัสดังกล่าวต้องแสดงผ่าน HTTPS แม้ว่า http://localhost จะเป็น ถือว่าปลอดภัยสำหรับการทดสอบในเครื่อง

คุณสามารถคลาสย่อย AudioWorkletNode เพื่อกำหนด โหนดที่กำหนดเองที่ได้รับการสนับสนุนโดยโปรเซสเซอร์ที่ทำงานบนเวิร์กเล็ต

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

เมธอด registerProcessor() ใน AudioWorkletGlobalScope ใช้เวลา สตริงสำหรับชื่อของตัวประมวลผลที่จะลงทะเบียนและคำจำกัดความคลาส หลังจากการประเมินโค้ดสคริปต์ในขอบเขตทั่วโลกเสร็จสมบูรณ์แล้ว สัญญาจาก AudioWorklet.addModule() จะได้รับการแก้ไขในการแจ้งเตือนผู้ใช้ คําจํากัดความคลาสพร้อมใช้งานในขอบเขตหลักทั่วโลกแล้ว

พารามิเตอร์เสียงที่กำหนดเอง

สิ่งหนึ่งที่มีประโยชน์เกี่ยวกับ AudioNodes คือพารามิเตอร์แบบจัดตารางเวลาได้ การทำงานอัตโนมัติด้วย AudioParam AudioWorkletNodes สามารถใช้สิ่งเหล่านี้เพื่อสร้าง ที่เปิดเผยซึ่งสามารถควบคุมในอัตราเสียงโดยอัตโนมัติ

วันที่ แผนภาพโหนดเวิร์กเล็ตและตัวประมวลผลเสียง
Fig.2

ประกาศพารามิเตอร์เสียงที่กำหนดโดยผู้ใช้ได้ใน AudioWorkletProcessor คำจำกัดความของชั้นเรียนได้โดยการตั้งค่าชุด AudioParamDescriptor เครื่องมือ WebAudio ที่สำคัญจะได้รับข้อมูลนี้ระหว่าง สร้าง AudioWorkletNode จากนั้นจะสร้างและลิงก์ AudioParam ออบเจ็กต์ไปยังโหนดตามนั้น

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

AudioWorkletProcessor.process() วิธี

การประมวลผลเสียงจริงจะเกิดขึ้นในเมธอด Callback process() ใน AudioWorkletProcessor ต้องดำเนินการโดยผู้ใช้ในชั้นเรียน ของเรา เครื่องมือ WebAudio จะเรียกฟังก์ชันนี้ในรูปเดี่ยว ในการส่งอินพุตและพารามิเตอร์ และดึงข้อมูลเอาต์พุต

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

นอกจากนี้ ยังใช้ค่าที่ส่งกลับมาจากเมธอด process() เพื่อทำสิ่งต่อไปนี้ได้ ควบคุมอายุการใช้งานของ AudioWorkletNode เพื่อให้นักพัฒนาแอปจัดการ หน่วยความจำเลย ส่งคืน false จากเครื่องหมายเมธอด process() โปรเซสเซอร์ไม่ทำงาน และเครื่องมือ WebAudio จะไม่ทำงาน หากต้องการให้ตัวประมวลผลทำงานต่อไป เมธอดต้องแสดงผล true มิฉะนั้น คู่โหนดและตัวประมวลผลจะเป็นขยะที่ระบบรวบรวมไว้ ในที่สุด

การสื่อสารแบบ 2 ทิศทางด้วย MessagePort

บางครั้ง AudioWorkletNode ที่กำหนดเองจะต้องการ แมปกับ AudioParam เช่น แอตทริบิวต์ type ที่ใช้สตริง ซึ่งใช้ในการควบคุมตัวกรองที่กำหนดเอง เพื่อจุดประสงค์นี้และนอกเหนือจากนั้น AudioWorkletNode และ AudioWorkletProcessor มาพร้อม MessagePortสำหรับการสื่อสารแบบ 2 ทิศทาง ข้อมูลที่กำหนดเองทุกประเภท แลกเปลี่ยนผ่านช่องทางนี้ได้

วันที่ Fig.2
Fig.2

เข้าถึง MessagePort ได้ด้วยแอตทริบิวต์ .port ทั้งในโหนดและ โปรเซสเซอร์ เมธอด port.postMessage() ของโหนดจะส่งข้อความถึง เครื่องจัดการ port.onmessage ของตัวประมวลผลที่เกี่ยวข้อง และในทางกลับกัน

/* 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 รองรับการโอนได้ ซึ่งช่วยให้คุณ โอนพื้นที่เก็บข้อมูลหรือโมดูล WASM ผ่านขอบเขตของเทรด การดำเนินการนี้จะเปิด ความเป็นไปได้มากมายในการใช้งานระบบ Audio Worklet

ศึกษาโดยละเอียด: สร้าง GainNode

นี่คือตัวอย่างที่สมบูรณ์ของ HelNode ที่สร้างต่อยอดจาก AudioWorkletNode และ AudioWorkletProcessor

ไฟล์ 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>

ไฟล์ 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);

ซึ่งครอบคลุมพื้นฐานของระบบ Audio Worklet มีเดโมแบบสดพร้อมให้รับชม ที่ที่เก็บ GitHub ของทีม Chrome WebAudio

การเปลี่ยนฟีเจอร์: เวอร์ชันทดลองเป็นเวอร์ชันเสถียร

ระบบจะเปิดใช้ Audio Worklet ตามค่าเริ่มต้นใน Chrome 66 ขึ้นไป ใน Chrome 64 และ 65 ฟีเจอร์นี้อยู่เบื้องหลังเวอร์ชันทดลอง