দুই ঘড়ির গল্প

নির্ভুলতার সাথে ওয়েব অডিও সময়সূচী

ক্রিস উইলসন
Chris Wilson

ভূমিকা

ওয়েব প্ল্যাটফর্ম ব্যবহার করে দুর্দান্ত অডিও এবং মিউজিক সফ্টওয়্যার তৈরিতে সবচেয়ে বড় চ্যালেঞ্জগুলির মধ্যে একটি হল সময় পরিচালনা করা। "কোড লেখার সময়" হিসাবে নয়, কিন্তু ঘড়ির সময় হিসাবে - ওয়েব অডিও সম্পর্কে সবচেয়ে কম বোঝার বিষয়গুলির মধ্যে একটি হল কীভাবে অডিও ঘড়ির সাথে সঠিকভাবে কাজ করা যায়৷ ওয়েব অডিও অডিও কনটেক্সট অবজেক্টের একটি বর্তমান সময়ের বৈশিষ্ট্য রয়েছে যা এই অডিও ঘড়িটি প্রকাশ করে।

বিশেষ করে ওয়েব অডিওর মিউজিক্যাল অ্যাপ্লিকেশনের জন্য - শুধু সিকোয়েন্সার এবং সিনথেসাইজার লেখার জন্য নয়, কিন্তু অডিও ইভেন্টের যেকোনো ছন্দময় ব্যবহার যেমন ড্রাম মেশিন , গেমস এবং অন্যান্য অ্যাপ্লিকেশন - অডিও ইভেন্টগুলির সামঞ্জস্যপূর্ণ, সুনির্দিষ্ট সময় থাকা খুবই গুরুত্বপূর্ণ; শুধু শব্দ শুরু এবং বন্ধ করা নয়, শব্দের পরিবর্তনের সময়সূচীও (যেমন ফ্রিকোয়েন্সি বা ভলিউম পরিবর্তন)। কখনও কখনও এটি সামান্য সময়-এলোমেলো ইভেন্ট থাকা বাঞ্ছনীয় - উদাহরণস্বরূপ, ওয়েব অডিও API সহ গেম অডিও বিকাশে মেশিন-গান ডেমোতে - তবে সাধারণত, আমরা বাদ্যযন্ত্রের নোটগুলির জন্য সামঞ্জস্যপূর্ণ এবং সঠিক সময় থাকতে চাই৷

ওয়েব অডিওর সাথে শুরু করা এবং ওয়েব অডিও API এর সাথে গেম অডিও বিকাশে ওয়েব অডিও নোটঅন এবং নোটঅফ (এখন নতুন নামকরণ করা হয়েছে এবং স্টপ) পদ্ধতির সময় প্যারামিটার ব্যবহার করে কীভাবে নোটের সময়সূচী নির্ধারণ করবেন তা আমরা ইতিমধ্যেই আপনাকে দেখিয়েছি; যাইহোক, আমরা গভীরভাবে আরও জটিল পরিস্থিতির অন্বেষণ করিনি, যেমন দীর্ঘ বাদ্যযন্ত্রের ক্রম বা তাল বাজানো। এটিতে ডুব দেওয়ার জন্য, প্রথমে আমাদের ঘড়ির উপর একটু ব্যাকগ্রাউন্ড দরকার।

সময়ের সেরা - ওয়েব অডিও ঘড়ি

ওয়েব অডিও API অডিও সাবসিস্টেমের হার্ডওয়্যার ঘড়িতে অ্যাক্সেস প্রকাশ করে। এই ঘড়িটি AudioContext অবজেক্টে তার .currentTime সম্পত্তির মাধ্যমে উন্মুক্ত করা হয়েছে, AudioContext তৈরি হওয়ার পর থেকে সেকেন্ডের ফ্লোটিং-পয়েন্ট সংখ্যা হিসাবে। এটি এই ঘড়িটিকে (এরপরে "অডিও ঘড়ি" বলা হয়) অত্যন্ত উচ্চ-নির্ভুল হতে সক্ষম করে; এটি একটি স্বতন্ত্র শব্দ নমুনা স্তরে প্রান্তিককরণ নির্দিষ্ট করতে সক্ষম হওয়ার জন্য ডিজাইন করা হয়েছে, এমনকি উচ্চ নমুনা হার সহ। যেহেতু একটি "ডাবল"-এ প্রায় 15 দশমিক সূক্ষ্মতার সংখ্যা রয়েছে, এমনকি যদি অডিও ঘড়িটি কয়েক দিন ধরে চলে, তবুও উচ্চ নমুনা হারেও একটি নির্দিষ্ট নমুনাকে নির্দেশ করার জন্য এটিতে প্রচুর বিট অবশিষ্ট থাকা উচিত।

