داستان دو ساعت

برنامه ریزی صدا وب با دقت

کریس ویلسون
Chris Wilson

معرفی

یکی از بزرگترین چالش ها در ساخت نرم افزارهای صوتی و موسیقی عالی با استفاده از بستر وب، مدیریت زمان است. نه مانند "زمان برای نوشتن کد"، بلکه مانند زمان ساعت - یکی از موضوعاتی که کمتر درک شده در مورد Web Audio این است که چگونه به درستی با ساعت صوتی کار کنید. شیء Web Audio AudioContext دارای یک ویژگی currentTime است که این ساعت صوتی را نشان می دهد.

مخصوصاً برای کاربردهای موسیقایی صوتی وب - نه فقط نوشتن ترتیب‌دهنده‌ها و سینت سایزرها، بلکه هرگونه استفاده ریتمیک از رویدادهای صوتی مانند ماشین‌های درام ، بازی‌ها و سایر برنامه‌ها - بسیار مهم است که زمان‌بندی دقیق و ثابت رویدادهای صوتی را داشته باشید. نه فقط شروع و متوقف کردن صداها، بلکه برنامه ریزی تغییرات صدا (مانند تغییر فرکانس یا حجم). گاهی اوقات مطلوب است که رویدادهای کمی تصادفی زمانی داشته باشیم - به عنوان مثال، در نسخه ی نمایشی مسلسل در توسعه صدای بازی با API صوتی وب - اما معمولاً ما می خواهیم زمان بندی ثابت و دقیقی برای نت های موسیقی داشته باشیم.

قبلاً به شما نشان داده‌ایم که چگونه می‌توانید یادداشت‌ها را با استفاده از پارامتر زمان روش‌های Web Audio noteOn و noteOff (که اکنون به شروع و توقف تغییر نام داده‌اند) در شروع کار با Web Audio و همچنین در توسعه صدای بازی با Web Audio API زمان‌بندی کنید. با این حال، ما سناریوهای پیچیده تر، مانند پخش سکانس های طولانی موسیقی یا ریتم ها را عمیقا بررسی نکرده ایم. برای فرو رفتن در آن، ابتدا به یک پس زمینه کوچک در مورد ساعت نیاز داریم.

بهترین زمان - ساعت صوتی وب

Web Audio API دسترسی به ساعت سخت افزاری زیرسیستم صوتی را نشان می دهد. این ساعت از طریق ویژگی .currentTime، به عنوان یک عدد ممیز شناور از ثانیه از زمان ایجاد AudioContext، روی شی AudioContext قرار می گیرد. این باعث می شود این ساعت (از این پس "ساعت صوتی" نامیده می شود) دقت بسیار بالایی داشته باشد. به گونه ای طراحی شده است که می تواند تراز را در سطح نمونه صدای فردی مشخص کند، حتی با نرخ نمونه بالا. از آنجایی که حدود 15 رقم اعشاری با دقت در یک "دبل" وجود دارد، حتی اگر ساعت صوتی برای روزها کار کرده باشد، همچنان باید مقدار زیادی بیت برای اشاره به یک نمونه خاص حتی با نرخ نمونه بالا باقی بماند.

ساعت صوتی برای زمان‌بندی پارامترها و رویدادهای صوتی در سرتاسر Web Audio API - البته برای start() و stop() و همچنین برای متدهای set*ValueAtTime() در AudioParams استفاده می‌شود. این به ما امکان می‌دهد رویدادهای صوتی با زمان بسیار دقیق را از قبل تنظیم کنیم. در واقع، وسوسه انگیز است که همه چیز را در 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 از پیش برنامه ریزی شده خود و خروجی انجام می دهند تا بتوانند صداهای خود را قطع کنند!)

به طور خلاصه، از آنجایی که برای تغییر سرعت یا پارامترهایی مانند فرکانس یا افزایش (یا توقف کامل زمان‌بندی) به انعطاف‌پذیری نیاز دارید، نمی‌خواهید بسیاری از رویدادهای صوتی را وارد صف کنید - یا به عبارت دقیق‌تر، نمی‌خواهید برای نگاه کردن به آینده در زمان بسیار دور، زیرا ممکن است بخواهید آن زمان بندی را به طور کامل تغییر دهید.

