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

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

Chris Wilson
Chris Wilson

परिचय

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

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

हमने आपको Web Audio का इस्तेमाल शुरू करना और Web Audio API की मदद से गेम का ऑडियो बनाना लेखों में, Web Audio के noteOn और noteOff (अब इनका नाम start और stop हो गया है) तरीकों के टाइम पैरामीटर का इस्तेमाल करके, नोट शेड्यूल करने का तरीका पहले ही बताया है. हालांकि, हमने लंबे संगीत वाले सीक्वेंस या लय चलाने जैसी ज़्यादा जटिल स्थितियों के बारे में ज़्यादा जानकारी नहीं दी है. इस बारे में जानने के लिए, हमें सबसे पहले घड़ियों के बारे में थोड़ी जानकारी चाहिए.

The Best of Times - the Web Audio Clock

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 घड़ी की अच्छी बात यह है कि इसमें कुछ बहुत काम के 'बाद में कॉल करें' window.setTimeout() और window.setInterval() तरीके हैं. इनकी मदद से, सिस्टम को किसी खास समय पर हमारे कोड को कॉल करने के लिए कहा जा सकता है.

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

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

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

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

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

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

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

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

आने वाले समय में होने वाली बिक्री का अनुमान लगाकर, सही समय पर प्रमोशन करना

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

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

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

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

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

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

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

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

असल में, इस उदाहरण में देखा जा सकता है कि बीच में 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 और 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;
  }
}

यह बहुत आसान है - हालांकि, यह समझना ज़रूरी है कि शेड्यूल करने के इस उदाहरण में, मैं “सीक्वेंस टाइम” का ट्रैक नहीं रख रहा हूं - यानी, मेट्रोनॉम शुरू होने के बाद का समय. हमें बस यह याद रखना होगा कि हमने आखिरी बार नोट कब बजाया था और अगला नोट कब बजाने के लिए शेड्यूल किया गया है. इस तरह, हम आसानी से गाने की गति बदल सकते हैं या उसे चलाना बंद कर सकते हैं.

शेड्यूल करने की इस तकनीक का इस्तेमाल, वेब पर कई अन्य ऑडियो ऐप्लिकेशन करते हैं. जैसे, वेब ऑडियो ड्रम मशीन, बहुत मज़ेदार ऐसिड डिफ़ेंडर गेम, और ग्रैनलर इफ़ेक्ट के डेमो जैसे ऑडियो के ज़्यादा बेहतर उदाहरण.

टाइमिंग का दूसरा सिस्टम

जैसा कि कोई अच्छा संगीतकार जानता है, हर ऑडियो ऐप्लिकेशन के लिए सबसे ज़्यादा ज़रूरी है अलग-अलग तरह के गाने. यह बताना ज़रूरी है कि विज़ुअल डिसप्ले करने का सही तरीका, तीसरे टाइमिंग सिस्टम का इस्तेमाल करना है!

क्यों, क्यों तुम्हें लगता है कि तुम्हें किसी और टाइमिंग की ज़रूरत क्यों है? खैर, इसे requestAnimationFrame API के ज़रिए विज़ुअल डिसप्ले के साथ सिंक किया जाता है. इसका मतलब है कि ग्राफ़िक रीफ़्रेश दर का मतलब होता है. मेट्रोनॉम के उदाहरण में बॉक्स बनाने के लिए, यह शायद कोई बड़ी बात न लगे, लेकिन जैसे-जैसे आपके ग्राफ़िक ज़्यादा से ज़्यादा जटिल होते जाते हैं, विज़ुअल रीफ़्रेश रेट के साथ सिंक करने के लिए 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 टाइमस्टैंप का इस्तेमाल नहीं कर रहे हैं. ऐसा इसलिए, क्योंकि हम ऑडियो सिस्टम की घड़ी का इस्तेमाल करके यह पता लगा रहे हैं कि हम समय के हिसाब से कहां हैं.

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

नतीजा

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