অডিও ঘড়িটি ওয়েব অডিও এপিআই জুড়ে পরামিতি এবং অডিও ইভেন্টের সময় নির্ধারণের জন্য ব্যবহার করা হয় - start() এবং stop() এর জন্য, অবশ্যই, তবে AudioParams-এ সেট*ValueAtTime() পদ্ধতির জন্যও। এটি আমাদেরকে আগে থেকেই খুব সুনির্দিষ্ট-সময়ের অডিও ইভেন্ট সেট আপ করতে দেয়। প্রকৃতপক্ষে, ওয়েব অডিওতে শুরু/স্টপ টাইম হিসাবে সবকিছু সেট আপ করার জন্য এটি লোভনীয় - তবে, বাস্তবে এর সাথে একটি সমস্যা রয়েছে।

উদাহরণস্বরূপ, আমাদের ওয়েব অডিও ইন্ট্রো থেকে এই হ্রাসকৃত কোড স্নিপেটটি দেখুন, যা একটি অষ্টম নোট হাই-হ্যাট প্যাটার্নের দুটি বার সেট আপ করে:

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);
  }

এই কোডটি দুর্দান্ত কাজ করবে। যাইহোক, আপনি যদি এই দুটি বারের মাঝখানে টেম্পো পরিবর্তন করতে চান - বা দুটি বার উঠে যাওয়ার আগে খেলা বন্ধ করতে চান - তাহলে আপনার ভাগ্যের বাইরে। (আমি দেখেছি যে ডেভেলপাররা তাদের পূর্বনির্ধারিত অডিওবাফারসোর্স নোড এবং আউটপুটের মধ্যে একটি লাভ নোড সন্নিবেশ করার মতো কাজ করে, যাতে তারা তাদের নিজস্ব শব্দ নিঃশব্দ করতে পারে!)

সংক্ষেপে, যেহেতু ফ্রিকোয়েন্সি বা লাভের মতো টেম্পো বা প্যারামিটারগুলি পরিবর্তন করার জন্য আপনার নমনীয়তার প্রয়োজন হবে (বা সম্পূর্ণভাবে সময়সূচী বন্ধ করুন), আপনি সারিতে অনেকগুলি অডিও ইভেন্ট ঠেলে দিতে চান না - বা, আরও সঠিকভাবে, আপনি চান না সময়ের মধ্যে অনেক দূরের দিকে তাকাতে, কারণ আপনি সেই সময়সূচীটিকে সম্পূর্ণরূপে পরিবর্তন করতে চাইতে পারেন।

সময়ের সবচেয়ে খারাপ - জাভাস্ক্রিপ্ট ঘড়ি

এছাড়াও আমাদের কাছে আমাদের অনেক প্রিয় এবং অনেক ক্ষতিকর জাভাস্ক্রিপ্ট ঘড়ি রয়েছে, যা Date.now() এবং setTimeout() দ্বারা উপস্থাপিত হয়। জাভাস্ক্রিপ্ট ঘড়ির ভাল দিক হল যে এটিতে বেশ কিছু দরকারী কল-মি-ব্যাক-লেটার window.setTimeout() এবং window.setInterval() পদ্ধতি রয়েছে, যা আমাদের সিস্টেমকে নির্দিষ্ট সময়ে আমাদের কোডকে আবার কল করতে দেয়।

জাভাস্ক্রিপ্ট ঘড়ির খারাপ দিক হল এটি খুব সুনির্দিষ্ট নয়। প্রারম্ভিকদের জন্য, Date.now() মিলিসেকেন্ডে একটি মান প্রদান করে - মিলিসেকেন্ডের একটি পূর্ণসংখ্যা - তাই আপনি যে সেরা নির্ভুলতা আশা করতে পারেন তা হল এক মিলিসেকেন্ড। কিছু বাদ্যযন্ত্রের প্রসঙ্গে এটি অবিশ্বাস্যভাবে খারাপ নয় - যদি আপনার নোটটি একটি মিলিসেকেন্ড আগে বা দেরিতে শুরু হয়, আপনি হয়তো খেয়ালও করবেন না - তবে 44.1kHz এর তুলনামূলকভাবে কম অডিও হার্ডওয়্যার হারেও, এটি ব্যবহার করার জন্য প্রায় 44.1 গুণ খুব ধীর। অডিও সময়সূচী ঘড়ি। মনে রাখবেন যে কোনও নমুনা একেবারে ফেলে দিলে অডিও সমস্যা হতে পারে - তাই আমরা যদি নমুনাগুলিকে একত্রে চেইন করি, তাহলে আমাদের সেগুলিকে সুনির্দিষ্টভাবে ক্রমানুসারে প্রয়োজন হতে পারে৷