بدترین زمان - ساعت جاوا اسکریپت

ما همچنین ساعت جاوا اسکریپت بسیار محبوب و بسیار مورد سوء استفاده خود را داریم که توسط Date.now() و setTimeout() نشان داده شده است. جنبه خوب ساعت جاوا اسکریپت این است که دارای چند روش بسیار مفید call-me-back-later window.setTimeout() و window.setInterval() است که به ما اجازه می دهد تا سیستم کد ما را در زمان های مشخصی دوباره فراخوانی کند.

جنبه بد ساعت جاوا اسکریپت این است که خیلی دقیق نیست. برای شروع، Date.now() مقداری را بر حسب میلی ثانیه برمی گرداند - عدد صحیحی از میلی ثانیه - بنابراین بهترین دقتی که می توانید به آن امیدوار باشید یک میلی ثانیه است. این در برخی زمینه‌های موسیقی خیلی بد نیست - اگر نت شما یک میلی‌ثانیه زود یا دیر شروع شده باشد، ممکن است متوجه نشوید - اما حتی با نرخ سخت‌افزار صوتی نسبتاً پایین 44.1 کیلوهرتز، تقریباً 44.1 برابر آهسته‌تر از آن است که بتوان از آن استفاده کرد. ساعت برنامه ریزی صوتی به یاد داشته باشید که اصلاً انداختن هر نمونه می‌تواند باعث اختلال در صدا شود - بنابراین اگر نمونه‌ها را به هم زنجیر کنیم، ممکن است نیاز داشته باشیم که آنها دقیقاً ترتیبی باشند.

مشخصات زمان با رزولوشن بالا در حال حاضر در واقع زمان فعلی دقیق تری را از طریق window.performance.now(); حتی در بسیاری از مرورگرهای فعلی (البته با پیشوند) پیاده سازی شده است. این می تواند در برخی شرایط کمک کند، اگرچه واقعاً به بدترین بخش APIهای زمان بندی جاوا اسکریپت مربوط نمی شود.

بدترین بخش APIهای زمان‌بندی جاوا اسکریپت این است که اگرچه دقت میلی‌ثانیه‌ای ()Date.now چندان بد به نظر نمی‌رسد، پاسخ تماس واقعی رویدادهای تایمر در جاوا اسکریپت (از طریق window.setTimeout() یا window.setInterval) به‌راحتی می‌توان ده‌ها میلی‌ثانیه یا بیشتر از طریق طرح‌بندی، رندر، جمع‌آوری زباله، و XMLHTTPRequest و سایر فراخوان‌ها - به طور خلاصه، با هر تعداد اتفاقی که در رشته اجرای اصلی اتفاق می‌افتد، منحرف شود. به یاد دارید که چگونه به «رویدادهای صوتی» اشاره کردم که می‌توانیم با استفاده از Web Audio API برنامه‌ریزی کنیم؟ خوب، همه آنها در یک رشته جداگانه پردازش می شوند - بنابراین حتی اگر موضوع اصلی به طور موقت برای انجام یک طرح بندی پیچیده یا کارهای طولانی دیگر متوقف شود، صدا همچنان دقیقاً در زمان هایی که به آنها گفته شده اتفاق می افتد - در واقع، حتی اگر شما در یک نقطه شکست در اشکال زدا متوقف شده اید، رشته صوتی به پخش رویدادهای برنامه ریزی شده ادامه می دهد!

استفاده از JavaScript setTimeout() در برنامه های صوتی

از آنجایی که رشته اصلی می‌تواند به راحتی برای چندین میلی ثانیه در یک زمان متوقف شود، استفاده از setTimeout جاوا اسکریپت برای شروع مستقیم پخش رویدادهای صوتی ایده بدی است، زیرا در بهترین حالت، یادداشت‌های شما در یک میلی‌ثانیه یا بیشتر از زمانی که واقعاً باید اجرا می‌شوند، منتشر می‌شوند. در بدترین حالت آنها حتی برای مدت طولانی تری به تعویق خواهند افتاد. بدتر از همه، برای آنچه که باید توالی ریتمیک باشد، آنها در فواصل زمانی دقیق شلیک نمی‌کنند، زیرا زمان‌بندی نسبت به موارد دیگری که در رشته اصلی جاوا اسکریپت اتفاق می‌افتد حساس است.

