Positionales Audio und WebGL mischen

Ilmari Heikkinen

Einleitung

In diesem Artikel geht es darum, wie Sie mit der Funktion für positionales Audio in der Web Audio API 3D-Ton in Ihre WebGL-Szenen einfügen. Um die Audioinhalte glaubwürdiger zu gestalten, werde ich Ihnen auch die Umwelteffekte vorstellen, die mit der Web Audio API möglich sind. Eine ausführlichere Einführung in die Web Audio API finden Sie im Artikel Getting Started with Web Audio API von Boris Smus.

Für Positionsaudio verwenden Sie den AudioPannerNode in der Web Audio API. AudioPannerNode definiert die Position, Ausrichtung und Geschwindigkeit eines Tons. Darüber hinaus verfügt der Audiokontext der Web Audio API über ein Listener-Attribut, mit dem Sie Position, Ausrichtung und Geschwindigkeit des Listeners definieren können. Mit diesen beiden Dingen kannst du Richtungsgeräusche mit Doppler-Effekten und 3D-Schwenken erzeugen.

Sehen wir uns an, wie der Audiocode für die obige Szene aussieht. Das ist ein sehr einfacher Audio API-Code. Sie erstellen mehrere Audio API-Knoten und verbinden diese miteinander. Die Audioknoten sind z. B. einzelne Töne, Lautstärkeregler, Effektknoten und Analysatoren. Nachdem Sie diese Grafik erstellt haben, müssen Sie sie mit dem Audiokontextziel verknüpfen, um sie hörbar zu machen.

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

Position

Positionsaudio verwendet die Position Ihrer Audioquellen und die Position des Hörers, um zu bestimmen, wie der Ton den Lautsprechern zugeteilt wird. Eine Audioquelle auf der linken Seite des Hörers wäre im linken Lautsprecher lauter und umgekehrt für die rechte Seite.

Erstellen Sie zuerst eine Audioquelle und verbinden Sie sie mit einem AudioPannerNode. Stellen Sie dann die Position des AudioPannerNode ein. Jetzt hast du einen beweglichen 3D-Sound. Die Position des Listeners für den Audiokontext liegt standardmäßig bei (0,0,0). Wenn sie auf diese Weise verwendet wird, ist die Position des AudioPannerNode relativ zur Kameraposition. Wenn Sie die Kamera bewegen, müssen Sie die Position des AudioPannerNode aktualisieren. Wenn Sie die Position des AudioPannerNode relativ zur Welt ändern möchten, müssen Sie die Position des Listeners für den Audiokontext in die Kameraposition ändern.

Um das Positions-Tracking einzurichten, müssen wir einen AudioPannerNode erstellen und mit der Hauptlautstärke verbinden.

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

Aktualisieren Sie in jedem Frame die Position der AudioPannerNodes. In den folgenden Beispielen verwende ich 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);
...

Um die Listener-Position zu verfolgen, legen Sie die Listener-Position des Audiokontexts so fest, dass sie der Kameraposition entspricht.

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

Geschwindigkeit

Nachdem wir nun die Positionen des Listeners und des AudioPannerNode kennen, wenden wir uns den jeweiligen Geschwindigkeiten zu. Wenn Sie die Geschwindigkeitseigenschaften des Listeners und des AudioPannerNode ändern, können Sie dem Ton einen Doppler-Effekt hinzufügen. Auf der Seite mit Beispielen für Web Audio API finden Sie einige schöne Doppler-Effekte.

Die einfachste Möglichkeit, die Geschwindigkeiten für den Listener und den AudioPannerNode zu ermitteln, besteht darin, die Positionen pro Frame zu verfolgen. Die Geschwindigkeit des Hörers ist die aktuelle Kameraposition abzüglich der Kameraposition im vorherigen Frame. Ebenso ist die Geschwindigkeit des AudioPannerNode seine aktuelle Position abzüglich seiner vorherigen Position.

Sie können die Geschwindigkeit verfolgen, indem Sie die vorherige Position des Objekts abrufen, sie von der aktuellen Position subtrahieren und das Ergebnis durch die seit dem letzten Frame verstrichene Zeit teilen. So funktioniert es 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);
...

Ausrichtung

Die Ausrichtung ist die Richtung, in die die Schallquelle zeigt, und die Richtung, in die der Zuhörer gerichtet ist. Wenn du die Ausrichtung festlegst, kannst du gerichtete Schallquellen simulieren. Denken Sie zum Beispiel an einen Richtungslautsprecher. Wenn Sie vor dem Lautsprecher stehen, ist der Ton lauter als wenn Sie hinter dem Lautsprecher stehen. Noch wichtiger ist aber, dass Sie die Ausrichtung des Zuhörers bestimmen können, um zu bestimmen, von welcher Seite des Hörers die Töne stammen. Ein Geräusch, das von links kommt, muss beim Drehen nach rechts wechseln.

Um den Ausrichtungsvektor für den AudioPannerNode zu erhalten, müssen Sie den Rotationsteil der Modellmatrix des schallaussendenden 3D-Objekts nehmen und vec3(0,0,1) damit multiplizieren, um zu sehen, wohin er letztendlich zeigt. Für die Ausrichtung des Kontext-Listeners müssen Sie den Ausrichtungsvektor der Kamera abrufen. Für die Ausrichtung des Listeners ist außerdem ein Up-Vektor erforderlich, da dieser den Rollwinkel des Kopfes des Listeners kennen muss. Um die Listener-Ausrichtung zu berechnen, rufen Sie den Drehteil der Ansichtsmatrix der Kamera ab und multiplizieren einen vec3(0,0,1) für die Ausrichtung und einen vec3(0,-1,0) für den up-Vektor.