আপ-এন্ড-আসিং হাই রেজোলিউশন টাইম স্পেসিফিকেশন আসলে window.performance.now(); এটি এমনকি অনেক বর্তমান ব্রাউজারে প্রয়োগ করা হয়েছে (যদিও প্রিফিক্সড)। এটি কিছু পরিস্থিতিতে সাহায্য করতে পারে, যদিও এটি জাভাস্ক্রিপ্ট টাইমিং API-এর সবচেয়ে খারাপ অংশের সাথে সত্যিই প্রাসঙ্গিক নয়।

জাভাস্ক্রিপ্ট টাইমিং এপিআই-এর সবচেয়ে খারাপ দিক হল যদিও Date.now() এর মিলিসেকেন্ড নির্ভুলতা খুব একটা খারাপ শোনায় না, জাভাস্ক্রিপ্টে টাইমার ইভেন্টের আসল কলব্যাক (window.setTimeout() বা window.setInterval এর মাধ্যমে) লেআউট, রেন্ডারিং, আবর্জনা সংগ্রহ, এবং XMLHTTPR অনুরোধ এবং অন্যান্য কলব্যাক - সংক্ষেপে, মূল এক্সিকিউশন থ্রেডে ঘটতে থাকা যেকোনো সংখ্যক জিনিস দ্বারা সহজেই দশ মিলিসেকেন্ড বা তার বেশি স্ক্যুড করা যেতে পারে। মনে রাখবেন কিভাবে আমি "অডিও ইভেন্ট" উল্লেখ করেছি যা আমরা ওয়েব অডিও API ব্যবহার করে শিডিউল করতে পারি? ঠিক আছে, সেগুলি একটি পৃথক থ্রেডে প্রক্রিয়া করা হচ্ছে - তাই যদিও মূল থ্রেডটি সাময়িকভাবে জটিল লেআউট বা অন্যান্য দীর্ঘ কাজ করার জন্য স্থগিত হয়ে যায়, অডিওটি তখনও ঠিক সেই সময়ে ঘটবে যখন তাদের ঘটতে বলা হয়েছিল - আসলে, এমনকি যদি আপনি ডিবাগারের একটি ব্রেকপয়েন্টে থামিয়েছেন, অডিও থ্রেড নির্ধারিত ইভেন্টগুলি চালাতে থাকবে!

অডিও অ্যাপে JavaScript setTimeout() ব্যবহার করা

যেহেতু মূল থ্রেডটি এক সময়ে একাধিক মিলিসেকেন্ডের জন্য সহজেই আটকে যেতে পারে, তাই সরাসরি অডিও ইভেন্টগুলি চালানো শুরু করার জন্য জাভাস্ক্রিপ্টের সেটটাইমআউট ব্যবহার করা একটি খারাপ ধারণা, কারণ আপনার নোটগুলি একটি মিলিসেকেন্ডের মধ্যে বা তারও বেশি সময়ে ফায়ার হবে যখন সেগুলি সত্যিই করা উচিত, এবং সবচেয়ে খারাপভাবে তারা আরও দীর্ঘ সময়ের জন্য বিলম্বিত হবে। সবচেয়ে খারাপ, ছন্দবদ্ধ ক্রমগুলি কী হওয়া উচিত, সেগুলি সুনির্দিষ্ট ব্যবধানে ফায়ার করবে না কারণ সময়টি প্রধান জাভাস্ক্রিপ্ট থ্রেডে ঘটতে থাকা অন্যান্য জিনিসগুলির প্রতি সংবেদনশীল হবে।

