ميزة Audio Worklet هي متاحة الآن بشكل تلقائي.

يتضمّن الإصدار 64 من Chrome ميزة جديدة طال انتظارها في Web Audio API، وهي AudioWorklet. ستتعرّف هنا على المفاهيم وكيفية استخدامها لإنشاءمعالج صوتي مخصّص باستخدام رمز JavaScript. اطّلِع على العروض التوضيحية المباشرة. ننصحك بقراءة المقالة التالية في السلسلة، نمط تصميم وحدات العمل الصوتية، للاطّلاع على معلومات مفيدة لإنشاء تطبيق صوتي متقدّم.

لمحة عن ScriptProcessorNode

يتم تشغيل معالجة الصوت في Web Audio API في سلسلة مهام منفصلة عن سلسلة مهام واجهة المستخدم الرئيسية، وذلك لكي تعمل بسلاسة. لتفعيل معالجة الصوت المخصّصة في JavaScript، اقترحت واجهة برمجة التطبيقات Web Audio API عنصر ScriptProcessorNode الذي يستخدم معالجي الأحداث لتشغيل نص المستخدم في سلسلة مهام واجهة المستخدم الرئيسية.

هناك مشكلتان في هذا التصميم: معالجة الأحداث غير متزامنة حسب التصميم، ويتم تنفيذ الرمز البرمجي في سلسلة المهام الرئيسية. يؤدي الأول إلى زيادة وقت الاستجابة، ويؤدي الثاني إلى زيادة الضغط على سلسلة التعليمات الرئيسية التي تكون عادةً مليئة بمهام مختلفة متعلقة بواجهة المستخدم وDOM، ما يؤدي إلى "تقطُّع" واجهة المستخدم أو "تعطُّل" الصوت. بسبب هذا الخلل الأساسي في التصميم، تم إيقاف ScriptProcessorNode نهائيًا من المواصفات وتم استبداله بـ AudioWorklet.

المفاهيم

تحافظ أداة Audio Worklet على رمز JavaScript المقدَّم من المستخدِم ضمن سلسلتَي معالجة الصوت. وهذا يعني أنّه ليس عليك الانتقال إلى السلسلة الأساسية لمعالجة الصوت. وهذا يعني أنّه يتم تشغيل رمز النص البرمجي الذي يقدّمه المستخدم في سلسلة المعالجة الصوتية (AudioWorkletGlobalScope) مع AudioNodes المضمّنة الأخرى، ما يضمن عدم حدوث أي وقت استجابة إضافي ومعالجة متزامنة.

مخطّط نطاق العمل الصوتي والنطاق العام الرئيسي
الشكل 1

التسجيل وإنشاء العناصر

يتألّف استخدام Audio Worklet من جزأين: 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، بما في ذلك 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 هذه للحصول على المَعلمات المعروضة التي يمكن التحكّم فيها بمعدّل الصوت تلقائيًا.

مخطّط بياني لعقدة "وحدة عمل الصوت" ومعالج الصوت
الشكل 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()

تحدث معالجة الصوت الفعلية في طريقة طلب إعادة الاتصال 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. وبخلاف ذلك، يجمع النظام القمامة من زوج العقدة والمعالج في نهاية المطاف.

التواصل ثنائي الاتجاه باستخدام MessagePort

في بعض الأحيان، تريد سمة AudioWorkletNode مخصّصة عرض عناصر تحكّم لا تتم ربطها بسمة AudioParam، مثل سمة type المستندة إلى سلسلة تتم استخدامها للتحكّم في فلتر مخصّص. لهذا الغرض وغير ذلك، تم تجهيز كل من AudioWorkletNode وAudioWorkletProcessor بأحد MessagePort للتواصل في الاتجاهين. يمكن تبادل أي نوع من البيانات المخصّصة من خلال هذه القناة.

Fig.2
الشكل 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

في ما يلي مثال كامل على GainNode تم إنشاؤه على أساس 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 تلقائيًا في الإصدار 66 من Chrome أو الإصدارات الأحدث. في الإصدارَين 64 و65 من Chrome، كانت الميزة متاحة من خلال علامة ميزة تجريبية.