بدء استخدام Web Audio API

قبل عنصر <audio> في HTML5، كان من الضروري استخدام Flash أو مكوّن إضافي آخر لإضفاء الحيوية على الويب. على الرغم من أنّ المحتوى الصوتي على الويب لم يعد يحتاج إلى مكوّن إضافي، تفرض علامة الصوت قيودًا كبيرة على تنفيذ الألعاب المتقدّمة والتطبيقات التفاعلية.

‫Web Audio API هي واجهة برمجة تطبيقات JavaScript عالية المستوى لمعالجة الملفات الصوتية وإنشاء محتوى صوتي في تطبيقات الويب. يهدف هدف واجهة برمجة التطبيقات هذه إلى تضمين الإمكانات المتوفّرة في محرّكات الصوت الحديثة للألعاب وبعض مهَم المزج والمعالجة والفلترة المتوفّرة في تطبيقات الإنتاج الصوتي الحديثة المخصّصة للكمبيوتر المكتبي. في ما يلي مقدمة بسيطة حول استخدام واجهة برمجة التطبيقات هذه القوية.

يُستخدَم عنصر AudioContext لإدارة جميع الأصوات وتشغيلها. لإنشاء صوت باستخدام Web Audio API، أنشئ مصدرًا صوتيًا واحدًا أو أكثر وصِّله بوجهة الصوت التي يوفّرها مثيل AudioContext. ولا يلزم أن يكون هذا الربط مباشرًا، ويمكن أن يمرّ عبر أي عدد من AudioNodes الوسيطة التي تعمل كوحدات معالجة للإشارة الصوتية. يمكنك الاطّلاع على مزيد من التفاصيل حول التوجيه في مواصفات Web Audio.

يمكن أن يتيح مثيل واحد من AudioContext استخدام مصادر صوت متعددة ورسوم بيانية صوتية معقّدة، لذا سنحتاج إلى مثيل واحد فقط منAudioContext لكل تطبيق صوتي ننشئه.

ينشئ المقتطف التالي AudioContext:

var context;
window
.addEventListener('load', init, false);
function init() {
   
try {
    context
= new AudioContext();
   
}
   
catch(e) {
    alert
('Web Audio API is not supported in this browser');
   
}
}

بالنسبة إلى المتصفّحات القديمة المستندة إلى WebKit، استخدِم البادئة webkit، كما هو الحال مع webkitAudioContext.

إنّ العديد من وظائف Web Audio API المثيرة للاهتمام، مثل إنشاء AudioNodes وفك ترميز بيانات الملفات الصوتية، هي طُرق AudioContext.

أصوات التحميل

تستخدِم واجهة برمجة التطبيقات Web Audio API عنصر AudioBuffer للأصوات القصيرة أو المتوسطة الطول. يتمثل النهج الأساسي في استخدام XMLHttpRequest ل retrievingملفّات الصوت.

تتيح واجهة برمجة التطبيقات تحميل بيانات الملفات الصوتية بتنسيقات متعددة، مثل WAV وMP3 وAAC وOGG وغير ذلك. تختلف إتاحة المتصفّحات لتنسيقات المحتوى الصوتي المختلفة.

يوضّح المقتطف التالي تحميل عيّنة صوتية:

var dogBarkingBuffer = null;
var context = new AudioContext();

function loadDogSound(url) {
   
var request = new XMLHttpRequest();
    request
.open('GET', url, true);
    request
.responseType = 'arraybuffer';

   
// Decode asynchronously
    request
.onload = function() {
    context
.decodeAudioData(request.response, function(buffer) {
        dogBarkingBuffer
= buffer;
   
}, onError);
   
}
    request
.send();
}

بيانات الملف الصوتي ثنائية (وليست نصية)، لذلك نضبط responseType للطلب على 'arraybuffer'. لمزيد من المعلومات حول ArrayBuffers، اطّلِع على هذه المقالة حول XHR2.