এটি প্রদর্শন করার জন্য, আমি একটি নমুনা লিখেছি "খারাপ" মেট্রোনোম অ্যাপ্লিকেশন - অর্থাৎ, যেটি নোটের সময়সূচীতে সরাসরি সেটটাইমআউট ব্যবহার করে - এবং অনেক লেআউটও করে। এই অ্যাপ্লিকেশানটি খুলুন, "খেলান" এ ক্লিক করুন এবং তারপরে প্লে করার সময় দ্রুত উইন্ডোটির আকার পরিবর্তন করুন; আপনি লক্ষ্য করবেন যে সময়টি লক্ষণীয়ভাবে অস্থির (আপনি শুনতে পাচ্ছেন যে তালটি সামঞ্জস্যপূর্ণ নয়)। "কিন্তু এটা কাল্পনিক!" তুমি বলো? ঠিক আছে, অবশ্যই - কিন্তু এর অর্থ এই নয় যে এটি বাস্তব জগতেও ঘটে না। এমনকি তুলনামূলকভাবে স্ট্যাটিক ইউজার ইন্টারফেসে রিলেআউটের কারণে সেটটাইমআউটে টাইমিং সমস্যা থাকবে - উদাহরণস্বরূপ, আমি লক্ষ্য করেছি যে উইন্ডোটি দ্রুত আকার পরিবর্তন করা হলে তা অন্যথায় চমৎকার WebkitSynth-এর সময় লক্ষণীয়ভাবে স্তব্ধ হয়ে যাবে। আপনি যখন আপনার অডিও সহ একটি সম্পূর্ণ মিউজিক্যাল স্কোর মসৃণ-স্ক্রোল করার চেষ্টা করছেন তখন কী ঘটবে তা এখন চিত্রিত করুন এবং আপনি সহজেই কল্পনা করতে পারেন যে এটি বাস্তব জগতে জটিল সঙ্গীত অ্যাপগুলিকে কীভাবে প্রভাবিত করবে।

আমি শুনি সবচেয়ে ঘন ঘন জিজ্ঞাসিত প্রশ্নগুলির মধ্যে একটি হল "কেন আমি অডিও ইভেন্ট থেকে কলব্যাক পেতে পারি না?" যদিও এই ধরনের কলব্যাকগুলির জন্য ব্যবহার হতে পারে, তবে তারা হাতে নির্দিষ্ট সমস্যার সমাধান করবে না - এটা বোঝা গুরুত্বপূর্ণ যে সেই ইভেন্টগুলি মূল জাভাস্ক্রিপ্ট থ্রেডে ফায়ার করা হবে, তাই সেগুলি একই রকম সম্ভাব্য বিলম্বের সাপেক্ষে হবে সেট টাইমআউট; অর্থাৎ, তারা প্রকৃতপক্ষে প্রক্রিয়া করার আগে নির্ধারিত সময়ের থেকে কিছু অজানা এবং পরিবর্তনশীল সংখ্যক মিলিসেকেন্ডের জন্য বিলম্বিত হতে পারে।

তাহলে আমরা কি করতে পারি? ঠিক আছে, টাইমিং পরিচালনা করার সর্বোত্তম উপায় হল জাভাস্ক্রিপ্ট টাইমার (setTimeout(), setInterval() বা requestAnimationFrame() - এর পরে আরও) এবং অডিও হার্ডওয়্যার শিডিউলিংয়ের মধ্যে একটি সহযোগিতা সেট আপ করা।

সামনের দিকে তাকিয়ে রক-সলিড টাইমিং পাওয়া

