दो घड़ियों की कहानी

वेब ऑडियो को सटीक तरीके से शेड्यूल करें

क्रिस विल्सन
क्रिस विल्सन

शुरुआती जानकारी

वेब प्लैटफ़ॉर्म का इस्तेमाल करके, ऑडियो और संगीत वाला बेहतरीन सॉफ़्टवेयर बनाने में सबसे बड़ी चुनौतियों में से एक है, समय को मैनेज करना. “कोड लिखने के समय” की तरह नहीं, लेकिन क्लॉक टाइम की तरह - वेब ऑडियो के बारे में सबसे कम समझ में आने वाले विषयों में से एक यह है कि ऑडियो घड़ी के साथ ठीक से कैसे काम किया जाए. वेब ऑडियो AudioContext ऑब्जेक्ट में मौजूदाTime प्रॉपर्टी है, जो इस ऑडियो घड़ी को दिखाती है.

खास तौर पर, वेब ऑडियो के म्यूज़िकल ऐप्लिकेशन के लिए न सिर्फ़ सीक्वेंसर और सिंथेसाइज़र लिखा जा सकता है, बल्कि ड्रम मशीन, गेम, और अन्य ऐप्लिकेशन जैसे ऑडियो इवेंट का लय में इस्तेमाल किया जाना भी ज़रूरी है. ऑडियो इवेंट के शुरू और बंद होने का सटीक समय तय करना बहुत ज़रूरी है. ऐसा न सिर्फ़ साउंड को शुरू और बंद करने के लिए करना चाहिए, बल्कि आवाज़ में बदलाव भी शेड्यूल करना चाहिए (जैसे, फ़्रीक्वेंसी या वॉल्यूम बदलना). कभी-कभी समय के हिसाब से इवेंट बनाने की ज़रूरत होती है - उदाहरण के लिए, मशीन-गन डेमो में, Web Audio API की मदद से गेम ऑडियो डेवलप करना में - लेकिन आम तौर पर, हम संगीत नोट के लिए एक जैसा और सटीक समय तय करना चाहते हैं.

हमने पहले ही आपको वेब ऑडियो NoteOn और NoteOff (अब इसका नाम बदलकर शुरू करें और बंद करना) मेथड के समय पैरामीटर का इस्तेमाल करके, वेब ऑडियो का इस्तेमाल शुरू करना और वेब ऑडियो एपीआई की मदद से गेम ऑडियो डेवलप करना में करके, नोट शेड्यूल करने का तरीका बताया है. हालांकि, हमने लंबे समय तक चलने वाले संगीत के क्रम या लय चलाने जैसी ज़्यादा मुश्किल स्थितियों के बारे में नहीं जाना है. इसके बारे में ज़्यादा जानने के लिए, हमें सबसे पहले घड़ियों के बैकग्राउंड की ज़रूरत होगी.

सबसे बढ़िया समय - वेब ऑडियो घड़ी

Web Audio API, ऑडियो सबसिस्टम की हार्डवेयर घड़ी का ऐक्सेस दिखाता है. इस घड़ी को AudioContext ऑब्जेक्ट में, अपनी .currentTime प्रॉपर्टी की मदद से दिखाया गया है. इसे AudioContext बनाए जाने के बाद से, सेकंड के फ़्लोटिंग-पॉइंट नंबर के तौर पर दिखाया जाता है. इसकी मदद से, इस घड़ी (जिसे अब “ऑडियो घड़ी” कहा जाता है) की सेटिंग भी बहुत सटीक हो जाती है. इसे इस तरह से डिज़ाइन किया गया है कि यह अलग-अलग आवाज़ के नमूने के स्तर पर अलाइनमेंट के बारे में बता सके, भले ही सैंपल दर ज़्यादा हो. एक “डबल” टेक्स्ट में सटीक होने के करीब 15 दशमलव अंक होते हैं. भले ही, ऑडियो क्लॉक कई दिनों से चल रहा हो, लेकिन सैंपल रेट ज़्यादा होने पर भी उसमें कुछ बिट बचे होने चाहिए, ताकि किसी सैंपल की जानकारी दी जा सके.

