पोज़िशनल ऑडियो और WebGL का मिक्स करना

Ilmari Heikkinen

परिचय

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

पोज़िशनल ऑडियो इस्तेमाल करने के लिए, Web Audio API में AudioPannerNode का इस्तेमाल करें. AudioPannerNode, किसी साउंड की पोज़िशन, ओरिएंटेशन, और वेग तय करता है. इसके अलावा, Web Audio API के ऑडियो कॉन्टेक्स्ट में एक लिसनर एट्रिब्यूट होता है. इसकी मदद से, लिसनर की पोज़िशन, ओरिएंटेशन, और वेग तय किया जा सकता है. इन दोनों चीज़ों की मदद से, डॉप्लर इफ़ेक्ट और 3D पैनिंग की मदद से, किसी दिशा से आने वाली आवाज़ें बनाई जा सकती हैं.

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

// Detect if the audio context is supported.
window.AudioContext = (
  window.AudioContext ||
  window.webkitAudioContext ||
  null
);

if (!AudioContext) {
  throw new Error("AudioContext not supported!");
} 

// Create a new audio context.
var ctx = new AudioContext();

// Create a AudioGainNode to control the main volume.
var mainVolume = ctx.createGain();
// Connect the main volume node to the context destination.
mainVolume.connect(ctx.destination);

// Create an object with a sound source and a volume control.
var sound = {};
sound.source = ctx.createBufferSource();
sound.volume = ctx.createGain();

// Connect the sound source to the volume control.
sound.source.connect(sound.volume);
// Hook up the sound volume control to the main volume.
sound.volume.connect(mainVolume);

// Make the sound source loop.
sound.source.loop = true;

// Load a sound file using an ArrayBuffer XMLHttpRequest.
var request = new XMLHttpRequest();
request.open("GET", soundFileName, true);
request.responseType = "arraybuffer";
request.onload = function(e) {

  // Create a buffer from the response ArrayBuffer.
  ctx.decodeAudioData(this.response, function onSuccess(buffer) {
    sound.buffer = buffer;

    // Make the sound source use the buffer and start playing it.
    sound.source.buffer = sound.buffer;
    sound.source.start(ctx.currentTime);
  }, function onFailure() {
    alert("Decoding the audio buffer failed");
  });
};
request.send();

स्थिति

स्पीकर में आवाज़ को कैसे मिक्स करना है, यह तय करने के लिए पोज़िशनल ऑडियो, ऑडियो सोर्स और सुनने वाले की पोज़िशन का इस्तेमाल करता है. ऐसे में, लिसनर की बाईं ओर मौजूद ऑडियो सोर्स की आवाज़, बाएं स्पीकर में और दाईं ओर वाले स्पीकर से ज़्यादा तेज़ होगी.

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

पोज़िशन ट्रैकिंग सेट अप करने के लिए, हमें AudioPannerNode बनाना होगा और उसे मुख्य वॉल्यूम पर जोड़ना होगा.

...
sound.panner = ctx.createPanner();
// Instead of hooking up the volume to the main volume, hook it up to the panner.
sound.volume.connect(sound.panner);
// And hook up the panner to the main volume.
sound.panner.connect(mainVolume);
...

हर फ़्रेम में, AudioPannerNodes की पोज़िशन अपडेट करें. यहां दिए गए उदाहरणों में, मैं Three.js का इस्तेमाल करूंगा.

...
// In the frame handler function, get the object's position.
object.position.set(newX, newY, newZ);
object.updateMatrixWorld();
var p = new THREE.Vector3();
p.setFromMatrixPosition(object.matrixWorld);

// And copy the position over to the sound of the object.
sound.panner.setPosition(p.x, p.y, p.z);
...

यह ट्रैक करने के लिए कि ऑडियो किस क्रम में है, ऑडियो के कॉन्टेक्स्ट के हिसाब से ऑडियो सुनने वाले व्यक्ति की पोज़िशन को कैमरे की पोज़िशन पर सेट करें.

...
// Get the camera position.
camera.position.set(newX, newY, newZ);
camera.updateMatrixWorld();
var p = new THREE.Vector3();
p.setFromMatrixPosition(camera.matrixWorld);

// And copy the position over to the listener.
ctx.listener.setPosition(p.x, p.y, p.z);
...

वेलोसिटी