আসুন সেই মেট্রোনোম ডেমোতে ফিরে যাই - আসলে, এই সহযোগী সময়সূচী কৌশলটি প্রদর্শন করার জন্য আমি এই সহজ মেট্রোনোম ডেমোর প্রথম সংস্করণটি সঠিকভাবে লিখেছি। ( কোডটি Github-এও পাওয়া যায় এটি বাজানোর সময়, বা যেকোন সময় প্লেব্যাক বন্ধ করুন - যেটি যেকোন বাস্তব-বিশ্বের রিদমিক সিকোয়েন্সারের জন্য একটি মূল বৈশিষ্ট্য। এই মেট্রোনোমটি উড়তে গিয়েও যে শব্দগুলি ব্যবহার করে তা পরিবর্তন করতে কোড যোগ করা বেশ সহজ।

রক-সলিড টাইমিং বজায় রাখার সময় এটি যেভাবে টেম্প কন্ট্রোলকে অনুমতি দেয় তা হল একটি সহযোগিতা: একটি সেটটাইমআউট টাইমার যা প্রতিবার একবার ফায়ার করে এবং ভবিষ্যতে পৃথক নোটের জন্য ওয়েব অডিও সময়সূচী সেট আপ করে। সেটটাইমআউট টাইমার মূলত শুধুমাত্র বর্তমান টেম্পোর উপর ভিত্তি করে কোন নোট "শীঘ্রই" নির্ধারিত করতে হবে কিনা তা পরীক্ষা করে এবং তারপরে তাদের সময়সূচী করে, যেমন:

setTimeout() এবং অডিও ইভেন্ট ইন্টারঅ্যাক্টিও।
setTimeout() এবং অডিও ইভেন্ট ইন্টারঅ্যাকশন।

অনুশীলনে, সেটটাইমআউট() কলগুলি বিলম্বিত হতে পারে, তাই সময়ের সাথে সাথে শিডিউলিং কলের সময় ঝিমঝিম করতে পারে (এবং আপনি কীভাবে সেটটাইমআউট ব্যবহার করেন তার উপর নির্ভর করে) - যদিও এই উদাহরণের ইভেন্টগুলি প্রায় 50 মি. ব্যবধানে দেখা যায়, সেগুলি প্রায়শই সামান্য হয় তার চেয়ে বেশি (এবং কখনও কখনও আরও অনেক বেশি)। যাইহোক, প্রতিটি কলের সময়, আমরা ওয়েব অডিও ইভেন্টের সময়সূচী করি না শুধুমাত্র যেকোন নোটের জন্য যা এখন প্লে করা দরকার (যেমন প্রথম নোট), কিন্তু এখন থেকে পরবর্তী ব্যবধানের মধ্যে যে কোন নোট প্লে করা দরকার।

প্রকৃতপক্ষে, আমরা সেটটাইমআউট() কলগুলির মধ্যে সুনির্দিষ্টভাবে ব্যবধানের মাধ্যমে সামনের দিকে তাকাতে চাই না - আমাদের এই টাইমার কল এবং পরেরটির মধ্যে কিছু সময়সূচী ওভারল্যাপ প্রয়োজন, সবচেয়ে খারাপ ক্ষেত্রে প্রধান থ্রেড আচরণকে সামঞ্জস্য করার জন্য - অর্থাৎ, প্রধান থ্রেডে আবর্জনা সংগ্রহ, লেআউট, রেন্ডারিং বা অন্যান্য কোডের সবচেয়ে খারাপ ক্ষেত্রে আমাদের পরবর্তী টাইমার কল বিলম্বিত হয়। আমাদের অডিও ব্লক-শিডিউলিংয়ের সময়ের জন্যও হিসাব করতে হবে - অর্থাৎ, অপারেটিং সিস্টেম তার প্রক্রিয়াকরণ বাফারে কতটা অডিও রাখে - যা অপারেটিং সিস্টেম এবং হার্ডওয়্যার জুড়ে পরিবর্তিত হয়, মিলিসেকেন্ডের কম একক সংখ্যা থেকে প্রায় 50ms পর্যন্ত। উপরে দেখানো প্রতিটি সেটটাইমআউট() কলের একটি নীল ব্যবধান থাকে যা সময়গুলির সম্পূর্ণ পরিসীমা দেখায় যে সময়ে এটি ইভেন্টগুলি নির্ধারণ করার চেষ্টা করবে; উদাহরণস্বরূপ, উপরের ডায়াগ্রামে নির্ধারিত চতুর্থ ওয়েব অডিও ইভেন্টটি "দেরিতে" চালানো হতে পারে যদি আমরা পরবর্তী সেটটাইমআউট কল না হওয়া পর্যন্ত এটি চালানোর জন্য অপেক্ষা করতাম, যদি সেই সেটটাইমআউট কলটি কয়েক মিলিসেকেন্ড পরে হয়। বাস্তব জীবনে, এই সময়ে ধাক্কাধাক্কি তার চেয়েও বেশি চরম হতে পারে, এবং এই ওভারল্যাপটি আরও গুরুত্বপূর্ণ হয়ে ওঠে কারণ আপনার অ্যাপটি আরও জটিল হয়ে ওঠে।

সামগ্রিক লুকআহেড লেটেন্সি টেম্পো কন্ট্রোল (এবং অন্যান্য রিয়েল-টাইম কন্ট্রোল) কতটা শক্ত হতে পারে তা প্রভাবিত করে; সময়সূচী কলের মধ্যে ব্যবধান হল ন্যূনতম লেটেন্সি এবং আপনার কোড কত ঘন ঘন প্রসেসরকে প্রভাবিত করে তার মধ্যে একটি ট্রেডঅফ। পরবর্তী ব্যবধানের শুরুর সময়ের সাথে লুকআহেড কতটা ওভারল্যাপ করে তা নির্ধারণ করে যে আপনার অ্যাপটি বিভিন্ন মেশিনে কতটা স্থিতিস্থাপক হবে এবং এটি আরও জটিল হয়ে উঠলে (এবং লেআউট এবং আবর্জনা সংগ্রহে আরও বেশি সময় লাগতে পারে)। সাধারণভাবে, ধীরগতির মেশিন এবং অপারেটিং সিস্টেমের প্রতি স্থিতিস্থাপক হওয়ার জন্য, একটি বড় সামগ্রিক দৃষ্টিভঙ্গি এবং যুক্তিসঙ্গতভাবে সংক্ষিপ্ত ব্যবধান থাকা ভাল। কম কলব্যাক প্রক্রিয়া করার জন্য আপনি ছোট ওভারল্যাপ এবং দীর্ঘ ব্যবধানের সাথে সামঞ্জস্য করতে পারেন, কিন্তু কিছু সময়ে, আপনি শুনতে শুরু করতে পারেন যে একটি বড় লেটেন্সি টেম্পো পরিবর্তনগুলি ঘটায়, ইত্যাদি, অবিলম্বে কার্যকর হবে না; বিপরীতভাবে, আপনি যদি তাকানকে খুব বেশি কমিয়ে দেন, তাহলে আপনি কিছু ঝাঁকুনি শুনতে শুরু করতে পারেন (যেহেতু একটি শিডিউল কলের জন্য অতীতে ঘটে যাওয়া ঘটনাগুলিকে "মেক আপ" করতে হতে পারে)।

নিম্নলিখিত টাইমিং ডায়াগ্রামটি দেখায় যে মেট্রোনোম ডেমো কোড আসলে কী করে: এটির 25ms এর একটি সেট টাইমআউট ব্যবধান রয়েছে, তবে অনেক বেশি স্থিতিস্থাপক ওভারল্যাপ: প্রতিটি কল পরবর্তী 100ms এর জন্য নির্ধারিত হবে। এই দীর্ঘ দৃষ্টিভঙ্গির নেতিবাচক দিক হল যে টেম্পো পরিবর্তন ইত্যাদি কার্যকর হতে এক সেকেন্ডের দশমাংশ সময় লাগবে; যাইহোক, আমরা বাধার জন্য অনেক বেশি স্থিতিস্থাপক:

দীর্ঘ ওভারল্যাপ সঙ্গে সময়সূচী.
দীর্ঘ ওভারল্যাপ সঙ্গে সময়সূচী

প্রকৃতপক্ষে, আপনি এই উদাহরণে বলতে পারেন মাঝখানে আমাদের একটি সেটটাইমআউট বাধা ছিল - আমাদের আনুমানিক 270ms এ একটি সেটটাইমআউট কলব্যাক করা উচিত ছিল, কিন্তু এটি হওয়া উচিত ছিল তার চেয়ে প্রায় 320ms - 50ms পরে কিছু কারণে এটি বিলম্বিত হয়েছে! যাইহোক, বড় লুকআহেড লেটেন্সি কোনো সমস্যা ছাড়াই টাইমিংকে এগিয়ে রেখেছিল, এবং আমরা একটি বীট মিস করিনি, যদিও আমরা তার আগে 240bpm-এ ষোড়শ নোট বাজানোর জন্য টেম্পো বাড়িয়েছিলাম (এমনকি হার্ডকোর ড্রাম এবং বেস টেম্পোও ছাড়িয়ে!)

এটাও সম্ভব যে প্রতিটি শিডিয়ুলার কল একাধিক নোটের সময়সূচী শেষ করতে পারে - চলুন একবার দেখে নেওয়া যাক যদি আমরা একটি দীর্ঘ সময়সূচী ব্যবধান ব্যবহার করি (250ms লুকআহেড, 200ms দূরে) এবং মাঝখানে একটি টেম্পো বৃদ্ধি ব্যবহার করি:

setTimeout() দীর্ঘ চেহারা এবং দীর্ঘ বিরতি সহ।
setTimeout() দীর্ঘ চেহারা এবং দীর্ঘ বিরতি সহ

এই কেসটি দেখায় যে প্রতিটি সেটটাইমআউট() কল একাধিক অডিও ইভেন্টের সময়সূচী শেষ করতে পারে - আসলে, এই মেট্রোনোমটি একটি সাধারণ ওয়ান-নোট-এ-টাইম অ্যাপ্লিকেশন, তবে আপনি সহজেই দেখতে পারেন যে এই পদ্ধতিটি একটি ড্রাম মেশিনের জন্য কীভাবে কাজ করে ( যেখানে প্রায়শই একাধিক যুগপত নোট থাকে) বা একটি সিকোয়েন্সার (যেটি নোটগুলির মধ্যে প্রায়শই অ-নিয়মিত ব্যবধান থাকতে পারে)।

অনুশীলনে, আপনি লেআউট, আবর্জনা সংগ্রহ এবং মূল জাভাস্ক্রিপ্ট এক্সিকিউশন থ্রেডে চলমান অন্যান্য জিনিসগুলির দ্বারা এটি কতটা প্রভাবিত হয়েছে তা দেখতে এবং টেম্পো-এর উপর নিয়ন্ত্রণের গ্রানুলারিটি টিউন করতে চান। আপনার যদি খুব জটিল লেআউট থাকে যা প্রায়শই ঘটে থাকে, উদাহরণস্বরূপ, আপনি সম্ভবত লুকআহেডকে আরও বড় করতে চাইবেন। মূল বিষয় হল যে আমরা চাই যে পরিমাণ "আগামী সময়সূচী" যা আমরা করছি যাতে কোনো বিলম্ব এড়ানোর জন্য যথেষ্ট বড় হতে পারে, কিন্তু টেম্পো কন্ট্রোল টুইক করার সময় লক্ষণীয় বিলম্ব সৃষ্টি করতে এত বড় নয়। এমনকি উপরের ক্ষেত্রে একটি খুব ছোট ওভারল্যাপ রয়েছে, তাই এটি একটি জটিল ওয়েব অ্যাপ্লিকেশন সহ একটি ধীর মেশিনে খুব স্থিতিস্থাপক হবে না। শুরু করার জন্য একটি ভাল জায়গা সম্ভবত 100ms "লুকহেড" সময়, ব্যবধান 25ms এ সেট করা। অনেক অডিও সিস্টেম লেটেন্সি সহ মেশিনে জটিল অ্যাপ্লিকেশনগুলিতে এটি এখনও সমস্যা হতে পারে, সেক্ষেত্রে আপনাকে দেখতে সময় বাড়াতে হবে; অথবা, কিছু স্থিতিস্থাপকতা হারানোর সাথে আপনার যদি কঠোর নিয়ন্ত্রণের প্রয়োজন হয়, তাহলে একটি সংক্ষিপ্ত চেহারা ব্যবহার করুন।

সময়সূচী প্রক্রিয়ার মূল কোডটি শিডিউলার() ফাংশনে রয়েছে -

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

এই ফাংশনটি কেবল বর্তমান অডিও হার্ডওয়্যার সময় পায়, এবং এটিকে ক্রমানুসারে পরবর্তী নোটের সময়ের সাথে তুলনা করে - বেশিরভাগ সময়* এই সুনির্দিষ্ট পরিস্থিতিতে এটি কিছুই করবে না (যেহেতু কোনও মেট্রোনোম "নোট" নির্ধারিত হওয়ার অপেক্ষায় নেই , কিন্তু যখন এটি সফল হয় তখন এটি ওয়েব অডিও API ব্যবহার করে সেই নোটটি নির্ধারণ করবে এবং পরবর্তী নোটে অগ্রসর হবে৷

ScheduleNote() ফাংশনটি আসলে পরবর্তী ওয়েব অডিও "নোট" চালানোর সময় নির্ধারণের জন্য দায়ী। এই ক্ষেত্রে, আমি বিভিন্ন ফ্রিকোয়েন্সিতে বিপিং শব্দ করতে অসিলেটর ব্যবহার করেছি; আপনি ঠিক তত সহজে অডিওবাফারসোর্স নোড তৈরি করতে পারেন এবং তাদের বাফারগুলিকে ড্রাম সাউন্ডে সেট করতে পারেন, বা আপনার ইচ্ছামত অন্য কোনো শব্দ।

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 এবং বর্তমান16thNote ভেরিয়েবল সেট করা:

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;
  }
}

