Konumsal ses ile WebGL'yi karıştırma

Ilmari Heikkinen

Giriş

Bu makalede, WebGL sahnelerinize 3D ses eklemek için Web Audio API'sındaki konumsal ses özelliğini nasıl kullanacağınızdan bahsedeceğim. Sesi daha inandırıcı hale getirmek için size Web Audio API'sının mümkün olduğu çevresel efektleri de tanıtacağım. Web Audio API ile ilgili daha kapsamlı bir giriş için Boris Smus'un Getting started with Web Audio API (Web Audio API'sını kullanmaya başlama) adlı makalesine göz atın.

Konumsal ses yapmak için Web Audio API'sında AudioPannerNode'u kullanırsınız. AudioPannerNode sesin konumunu, yönünü ve hızını tanımlar. Buna ek olarak, Web Audio API ses bağlamı, dinleyicinin konumunu, yönünü ve hızını tanımlamanızı sağlayan bir dinleyici özelliğine sahiptir. Bu iki şeyle, doppler efektleri ve 3D kaydırma ile yönlü sesler oluşturabilirsiniz.

Yukarıdaki sahnede ses kodunun nasıl göründüğüne bakalım. Bu, çok temel bir Audio API kodudur. Bir grup Audio API düğümü oluşturup bunları birbirine bağlayın. Ses düğümleri; bağımsız sesler, ses denetleyicileri, efekt düğümleri, analizciler ve benzerlerinden oluşur. Bu grafiği oluşturduktan sonra, işitsel hale getirmek için grafiği ses bağlamı hedefine bağlamanız gerekir.

// 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();

Konum

Konumsal ses, sesin hoparlörlere nasıl miksleneceğini belirlemek için ses kaynaklarınızın konumunu ve dinleyicinin konumunu kullanır. Dinleyicinin sol tarafındaki bir ses kaynağının sol tarafındaki ses kaynağı sol hoparlörde daha yüksek, sağ tarafta ise ses kaynağı daha yüksek olur.

Başlamak için bir ses kaynağı oluşturun ve bunu bir AudioPannerNode'a ekleyin. Ardından, AudioPannerNode'un konumunu ayarlayın. Artık taşınabilir bir 3D sese sahipsiniz. Ses bağlamı dinleyicisinin konumu varsayılan olarak (0,0,0) konumundadır. Dolayısıyla, bu şekilde kullanıldığında AudioPannerNode konumu kameranın konumuna göre olur. Kamerayı her hareket ettirdiğinizde, AudioPannerNode konumunu güncellemeniz gerekir. AudioPannerNode konumunu dünyaya göre ayarlamak için ses bağlamı dinleyicisinin konumunu kameranızın konumuyla değiştirmeniz gerekir.

Konum izlemeyi kurmak için bir AudioPannerNode oluşturmamız ve bunu ana sese bağlamamız gerekir.

...
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);
...

Her karede AudioPannerNode'ların konumlarını güncelleyin. Aşağıdaki örneklerde Three.js kullanacağım.

...
// 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);
...

Dinleyicinin konumunu izlemek için ses bağlamının dinleyici konumunu kamera ile eşleşecek şekilde ayarlayın.

...
// 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);
...

Hız

Artık dinleyicinin ve AudioPannerNode'un pozisyonlarını öğrendiğimize göre, dikkatimizi onların hızlarına çevirelim. Dinleyicinin ve AudioPannerNode'un hız özelliklerini değiştirerek sese bir doppler efekti ekleyebilirsiniz. Web Audio API'sının örnekleri sayfasında bazı güzel doppler etkisi örnekleri bulunmaktadır.

Dinleyici ve AudioPannerNode için hızları almanın en kolay yolu, her birinin kare başına konumunu takip etmektir. Dinleyicinin hızı, kameranın mevcut konumundan önceki karedeki konumunun çıkarılmasıyla elde edilir. Benzer bir şekilde, AudioPannerNode'un hızı, mevcut konumu eksi önceki konumudur.

Hızı izlemek için nesnenin bir önceki konumu alınıp mevcut konumdan çıkarılabilir ve sonucu son kareden bu yana geçen süreye bölünür. Bunu Three.js'de şu şekilde yapabilirsiniz:

...
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);
...

Yön

Yön, ses kaynağının işaret ettiği ve dinleyicinin baktığı yöndür. Yönü kullanarak yönlü ses kaynaklarını simüle edebilirsiniz. Örneğin, bir yön konuşmacısını düşünün. Hoparlörün önünde durursanız ses, hoparlörün arkasında olduğunuza göre daha yüksek olur. Daha da önemlisi, seslerin dinleyicinin hangi tarafından geldiğini belirlemek için dinleyicinin yönünü belirlemeniz gerekir. Geri döndüğünüzde sol taraftan gelen bir sesin sağa geçmesi gerekir.

AudioPannerNode'un yön vektörünü elde etmek için, ses yayan 3D nesnenin model matrisinin dönüş kısmını almanız ve nereye işaret ettiğini görmek için bir vec3(0,0,1) değerini bununla çarpmanız gerekir. Bağlam dinleyici yönü için kameranın yön vektörünü almanız gerekir. Dinleyicinin yönünün de dinleyicinin başının yuvarlanma açısını bilmesi gerektiği için bir yukarı vektöre ihtiyacı vardır. Dinleyici yönünü hesaplamak için kameranın görüş matrisinin döndürme kısmını alın ve yön için bir vec3(0,0,1) ile yukarı vektör için bir vec3(0,-1,0) ile çarpın.

