Mencampur audio posisi dan WebGL

Ilmari Heikkinen

Pengantar

Dalam artikel ini, saya akan membahas cara menggunakan fitur audio posisional di Web Audio API untuk menambahkan suara 3D ke dalam scene WebGL Anda. Agar audio lebih meyakinkan, saya juga akan memperkenalkan efek lingkungan yang dapat dilakukan dengan Web Audio API. Untuk mendapatkan pengantar yang lebih menyeluruh tentang Web Audio API, lihat artikel Getting started with Web Audio API oleh Boris Smus.

Untuk melakukan audio posisional, Anda menggunakan AudioPannerNode di Web Audio API. AudioPannerNode menentukan posisi, orientasi, dan kecepatan suara. Selain itu, konteks audio Web Audio API memiliki atribut pemroses yang memungkinkan Anda menentukan posisi, orientasi, dan kecepatan pemroses. Dengan dua hal ini, Anda dapat membuat suara terarah dengan efek doppler dan panning 3D.

Mari kita lihat tampilan kode audio untuk scene di atas. Ini adalah kode Audio API yang sangat mendasar. Anda akan membuat sekumpulan node Audio API dan menghubungkannya. Node audio terdiri dari suara individual, pengontrol volume, node efek dan penganalisis, dan sejenisnya. Setelah membuat grafik ini, Anda perlu menghubungkannya ke tujuan konteks audio agar dapat didengar.

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

Posisi

Audio posisional menggunakan posisi sumber audio dan posisi pendengar untuk menentukan cara mencampur suara ke speaker. Sumber audio di sisi kiri pendengar akan lebih keras di speaker kiri, dan sebaliknya untuk sisi kanan.

Untuk memulai, buat sumber audio dan lampirkan ke AudioPannerNode. Kemudian, tetapkan posisi AudioPannerNode. Sekarang Anda memiliki suara 3D yang dapat dipindahkan. Posisi pemroses konteks audio berada di (0,0,0) secara default, jadi saat digunakan dengan cara ini, posisi AudioPannerNode relatif terhadap posisi kamera. Setiap kali memindahkan kamera, Anda perlu memperbarui posisi AudioPannerNode. Untuk membuat posisi AudioPannerNode relatif terhadap dunia, Anda perlu mengubah posisi pemroses konteks audio ke posisi kamera.

Untuk menyiapkan pelacakan posisi, kita perlu membuat AudioPannerNode dan menghubungkannya ke volume utama.

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

Pada setiap frame, perbarui posisi AudioPannerNodes. Saya akan menggunakan Three.js dalam contoh di bawah.

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

Untuk melacak posisi pemroses, tetapkan posisi pemroses konteks audio agar cocok dengan posisi kamera.

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

Kecepatan

Setelah memiliki posisi pemroses dan AudioPannerNode, mari kita alihkan perhatian ke kecepatannya. Dengan mengubah properti kecepatan pemroses dan AudioPannerNode, Anda dapat menambahkan efek doppler ke suara. Ada beberapa contoh efek Doppler yang bagus di halaman contoh Web Audio API.

Cara termudah untuk mendapatkan kecepatan bagi pemroses dan AudioPannerNode adalah dengan melacak posisi per frame-nya. Kecepatan pemroses adalah posisi kamera saat ini dikurangi posisi kamera di frame sebelumnya. Demikian pula, kecepatan AudioPannerNode adalah posisinya saat ini dikurangi posisi sebelumnya.

Melacak kecepatan dapat dilakukan dengan mendapatkan posisi objek sebelumnya, menguranginya dari posisi saat ini dan membagi hasilnya dengan waktu yang berlalu sejak frame terakhir. Berikut cara melakukannya di 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);
...

Orientasi

Orientasi adalah arah sumber suara dan arah yang dituju pendengar. Dengan orientasi, Anda dapat menyimulasikan sumber suara terarah. Misalnya, bayangkan speaker terarah. Jika Anda berdiri di depan speaker, suara akan lebih keras daripada jika Anda berdiri di belakang speaker. Yang lebih penting, Anda memerlukan orientasi pemroses untuk menentukan sisi pemroses mana yang menghasilkan suara. Suara yang berasal dari kiri harus dialihkan ke kanan saat Anda berputar.

Untuk mendapatkan vektor orientasi AudioPannerNode, Anda harus mengambil bagian rotasi dari matriks model objek 3D yang memancarkan suara dan mengalikan vec3(0,0,1) dengan objek tersebut untuk melihat ke mana akhirnya objek tersebut mengarah. Untuk orientasi pemroses konteks, Anda perlu mendapatkan vektor orientasi kamera. Orientasi pemroses juga memerlukan vektor naik, karena perlu mengetahui sudut rol kepala pemroses. Untuk menghitung orientasi pemroses, dapatkan bagian rotasi dari matriks tampilan kamera dan kalikan vec3(0,0,1) untuk orientasi dan vec3(0,-1,0) untuk up-vector.