بعد استلام بيانات ملف الصوت (غير المُشفَّرة)، يمكن الاحتفاظ بها لفك تشفيرها لاحقًا، أو يمكن فك تشفيرها على الفور باستخدام الأسلوب decodeAudioData() في AudioContext. تأخذ هذه الطريقة ArrayBuffer لبيانات الملف الصوتي المخزّنة في request.response و ترميزها بشكل غير متزامن (بدون حظر مسار تنفيذ JavaScript الرئيسي).

عند اكتمال decodeAudioData()، يتم استدعاء دالة ردّ اتصال تهدف إلى تقديم بيانات الصوت بتنسيق PCM التي تم فك ترميزها كملف AudioBuffer.

تشغيل الأصوات

رسم بياني صوتي بسيط
رسم بياني صوتي بسيط

بعد تحميل AudioBuffers واحد أو أكثر، سنكون جاهزين لتشغيل الأصوات. لنفترض أنّنا حمّلنا للتو AudioBuffer يتضمّن صوتًا لكلّب ينبح وأنّ عملية التحميل قد اكتملت. بعد ذلك، يمكننا تشغيل هذا المخزن المؤقت باستخدام الرمز البرمجي التالي.

var context = new AudioContext();

function playSound(buffer) {
   
var source = context.createBufferSource(); // creates a sound source
    source
.buffer = buffer;                    // tell the source which sound to play
    source
.connect(context.destination);       // connect the source to the context's destination (the speakers)
    source
.noteOn(0);                          // play the source now
}

يمكن استدعاء دالة playSound() هذه كلّ مرّة يضغط فيها أحد الأشخاص على مفتاح أو ينقر على عنصر باستخدام الماوس.

تسهِّل الدالة noteOn(time) جدولة تشغيل محتوى صوتي بدقة في الألعاب والتطبيقات الأخرى الحسّاسة للوقت. ومع ذلك، لكي يعمل جدولة التشغيل هذه بشكل صحيح، تأكَّد من تحميل ملفّات التخزين المؤقت للصوت مسبقًا.

تبسيط واجهة برمجة التطبيقات Web Audio API

بالطبع، من الأفضل إنشاء نظام تحميل أكثر عمومية غير مُبرمَج لتحميل هذا الصوت تحديدًا. هناك العديد من الطرق للتعامل مع العديد من الأصوات القصيرة أو المتوسطة الطول التي سيستخدمها تطبيق صوتي أو لعبة. في ما يلي إحدى الطرق باستخدام BufferLoader (ليس جزءًا من معيار الويب).

في ما يلي مثال على كيفية استخدام فئة BufferLoader. لننشئ AudioBuffers، وبعد تحميلهما، لنعيد تشغيلهما في الوقت نفسه.

window.onload = init;
var context;
var bufferLoader;

function init() {
    context
= new AudioContext();

    bufferLoader
= new BufferLoader(
    context
,
   
[
       
'../sounds/hyper-reality/br-jam-loop.wav',
       
'../sounds/hyper-reality/laughter.wav',
   
],
    finishedLoading
   
);

    bufferLoader
.load();
}

function finishedLoading(bufferList) {
   
// Create two sources and play them both together.
   
var source1 = context.createBufferSource();
   
var source2 = context.createBufferSource();
    source1
.buffer = bufferList[0];
    source2
.buffer = bufferList[1];

    source1
.connect(context.destination);
    source2
.connect(context.destination);
    source1
.noteOn(0);
    source2
.noteOn(0);
}

التعامل مع الوقت: تشغيل الأصوات مع الإيقاع

تتيح واجهة برمجة التطبيقات Web Audio API للمطوّرين جدولة التشغيل بدقة. لنوضّح ذلك، لنعدّ مسارًا إيقاعيًا بسيطًا. من المحتمل أن يكون نمط مجموعة الطبول الأكثر شهرة على النحو التالي:

نمط بسيط للطبل في موسيقى الروك
نمط طبول روك بسيط

يتم تشغيل طبلة الإيقاع كل نغمة ثامنة، ويتم تشغيل طبلة الجهير والدف بشكل متناوب كل ربع نغمة، في وقت 4/4.