ऑडियो घड़ी का इस्तेमाल पूरे वेब ऑडियो एपीआई में पैरामीटर और ऑडियो इवेंट शेड्यूल करने के लिए किया जाता है. यह start() और stop() के लिए किया जाता है. हालांकि, AudioParams पर set*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);
  }

यह कोड बहुत अच्छा काम करेगा. हालांकि, अगर आपको उन दो बार के बीच की रफ़्तार बदलनी है या दोनों बार के ऊपर आने से पहले खेलना बंद करना है, तो यह किस्मत अच्छी नहीं है. (मैंने देखा है कि डेवलपर पहले से शेड्यूल किए गए AudioBufferSourceNodes और आउटपुट के बीच एक गेन नोड डालने जैसे काम करते हैं, ताकि वे अपनी खुद की आवाज़ें म्यूट कर सकें!)

कम शब्दों में कहें, तो आपको फ़्रीक्वेंसी या फ़ायदे जैसे पैरामीटर बदलने या सुविधा बढ़ाने (या शेड्यूल को पूरी तरह बंद करने) की सुविधा की ज़रूरत होगी. इसलिए, आपको सूची में बहुत ज़्यादा ऑडियो इवेंट शामिल करने की ज़रूरत नहीं होगी. सटीक बात यह है कि आपको आने वाले समय में बहुत ज़्यादा समय तक इवेंट पर जाना नहीं पड़ेगा, क्योंकि शायद आप उस शेड्यूल में पूरी तरह से बदलाव करना चाहें.

सबसे खराब समय - JavaScript घड़ी

हमारे पास अपनी बहुत ही पसंदीदा और ज़्यादा खराब JavaScript घड़ी भी है, जिसे Date.now() और setTimeout() से दिखाया जाता है. JavaScript घड़ी की सबसे अच्छी बात यह है कि इसमें कुछ बहुत उपयोगी call-me-back-later window.setTimeout() और window.setInterval() विधियां हैं, जिनकी मदद से हम सिस्टम को खास समय पर हमारे कोड को वापस कॉल कर सकते हैं.

JavaScript घड़ी की खराब बात यह है कि यह बहुत सटीक नहीं होती. स्टार्टर के लिए, Date.now() मिलीसेकंड में एक मान दिखाता है - जो मिलीसेकंड की एक पूर्णांक संख्या है. इसलिए, आपको सबसे ज़्यादा सटीक जानकारी की उम्मीद एक मिलीसेकंड है. संगीत के मामले में यह बहुत ज़्यादा बुरा नहीं है - अगर आपका नोट मिलीसेकंड से पहले या देर से शुरू हुआ था, तो शायद आपको इस पर ध्यान न जाए. हालांकि, ऑडियो हार्डवेयर रेट 44.1 किलोहर्ट्ज़ (kHz) से कम होने पर भी, इसे ऑडियो शेड्यूलिंग क्लॉक के तौर पर इस्तेमाल करने में 44.1 गुना धीमा लग जाता है. याद रखें कि किसी भी सैंपल को छोड़ने से ऑडियो ग्लिच हो सकता है. इसलिए, अगर हम सैंपल की चेन को एक साथ रखते हैं, तो हो सकता है कि हमें उनका क्रम सटीक रखना पड़े.

हाई रिज़ॉल्यूशन टाइम स्पेसिफ़िकेशन की मदद से, window.performance.now() का इस्तेमाल करके हमें मौजूदा समय की ज़्यादा सटीक जानकारी मिलती है. हालांकि, इसे कई मौजूदा ब्राउज़र में भी लागू किया जाता है (हालांकि, यह पहले से लागू होता है). हालांकि, इससे कुछ मामलों में मदद मिल सकती है. हालांकि, यह JavaScript समय के एपीआई के सबसे खराब हिस्से के लिए वाकई काम का नहीं है.