Agar orientasi memiliki efek pada suara, Anda juga perlu menentukan cone untuk suara. Kerucut suara menggunakan sudut dalam, sudut luar, dan penguatan luar. Suara diputar dengan volume normal di dalam sudut dalam dan secara bertahap mengubah gain ke gain luar saat Anda mendekati sudut luar. Di luar sudut luar, suara diputar dengan penguatan luar.

Melacak orientasi di Three.js sedikit lebih rumit karena melibatkan beberapa matematika vektor dan mereset bagian terjemahan dari matriks dunia 4x4 ke nol. Namun, tidak banyak baris kode.

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

Pelacakan orientasi kamera juga memerlukan vektor atas, sehingga Anda perlu mengalikan vektor atas dengan matriks transformasi.

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

Untuk menetapkan kerucut suara untuk suara, Anda menetapkan properti node panner yang sesuai. Sudut kerucut dalam derajat dan berjalan dari 0 hingga 360.

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

Semuanya

Jika digabungkan, pemroses konteks audio akan mengikuti posisi, orientasi, dan kecepatan kamera, dan AudioPannerNodes akan mengikuti posisi, orientasi, dan kecepatan sumber audio masing-masing. Anda perlu memperbarui posisi, kecepatan, dan orientasi AudioPannerNodes serta pemroses konteks audio di setiap frame.

Efek lingkungan

Setelah menyiapkan audio posisi, Anda dapat menyetel efek lingkungan untuk audio Anda guna meningkatkan kualitas imersif pemandangan 3D Anda. Misalkan scene Anda berada di dalam katedral besar. Pada setelan default, suara di tampilan Anda terdengar seperti Anda berdiri di luar ruangan. Perbedaan antara visual dan audio ini memecah imersif dan membuat adegan Anda kurang mengesankan.

Web Audio API memiliki ConvolverNode yang memungkinkan Anda menetapkan efek lingkungan untuk suara. Tambahkan ke grafik pemrosesan untuk sumber audio dan Anda akan dapat membuat suara sesuai dengan setelan. Anda dapat menemukan contoh respons impulsif di web yang dapat digunakan dengan ConvolverNodes, dan Anda juga dapat membuatnya sendiri. Pengalaman ini mungkin sedikit rumit karena Anda perlu merekam respons impuls tempat yang ingin disimulasikan, tetapi kemampuan ini tersedia jika Anda membutuhkannya.

Penggunaan ConvolverNodes untuk melakukan audio lingkungan memerlukan pemasangan ulang grafik pemrosesan audio. Daripada meneruskan suara langsung ke volume utama, Anda harus merutekannya melalui ConvolverNode. Selain itu, karena Anda mungkin ingin mengontrol kekuatan efek lingkungan, Anda juga perlu merutekan audio di sekitar ConvolverNode. Untuk mengontrol volume campuran, ConvolverNode dan audio biasa harus memiliki GainNode yang terpasang.

Grafik pemrosesan audio akhir yang saya gunakan memiliki audio dari objek yang melewati GainNode yang digunakan sebagai mixer pass-through. Dari mixer, saya meneruskan audio ke ConvolverNode dan GainNode lainnya, yang digunakan untuk mengontrol volume audio biasa. ConvolverNode terhubung ke GainNode-nya sendiri untuk mengontrol volume audio yang di konvolusi. Output GainNodes terhubung ke pengontrol volume utama.

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

Agar ConvolverNode berfungsi, Anda harus memuat sampel respons impuls ke dalam buffer dan membuat ConvolverNode menggunakannya. Pemuatan sampel dilakukan dengan cara yang sama seperti sampel suara normal. Berikut adalah contoh salah satu cara untuk melakukannya:

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

Ringkasan

Dalam artikel ini, Anda telah mempelajari cara menambahkan audio posisi ke adegan 3D menggunakan Web Audio API. Web Audio API memberi Anda cara untuk menetapkan posisi, orientasi, dan kecepatan sumber audio serta pemroses. Dengan menyetelnya untuk melacak objek di tampilan 3D, Anda dapat membuat lanskap suara yang kaya untuk aplikasi 3D.

Untuk membuat pengalaman audio lebih menarik, Anda dapat menggunakan ConvolverNode di Web Audio API untuk menyiapkan suara umum lingkungan. Dari katedral hingga ruangan tertutup, Anda dapat menyimulasikan berbagai efek dan lingkungan menggunakan Web Audio API.

Referensi