Mencampur audio posisi dan WebGL

Ilmari Heikkinen

Pengantar

Dalam artikel ini, saya akan membahas cara menggunakan fitur audio posisi di Web Audio API untuk menambahkan suara 3D ke tampilan WebGL Anda. Agar audio lebih meyakinkan, saya juga akan memperkenalkan efek lingkungan yang mungkin terjadi dengan Web Audio API. Untuk mendapatkan pengantar yang lebih menyeluruh tentang Web Audio API, lihat artikel Memulai penggunaan Web Audio API oleh Boris Smus.

Untuk menggunakan audio posisi, gunakan 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 penggeseran 3D.

Mari kita lihat seperti apa kode audio untuk adegan di atas. Ini adalah kode Audio API yang sangat dasar. Anda membuat sekumpulan node Audio API dan menghubungkannya. Node audio adalah suara individual, pengontrol volume, node serta penganalisis efek, dan sejenisnya. Setelah membuat grafik ini, Anda perlu mengaitkannya 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 posisi menggunakan posisi sumber audio dan posisi pendengar untuk menentukan cara mencampur suara ke speaker. Sumber audio di sisi kiri pendengar akan lebih nyaring di speaker kiri, dan sebaliknya di sisi kanan.

Untuk memulai, buat sumber audio dan lampirkan ke AudioPannerNode. Lalu, tetapkan posisi AudioPannerNode. Sekarang Anda memiliki suara 3D yang dapat bergerak. Posisi pemroses konteks audio adalah pada (0,0,0) secara default, sehingga bila digunakan dengan cara ini, posisi AudioPannerNode relatif terhadap posisi kamera. Setiap kali menggerakkan 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);
...

Di setiap frame, perbarui posisi AudioPannerNode. Saya akan menggunakan Three.js dalam contoh di bawah ini.

...
// 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 pendengar dan AudioPannerNode adalah dengan melacak posisi per frame. Kecepatan pemroses adalah posisi kamera saat ini dikurangi posisi kamera dalam 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 tunjuk sumber suara dan arah hadap pendengar. Dengan orientasi, Anda dapat menyimulasikan sumber suara terarah. Misalnya, pikirkan tentang pembicara terarah. Jika Anda berdiri di depan speaker, suaranya akan lebih keras dibandingkan jika Anda berdiri di belakang speaker. Yang lebih penting, Anda memerlukan orientasi pendengar untuk menentukan dari sisi pendengar mana suara berasal. Suara yang berasal dari kiri perlu dialihkan ke kanan saat Anda berputar.

Untuk mendapatkan vektor orientasi untuk AudioPannerNode, Anda perlu mengambil bagian rotasi dari matriks model objek 3D yang memancarkan suara dan mengalikan vec3(0,0,1) dengannya untuk melihat di mana akhirnya menunjuk. Untuk orientasi pemroses konteks, Anda perlu mendapatkan vektor orientasi kamera. Orientasi pemroses juga memerlukan vektor naik, karena perlu mengetahui sudut roll 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 vektor naik.

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

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

...
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 naik, jadi Anda perlu mengalikan vektor naik 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 menyetel kerucut suara untuk suara, Anda menyetel properti node panner yang sesuai. Sudut kerucut dibuat dalam derajat dan mulai dari 0 hingga 360.

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

Semuanya

Dengan menggabungkan semuanya, pemroses konteks audio mengikuti posisi, orientasi, dan kecepatan kamera, dan AudioPannerNode mengikuti posisi, orientasi, serta kecepatan sumber audio masing-masing. Anda perlu memperbarui posisi, kecepatan, dan orientasi AudioPannerNodes dan pemroses konteks audio di setiap frame.

Dampak terhadap lingkungan

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

Web Audio API memiliki ConvolverNode yang memungkinkan Anda menyetel efek lingkungan untuk suara. Tambahkan audio ke grafik pemrosesan untuk sumber audio dan Anda akan dapat menyesuaikan suara dengan setelannya. Anda dapat menemukan contoh respons impuls di web yang dapat Anda gunakan dengan ConvolverNodes, dan Anda juga dapat membuatnya sendiri. Mungkin ini akan menjadi pengalaman yang sedikit rumit karena Anda perlu merekam respons impuls dari tempat yang ingin Anda simulasikan, tetapi kemampuan itu ada jika Anda membutuhkannya.

Penggunaan ConvolverNodes untuk merekam audio lingkungan memerlukan pemasangan ulang grafik pemrosesan audio. Daripada meneruskan suara langsung ke volume utama, Anda harus mengarahkannya melalui ConvolverNode. Dan karena Anda mungkin ingin mengontrol kekuatan efek lingkungan, Anda juga perlu mengarahkan audio di sekitar ConvolverNode. Untuk mengontrol volume campuran, ConvolverNode dan audio biasa harus memasang GainNodes padanya.

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 lain, yang digunakan untuk mengontrol volume audio biasa. ConvolverNode dihubungkan ke GainNode sendiri untuk mengontrol volume audio yang berbelit-belit. Output GainNode 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 perlu memuat sampel respons impuls ke 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 dalam adegan 3D, Anda dapat membuat pemandangan suara yang kaya untuk aplikasi 3D.

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

Referensi