Yönün sesleriniz üzerinde etkisi olması için sesin konisini de tanımlamanız gerekir. Ses konisi bir iç açı, dış açı ve dış kazanım alır. Ses, iç açı içinde normal ses seviyesinde çalınır ve dış açıya yaklaştıkça yavaş yavaş dış kazanıma dönüşür. Dış açının dışında, ses dış kazançta çalınır.

Three.js'de yönün izlenmesi, bazı vektör hesaplamaları gerektirdiği ve 4x4 dünya matrislerinin çeviri kısmını sıfırlamayı gerektirdiği için biraz karmaşıktır. Yine de çok fazla kod satırı yok.

...
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;
...

Kamera yönünü izlemede yukarı vektör de gerekir. Bu nedenle, bir yukarı vektörü dönüşüm matrisiyle çarpmanız gerekir.

...
// 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;
...

Ses konisini ses düzeyinize göre ayarlamak için kaydırma çubuğunun ilgili özelliklerini ayarlarsınız. Koni açıları derece cinsindendir ve 0 ile 360 arasında değişir.

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

Hepsi bir arada

Hepsini bir araya getirdiğimizde, ses bağlamı dinleyicisi kameranın konumunu, yönünü ve hızını takip ederken AudioPannerNodes da ilgili ses kaynaklarının konumlarını, yönlerini ve hızlarını takip eder. AudioPannerNode'ların ve ses içeriği dinleyicisinin her karede konumlarını, hızlarını ve yönlerini güncellemeniz gerekir.

Çevresel etkiler

Konumsal sesi ayarladıktan sonra, 3D sahnenizin gerçekçiliğini artırmak için sesinize ilişkin ortam efektlerini ayarlayabilirsiniz. Sahnenizin büyük bir katedralin içinde olduğunu varsayalım. Varsayılan ayarlarda, sahnenizdeki sesler dışarıda duruyormuşsunuz gibi duyulur. Görsel ve ses arasındaki bu uyumsuzluk, izleyiciyi sürüklüyor ve sahnenizin etkisini azaltıyor.

Web Audio API'sında ses için ortam efektini ayarlamanıza olanak tanıyan bir ConvolverNode vardır. Bunu ses kaynağının işleme grafiğine eklediğinizde sesi ilgili ayara uygun hale getirebilirsiniz. Web'de ConvolverNodes ile kullanabileceğiniz anlık yanıt örnekleri bulabilir veya kendi örneklerinizi de oluşturabilirsiniz. Simüle etmek istediğiniz yerin dürtüsel yanıtını kaydetmeniz gerektiğinden bu biraz zahmetli bir deneyim olabilir, ancak ihtiyacınız varsa bu olanağı kullanabilirsiniz.

Çevresel ses yapmak için ConvolverNodes'u kullanmak, ses işleme grafiğinin yeniden kurulmasını gerektirir. Sesi doğrudan ana sese iletmek yerine, ConvolverNode üzerinden yönlendirmeniz gerekir. Çevresel etkinin gücünü kontrol etmek isteyebileceğinizden sesi ConvolverNode etrafında yönlendirmeniz de gerekir. ConvolverNode ve sade sesin mix ses düzeylerini kontrol etmek için bunlara GainNodes bağlı olması gerekir.

Kullandığım son ses işleme grafiğinde, geçişli mikser olarak kullanılan bir GainNode'dan geçen nesnelere ait sesler yer alıyor. Sesi, mikserden ConvolverNode'a ve yalın sesin ses düzeyini kontrol etmek için kullanılan başka bir GainNode'a iletiyorum. ConvolverNode, kıvrımlı ses düzeyini kontrol etmek için kendi GainNode'una bağlanmış. GainNodes çıkışları ana ses seviyesi denetleyicisine bağlıdır.

...
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'un çalışması için bir dürtü yanıtı örneğini bir arabelleğe yüklemeniz ve ConvolverNode'un bunu kullanmasını sağlamanız gerekir. Örnek yükleme, normal ses örnekleriyle aynı şekilde gerçekleşir. Aşağıda bunu yapmanın bir yolu verilmiştir:

...
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();
}

Özet

Bu makalede, Web Audio API'sını kullanarak 3D sahnelerinize nasıl konumsal ses ekleyeceğinizi öğrendiniz. Web Audio API'sı, ses kaynaklarının ve dinleyicinin konumunu, yönünü ve hızını ayarlamanıza olanak tanır. Bunları, 3D sahnenizdeki nesneleri izleyecek şekilde ayarlayarak, 3D uygulamalarınız için zengin bir ses ortamı oluşturabilirsiniz.

Ses deneyimini daha da çekici hale getirmek için Web Audio API'sındaki ConvolverNode'u kullanarak ortamdaki genel sesi ayarlayabilirsiniz. Web Audio API'sını kullanarak katedrallerden kapalı odalara çeşitli efektleri ve ortamları simüle edebilirsiniz.

Referanslar