अब हमारे पास लिसनर और AudioPannerNode की पोज़िशन है. अब इनके वेग पर ध्यान दें. लिसनर और AudioPannerNode की वेलोसिटी प्रॉपर्टी को बदलकर, साउंड में डॉपलर इफ़ेक्ट जोड़ा जा सकता है. Web Audio API के उदाहरण पेज पर अच्छे डॉपलर इफ़ेक्ट के कुछ उदाहरण दिए गए हैं.

Listener और AudioPannerNode के वेग का पता लगाने का सबसे आसान तरीका यह है कि हर फ़्रेम में उनकी पोज़िशन का ट्रैक रखा जाए. दर्शक की गति, कैमरे की मौजूदा पोज़िशन में पिछले फ़्रेम की पोज़िशन को घटाने पर मिलती है. इसी तरह, AudioPannerNode का वेग, उसकी मौजूदा पोज़िशन में से पिछली पोज़िशन को घटाने पर मिलता है.

वेग को ट्रैक करने के लिए, ऑब्जेक्ट की पिछली पोज़िशन को उसकी मौजूदा पोज़िशन से घटाएं. इसके बाद, इस अंतर को पिछले फ़्रेम से बीते समय से भाग दें. Three.js में ऐसा करने का तरीका यहां बताया गया है:

...
var dt = secondsSinceLastFrame;

var p = new THREE.Vector3();
p.setFromMatrixPosition(object.matrixWorld);
var px = p.x, py = p.y, pz = p.z;

object.position.set(newX, newY, newZ);
object.updateMatrixWorld();

var q = new THREE.Vector3();
q.setFromMatrixPosition(object.matrixWorld);
var dx = q.x-px, dy = q.y-py, dz = q.z-pz;

sound.panner.setPosition(q.x, q.y, q.z);
sound.panner.setVelocity(dx/dt, dy/dt, dz/dt);
...

ओरिएंटेशन

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

AudioPannerNode के लिए ओरिएंटेशन वेक्टर पाने के लिए, आपको साउंड-इमिटिंग 3D ऑब्जेक्ट के मॉडल मैट्रिक्स के रोटेशन वाले हिस्से को लेना होगा. इसके बाद, उसमें vec3(0,0,1) को गुणा करके देखना होगा कि वह कहां पर पॉइंट कर रहा है. कॉन्टेक्स्ट लिसनर ओरिएंटेशन के लिए, आपको कैमरे का ओरिएंटेशन वेक्टर चाहिए. लिसनर ओरिएंटेशन के लिए भी अप वेक्टर की ज़रूरत होती है, क्योंकि उसे लिसनर के सिर के रोल ऐंगल के बारे में पता होना चाहिए. लिसनर के ओरिएंटेशन का हिसाब लगाने के लिए, कैमरे के व्यू मैट्रिक्स का रोटेशन हिस्सा लें. इसके बाद, ओरिएंटेशन के लिए vec3(0,0,1) और अप-वेक्टर के लिए vec3(0,-1,0) से गुणा करें.

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

Three.js में ऑरिएंटेशन को ट्रैक करना थोड़ा मुश्किल है, क्योंकि इसमें वेक्टर मैथ और 4x4 वर्ल्ड मैट्रिक्स के ट्रांसलेशन वाले हिस्से को शून्य करना शामिल है. हालांकि, कोड की लाइनें ज़्यादा नहीं होनी चाहिए.

...
var vec = new THREE.Vector3(0,0,1);
var m = object.matrixWorld;

// Save the translation column and zero it.
var mx = m.elements[12], my = m.elements[13], mz = m.elements[14];
m.elements[12] = m.elements[13] = m.elements[14] = 0;

// Multiply the 0,0,1 vector by the world matrix and normalize the result.
vec.applyProjection(m);
vec.normalize();

sound.panner.setOrientation(vec.x, vec.y, vec.z);

// Restore the translation column.
m.elements[12] = mx;
m.elements[13] = my;
m.elements[14] = mz;
...

कैमरे के ओरिएंटेशन को ट्रैक करने के लिए भी अप वेक्टर की ज़रूरत होती है. इसलिए, आपको अप वेक्टर को ट्रांसफ़ॉर्मेशन मैट्रिक्स से गुणा करना होगा.

...
// The camera's world matrix is named "matrix".
var m = camera.matrix;