এটি বেশ সহজবোধ্য - যদিও এটি বোঝা গুরুত্বপূর্ণ যে এই সময়সূচী উদাহরণে, আমি "সিকোয়েন্স টাইম" এর ট্র্যাক রাখছি না - অর্থাৎ মেট্রোনোম শুরু করার শুরু থেকে সময়। আমাদের যা করতে হবে তা হল আমরা কখন শেষ নোটটি খেলেছিলাম তা মনে রাখতে হবে এবং পরবর্তী নোটটি কখন খেলতে হবে তা নির্ধারণ করতে হবে। এইভাবে, আমরা খুব সহজেই টেম্পো পরিবর্তন করতে পারি (বা খেলা বন্ধ করতে পারি)।

এই সময়সূচী কৌশলটি ওয়েবে অন্যান্য অডিও অ্যাপ্লিকেশনের একটি সংখ্যা দ্বারা ব্যবহৃত হয় - উদাহরণস্বরূপ, ওয়েব অডিও ড্রাম মেশিন , খুব মজাদার অ্যাসিড ডিফেন্ডার গেম এবং আরও গভীরতর অডিও উদাহরণ যেমন গ্রানুলার ইফেক্টস ডেমো

এখনো আরেকটি টাইমিং সিস্টেম

এখন, যে কোন ভাল সঙ্গীতজ্ঞ জানেন, প্রতিটি অডিও অ্যাপ্লিকেশনের জন্য যা প্রয়োজন তা হল আরও কাউবেল - এর, আরও টাইমার৷ এটা উল্লেখ করার মতো যে ভিজ্যুয়াল ডিসপ্লে করার সঠিক উপায় হল তৃতীয় টাইমিং সিস্টেম ব্যবহার করা!