JavaScript टाइमिंग के एपीआई का सबसे खराब हिस्सा यह है कि Date.now() की मिलीसेकंड सटीक जानकारी के साथ लाइव होने में कोई समस्या नहीं है, फिर भी JavaScript में टाइमर इवेंट का वास्तविक कॉलबैक (window.setTimeout() या window.setInterval के ज़रिए) में, लेआउट, रेंडरिंग, गार्बेज कलेक्शन, और XMLHTTPRequest और दूसरे कॉलबैक के हिसाब से दस मिलीसेकंड या उससे ज़्यादा समय में आसानी से बदलाव किया जा सकता है. यह काम, थ्रेड पर अलग-अलग तरह से किया जा सकता है. याद है कि मैंने उन “ऑडियो इवेंट” के बारे में कैसे बताया था जिन्हें हम Web Audio API का इस्तेमाल करके शेड्यूल कर सकते थे? खैर, उन सभी को एक अलग थ्रेड पर प्रोसेस किया जा रहा है - इसलिए, भले ही मुख्य थ्रेड को किसी जटिल लेआउट या कोई लंबा टास्क करते हुए कुछ समय के लिए बंद कर दिया गया हो, फिर भी ऑडियो ठीक उसी समय पर सुना जाएगा जब उन्हें कहा गया था. भले ही, आपको डीबगर में ब्रेकपॉइंट पर रोका गया हो, फिर भी ऑडियो थ्रेड शेड्यूल किए गए इवेंट चलाता रहेगा!

ऑडियो ऐप्लिकेशन में JavaScript setTimeout() का इस्तेमाल करना

मुख्य थ्रेड एक बार में कई मिलीसेकंड के लिए आसानी से बंद हो सकता है. इसलिए, सीधे ऑडियो इवेंट चलाने के लिए JavaScript के setTimeout का इस्तेमाल करना अच्छा आइडिया नहीं है. ऐसा इसलिए, क्योंकि बहुत ज़रूरी है कि आपके नोट मिलीसेकंड में या इतने समय में फ़ायर हो जाएं, और सबसे खराब बात यह है कि उनमें और भी ज़्यादा समय लग सकता है. सबसे खराब बात यह है कि लय में चलने वाले क्रम होने पर, वे सही इंटरवल पर फ़ायर नहीं होंगे, क्योंकि टाइमिंग मुख्य JavaScript थ्रेड में होने वाली अन्य चीज़ों के प्रति संवेदनशील होगी.

इसे दिखाने के लिए, मैंने “खराब” मेट्रोनोम ऐप्लिकेशन का एक सैंपल लिखा - जो कि नोट शेड्यूल करने के लिए सीधे setTimeout का इस्तेमाल करता है - और लेआउट में भी काफ़ी बदलाव करता है. इस ऐप्लिकेशन को खोलें, “चलाएं” पर क्लिक करें, और इसके चलते रहने के दौरान विंडो का तेज़ी से आकार बदलें; आपको समय पर बहुत परेशानी होगी (आपको सुनाई देगा कि लय एक जैसी नहीं है). “लेकिन यह विरोधाभासी बात है!” आप कहते हैं? वैसे, इसका मतलब यह नहीं है कि असल दुनिया में भी ऐसा नहीं होता. यहां तक कि रिलेआउट की वजह से सेट टाइम आउट में समय की गड़बड़ी भी होगी - उदाहरण के लिए, मैंने देखा कि विंडो का साइज़ तेज़ी से बदलने से, WebkitSynth पर असर डालने में लगने वाला समय साफ़ तौर पर दिखना बंद हो जाएगा. अब यह सोचें कि जब आपको अपने ऑडियो के साथ-साथ, पूरे म्यूज़िकल स्कोर को स्मूद-स्क्रोल करने की कोशिश करनी होगी, तो क्या होगा. यह अब कल्पना भी की जा सकती है कि संगीत के मुश्किल ऐप्लिकेशन पर इसका क्या असर पड़ेगा.

