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

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

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

بدء استخدام AudioContext

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

يمكن أن يتيح مثيل واحد من 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 المثيرة للاهتمام مثل إنشاء تُستخدَم عُقد الصوت وفك ترميز بيانات الملفات الصوتية في AudioContext.

جارٍ تحميل الأصوات

تستخدم واجهة برمجة تطبيقات Web Audio مخزن صوت للمقاطع القصيرة إلى المتوسطة أصوات. إن الأسلوب الأساسي هو استخدام XMLHttpRequest جلب الملفات الصوتية.

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

بعد استلام بيانات الملف الصوتي (التي تم فك ترميزها)، يمكن الاحتفاظ بها. لفك الترميز لاحقًا، أو يمكن فك ترميزه على الفور باستخدام AudioContext (السياق الصوتي) decodeAudioData(). تأخذ هذه الطريقة تم تخزين 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

بالطبع، سيكون من الأفضل إنشاء نظام تحميل أكثر عمومية التي لا تتضمّن ترميزًا ثابتًا لتحميل هذا الصوت المحدَّد هناك العديد من الأساليب المختلفة للتعامل مع العديد من الأصوات القصيرة إلى المتوسطة الطول تطبيق صوتي أو لعبة - إليك طريقة واحدة باستخدام 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، يمكنك استخدام واجهة 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);

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

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

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

الاستماع إلى المزيد

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

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

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