يتضمّن الإصدار 64 من Chrome ميزة جديدة طال انتظارها في Web Audio API، وهي AudioWorklet. ستتعرّف هنا على المفاهيم وكيفية استخدامها لإنشاءمعالج صوتي مخصّص باستخدام رمز JavaScript. اطّلِع على العروض التوضيحية المباشرة. ننصحك بقراءة المقالة التالية في السلسلة، نمط تصميم وحدات العمل الصوتية، للاطّلاع على معلومات مفيدة لإنشاء تطبيق صوتي متقدّم.
لمحة عن ScriptProcessorNode
يتم تشغيل معالجة الصوت في Web Audio API في سلسلة مهام منفصلة عن سلسلة مهام واجهة المستخدم الرئيسية، وذلك لكي تعمل بسلاسة. لتفعيل معالجة الصوت المخصّصة في JavaScript، اقترحت واجهة برمجة التطبيقات Web Audio API عنصر ScriptProcessorNode الذي يستخدم معالجي الأحداث لتشغيل نص المستخدم في سلسلة مهام واجهة المستخدم الرئيسية.
هناك مشكلتان في هذا التصميم: معالجة الأحداث غير متزامنة
حسب التصميم، ويتم تنفيذ الرمز البرمجي في سلسلة المهام الرئيسية. يؤدي الأول إلى زيادة وقت الاستجابة، ويؤدي الثاني إلى زيادة الضغط على سلسلة التعليمات الرئيسية التي تكون عادةً مليئة بمهام مختلفة متعلقة بواجهة المستخدم وDOM، ما يؤدي إلى "تقطُّع" واجهة المستخدم أو "تعطُّل" الصوت. بسبب هذا الخلل الأساسي في التصميم،
تم إيقاف ScriptProcessorNode
نهائيًا من المواصفات
وتم استبداله بـ AudioWorklet.
المفاهيم
تحافظ أداة Audio Worklet على رمز JavaScript المقدَّم من المستخدِم ضمن سلسلتَي معالجة الصوت. وهذا يعني أنّه ليس عليك الانتقال إلى السلسلة
الأساسية لمعالجة الصوت. وهذا يعني أنّه يتم تشغيل رمز النص البرمجي الذي يقدّمه المستخدم
في سلسلة المعالجة الصوتية (AudioWorkletGlobalScope
) مع AudioNodes
المضمّنة الأخرى، ما يضمن عدم حدوث أي وقت استجابة إضافي ومعالجة
متزامنة.
التسجيل وإنشاء العناصر
يتألّف استخدام 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 هذه للحصول على
المَعلمات المعروضة التي يمكن التحكّم فيها بمعدّل الصوت تلقائيًا.
يمكن تحديد مَعلمات الصوت التي يحدّدها المستخدم في تعريف 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
للتواصل في الاتجاهين. يمكن تبادل أي نوع من البيانات المخصّصة
من خلال هذه القناة.
يمكن الوصول إلى 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، كانت الميزة متاحة من خلال علامة ميزة تجريبية.