अक्सर पूछे जाने वाले सवालों में से एक यह है कि “मुझे ऑडियो इवेंट से कॉलबैक क्यों नहीं मिल सकते?” हालांकि, इस तरह के कॉलबैक का इस्तेमाल किया जा सकता है, लेकिन उनसे किसी समस्या का तुरंत समाधान नहीं होगा - यह समझना ज़रूरी है कि वे इवेंट मुख्य JavaScript थ्रेड में फ़ायर किए जाएंगे. इसलिए, उन्हें सेटटाइम आउट की तरह ही प्रोसेस किया जाएगा. इसका मतलब है कि उन्हें कुछ मिलीसेकंड से पहले ही प्रोसेस किया जा सकता है.

तो हम क्या कर सकते हैं? समय को हैंडल करने का सबसे अच्छा तरीका यह है कि JavaScript टाइमर (setTimeout(), setInterval() या requestAnimationFrame() - बाद में उस पर और काम करें) और ऑडियो हार्डवेयर शेड्यूलिंग के बीच मिलकर काम करें.

आगे की ओर देखकर रॉक-सॉलिड टाइमिंग हासिल करना

आइए, उस मेट्रोनोम डेमो पर वापस चलते हैं - इसी तरह, मैंने साथ मिलकर शेड्यूल करने की इस तकनीक को दिखाने के लिए, इस आसान मेट्रोनोम डेमो का पहला वर्शन सही तरीके से लिखा था. (यह कोड GitHub पर भी उपलब्ध है यह डेमो, बीट के आधार पर पिच में बदलाव करते हुए, हर सोलहवें, आठवें या तिमाही के नोट पर बहुत सटीक तरीके से बीप की आवाज़ (ऑसिलेटर से जनरेट करता है) चलाता है. इससे, गेम चलने के दौरान उसकी ताल और नोट का अंतराल बदला जा सकता है. इसके अलावा, वीडियो को किसी भी समय रोका जा सकता है. यह असल दुनिया के किसी भी लयबद्ध सीक्वेंसर के लिए एक अहम सुविधा है. साथ ही, यह मेट्रोनोम तुरंत जिन आवाज़ों का इस्तेमाल करता है उन्हें बदलने के लिए कोड जोड़ना बहुत आसान होगा.

इसके ज़रिए लगातार अच्छी क्वालिटी बनाए रखने के साथ-साथ तापमान को भी कंट्रोल किया जा सकता है. यह एक ऐसा सेटटाइम टाइमर है जो नियमित तौर पर एक बार फ़ायर होता है. साथ ही, आने वाले समय में हर नोट के लिए वेब ऑडियो शेड्यूलिंग भी सेट अप करता है. setTimeout टाइमर सिर्फ़ यह जांचता है कि क्या किसी नोट को मौजूदा टेंपो के हिसाब से "जल्द ही" शेड्यूल करने की ज़रूरत है. इसके बाद, उसे इस तरह शेड्यूल किया जाता है:

setTimeout() और ऑडियो इवेंट इंटरैक्शन.
setTimeout() और ऑडियो इवेंट इंटरैक्शन.

असल में, setTimeout() कॉल में देरी हो सकती है. इसलिए, समय के साथ कॉल को शेड्यूल करने में मुश्किल हो सकती है और यह इस पर निर्भर करता है कि आपने setTimeout का इस्तेमाल कैसे किया है. इस उदाहरण में, इवेंट के बीच करीब 50 मि॰से॰ का अंतर होता है. हालांकि, वे अक्सर उससे थोड़ा ज़्यादा (और कभी-कभी ज़्यादा) भी होते हैं. हालांकि, हर कॉल के दौरान हम वेब ऑडियो इवेंट को न सिर्फ़ शेड्यूल किए गए ऐसे नोट के लिए शेड्यूल करते हैं जिन्हें अभी चलाए जाने की ज़रूरत हो, बल्कि ऐसे नोट को भी शेड्यूल किया जाता है जिन्हें अब और अगले इंटरवल के बीच चलाया जाना चाहिए.

असल में, हम सिर्फ़ setTimeout() कॉल के बीच का सटीक इंटरवल देखना नहीं चाहते हैं - हमें इस टाइमर कॉल और अगले टाइमर के बीच कुछ शेड्यूलिंग ओवरलैप की भी ज़रूरत होती है, ताकि सबसे खराब मामले में मुख्य थ्रेड के व्यवहार को ठीक किया जा सके - यानी, मुख्य थ्रेड पर खराब ढंग से इकट्ठा होने, लेआउट, रेंडरिंग या अन्य कोड के होने पर, अगले टाइमर कॉल में देरी होती है. हमें ऑडियो ब्लॉक-शेड्यूलिंग में लगने वाले समय का भी ध्यान रखना होगा. इसका मतलब है कि ऑपरेटिंग सिस्टम, प्रोसेस होने में कितना समय बिताता है. यह समय ऑपरेटिंग सिस्टम और हार्डवेयर के हिसाब से अलग-अलग होता है. इसमें मिलीसेकंड के छोटे अंक से लेकर करीब 50 मि॰से॰ तक का समय होता है. ऊपर दिखाए गए हर setTimeout() कॉल में एक नीला इंटरवल होता है. इससे यह पता चलता है कि इवेंट शेड्यूल करने की पूरी रेंज तय होने के दौरान, ऊपर दिए गए डायग्राम में शेड्यूल किए गए चौथे वेब ऑडियो इवेंट को "देर से" तब तक चलाया जा सकता है, जब तक कि setTimeout कॉल के कुछ मिलीसेकंड बाद ही सेट किया जाता. असल ज़िंदगी में इस तरह के हालात में, आस-पास के माहौल में गड़बड़ी और भी ज़्यादा गंभीर हो सकती है. जैसे-जैसे आपका ऐप्लिकेशन ज़्यादा जटिल होता जाता है, यह ओवरलैप और भी ज़्यादा अहम हो जाता है.

टेकअहेड के लिए लगने वाले कुल समय से इस बात पर असर पड़ता है कि टेंपो कंट्रोल और अन्य रीयल-टाइम कंट्रोल, दोनों कितना कम समय के लिए इस्तेमाल किए जा सकते हैं. साथ ही, कॉल शेड्यूल करने के बीच के समय में, प्रोसेसर पर आपके कोड से कितनी बार असर पड़ता है और उसके इंतज़ार में लगने वाला कम से कम समय, दोनों के बीच का अंतर एक ही होता है. अगले इंटरवल के शुरू होने के समय के साथ लुकहेड कितना ओवरलैप होगा, इससे यह तय होता है कि अलग-अलग मशीनों पर आपका ऐप्लिकेशन कितना टिकाऊ होगा. साथ ही, यह ज़्यादा जटिल होने के साथ-साथ लेआउट और कूड़ा इकट्ठा करने में ज़्यादा समय भी लग सकता है. आम तौर पर, धीमी मशीनों और ऑपरेटिंग सिस्टम के हिसाब से काम करने के लिए, बेहतर होगा कि सब कुछ पहले से ज़्यादा सटीक हो और कम समय अंतराल का इस्तेमाल किया जाए. कम कॉलबैक प्रोसेस करने के लिए, इसमें छोटे ओवरलैप और लंबे इंटरवल सेट किए जा सकते हैं. हालांकि, कभी-कभी आपको यह सुनना शुरू कर सकता है कि ज़्यादा इंतज़ार के समय की वजह से टेंपो में बदलाव वगैरह तुरंत लागू नहीं होते. इसके उलट, अगर आपने लुक को बहुत कम कर दिया है, तो आपको कुछ परेशान करने वाले इवेंट सुनाई देने शुरू हो सकते हैं, क्योंकि शेड्यूल कॉल में ऐसी "मेक अप" करनी पड़ सकती है जो पहले हो चुके थे.

नीचे दिए गए टाइमिंग डायग्राम में दिखाया गया है कि मेट्रोनोम डेमो कोड असल में क्या करता है: इसमें 25 मि॰से॰ का सेटटाइम इंटरवल है. हालांकि, इससे बेहतर ढंग से ओवरलैप होने पर, हर कॉल को अगले 100 मि॰से॰ के लिए शेड्यूल किया जाएगा. इस लंबे लुक के साथ एक समस्या यह है कि रफ़्तार में बदलाव वगैरह को लागू होने में दस सेकंड से ज़्यादा समय लगता है. हालांकि, हम किसी भी तरह की रुकावट से बचने की बेहतर कोशिश करते हैं:

लंबे ओवरलैप के साथ शेड्यूल करना.
लंबे समय तक ओवरलैप करने वाले वीडियो को शेड्यूल करना

असल में, इस उदाहरण में आपको बताया जा सकता है कि हमारे बीच में setTimeout की वजह से एक रुकावट आई थी - हमें करीब 270 मि॰से॰ का setTimeout कॉलबैक मिलना चाहिए था. हालांकि, किसी वजह से इसमें करीब 320 मि॰से॰ - 50 मि॰से॰ बाद तक की देरी हुई! हालांकि, इंतज़ार का समय ज़्यादा होने की वजह से, कोई रुकावट नहीं आई और हम कोई भी रुकावट नहीं चूके. हालांकि, इससे पहले हमने टेंपो को बढ़ाकर 240 बीपीएम पर सोलहवें नोट की रफ़्तार बढ़ा दी थी. हालांकि, इसमें हार्डकोर ड्रम और बेस की रफ़्तार भी काफ़ी नहीं थी!

यह भी हो सकता है कि हर शेड्यूलर कॉल के लिए एक से ज़्यादा नोट शेड्यूल किए जाएं. आइए देखते हैं कि अगर हम एक लंबे शेड्यूलिंग अंतराल (250 मि॰से॰ के आगे, 200 मि॰से॰ की दूरी पर) और बीच में स्पीड में बढ़ोतरी का इस्तेमाल करते हैं, तो क्या होता है:

लंबे लुकअहेड और लंबे इंटरवल के साथ setTimeout() का इस्तेमाल करें.
लंबे लुकअप और लंबे इंटरवल के साथ setTimeout() फ़ंक्शन का इस्तेमाल करें

यह केस दिखाता है कि हर setTimeout() कॉल, कई ऑडियो इवेंट शेड्यूल कर सकती है - असल में, यह मेट्रोनोम एक बार में सिर्फ़ एक नोट वाला ऐप्लिकेशन है. हालांकि, यह आसानी से देखा जा सकता है कि यह तरीका किसी ड्रम मशीन (जहां एक साथ कई नोट होते हैं) या सीक्वेंसर (जिसमें नोट के बीच अक्सर अनियमित इंटरवल होते हैं) के लिए यह तरीका कैसे काम करता है.

व्यावहारिक रूप से, आपको अपने शेड्यूलिंग इंटरवल और लुक को ट्यून करके यह देखना होगा कि लेआउट, गार्बेज कलेक्शन, और मुख्य JavaScript एक्ज़ीक्यूशन थ्रेड में हो रही अन्य चीज़ों से इस पर कितना असर पड़ा है. साथ ही, टेंपो वगैरह पर कंट्रोल के विवरण को बेहतर बनाने के लिए भी आपको ऐसा करना होगा. अगर आपका लेआउट बार-बार होता है, तो आपको उसे बड़ा करके देखना चाहिए. मुख्य बात यह है कि हम चाहते हैं कि "शेड्यूलिंग की जा रही" मात्रा इतनी बड़ी हो कि किसी भी देरी से बचा जा सके. हालांकि, हम इतना भी नहीं चाहते कि हमारी रफ़्तार को कम किया जा सके. यहां तक कि ऊपर दिए गए मामले में भी बहुत कम ओवरलैप हैं, इसलिए यह जटिल वेब ऐप्लिकेशन वाली धीमी मशीन पर बहुत ज़्यादा लचीला नहीं होगा. शुरुआत करने के लिए, 100 मि॰से॰ "लुकअहेड" समय का इस्तेमाल किया जा सकता है. साथ ही, इंटरवल 25 मि॰से॰ पर सेट किया जा सकता है. इसमें अब भी ऑडियो सिस्टम के बहुत ज़्यादा इंतज़ार करने वाली मशीनों पर जटिल ऐप्लिकेशन में समस्याएं हो सकती हैं. ऐसी स्थिति में आपको पहले से ही अंदाज़ा लगाना होगा. इसके अलावा, अगर आपको पहले से ज़्यादा कंट्रोल की ज़रूरत है, तो छोटे लुकहेड का इस्तेमाल करें.

शेड्यूलिंग प्रोसेस का कोर कोड, Scheduler() फ़ंक्शन में है -

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

यह फ़ंक्शन सिर्फ़ मौजूदा ऑडियो हार्डवेयर का समय लेता है और इसकी तुलना क्रम में अगले नोट के समय से करता है - इस सटीक स्थिति में ज़्यादातर समय*, यह कुछ नहीं करेगा (क्योंकि शेड्यूल किए जाने के लिए कोई मेट्रोनोम “नोट” नहीं हैं. हालांकि, इसके सफल होने पर, उस नोट को Web Audio API का इस्तेमाल करके शेड्यूल किया जाएगा. इसके बाद, अगले नोट पर जाएं.

ScheduleNote() फ़ंक्शन ही अगले वेब ऑडियो "नोट" को चलाए जाने के लिए शेड्यूल करता है. इस मामले में, मैंने अलग-अलग फ़्रीक्वेंसी पर बीप की आवाज़ निकालने के लिए दोलकों का इस्तेमाल किया है. आपके पास भी आसानी से 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 और मौजूदा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 API के ज़रिए सिंक किया जाता है. हमारे मेट्रोनोम उदाहरण में बॉक्स बनाना काफ़ी मुश्किल लग सकता है, लेकिन जैसे-जैसे आपके ग्राफ़िक ज़्यादा जटिल होते जा रहे हैं, वैसे-वैसे विज़ुअल रीफ़्रेश दर के साथ सिंक करने के लिए requestAnimationFrame() का इस्तेमाल करना उतना ही आसान होता जा रहा है जितना कि setTimeout() का इस्तेमाल करके! बहुत ही जटिल ऑडियो टैग और म्यूज़िकल नोट को चलाने पर,

हमने शेड्यूलर में, सूची में मौजूद बीट को ट्रैक किया:

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

हमारे मेट्रोनोम के मौजूदा समय के बारे में जानकारी, Due() तरीके में मिल सकती है. इसे तब (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 टाइमस्टैंप का बिलकुल भी इस्तेमाल नहीं कर रहे हैं. ऐसा इसलिए, क्योंकि हम ऑडियो सिस्टम की घड़ी का इस्तेमाल करके यह पता लगा रहे हैं कि समय किस समय पर है.

बेशक मैं एक setTimeout() कॉलबैक का इस्तेमाल करना छोड़ सकता था और अपने नोट शेड्यूलर को requestAnimationFrame कॉलबैक में डाल देता था. इसके बाद, हम फिर से दो टाइमर सेट कर देते थे. ऐसा भी किया जा सकता है, लेकिन यह समझना ज़रूरी है कि इस मामले में requestAnimationFrame सिर्फ़ एक खास तरह के setTimeout() की तरह है. आपको अब भी असली नोट के लिए, वेब ऑडियो टाइमिंग को शेड्यूल करने की सटीक जानकारी चाहिए.

नतीजा

मुझे उम्मीद है कि इस ट्यूटोरियल से आपको घड़ी और टाइमर के बारे में जानकारी मिलेगी. साथ ही, वेब ऑडियो ऐप्लिकेशन में सही समय सेट करने में भी मदद मिलेगी. इन तकनीकों का इस्तेमाल करके, क्रम से चलने वाले प्लेयर, ड्रम मशीन वगैरह बनाए जा सकते हैं. फिर मिलेंगे...