feat: 3D audio positioning — sounds come from the direction of their source (#255)

Refs #255
Agent: groq
This commit is contained in:
Alexander Whitestone
2026-03-24 00:44:05 -04:00
parent 39e0eecb9e
commit 06407a829e
2 changed files with 41 additions and 0 deletions

36
app.js
View File

@@ -438,6 +438,11 @@ function updateFocusDisplay() {
if (photoFocusDisplay) {
photoFocusDisplay.textContent = bokehPass.uniforms['focus'].value.toFixed(1);
}
// 3D Audio: Update panner position to match camera
if (audioPanner) {
audioPanner.setPosition(camera.position.x, camera.position.y, camera.position.z);
}
}
document.addEventListener('keydown', (e) => {
@@ -797,6 +802,37 @@ document.getElementById('debug-toggle').addEventListener('click', () => {
}
});
// === 3D AUDIO SETUP ===
let audioContext;
let audioSource;
let audioPanner;
let is3DAudioEnabled = true;
// Replace the <audio> element with Web Audio API
const ambientSound = document.getElementById('ambient-sound');
ambientSound.remove();
const initAudio = async () => {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
const response = await fetch('ambient.mp3');
const arrayBuffer = await response.arrayBuffer();
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
audioSource = audioContext.createBufferSource();
audioSource.buffer = audioBuffer;
audioPanner = audioContext.createPanner();
audioPanner.panningModel = 'HRTF';
audioPanner.distanceModel = 'inverse';
audioSource.connect(audioPanner);
audioPanner.connect(audioContext.destination);
audioSource.loop = true;
audioSource.start();
};
initAudio();
// === WEBSOCKET CLIENT ===
import { wsClient } from './ws-client.js';

View File

@@ -54,6 +54,11 @@ canvas {
#audio-toggle.muted {
background-color: var(--color-text-muted);
color: var(--color-bg);
}
#audio-toggle.muted::before {
content: "🔇";
}
/* === DEBUG MODE === */