برای نشان دادن این موضوع، من یک نمونه برنامه مترونوم "بد" نوشتم - یعنی برنامه ای که مستقیماً از setTimeout برای برنامه ریزی یادداشت ها استفاده می کند - و همچنین طرح بندی زیادی را انجام می دهد. این برنامه را باز کنید، روی "play" کلیک کنید و سپس اندازه پنجره را به سرعت در حین پخش تغییر دهید. متوجه خواهید شد که زمان بندی به طور قابل توجهی پریشان است (می توانید بشنوید که ریتم ثابت نمی ماند). "اما این ساختگی است!" تو بگو؟ خوب، البته - اما این بدان معنا نیست که در دنیای واقعی هم اتفاق نمی افتد. حتی رابط کاربری نسبتاً ایستا به دلیل رله‌آوت‌ها در setTimeout مشکلات زمان‌بندی دارد - برای مثال، متوجه شدم که تغییر اندازه سریع پنجره باعث می‌شود زمان‌بندی WebkitSynth که در غیر این صورت عالی بود، به‌طور محسوسی دچار لکنت شود. اکنون تصور کنید که چه اتفاقی می‌افتد وقتی می‌خواهید یک موسیقی کامل را همراه با صدای خود اسکرول کنید، و به راحتی می‌توانید تصور کنید که این موضوع چگونه بر برنامه‌های موسیقی پیچیده در دنیای واقعی تأثیر می‌گذارد.

یکی از متداول‌ترین سؤالاتی که می‌شنوم این است که «چرا نمی‌توانم از رویدادهای صوتی پاسخ تماس دریافت کنم؟» اگرچه ممکن است برای این نوع تماس‌ها استفاده‌هایی وجود داشته باشد، اما مشکل خاصی را حل نمی‌کند - مهم است که بدانیم آن رویدادها در رشته اصلی جاوا اسکریپت اجرا می‌شوند، بنابراین در معرض تمام تاخیرهای احتمالی مشابهی خواهند بود. setTimeout; یعنی می‌توان آن‌ها را برای تعدادی ناشناخته و متغیر میلی‌ثانیه از زمان دقیق برنامه‌ریزی شده قبل از پردازش واقعی به تأخیر انداخت.

پس چه می توانیم بکنیم؟ خوب، بهترین راه برای مدیریت زمان‌بندی، راه‌اندازی همکاری بین تایمرهای جاوا اسکریپت (setTimeout)، setInterval() یا requestAnimationFrame() - در ادامه در مورد آن) و زمان‌بندی سخت‌افزار صوتی است.

به دست آوردن زمان بندی سنگ-جامد با نگاه کردن به آینده

