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 ottenere l'audio posizionale, utilizza AudioPannerNode nell'API Web Audio. L'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 ti consente di definire la posizione, l'orientamento e la velocità dell'ascoltatore. Con questi due strumenti puoi creare suoni direzionali con effetti doppler e panning 3D.

Vediamo come si presenta il codice audio della scena riportata sopra. Si tratta di un codice API Audio di base. Crei un gruppo di nodi dell'API Audio e li connetti. I nodi audio sono suoni individuali, controller di volume, nodi e analizzatori di effetti e simili. Dopo aver creato il 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 dell'ascoltatore per determinare come mixare il suono per gli altoparlanti. Una sorgente audio sul lato sinistro dell'ascoltatore sarebbe più forte per l'altoparlante sinistro e viceversa per il lato destro.

Per iniziare, crea una sorgente audio e collegala a un AudioPannerNode. Quindi imposta la posizione di AudioPannerNode. Ora hai un suono 3D spostabile. Per impostazione predefinita, la posizione del listener di contesto audio è su (0,0,0), quindi, quando utilizzata in questo modo, la posizione AudioPannerNode è relativa alla posizione della videocamera. Ogni volta che sposti la videocamera, devi aggiornare la posizione AudioPannerNode. Per rendere la posizione AudioPannerNode relativa al mondo, devi modificare la posizione del listener del contesto audio nella posizione della videocamera.

Per impostare il tracciamento 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);
...

Aggiorna le posizioni degli AudioPannerNodes su ogni frame. Negli esempi di seguito userò 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 tracciare la posizione dell'ascoltatore, imposta la posizione dell'ascoltatore del contesto audio in modo che corrisponda alla posizione della fotocamera.

...
// 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, volgiamo l'attenzione alle loro velocità. Modificando le proprietà di velocità dell'ascoltatore e dell'AudioPannerNode, puoi aggiungere un effetto doppler al suono. Nella pagina di esempi dell'API Web Audio sono disponibili alcuni esempi interessanti di effetti doppler.

Il modo più semplice per ottenere le velocità per l'ascoltatore e l'AudioPannerNode è tenere traccia delle posizioni per frame. La velocità dell'ascoltatore corrisponde alla posizione corrente della videocamera meno la posizione della videocamera nel frame precedente. Analogamente, la velocità dell'AudioPannerNode corrisponde alla posizione attuale meno la posizione precedente.

Puoi monitorare la velocità ottenendo la posizione precedente dell'oggetto, sottraendola dalla posizione attuale 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 a cui punta la sorgente audio e la direzione in cui è rivolto l'ascoltatore. Con l'orientamento, puoi simulare sorgenti sonore direzionali. Ad esempio, pensa a un altoparlante direzionale. Se ti trovi di fronte allo speaker, il suono sarà più forte rispetto a quando ti trovi dietro allo speaker. Ancora più importante, è necessario orientare l'ascoltatore per determinare da quale lato dell'ascoltatore provengono i suoni. Un suono proveniente dalla tua sinistra deve passare a destra quando ti giri.

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

Affinché l'orientamento abbia effetto sui suoni, devi anche definire il cono del suono. Il cono del suono 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 quando ti avvicini all'angolo esterno. Al di fuori dell'angolo esterno, il suono viene riprodotto con guadagno esterno.

Il monitoraggio dell'orientamento in Three.js è un po' più complesso poiché comporta l'uso di calcoli vettoriali e l'azzeramento della parte di traslazione delle matrici del mondo 4x4. Ma non 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 tracciamento dell'orientamento della videocamera richiede anche il vettore verso l'alto, quindi devi moltiplicare un vettore verso l'alto per 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 il tuo suono, imposta le proprietà appropriate del nodo Panner. Gli angoli del cono sono espressi in gradi e vanno da 0 a 360.

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

Tutti insieme

Per riassumere, l'ascoltatore del contesto audio segue la posizione, l'orientamento e la velocità della videocamera, mentre gli AudioPannerNode seguono le posizioni, gli orientamenti e le velocità delle rispettive sorgenti audio. Devi aggiornare posizioni, velocità e orientamenti degli AudioPannerNodes e del listener di contesto audio su ogni frame.

Effetti ambientali

Dopo aver configurato l'audio posizionale, puoi impostare gli effetti ambientali per l'audio per migliorare l'impermeabilità della tua scena 3D. Supponiamo che la scena sia ambientata all'interno di una grande cattedrale. Se hai configurato le impostazioni predefinite, i suoni della scena ricordano di essere all'aperto. Questa discrepanza tra le immagini e l'audio interrompe l'immersione e rende la scena meno impressionante.

L'API Web Audio ha un ConvolverNode che ti consente di impostare l'effetto ambientale di un suono. Aggiungila al grafico di elaborazione della sorgente audio per adattare il suono all'impostazione. Sul web puoi trovare esempi di risposta all'impulso che puoi utilizzare con ConvolverNodes, ma puoi anche creare i tuoi. Potrebbe essere un'esperienza leggermente gravosa, in quanto è necessario registrare la risposta all'impulso del luogo che vuoi simulare, ma se necessario, la funzionalità è sempre a tua disposizione.

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

Il grafico finale di elaborazione audio che sto utilizzando mostra l'audio degli oggetti che passa attraverso un GainNode, usato 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 convolto. Le uscite dei GainNode 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 il ConvolverNode, devi caricare un campione di risposta all'impulso in un buffer e far sì che venga utilizzato da ConvolverNode. Il caricamento del Sample viene eseguito come per i normali campioni audio. Ecco un esempio di un modo per farlo:

...
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 come aggiungere l'audio posizionale alle scene 3D utilizzando l'API Web Audio. L'API Web Audio consente di impostare la posizione, l'orientamento e la velocità delle sorgenti audio e dell'ascoltatore. Impostandoli per tracciare gli oggetti nella scena 3D, puoi creare un ricco paesaggio sonoro per le tue applicazioni 3D.

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

Riferimenti