Unione di audio posizionale e WebGL

Ilmari Heikkinen

Introduzione

In questo articolo parlerò di come utilizzare la funzionalità audio posizionale nell'API Web Audio per aggiungere audio 3D alle scene WebGL. Per rendere l'audio più credibile, ti presenterò anche gli effetti ambientali possibili con l'API Web Audio. Per un'introduzione più approfondita all'API Web Audio, consulta l'articolo Introduzione all'API Web Audio di Boris Smus.

Per creare l'audio posizionale, utilizza AudioPannerNode nell'API Web Audio. AudioPannerNode definisce la posizione, l'orientamento e la velocità di un suono. Inoltre, il contesto audio dell'API Web Audio ha un attributo listener che consente di definire la posizione, l'orientamento e la velocità del listener. Con questi due elementi puoi creare suoni direzionali con effetti Doppler e panoramica 3D.

Vediamo come appare il codice audio per la scena sopra. Questo è un codice API Audio molto di base. Puoi creare una serie di nodi dell'API Audio e collegarli tra loro. I nodi audio sono singoli suoni, controlli del volume, nodi di effetti e analizzatori e così via. Dopo aver creato questo grafico, devi collegarlo alla destinazione del contesto audio per renderlo udibile.

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

Posizione

L'audio posizionale utilizza la posizione delle sorgenti audio e quella dell'ascoltatore per determinare come mixare l'audio per gli altoparlanti. Una sorgente audio sul lato sinistro di chi ascolta risulterebbe più forte nell'altoparlante sinistro e viceversa per il lato destro.

Per iniziare, crea un'origine audio e associala a un AudioPannerNode. Imposta quindi la posizione di AudioPannerNode. Ora hai un audio 3D mobile. La posizione dell'ascoltatore del contesto audio è (0,0,0) per impostazione predefinita, quindi, se utilizzata in questo modo, la posizione di AudioPannerNode è relativa alla posizione della videocamera. Ogni volta che sposti la videocamera, devi aggiornare la posizione di AudioPannerNode. Per rendere la posizione di AudioPannerNode relativa al mondo, devi modificare la posizione dell'ascoltatore del contesto audio in base alla posizione della videocamera.

Per configurare il monitoraggio della posizione, dobbiamo creare un AudioPannerNode e collegarlo al volume principale.

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

In ogni frame, aggiorna le posizioni degli AudioPannerNode. Negli esempi riportati di seguito utilizzerò 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);
...

Per monitorare la posizione dell'ascoltatore, imposta la posizione dell'ascoltatore del contesto audio in modo che corrisponda alla posizione della videocamera.

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

Velocità

Ora che abbiamo le posizioni dell'ascoltatore e dell'AudioPannerNode, concentriamoci sulle loro velocità. Modificando le proprietà di velocità dell'ascoltatore e di AudioPannerNode, puoi aggiungere un effetto Doppler al suono. Nella pagina degli esempi dell'API Web Audio sono disponibili alcuni esempi di effetto Doppler.

Il modo più semplice per ottenere le velocità per l'ascoltatore e l'AudioPannerNode è tenere traccia delle relative posizioni per frame. La velocità di chi ascolta è la posizione corrente della videocamera meno la posizione della videocamera nel fotogramma precedente. Analogamente, la velocità di AudioPannerNode è la sua posizione attuale meno la posizione precedente.

Il monitoraggio della velocità può essere effettuato calcolando la posizione precedente dell'oggetto, sottraendo la posizione dalla posizione corrente e dividendo il risultato per il tempo trascorso dall'ultimo frame. Ecco come farlo in 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);
...

Orientamento

L'orientamento è la direzione in cui è rivolta la sorgente sonora e la direzione in cui si trova l'ascoltatore. Con l'orientamento, puoi simulare sorgenti sonore direzionali. Ad esempio, pensa a un altoparlante direzionale. Se ti trovi di fronte all'altoparlante, il suono sarà più alto rispetto a quando ti trovi dietro. È importante anche l'orientamento dell'ascoltatore per determinare da quale lato provengono i suoni. Un suono proveniente da sinistra deve spostarsi verso destra quando ti giri.

Per ottenere il vettore di orientamento per l'AudioPannerNode, è necessario prendere la parte di rotazione della matrice del modello dell'oggetto 3D che emette il suono e moltiplicare un vec3(0,0,1) con questo per vedere dove finisce. Per l'orientamento dell'ascoltatore di contesto, devi ottenere il vettore di orientamento della fotocamera. L'orientamento dell'ascoltatore richiede anche un vettore verso l'alto, poiché deve conoscere l'angolo di inclinazione della testa dell'ascoltatore. Per calcolare l'orientamento dell'ascoltatore, ottieni la parte di rotazione della matrice di visualizzazione della videocamera e moltiplica un vettore vec3(0,0,1) per l'orientamento e un vettore vec3(0,-1,0) per il vettore verso l'alto.