لنفترض أنّنا حمّلنا ملفّات التخزين المؤقت kick وsnare وhihat، ويكون الرمز البرمجي للقيام بذلك بسيطًا:

for (var bar = 0; bar < 2; bar++) {
   
var time = startTime + bar * 8 * eighthNoteTime;
   
// Play the bass (kick) drum on beats 1, 5
    playSound
(kick, time);
    playSound
(kick, time + 4 * eighthNoteTime);

   
// Play the snare drum on beats 3, 7
    playSound
(snare, time + 2 * eighthNoteTime);
    playSound
(snare, time + 6 * eighthNoteTime);

   
// Play the hi-hat every eighth note.
   
for (var i = 0; i < 8; ++i) {
    playSound
(hihat, time + i * eighthNoteTime);
   
}
}

في هذه الحالة، نكرّر الأغنية مرة واحدة فقط بدلاً من تكرارها بلا حدود كما هو الحال في الموسيقى المكتوبة. الدالة playSound هي طريقة لتشغيل ملف تدفق في وقت محدّد، على النحو التالي:

function playSound(buffer, time) {
   
var source = context.createBufferSource();
    source
.buffer = buffer;
    source
.connect(context.destination);
    source
.noteOn(time);
}

تغيير مستوى صوت

من أبسط العمليات التي قد تريد إجراؤها على الصوت هو تغيير مستوى صوته. باستخدام Web Audio API، يمكننا توجيه مصدرنا إلى وجهته من خلال AudioGainNode من أجل التحكّم في مستوى الصوت:

رسم بياني صوتي يتضمّن عقدة كسب
رسم بياني صوتي يتضمّن عقدة "مُضاعِف الكسب"

يمكن إجراء عملية إعداد الاتصال هذه على النحو التالي:

// Create a gain node.
var gainNode = context.createGainNode();
// Connect the source to the gain node.
source
.connect(gainNode);
// Connect the gain node to the destination.
gainNode
.connect(context.destination);

بعد إعداد الرسم البياني، يمكنك تغيير مستوى الصوت آليًا من خلال التلاعب بالعنصر gainNode.gain.value على النحو التالي:

// Reduce the volume.
gainNode
.gain.value = 0.5;

التلاشي المتبادل بين صوتَين

لنفترض الآن أنّ لدينا سيناريو أكثر تعقيدًا قليلاً، حيث يتم تشغيل عدة أصوات ولكن نريد دمجها معًا. هذه حالة شائعة في تطبيق مشابه لتطبيق الدي جي، حيث يكون لدينا قرصان دوّاران وأحدهما يريد التمرير من مصدر صوت إلى آخر.

ويمكن إجراء ذلك باستخدام الرسم البياني الصوتي التالي:

رسم بياني صوتي لمصدرَين مرتبطَين من خلال عقدة الكسب
رسم بياني للصوت يتضمّن مصدرَين متصلَين من خلال عقدة الكسب

لإعداد ذلك، ما عليك سوى إنشاء عنصرَي AudioGainNodes وربط كل مصدر من خلال العقد، باستخدام دالة مثل هذه الدالة:

function createSource(buffer) {
   
var source = context.createBufferSource();
   
// Create a gain node.
   
var gainNode = context.createGainNode();
    source
.buffer = buffer;
   
// Turn on looping.
    source
.loop = true;
   
// Connect source to gain.
    source
.connect(gainNode);
   
// Connect gain to destination.
    gainNode
.connect(context.destination);

   
return {
    source
: source,
    gainNode
: gainNode
   
};
}

تمويه متساوٍ للطاقة

يعرض الأسلوب البسيط لعملية التلاشي الخطي انخفاضًا في مستوى الصوت عند التمرير بين العيّنات.

انتقال مرئي خطي
محوّل تمويه خطي