কেন, কেন, হে প্রিয় স্বর্গ, কেন আমাদের অন্য টাইমিং সিস্টেম দরকার? ঠিক আছে, এটি ভিজ্যুয়াল ডিসপ্লেতে সিঙ্ক্রোনাইজ করা হয়েছে - অর্থাৎ, গ্রাফিক্স রিফ্রেশ রেট - অনুরোধ অ্যানিমেশনফ্রেম এপিআই এর মাধ্যমে। আমাদের মেট্রোনোম উদাহরণে বাক্স আঁকার জন্য, এটি সত্যিই একটি বড় ব্যাপার বলে মনে হতে পারে না, কিন্তু আপনার গ্রাফিক্স যত বেশি জটিল হচ্ছে, ভিজ্যুয়াল রিফ্রেশ হারের সাথে সিঙ্ক করার জন্য অনুরোধ অ্যানিমেশনফ্রেম() ব্যবহার করা আরও বেশি গুরুত্বপূর্ণ হয়ে উঠছে - এবং এটা আসলে সেটটাইমআউট() ব্যবহার করার মতই শুরু থেকে ব্যবহার করা সহজ! খুব জটিল সিঙ্ক করা গ্রাফিক্সের সাথে (যেমন ঘন মিউজিক্যাল নোটের সুনির্দিষ্ট প্রদর্শন যখন তারা একটি মিউজিক্যাল নোটেশন প্যাকেজে বাজায়), requestAnimationFrame() আপনাকে মসৃণ, সবচেয়ে সুনির্দিষ্ট গ্রাফিক এবং অডিও সিঙ্ক্রোনাইজেশন দেবে।