var mx = m.elements[12], my = m.elements[13], mz = m.elements[14];
m.elements[12] = m.elements[13] = m.elements[14] = 0;

// Multiply the orientation vector by the world matrix of the camera.
var vec = new THREE.Vector3(0,0,1);
vec.applyProjection(m);
vec.normalize();

// Multiply the up vector by the world matrix.
var up = new THREE.Vector3(0,-1,0);
up.applyProjection(m);
up.normalize();

// Set the orientation and the up-vector for the listener.
ctx.listener.setOrientation(vec.x, vec.y, vec.z, up.x, up.y, up.z);

m.elements[12] = mx;
m.elements[13] = my;
m.elements[14] = mz;
...

अपनी आवाज़ के लिए साउंड कोन सेट करने के लिए, पैनर नोड की सही प्रॉपर्टी सेट करें. शंकु के कोण डिग्री में होते हैं और 0 से 360 तक होते हैं.

...
sound.panner.coneInnerAngle = innerAngleInDegrees;
sound.panner.coneOuterAngle = outerAngleInDegrees;
sound.panner.coneOuterGain = outerGainFactor;
...

सभी एक साथ

इन सभी को एक साथ जोड़ने पर, ऑडियो कॉन्टेक्स्ट लिसनर, कैमरे की पोज़िशन, ओरिएंटेशन, और वेग का पालन करता है. साथ ही, AudioPannerNodes अपने-अपने ऑडियो सोर्स की पोज़िशन, ओरिएंटेशन, और वेग का पालन करते हैं. आपको हर फ़्रेम पर, AudioPannerNodes और ऑडियो कॉन्टेक्स्ट लिसनर की पोज़िशन, वेग, और ओरिएंटेशन अपडेट करने होंगे.

पर्यावरण पर असर

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

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

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

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

...
var ctx = new webkitAudioContext();
var mainVolume = ctx.createGain();

// Create a convolver to apply environmental effects to the audio.
var convolver = ctx.createConvolver();

// Create a mixer that receives sound from the panners.
var mixer = ctx.createGain();

sounds.forEach(function(sound){
  sound.panner.connect(mixer);
});

// Create volume controllers for the plain audio and the convolver.
var plainGain = ctx.createGain();
var convolverGain = ctx.createGain();

// Send audio from the mixer to plainGain and the convolver node.
mixer.connect(plainGain);
mixer.connect(convolver);

// Hook up the convolver to its volume control.
convolver.connect(convolverGain);

// Send audio from the volume controls to the main volume control.
plainGain.connect(mainVolume);
convolverGain.connect(mainVolume);

// Finally, connect the main volume to the audio context's destination.
volume.connect(ctx.destination);
...

ConvolverNode को काम करने के लिए, आपको बफ़र में इंपल्स रिस्पॉन्स सैंपल लोड करना होगा और ConvolverNode को इसका इस्तेमाल करने के लिए कहना होगा. सैंपल को लोड करने का तरीका, सामान्य साउंड सैंपल के लोड करने के तरीके जैसा ही होता है. नीचे ऐसा करने के एक तरीके का उदाहरण दिया गया है:

...
loadBuffer(ctx, "impulseResponseExample.wav", function(buffer){
  convolver.buffer = buffer;
  convolverGain.gain.value = 0.7;
  plainGain.gain.value = 0.3;
})
...
function loadBuffer(ctx, filename, callback) {
  var request = new XMLHttpRequest();
  request.open("GET", soundFileName, true);
  request.responseType = "arraybuffer";
  request.onload = function() {
    // Create a buffer and keep the channels unchanged.
    ctx.decodeAudioData(request.response, callback, function() {
      alert("Decoding the audio buffer failed");
    });
  };
  request.send();
}

खास जानकारी

इस लेख में आपको Web Audio API का इस्तेमाल करके, अपने 3D सीन में पोज़िशनल ऑडियो जोड़ने का तरीका बताया गया है. Web Audio API की मदद से, ऑडियो सोर्स और सुनने वाले की पोज़िशन, ओरिएंटेशन, और वेग सेट किया जा सकता है. अपने 3D सीन में ऑब्जेक्ट को ट्रैक करने के लिए उन्हें सेट करके, अपने 3D ऐप्लिकेशन के लिए बेहतर साउंडस्केप बनाया जा सकता है.

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

रेफ़रंस