لحلّ هذه المشكلة، نستخدم منحنىً متساويًا للطاقة، تكون فيه منحنيات الاستفادة المقابلة غير خطية وتتقاطع عند سعة أعلى. يقلل ذلك من انخفاض مستوى الصوت بين مناطق الصوت، ما يؤدي بدوره إلى استخدام ميزة التلاشي المتقاطع بشكلٍ أكثر سلاسة بين المناطق التي قد تختلف مستويات الصوت فيها قليلاً.

انتقال متساوٍ للطاقة
محوّل طاقة متساوٍ

دمج المقاطع الصوتية في قائمة تشغيل

من التطبيقات الشائعة الأخرى لميزة "محوّل الصوت" هي تطبيق مشغّل الموسيقى. عند تغيير الأغنية، نريد أن يتم إخفاء المقطع الصوتي الحالي وإظهار المقطع الجديد بشكل تدريجي لتجنُّب حدوث انتقال مزعج. لإجراء ذلك، حدِّد موعدًا لتطبيق تأثير التداخل في المستقبل. على الرغم من أنّه يمكننا استخدام setTimeout لإجراء عملية تحديد الموعد هذه، إلا أنّها غير دقيقة. باستخدام Web Audio API، يمكننا استخدام واجهة AudioParam لجدولة قيم مستقبلية للمَعلمات، مثل قيمة الكسب لعنصر AudioGainNode.

وبالتالي، في حال توفّر قائمة تشغيل، يمكننا الانتقال بين الأغاني من خلال جدولة انخفاض في مستوى الصوت في الأغنية التي يتم تشغيلها حاليًا وزيادة في مستوى الصوت في الأغنية التالية، وكلاهما قبل انتهاء تشغيل الأغنية الحالية بقليل:

function playHelper(bufferNow, bufferLater) {
   
var playNow = createSource(bufferNow);
   
var source = playNow.source;
   
var gainNode = playNow.gainNode;
   
var duration = bufferNow.duration;
   
var currTime = context.currentTime;
   
// Fade the playNow track in.
    gainNode
.gain.linearRampToValueAtTime(0, currTime);
    gainNode
.gain.linearRampToValueAtTime(1, currTime + ctx.FADE_TIME);
   
// Play the playNow track.
    source
.noteOn(0);
   
// At the end of the track, fade it out.
    gainNode
.gain.linearRampToValueAtTime(1, currTime + duration-ctx.FADE_TIME);
    gainNode
.gain.linearRampToValueAtTime(0, currTime + duration);
   
// Schedule a recursive track change with the tracks swapped.
   
var recurse = arguments.callee;
    ctx
.timer = setTimeout(function() {
    recurse
(bufferLater, bufferNow);
   
}, (duration - ctx.FADE_TIME) - 1000);
}

توفّر واجهة برمجة التطبيقات Web Audio API مجموعة ملائمة من طرق RampToValue ل تغيير قيمة مَعلمة تدريجيًا، مثل linearRampToValueAtTime وexponentialRampToValueAtTime.

على الرغم من أنّه يمكن اختيار دالة توقيت الانتقال من بين الدوالّ الخطية والأسية المضمّنة (كما هو موضح أعلاه)، يمكنك أيضًا تحديد منحنى قيمة خاص بك من خلال صفيف من القيم باستخدام الدالة setValueCurveAtTime.

تطبيق تأثير فلتر بسيط على صوت

رسم بياني صوتي يتضمّن BiquadFilterNode
رسم بياني صوتي يتضمّن BiquadFilterNode

تتيح لك واجهة برمجة التطبيقات Web Audio API توجيه الصوت من عقدة صوتية إلى أخرى، وإنشاء سلسلة معقدة من المعالجات لإضافة تأثيرات معقدة إلى أشكال الصوت.

تتمثل إحدى طرق إجراء ذلك في وضع عناصر BiquadFilterNode بين مصدر الصوت ووجهته. يمكن لهذا النوع من عقد الصوت تنفيذ مجموعة متنوعة من الفلاتر منخفضة الترتيب التي يمكن استخدامها لإنشاء معادلات صوتية بيانية وحتى تأثيرات أكثر تعقيدًا، ويرتبط ذلك في الغالب باختيار أجزاء من مخطط تردد الصوت التي يجب التأكيد عليها وأجزاء أخرى يجب تخفيف وضوحها.

