قصة عقارب الساعة

جدولة صوت الويب بدقة

كريس ويلسون
كريس ويلسون

مقدمة

تُعدّ إدارة الوقت من أكبر التحديات التي تُواجهها في إنشاء برامج رائعة للصوت والموسيقى باستخدام منصة الويب. هذا الأمر يختلف عن "وقت كتابة التعليمات البرمجية"، ولكن كما هو الحال في وقت الساعة، فأحد المواضيع الأقل فهمًا حول Web Audio هو كيفية العمل بشكلٍ صحيح مع الساعة الصوتية. يحتوي كائن Web AudioContext على الخاصية currentTime التي تعرض هذه الساعة الصوتية.

وخاصةً في ما يتعلق بالتطبيقات الموسيقية للصوت على الويب، والتي لا تقتصر على كتابة أدوات التسلسل والتوليف فقط، ولكن مع أي استخدام إيقاعي للأحداث الصوتية مثل آلات الطبول والألعاب والتطبيقات الأخرى. من المهم جدًا استخدام توقيت ثابت ودقيق للأحداث الصوتية، ليس فقط بدء الأصوات وإيقافها، ولكن أيضًا جدولة التغييرات على الصوت (مثل تغيير التردد أو مستوى الصوت). قد يكون من المستحسن أحيانًا تنظيم أحداث عشوائية بشكل عشوائي، مثل العرض التوضيحي للمسدس الآلي في Developing Game Audio with the Web Audio API، ولكن عادةً ما نرغب في تحديد توقيت متّسق ودقيق للنغمات الموسيقية.

سبق أن أوضحنا لك كيفية جدولة الملاحظات باستخدام طريقتَي Web Audio NoteOn وnote Off (تمت إعادة تسميتهما الآن أعادت تسميتهما البدء والإيقاف) في Getting Started with Web Audio وفي قسم تطوير Game Audio باستخدام واجهة برمجة التطبيقات Web Audio. ومع ذلك، لم نستكشف بالتفصيل سيناريوهات أكثر تعقيدًا، مثل تشغيل تسلسلات أو إيقاعات موسيقية طويلة. للتعمق في ذلك، نحتاج أولاً إلى معلومات أساسية بسيطة عن الساعات.

أفضل الأوقات - ساعة الويب الصوتية

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

تُستخدم الساعة الصوتية لجدولة المعلمات والأحداث الصوتية من خلال Web Audio API في إطار start() وstop()، وكذلك في طريقة set*ValueAtTime() على AudioParams. ويتيح لنا ذلك إمكانية إعداد أحداث صوتية موقَّعة بدقة شديدة مسبقًا. في الواقع، من المغري إعداد كل شيء في Web Audio كأوقات بدء/توقف - ومع ذلك، هناك مشكلة في ذلك من الناحية العملية.

على سبيل المثال، انظر إلى مقتطف الرمز المخفَّض هذا من مقدمة Web Audio، والذي أعدّ شريطين لنمط قبعة عالية للملاحظة الثامنة:

for (var bar = 0; bar < 2; bar++) {
  var time = startTime + bar * 8 * eighthNoteTime;

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

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

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

أسوأ الأوقات - ساعة JavaScript

كما أن لدينا ساعة JavaScript محبوبة للغاية ومألوفة إلى حد كبير، ممثلة في Date.now() وsetquery(). إن الجانب الجيد من ساعة JavaScript هو أنها تحتوي على طريقتين مفيدتين للغاية من حيث الاتصال-me-back-later window.setSession() وwindow.setInterval()، مما يتيح لنا استدعاء النظام للرمز الخاص بنا في أوقات محددة.

والجانب السلبي لساعة JavaScript هو أنها ليست دقيقة للغاية. بالنسبة للمبتدئين، تعرض Date.now() قيمة بالمللي ثانية - عدد صحيح بالمللي ثانية - لذا فإن أفضل دقة يمكن أن تأمل فيها هي واحد ميلي ثانية. وهذا ليس سيئًا للغاية في بعض السياقات الموسيقية - فإذا بدأت الملاحظة مبكرًا أو متأخرًا بالمللي ثانية، فقد لا تلاحظ ذلك - ولكن حتى عند انخفاض معدل أجهزة الصوت نسبيًا 44.1 كيلوهرتز، يكون استخدام الملاحظة بطيء جدًا بمقدار 44.1 مرة تقريبًا لاستخدامها كساعة لجدولة الصوت. تجدر الإشارة إلى أنّ تجاهل أي عيّنات قد يؤدي إلى حدوث خلل في الصوت، وبالتالي إذا كنا سنربط العيّنات معًا، قد نضطر إلى أن تكون متسلسلة بدقة.

في الواقع، تمنحنا مواصفات وقت الدقة العالية القادمة مستوى أفضل من الدقة في الوقت الحالي من خلال window.performance.now()، ويمكن تنفيذه (وإن كان بادئًا) في العديد من المتصفِّحات الحالية. يمكن أن يكون ذلك مفيدًا في بعض الحالات، على الرغم من أنّه ليس مرتبطًا بأسوأ جزء في واجهات برمجة تطبيقات توقيت JavaScript.

أسوء جزء في واجهات برمجة التطبيقات لتوقيت JavaScript هو أنّه على الرغم من أنّ دقة مقياس المللي ثانية في Date.now() لا تبدو سيئة للغاية، لكن يمكن بسهولة تغيُّر معاودة الاتصال الفعلية لأحداث الموقت في JavaScript (من خلال window.setوها() أو window.setInterval) بعشرات الملي ثانية أو أكثر من خلال التنسيق والعرض وجمع البيانات غير المهمّة وXML-HTTPRequest وغيرها من الأمور من خلال عمليات الاستدعاء الرئيسية، وذلك عن طريق أي عمليات تنفيذ ذات صلة. هل تتذكر كيف ذكرت "الأحداث الصوتية" التي يمكننا تحديد موعد لها باستخدام Web Audio API؟ تتم معالجة كل هذه المشاكل في سلسلة محادثات منفصلة. لذلك، حتى إذا توقّفت سلسلة التعليمات الرئيسية مؤقتًا أثناء تنفيذ تنسيق معقّد أو أثناء مهمة طويلة أخرى، سيستمرّ تشغيل الصوت في الأوقات المحدَّدة التي تم إخبارها فيها بحدوثها. في الواقع، حتى إذا توقفت عند نقطة إيقاف في برنامج تصحيح الأخطاء، ستستمر سلسلة المحادثات الصوتية في تشغيل الأحداث المُجدوَلة.

استخدام وظيفة setout() في JavaScript في التطبيقات الصوتية

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

ولتوضيح ذلك، كتبت نموذجًا لتطبيق بندول "سيئ" - أي تطبيق يستخدم الدالة setمهلة لجدولة الملاحظات بشكل مباشر - وينفذ أيضًا الكثير من التخطيطات. افتح هذا التطبيق وانقر على "تشغيل"، ثم غيّر حجم النافذة بسرعة أثناء التشغيل؛ وستلاحظ أن التوقيت مزعج بشكل ملحوظ (يمكنك سماع أن الإيقاع لا يتغير). "ولكن هل يمكن التغلب على هذا!" بقولك؟ حسنًا بالطبع - لكن هذا لا يعني أن ذلك لا يحدث في العالم الحقيقي أيضًا. فواجهة المستخدم الثابتة نسبيًا ستواجه مشاكل في التوقيت بسبب عمليات إعادة التنسيق بسبب عمليات إعادة التنسيق، على سبيل المثال، لاحظت أنّ تغيير حجم النافذة بسرعة سيؤدي إلى حدوث تقطُّع ملحوظ في التوقيت على واجهة WebkitSynth الممتازة. الآن، تخيل ما سيحدث عند محاولة تسجيل نوتة موسيقية كاملة مع الصوت بسلاسة، وشرح كيفية تأثير ذلك على تطبيقات الموسيقى المعقدة في العالم الحقيقي.

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

إذًا، ما الذي يمكننا فعله؟ إن أفضل طريقة للتعامل مع التوقيت هي إعداد عملية تعاون بين مؤقتات JavaScript (setTool() أو setInterval() أو requestAnimationFrame() - المزيد حول ذلك لاحقًا) وجدولة أجهزة الصوت.

تحقيق توقيت صلب من خلال النظر للمستقبل

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

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

setمهلة() وتفاعل حدث صوتي.
واجهة برمجة التطبيقات setمهلة() والتفاعل مع الحدث الصوتي.

من الناحية العملية، قد تتأخر استدعاءات setمهلة() ، وبالتالي قد يكون توقيت استدعاءات الجدولة غير مستقر (والانحراف، اعتمادًا على كيفية استخدامك لـ setمهلة) بمرور الوقت - على الرغم من أن الأحداث في هذا المثال تفصل 50 ملي ثانية تقريبًا، إلا أنها تكون غالبًا أكثر من ذلك بقليل (وأحيانًا أكثر من ذلك بكثير). ومع ذلك، خلال كل مكالمة، نجدولة أحداث Web Audio، ليس فقط لأي ملاحظات يجب تشغيلها الآن (مثل الملاحظة الأولى)، ولكن أيضًا لأي ملاحظات يلزم تشغيلها من الآن وحتى الفاصل الزمني التالي.

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

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

يوضِّح مخطط التوقيت التالي ما يفعله الرمز التجريبي لآلة الإيقاع في الواقع: يتضمن فاصل ضبط المهلة 25 ملي ثانية، مع تداخل أكثر مرونة: حيث تتم جدولة كل اتصال خلال 100 ملي ثانية. والجانب السلبي لهذا التغيير الطويل الأمد هو أنّ التغييرات في الوتيرة قد تستغرق 10 ثوانٍ من بدئها، إلا أنّنا نبقى أكثر مرونة في مواجهة الانقطاعات:

الجدولة مع تداخلات طويلة.
الجدولة مع تداخلات طويلة

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

من المحتمل أيضًا أن يؤدي كل استدعاء لنظام جدولة

setمهلة() بنظرة مسبقة طويلة وفواصل طويلة.
set تتضمن مهلة (()) مراجعة طويلة وفواصل زمنية طويلة

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

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

توجد التعليمة البرمجية الأساسية لعملية الجدولة في دالة scheduler() -

while (nextNoteTime < audioContext.currentTime + scheduleAheadTime ) {
  scheduleNote( current16thNote, nextNoteTime );
  nextNote();
}

لا تعتمِد هذه الدالة على الوقت الحالي لجهاز الصوت، وتقارنه بوقت الملاحظة التالية في التسلسل - في أغلب الأحيان* في هذا السيناريو الدقيق، لن يكون هناك أي تأثير (لأنه لا توجد "ملاحظات" آلات إيقاعية في انتظار جدولتها، ولكن عندما تنجح، ستتم جدولة تلك الملاحظة باستخدام واجهة برمجة تطبيقات Web Audio، ثم الانتقال إلى الملاحظة التالية.

تعد دالة scheduleNote() مسؤولة عن جدولة "ملاحظة" Web Audio التالية ليتم تشغيلها. في هذه الحالة، استخدمت المذبذبات لإصدار أصوات تنبيه بترددات مختلفة؛ يمكنك بسهولة إنشاء عُقد AudioBufferSource وضبط الموارد الاحتياطية على أصوات الطبول أو أي أصوات أخرى تريدها.

currentNoteStartTime = time;

// create an oscillator
var osc = audioContext.createOscillator();
osc.connect( audioContext.destination );

if (! (beatNumber % 16) )         // beat 0 == low pitch
  osc.frequency.value = 220.0;
else if (beatNumber % 4)          // quarter notes = medium pitch
  osc.frequency.value = 440.0;
else                              // other 16th notes = high pitch
  osc.frequency.value = 880.0;
osc.start( time );
osc.stop( time + noteLength );

بعد جدولة أجهزة الذبذبات هذه وتوصيلها، يمكن لهذا الرمز أن يتجاهلها تمامًا؛ ستبدأ، ثم تتوقف، ثم يتم جمع البيانات غير المرغوب فيها تلقائيًا.

طريقة nextNote() مسؤولة عن التقدم إلى الملاحظة السادسة عشرة التالية - وهي تعيين المتغيرات nextNoteTime وcurrent16thNote على الملاحظة التالية:

function nextNote() {
  // Advance current note and time by a 16th note...
  var secondsPerBeat = 60.0 / tempo;    // picks up the CURRENT tempo value!
  nextNoteTime += 0.25 * secondsPerBeat;    // Add 1/4 of quarter-note beat length to time

  current16thNote++;    // Advance the beat number, wrap to zero
  if (current16thNote == 16) {
    current16thNote = 0;
  }
}

هذا واضح ومباشر، على الرغم من أنه من المهم أن نفهم أنه في مثال الجدولة هذا، لا أتتبع "وقت التسلسل" - أي الوقت منذ بداية تشغيل الآلات الموسيقية. ما علينا سوى تذكُّر وقت تشغيل النوتة الأخيرة ومعرفة الوقت المجدوَل لتشغيل الملاحظة التالية. بهذه الطريقة، يمكننا تغيير السرعة (أو التوقف عن اللعب) بسهولة كبيرة.

ويتم استخدام أسلوب الجدولة هذا في عدد من التطبيقات الصوتية الأخرى على الويب، على سبيل المثال Web Audio Drum Machine ولعبة Acid Defender الممتعة للغاية والأمثلة الصوتية الأكثر تفصيلاً مثل العرض التوضيحي Granular Effects.

نظام توقيت آخر

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

لماذا نحتاج إلى نظام توقيت آخر؟ تتم مزامنة هذه اللوحة مع العرض المرئي، أي معدّل تحديث الرسومات، عبر requestAnimationFrame API. بالنسبة لمربعات الرسم في مثال الآلات الإلكترونية، قد لا يبدو ذلك مشكلة كبيرة، ولكن عندما تصبح رسوماتك أكثر تعقيدًا، يصبح من الأكثر أهمية استخدام طريقة requestAnimationFrame() للمزامنة مع معدل التحديث المرئي - وهو في الواقع سهل الاستخدام من البداية مثل استخدام setSession()! باستخدام الرسومات المتزامنة المعقدة للغاية (على سبيل المثال، العرض الدقيق للملاحظات الكثيفة (على سبيل المثال) للرسومات الكثيفة والتي تؤدي إلى تشغيل

قمنا بتتبع الإيقاعات في قائمة الانتظار في أداة جدولة:

notesInQueue.push( { note: beatNumber, time: time } );

يمكن العثور على التفاعل مع الوقت الحالي للآلة الموسيقية في طريقة draw()، والتي يتم استدعاءها (باستخدام requestAnimationFrame) عندما يكون نظام الرسومات جاهزًا للتحديث:

var currentTime = audioContext.currentTime;

while (notesInQueue.length && notesInQueue[0].time < currentTime) {
  currentNote = notesInQueue[0].note;
  notesInQueue.splice(0,1);   // remove note from queue
}

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

وبالطبع، كان بإمكاني تخطي استخدام معاودة الاتصال بـ setquery() تمامًا، ووضع برنامج جدولة الملاحظات في معاودة الاتصال requestAnimationFrame - ثم سنعود إلى مؤقتين مرة أخرى. لا بأس في القيام بذلك أيضًا، ولكن من المهم أن نفهم أن requestAnimationFrame هو مجرد إعداد لـ setمهلة() في هذه الحالة؛ ستظل بحاجة إلى دقة جدولة توقيت Web Audio للملاحظات الفعلية.

الخلاصة

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