ترکیب صدای موقعیتی و WebGL

Ilmari Heikkinen

مقدمه

در این مقاله قصد دارم در مورد نحوه استفاده از ویژگی صوتی موقعیتی در Web Audio API برای افزودن صدای سه بعدی به صحنه های WebGL صحبت کنم. برای باورپذیرتر کردن صدا، همچنین شما را با اثرات محیطی ممکن با Web Audio API آشنا خواهم کرد. برای آشنایی کامل‌تر با Web Audio API، مقاله شروع با Web Audio API توسط Boris Smus را بررسی کنید.

برای انجام صدای موقعیتی، از AudioPannerNode در Web Audio API استفاده می کنید. AudioPannerNode موقعیت، جهت و سرعت صدا را تعریف می کند. علاوه بر این، زمینه صوتی Web Audio API دارای ویژگی شنونده است که به شما امکان می دهد موقعیت، جهت و سرعت شنونده را تعریف کنید. با این دو چیز می توانید صداهای جهت دار با جلوه های داپلر و پانینگ سه بعدی ایجاد کنید.

بیایید ببینیم کد صوتی برای صحنه بالا چگونه است. این کد بسیار ابتدایی 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 را تنظیم کنید. حالا شما یک صدای سه بعدی متحرک دارید. موقعیت شنونده زمینه صوتی به طور پیش فرض در (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، باید قسمت چرخش ماتریس مدل شی 3 بعدی ساطع کننده صدا را بگیرید و یک vec3(0,0,1) را با آن ضرب کنید تا ببینید در نهایت به کجا اشاره می کند. برای جهت گیری شنونده زمینه، باید بردار جهت دوربین را بدست آورید. جهت شنونده نیز به بردار بالا نیاز دارد، زیرا باید زاویه چرخش سر شنونده را بداند. برای محاسبه جهت شنونده، قسمت چرخش ماتریس دید دوربین را بدست آورید و vec3(0,0,1) را برای جهت و vec3(0,-1,0) را برای بردار بالا ضرب کنید.

برای اینکه جهت گیری روی صداهای شما تأثیر بگذارد، باید مخروط صدا را نیز مشخص کنید. مخروط صدا دارای زاویه داخلی، زاویه بیرونی و بهره بیرونی است. صدا با ولوم معمولی در داخل زاویه داخلی پخش می شود و با نزدیک شدن به زاویه بیرونی، به تدریج بهره به بهره بیرونی تغییر می کند. در خارج از زاویه بیرونی صدا با بهره بیرونی پخش می شود.

ردیابی جهت‌گیری در Three.js کمی پیچیده‌تر است، زیرا شامل برخی ریاضیات برداری و صفر کردن بخش ترجمه ماتریس‌های جهان ۴×۴ است. با این حال، خطوط کد زیادی وجود ندارد.

...
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 و شنونده زمینه صوتی را در هر فریم به روز کنید.

اثرات زیست محیطی

پس از تنظیم صدای موقعیتی، می توانید جلوه های محیطی را برای صدای خود تنظیم کنید تا غوطه وری صحنه سه بعدی خود را افزایش دهید. فرض کنید صحنه شما در داخل یک کلیسای جامع بزرگ تنظیم شده است. در تنظیمات پیش فرض، صداهای صحنه شما به نظر می رسد که شما در فضای باز ایستاده اید. این اختلاف بین تصاویر و صدا، غوطه وری را در هم می شکند و صحنه شما را کمتر چشمگیر می کند.

Web Audio API یک ConvolverNode دارد که به شما امکان می دهد جلوه محیطی صدا را تنظیم کنید. آن را به نمودار پردازش منبع صوتی اضافه کنید و می‌توانید صدا را متناسب با تنظیمات تنظیم کنید. می‌توانید نمونه‌های پاسخ ضربه‌ای را در وب پیدا کنید که می‌توانید با ConvolverNodes از آن‌ها استفاده کنید، و همچنین می‌توانید نمونه‌های خود را بسازید. ممکن است تجربه کمی دست و پا گیر باشد زیرا باید پاسخ ضربه ای مکانی را که می خواهید شبیه سازی کنید ضبط کنید، اما در صورت نیاز این قابلیت وجود دارد.

استفاده از ConvolverNodes برای انجام صدای محیطی نیاز به سیم کشی مجدد نمودار پردازش صدا دارد. به جای ارسال مستقیم صدا به ولوم اصلی، باید آن را از طریق ConvolverNode هدایت کنید. و همانطور که ممکن است بخواهید قدرت اثر محیطی را کنترل کنید، همچنین باید صدا را در اطراف ConvolverNode هدایت کنید. برای کنترل حجم های میکس، ConvolverNode و صدای ساده باید GainNode را به آنها متصل کنند.

نمودار نهایی پردازش صوتی که من استفاده می‌کنم، صدای اشیایی را دارد که از 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 صدای موقعیتی را به صحنه های سه بعدی خود اضافه کنید. Web Audio API راهی برای تنظیم موقعیت، جهت و سرعت منابع صوتی و شنونده در اختیار شما قرار می دهد. با تنظیم آن ها برای ردیابی اشیاء در صحنه سه بعدی خود، می توانید یک منظره صوتی غنی برای برنامه های سه بعدی خود ایجاد کنید.

برای جذاب‌تر کردن تجربه صوتی، می‌توانید از ConvolverNode در Web Audio API برای تنظیم صدای کلی محیط استفاده کنید. از کلیساها گرفته تا اتاق‌های بسته، می‌توانید با استفاده از Web Audio API جلوه‌ها و محیط‌های مختلفی را شبیه‌سازی کنید.

مراجع