تشمل أنواع الفلاتر المتاحة ما يلي:

  • فلتر الترددات المنخفضة
  • فلتر الترددات العالية
  • فلتر تمرير النطاق
  • فلتر الرفّ السفلي
  • فلتر الرف العلوي
  • فلتر الذروة
  • فلتر الخطوط
  • فلتر "جميع البطاقات"

وتتضمّن جميع الفلاتر مَعلمات لتحديد مقدار الزيادة ومعدّل تكرار تطبيق الفلتر ومعدّل الجودة. يحافظ فلتر الترددات المنخفضة على نطاق الترددات المنخفضة، ولكنه يتخلّص من التردّدات العالية. يتم تحديد نقطة التوقف حسب قيمة معدّل التكرار، وعامل Q لا يستخدم أي وحدات، وهو يحدّد شكل الرسم البياني. لا يؤثر الكسب إلا في فلاتر معيّنة، مثل فلاتر الالتواء المنخفض و الالتواء العالي، وليس في فلتر الترددات المنخفضة هذا.

لنعدّل فلترًا منخفض التردد لإخراج الأصوات الأساسية فقط من عيّنة صوتية:

// Create the filter
var filter = context.createBiquadFilter();
// Create the audio graph.
source
.connect(filter);
filter
.connect(context.destination);
// Create and specify parameters for the low-pass filter.
filter
.type = 0; // Low-pass filter. See BiquadFilterNode docs
filter
.frequency.value = 440; // Set cutoff to 440 HZ
// Playback the sound.
source
.noteOn(0);

بشكل عام، يجب تعديل عناصر التحكّم في الترددات للعمل على قياس logarithmic scale لأنّ السمع البشري نفسه يعمل على المبدأ نفسه (أي أنّ A4 هو 440 هرتز وA5 هو 880 هرتز). لمزيد من التفاصيل، اطّلِع على دالة FilterSample.changeFrequency في رابط رمز المصدر أعلاه.

أخيرًا، يُرجى العلم أنّ نموذج الرمز يتيح لك ربط ملف ترشيح وإزالته، ما يؤدي إلى تغيير الرسم البياني AudioContext بشكل ديناميكي. يمكننا إلغاء ربط AudioNodes بالرسم البياني من خلال الاتصال بالرقم node.disconnect(outputNumber). على سبيل المثال، لإعادة توجيه الرسم البياني من خلال فلتر إلى اتصال مباشر، يمكننا إجراء ما يلي:

// Disconnect the source and filter.
source
.disconnect(0);
filter
.disconnect(0);
// Connect the source directly.
source
.connect(context.destination);

الاستماع إلى المزيد من المحتوى

لقد تناولنا أساسيات واجهة برمجة التطبيقات، بما في ذلك تحميل نماذج المحتوى الصوتي وتشغيلها. لقد أنشأنا رسومات بيانية للصوت تتضمّن عقدًا وفلاتر لزيادة الصوت، وأصواتًا مجدوَلة وتعديلات على مَعلمات الصوت لتفعيل بعض التأثيرات الصوتية الشائعة. في هذه المرحلة، تكون جاهزًا لإنشاء بعض التطبيقات الرائعة لتشغيل المحتوى الصوتي على الويب.

إذا كنت بحاجة إلى أفكار جديدة، يمكنك الاطّلاع على التطبيقات التي أنشأها العديد من المطوّرين باستخدام Web Audio API والتي تُعدّ رائعة. في ما يلي بعض التطبيقات المفضّلة لدي:

  • AudioJedit، وهي أداة لقطع المقاطع الصوتية داخل المتصفّح تستخدم روابط دائمة في SoundCloud
  • ToneCraft، أداة لترتيب الأصوات يتم من خلالها إنشاء الأصوات من خلال تجميع كتل ثلاثية الأبعاد
  • Plink، وهي لعبة تعاونية لإنشاء الموسيقى باستخدام Web Audio وWeb Sockets