আমরা শিডিউলারে সারিতে থাকা বিটগুলির ট্র্যাক রাখি:

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

আমাদের মেট্রোনোমের বর্তমান সময়ের সাথে মিথস্ক্রিয়াটি draw() পদ্ধতিতে পাওয়া যেতে পারে, যাকে বলা হয় (অনুরোধ অ্যানিমেশন ফ্রেম ব্যবহার করে) যখনই গ্রাফিক্স সিস্টেম আপডেটের জন্য প্রস্তুত থাকে:

var currentTime = audioContext.currentTime;

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

আবার, আপনি লক্ষ্য করবেন যে আমরা অডিও সিস্টেমের ঘড়িটি পরীক্ষা করছি - কারণ এটি আসলেই আমরা যার সাথে সিঙ্ক্রোনাইজ করতে চাই, যেহেতু এটি আসলে নোটগুলি চালাবে - আমাদের একটি নতুন বাক্স আঁকতে হবে কিনা তা দেখতে। আসলে, আমরা আসলেই রিকোয়েস্ট অ্যানিমেশনফ্রেম টাইমস্ট্যাম্প ব্যবহার করছি না, যেহেতু আমরা অডিও সিস্টেম ঘড়ি ব্যবহার করছি আমরা কোথায় আছি তা বের করতে।

অবশ্যই, আমি সম্পূর্ণভাবে একটি setTimeout() কলব্যাক ব্যবহার করে এড়িয়ে যেতে পারতাম, এবং আমার নোট শিডিউলারকে রিকোয়েস্ট অ্যানিমেশনফ্রেম কলব্যাকের মধ্যে রাখতাম - তাহলে আমরা আবার দুটি টাইমারে ফিরে আসব। এটা করাও ঠিক আছে, কিন্তু এটা বোঝা গুরুত্বপূর্ণ যে রিকোয়েস্ট অ্যানিমেশনফ্রেম এই ক্ষেত্রে সেটটাইমআউট() এর জন্য একটি স্ট্যান্ড-ইন; আপনি এখনও প্রকৃত নোটের জন্য ওয়েব অডিও সময় নির্ধারণের নির্ভুলতা চাইবেন।

উপসংহার

আমি আশা করি এই টিউটোরিয়ালটি ঘড়ি, টাইমার এবং ওয়েব অডিও অ্যাপ্লিকেশনগুলিতে কীভাবে দুর্দান্ত সময় তৈরি করা যায় তা ব্যাখ্যা করতে সহায়ক হয়েছে। এই একই কৌশলগুলি সিকোয়েন্স প্লেয়ার, ড্রাম মেশিন এবং আরও অনেক কিছু তৈরি করতে সহজেই এক্সট্রাপোলেট করা যেতে পারে। পরের বার পর্যন্ত…