Damit sich die Ausrichtung auf Ihre Töne auswirkt, müssen Sie auch den Kegel für den Ton definieren. Der Schallkegel nimmt einen inneren, einen äußeren Winkel und einen äußeren Winkel an. Der Klang wird innerhalb des inneren Winkels in normaler Lautstärke wiedergegeben und verändert sich langsam in den äußeren Verstärkungswinkel, wenn du dich dem äußeren Winkel näherst. Außerhalb des äußeren Winkels wird der Ton mit der äußeren Verstärkung wiedergegeben.

Das Nachverfolgen der Ausrichtung in Three.js ist etwas schwieriger, da es einige Vektorberechnungen und die Nullsetzung des Übersetzungsteils der 4:4-Weltmatrizen erfordert. Trotzdem nicht viele Codezeilen.

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

Für die Erfassung der Kameraausrichtung ist außerdem der Up-Vektor erforderlich, daher müssen Sie einen Up-Vektor mit der Transformationsmatrix multiplizieren.

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

Zum Einstellen des Tonkegels legen Sie die entsprechenden Eigenschaften des Panner-Knotens fest. Die Kegelwinkel sind in Grad und verlaufen von 0 bis 360.

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

Ein perfektes Team

Wenn alles zusammengefügt ist, folgt der Audiokontext-Listener der Position, Ausrichtung und Geschwindigkeit der Kamera, während die AudioPannerNodes die Positionen, Ausrichtungen und Geschwindigkeiten ihrer jeweiligen Audioquellen verfolgen. Sie müssen die Positionen, Geschwindigkeiten und Ausrichtungen der AudioPannerNodes und des Audiokontext-Listeners für jeden Frame aktualisieren.

Umweltauswirkungen

Nachdem du das positionale Audio eingerichtet hast, kannst du die Umgebungseffekte für dein Audio festlegen, um deine 3D-Szene noch eindrucksvoller zu gestalten. Angenommen, Ihre Szene spielt in einer großen Kathedrale. In den Standardeinstellungen klingen die Geräusche in Ihrer Szene so, als stünden Sie im Freien. Diese Diskrepanz zwischen Bild und Audio unterbricht das Eintauchen und macht Ihre Szene weniger beeindruckend.

Die Web Audio API verfügt über einen ConvolverNode, mit dem Sie den Umgebungseffekt eines Tons festlegen können. Wenn du es zur Verarbeitungsgrafik der Audioquelle hinzufügst, kannst du den Ton an die Einstellung anpassen. Im Web finden Sie Beispiele für Impulsantworten, die Sie mit ConvolverNodes verwenden oder selbst erstellen können. Es kann eine etwas umständliche Erfahrung sein, da Sie die Impulsreaktion des Ortes aufzeichnen müssen, den Sie simulieren möchten. Die entsprechende Fähigkeit ist jedoch verfügbar, wenn Sie sie brauchen.

Wenn Sie ConvolverNodes für Umgebungsaudio verwenden möchten, muss die Audioverarbeitungsgrafik neu verkabelt werden. Anstatt den Ton direkt an die Hauptlautstärke zu leiten, musst du ihn über den ConvolverNode leiten. Um die Stärke des Umgebungseffekts zu steuern, musst du auch das Audio um den ConvolverNode herum leiten. Damit die Mix-Lautstärke gesteuert werden kann, müssen dem ConvolverNode und dem einfachen Audio GainNodes zugeordnet sein.

In der letzten Audioverarbeitungsgrafik, die ich verwende, wird das Audio der Objekte durch einen GainNode geleitet, der als Durchgangsmischer verwendet wird. Über den Mischpult leite ich das Audio an den ConvolverNode und einen weiteren GainNode weiter, mit dem die Lautstärke des Klartontons gesteuert wird. Der ConvolverNode ist mit einem eigenen GainNode verbunden, der die Lautstärke der gefalteten Audiospur regelt. Die Ausgaben des GainNodes sind mit dem Haupt-Volume-Controller verbunden.

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

Damit ConvolverNode funktioniert, müssen Sie ein Beispiel für eine Impulsantwort in einen Puffer laden und ConvolverNode dafür sorgen, dass es verwendet wird. Das Laden des Samples erfolgt auf die gleiche Weise wie bei normalen Audio-Samples. Hier ist ein Beispiel dafür:

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

Zusammenfassung

In diesem Artikel haben Sie erfahren, wie Sie mit der Web Audio API Positionsaudio zu Ihren 3D-Szenen hinzufügen können. Das Web Audio API bietet Ihnen eine Möglichkeit, die Position, Ausrichtung und Geschwindigkeit von Audioquellen und des Listeners festzulegen. Wenn Sie diese so einstellen, dass die Objekte in Ihrer 3D-Szene verfolgt werden, können Sie eine satte Klangwelt für Ihre 3D-Anwendungen schaffen.

Um das Audioerlebnis noch ansprechender zu gestalten, können Sie mit ConvolverNode in der Web Audio API den allgemeinen Klang der Umgebung einrichten. Von Kathedralen bis hin zu geschlossenen Räumen – mit der Web Audio API kannst du verschiedene Effekte und Umgebungen simulieren.

Verweise