بیایید به آن نسخه ی نمایشی مترونوم برگردیم - در واقع، من اولین نسخه از این نسخه ی نمایشی مترونوم ساده را به درستی نوشتم تا این تکنیک زمان بندی مشترک را نشان دهم. ( این کد در Github نیز موجود است. این نسخه نمایشی صداهای بوق (تولید شده توسط یک نوسانگر) را با دقت بالا در هر نت شانزدهم، هشتم یا یک چهارم پخش می کند و گام را بسته به ضرب آهنگ تغییر می دهد. همچنین به شما امکان می دهد سرعت و فاصله نت را تغییر دهید. در حین پخش، یا در هر زمان پخش را متوقف کنید - که یک ویژگی کلیدی برای هر ترتیب‌دهنده ریتمیک در دنیای واقعی است. افزودن کد برای تغییر صداهایی که این مترونوم در حال پرواز استفاده می‌کند نیز بسیار آسان است.

روشی که می‌تواند اجازه کنترل دما را در عین حفظ زمان‌بندی ثابت داشته باشد، یک همکاری است: یک تایمر setTimeout که هر چند وقت یکبار فعال می‌شود و زمان‌بندی صوتی وب را در آینده برای یادداشت‌های فردی تنظیم می‌کند. تایمر setTimeout اساساً فقط بررسی می کند که آیا هر یادداشتی باید «به زودی» بر اساس سرعت فعلی برنامه ریزی شود یا خیر، و سپس آنها را مانند این زمان بندی می کند:

()setTimeout و تعامل صوتی.
setTimeout() و تعامل صوتی.

در عمل، تماس‌های setTimeout() ممکن است به تأخیر بیفتند، بنابراین زمان‌بندی تماس‌های زمان‌بندی ممکن است به مرور زمان دچار لرزش شود (و بسته به نحوه استفاده شما از setTimeout کج شود) - اگرچه رویدادهای این مثال با فاصله تقریباً 50 میلی‌ثانیه پخش می‌شوند، اما اغلب کمی تغییر می‌کنند. بیشتر از آن (و گاهی اوقات خیلی بیشتر). با این حال، در طول هر تماس، رویدادهای صوتی وب را نه تنها برای هر یادداشتی که اکنون باید پخش شود (مثلاً اولین نت)، بلکه همچنین برای هر یادداشتی که باید بین حال و فاصله بعدی پخش شود، برنامه ریزی می کنیم.

در واقع، ما نمی‌خواهیم دقیقاً با فاصله بین فراخوانی‌های setTimeout() به آینده نگاه کنیم - همچنین به همپوشانی زمان‌بندی بین این تماس تایمر و تماس بعدی نیاز داریم تا بدترین حالت رفتار رشته اصلی را تطبیق دهیم - یعنی: بدترین حالت جمع‌آوری زباله، طرح‌بندی، رندر یا سایر کدهایی که روی رشته اصلی اتفاق می‌افتد، تماس تایمر بعدی ما را به تاخیر می‌اندازد. ما همچنین باید زمان برنامه ریزی بلوک صوتی را در نظر بگیریم - یعنی چه مقدار صدا را سیستم عامل در بافر پردازش خود نگه می دارد - که در سیستم عامل ها و سخت افزار متفاوت است، از تک رقمی پایین میلی ثانیه تا حدود 50 میلی ثانیه. هر فراخوانی setTimeout() که در بالا نشان داده شده است دارای یک بازه آبی است که کل بازه زمانی را نشان می دهد که در طی آن سعی می شود رویدادها را زمان بندی کند. برای مثال، چهارمین رویداد صوتی وب برنامه‌ریزی‌شده در نمودار بالا ممکن است «دیر» پخش شود، اگر منتظر می‌ماندیم تا تماس بعدی setTimeout انجام شود، اگر آن تماس setTimeout فقط چند میلی‌ثانیه بعد بود. در زندگی واقعی، عصبانیت در این زمان‌ها می‌تواند حتی شدیدتر از آن باشد، و این همپوشانی با پیچیده‌تر شدن برنامه شما مهم‌تر می‌شود.

تأخیر کلی پیش بینی تأثیر می گذارد که کنترل سرعت (و سایر کنترل های بلادرنگ) چقدر می تواند فشرده باشد. فاصله زمانی بین زمان‌بندی تماس‌ها، تعادلی بین حداقل تأخیر و میزان تأثیر کد شما بر پردازنده است. میزان همپوشانی پیش‌بینی‌ها با زمان شروع بازه بعدی تعیین می‌کند که اپلیکیشن شما در دستگاه‌های مختلف چقدر انعطاف‌پذیر خواهد بود، و با پیچیده‌تر شدن آن (و چیدمان و جمع‌آوری زباله‌ها ممکن است بیشتر طول بکشد). به طور کلی، برای انعطاف‌پذیری در برابر ماشین‌ها و سیستم‌های عامل کندتر، بهتر است چشم‌انداز کلی بزرگ و فاصله زمانی نسبتاً کوتاهی داشته باشید. می‌توانید برای پردازش تماس‌های کمتر، همپوشانی‌های کوتاه‌تر و فواصل طولانی‌تر را تنظیم کنید، اما در برخی مواقع ممکن است شنیده شود که تأخیر زیاد باعث تغییرات سرعت و غیره می‌شود که فوراً اعمال نمی‌شوند. برعکس، اگر بیش از حد انتظار را کاهش داده باشید، ممکن است شروع به شنیدن صدای لرزان کنید (چون یک تماس برنامه ریزی ممکن است مجبور شود رویدادهایی را که باید در گذشته اتفاق می افتاد، "تشکیل" کند).

نمودار زمان‌بندی زیر نشان می‌دهد که کد نمایشی مترونوم واقعاً چه کار می‌کند: فاصله زمانی setTimeout 25 میلی‌ثانیه دارد، اما همپوشانی بسیار انعطاف‌پذیرتری دارد: هر تماس برای 100 میلی‌ثانیه بعدی برنامه‌ریزی می‌شود. نقطه ضعف این چشم‌انداز طولانی این است که تغییرات سرعت و غیره یک دهم ثانیه طول می‌کشد تا اعمال شود. با این حال، ما در برابر وقفه ها بسیار مقاوم تر هستیم:

برنامه ریزی با همپوشانی های طولانی.
برنامه ریزی با همپوشانی های طولانی

در واقع، می‌توانید در این مثال بگویید که ما یک وقفه setTimeout در وسط داشتیم - ما باید یک تماس برگشتی setTimeout در حدود 270 میلی‌ثانیه داشتیم، اما به دلایلی تا حدود 320 میلی‌ثانیه - 50 میلی‌ثانیه دیرتر از آنچه که باید بود، به تعویق افتاد! با این حال، تأخیر زیاد پیش‌بینی زمان‌بندی را بدون مشکل نگه داشت، و ما یک ضرب را از دست ندادیم، حتی اگر درست قبل از آن سرعت را به نواختن نت‌های شانزدهم با سرعت 240bpm افزایش دادیم (فراتر از سرعت درام و باس هاردکور!)

همچنین این امکان وجود دارد که هر تماس زمان‌بندی‌کننده ممکن است به زمان‌بندی چندین یادداشت منجر شود - بیایید نگاهی بیندازیم که اگر از فاصله زمان‌بندی طولانی‌تری استفاده کنیم (250 میلی‌ثانیه پیش‌بینی، با فاصله 200 میلی‌ثانیه) و افزایش سرعت در وسط، چه اتفاقی می‌افتد:

setTimeout() با lookahead طولانی و فواصل طولانی.
setTimeout() با lookahead طولانی و فواصل طولانی

این مورد نشان می‌دهد که هر فراخوانی setTimeout() ممکن است به برنامه‌ریزی چندین رویداد صوتی ختم شود - در واقع، این مترونوم یک برنامه کاربردی ساده یک نت در یک زمان است، اما شما به راحتی می‌توانید ببینید که چگونه این رویکرد برای یک درام ماشین کار می‌کند ( جایی که اغلب چندین نت به طور همزمان وجود دارد) یا یک ترتیب دهنده (که ممکن است اغلب فواصل غیر منظم بین نت ها داشته باشد).

در عمل، می‌خواهید بازه زمانی برنامه‌ریزی و چشم‌انداز خود را تنظیم کنید تا ببینید چقدر تحت‌تاثیر چیدمان، جمع‌آوری زباله و سایر مواردی است که در رشته اصلی اجرای جاوا اسکریپت می‌گذرد، و میزان دقیق کنترل سرعت و غیره را تنظیم کنید. برای مثال، اگر چیدمان بسیار پیچیده ای دارید که اغلب اتفاق می افتد، احتمالاً می خواهید چشم انداز را بزرگتر کنید. نکته اصلی این است که ما می‌خواهیم مقدار «برنامه‌ریزی پیش‌رو» که انجام می‌دهیم به اندازه‌ای باشد که از هرگونه تأخیر جلوگیری شود، اما نه آنقدر بزرگ باشد که در هنگام دستکاری کنترل سرعت، تأخیر قابل‌توجهی ایجاد شود. حتی مورد فوق دارای همپوشانی بسیار کوچکی است، بنابراین در یک ماشین کند با یک برنامه وب پیچیده چندان انعطاف پذیر نخواهد بود. یک مکان خوب برای شروع، احتمالاً 100 میلی‌ثانیه زمان نگاه به جلو است، با فواصل زمانی که روی 25 میلی‌ثانیه تنظیم شده است. این ممکن است همچنان در کاربردهای پیچیده در ماشین‌هایی با تأخیر زیاد سیستم صوتی مشکلاتی داشته باشد، در این صورت باید زمان پیش‌بینی را افزایش دهید. یا، اگر به کنترل شدیدتر با از دست دادن مقداری انعطاف‌پذیری نیاز دارید، از چشم‌انداز کوتاه‌تری استفاده کنید.

کد اصلی فرآیند زمان‌بندی در تابع ()scheduler است -

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

این تابع فقط زمان سخت افزار صوتی فعلی را دریافت می کند و آن را با زمان نت بعدی در دنباله مقایسه می کند - در اکثر مواقع* در این سناریوی دقیق هیچ کاری انجام نمی دهد (زیرا هیچ "یادداشت" مترونومی وجود ندارد که منتظر برنامه ریزی باشد. ، اما وقتی موفق شد آن یادداشت را با استفاده از Web Audio API برنامه ریزی می کند و به یادداشت بعدی می رود.

تابع ()schemeNote مسئول برنامه ریزی واقعی "یادداشت" صوتی وب بعدی برای پخش است. در این مورد، من از اسیلاتورها برای ایجاد صداهای بوق در فرکانس های مختلف استفاده کردم. به همین راحتی می توانید گره های 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 .

یک سیستم زمان بندی دیگر

اکنون، همانطور که هر نوازنده خوبی می داند، چیزی که هر برنامه صوتی به آن نیاز دارد زنگ گاوچران بیشتر است - البته، تایمرهای بیشتری. شایان ذکر است که روش صحیح نمایش بصری استفاده از سیستم زمان بندی سوم است!

چرا، چرا، ای بهشت ​​های عزیز، چرا به سیستم زمان بندی دیگری نیاز داریم؟ خوب، این یکی از طریق API requestAnimationFrame با صفحه نمایش بصری - یعنی نرخ تازه سازی گرافیکی - همگام شده است. برای ترسیم جعبه‌ها در مثال مترونوم ما، این ممکن است واقعاً چیز مهمی به نظر نرسد، اما با پیچیده‌تر شدن گرافیک‌های شما، استفاده از requestAnimationFrame() برای همگام‌سازی با نرخ تازه‌سازی بصری، حیاتی‌تر و حیاتی‌تر می‌شود. در واقع استفاده از آن از همان ابتدا مانند استفاده از setTimeout () آسان است! با گرافیک های همگام سازی شده بسیار پیچیده (مثلاً نمایش دقیق نت های موسیقی متراکم در حین پخش در یک بسته نت موسیقی)، requestAnimationFrame() روان ترین، دقیق ترین همگام سازی گرافیکی و صوتی را به شما ارائه می دهد.

ما ضربات موجود در صف را در زمانبندی پیگیری کردیم:

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 استفاده نمی‌کنیم، زیرا از ساعت سیستم صوتی استفاده می‌کنیم تا بفهمیم به موقع کجا هستیم.

البته، می‌توانستم به‌کلی از یک callback ()setTimeout صرفنظر کنم، و زمان‌بندی یادداشت‌هایم را در requestAnimationFrame callback قرار دهم - سپس دوباره به دو تایمر برمی‌گردیم. انجام این کار نیز اشکالی ندارد، اما درک این نکته مهم است که requestAnimationFrame در این مورد فقط یک پایه برای setTimeout() است. شما همچنان می خواهید دقت برنامه ریزی زمان بندی صوتی وب برای یادداشت های واقعی را داشته باشید.

نتیجه

امیدوارم این آموزش در توضیح ساعت ها، تایمرها و نحوه ساخت زمان بندی عالی در برنامه های صوتی وب مفید بوده باشد. همین تکنیک ها را می توان به راحتی برای ساخت نوازنده های سکانس، درام ماشین ها و موارد دیگر تعمیم داد. تا دفعه بعد…