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

Ilmari Heikkinen

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

इस लेख में, हम आपको बताएंगे कि अपने WebGL सीन में 3D साउंड जोड़ने के लिए, Web Audio API में पोज़िशनल ऑडियो की सुविधा का इस्तेमाल कैसे किया जाता है. ऑडियो को ज़्यादा भरोसेमंद बनाने के लिए, हम आपको Web Audio API की मदद से, पर्यावरण पर पड़ने वाले असर के बारे में जानकारी देंगे. Web Audio API के बारे में पूरी जानकारी पाने के लिए, बोरिस Smus का 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 के उदाहरण वाले पेज पर, डॉप्लर इफ़ेक्ट के कुछ अच्छे उदाहरण दिए गए हैं.

लिसनर और 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) को गुणा करें.

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

थ्री.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 और सादे ऑडियो को कंट्रोल करने के लिए, GainNodes को जोड़ना ज़रूरी है.

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

...
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 का इस्तेमाल करके, कई तरह के इफ़ेक्ट और माहौल को सिम्युलेट किया जा सकता है.

रेफ़रंस