Affinché l'orientamento abbia un effetto sui suoni, devi anche definire il cono per il suono. Il cono acustico prende un angolo interno, un angolo esterno e un guadagno esterno. Il suono viene riprodotto al volume normale all'interno dell'angolo interno e cambia gradualmente il guadagno in guadagno esterno man mano che ti avvicini all'angolo esterno. Al di fuori dell'angolo esterno, l'audio viene riprodotto con guadagno esterno.

Il monitoraggio dell'orientamento in Three.js è un po' più complicato in quanto prevede alcune operazioni matematiche sui vettori e l'azzeramento della parte di traslazione delle matrici mondiali 4x4. Tuttavia, non sono molte righe di codice.

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

Il monitoraggio dell'orientamento della fotocamera richiede anche il vettore verso l'alto, quindi devi moltiplicare un vettore verso l'alto con la matrice di trasformazione.

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

Per impostare il cono sonoro per l'audio, imposta le proprietà appropriate del nodo panner. Gli angoli del cono sono in gradi e vanno da 0 a 360.

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

Tutti insieme

Riassumendo, l'ascoltatore del contesto audio segue la posizione, l'orientamento e la velocità della videocamera, mentre AudioPannerNodes segue le posizioni, gli orientamenti e le velocità delle rispettive sorgenti audio. Devi aggiornare le posizioni, le velocità e gli orientamenti di AudioPannerNodes e dell'ascoltatore del contesto audio in ogni frame.

Effetti ambientali

Dopo aver configurato l'audio posizionale, puoi impostare gli effetti ambientali per l'audio in modo da migliorare l'esperienza immersiva della scena 3D. Supponiamo che la tua scena si trovi all'interno di una grande cattedrale. Con le impostazioni predefinite, i suoni nella scena suonano come se fossi all'aperto. Questa discrepanza tra le immagini e l'audio interrompe l'immersività e rende la scena meno impressionante.

L'API Web Audio include un ConvolverNode che consente di impostare l'effetto ambientale di un suono. Aggiungilo al grafico di elaborazione per l'origine audio e potrai adattare l'audio all'impostazione. Sul web puoi trovare esempi di risposta all'impulso che puoi utilizzare con ConvolverNodes e puoi anche crearne di tuoi. L'esperienza potrebbe risultare poco ingombrante perché è necessario registrare la risposta d'impulso del luogo in cui si desidera simulare il video, ma la funzionalità è sempre disponibile, se necessario.

L'utilizzo di ConvolverNodes per l'audio ambientale richiede il ricollaudo del grafico di elaborazione audio. Anziché passare il suono direttamente al volume principale, devi indirizzarlo attraverso il ConvolverNode. Poiché potresti voler controllare l'intensità dell'effetto ambientale, devi anche instradare l'audio intorno a ConvolverNode. Per controllare i volumi della traccia, ConvolverNode e l'audio normale devono avere GainNode collegati.

Il grafico finale di elaborazione dell'audio che sto utilizzando mostra l'audio degli oggetti che passano attraverso un GainNode e viene utilizzato come mixer pass-through. Dal mixer passo l'audio al ConvolverNode e a un altro GainNode, che viene utilizzato per controllare il volume dell'audio normale. ConvolverNode è collegato al proprio GainNode per controllare il volume dell'audio convogliato. Le uscite dei GainNodes sono collegate al controller del volume principale.

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

Per far funzionare ConvolverNode, devi caricare un sample di risposta all'impulso in un buffer e fare in modo che ConvolverNode lo utilizzi. Il caricamento del Sample avviene nello stesso modo dei normali Sample audio. Di seguito è riportato un esempio di come procedere:

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

Riepilogo

In questo articolo hai imparato ad aggiungere audio posizionale alle tue scene 3D utilizzando l'API Web Audio. L'API Web Audio ti consente di impostare la posizione, l'orientamento e la velocità delle sorgenti audio e dell'ascoltatore. Impostandoli per monitorare gli oggetti nella scena 3D, puoi creare un paesaggio sonoro ricco per le tue applicazioni 3D.

Per rendere l'esperienza audio ancora più coinvolgente, puoi utilizzare ConvolverNode nell'API Web Audio per configurare il suono generale dell'ambiente. Dalle cattedrali alle stanze chiuse, puoi simulare una serie di effetti e ambienti utilizzando l'API Web Audio.

Riferimenti