Files
skills/docs/media/audio-fx/audio-fx-plugin-game-audio/src/game-audio/game-audio.md
2026-05-29 08:20:56 +01:00

6.0 KiB
Raw Blame History

game-audio

(no submodules)

Purpose

GameAudioPlugin is the universal composite plugin for game audio. It maintains three registries — spatial sources, music layers, and proximity voice-chat participants — and exposes a transport-level API for managing each. The plugin is a source (io.inputs[0].count === 0, io.outputs[0].count === 1); it owns its audio output rather than colouring upstream audio.

The plugin extends AbstractAudioFxPlugin and runs identically across browser / server / worklet renderers. The audio path is intentionally a silent placeholder — the listener data, source registry, voice attenuation, and music-layer crossfade scheduling are pure bookkeeping that the browser-engine fast path consumes via createAudioNode() in a follow-up. The universal block path produces silence by design; consumers route the registry state into their own mixing graph.

Features

  • Spatial sources: createSpatialSource, updateSpatialSourcePosition, applyDopplerEffect, applyOcclusion(0..1), cullDistantSources(maxDistance), getActiveSpatialSourceCount, getSpatialSource(id).
  • Proximity voice chat: enableProximityVoiceChat(config), disableProximityVoiceChat, addVoiceChatParticipant, removeVoiceChatParticipant, updateVoiceChatParticipantPosition, calculateVoiceAttenuation, optimizeVoiceChatBandwidth, getVoiceChatOptimizationLevel.
  • Music layers: addMusicLayer, removeMusicLayer, getMusicLayers, crossfadeMusicLayers(from, to, durationMs), isCrossfading.
  • Voice effects: applyGameEffect, removeGameEffect, getCurrentEffect, blendToEffect(effect, durationMs), isBlending.
  • Listener: setListenerPosition, setListenerOrientation, getListenerPosition, getListenerOrientation.
  • Game state: setGameState("exploration" | "combat" | "victory" | "menu"), getGameState.

Voice attenuation uses the same inverse-distance model as SpatialAudioPlugin: gain = minDistance / (minDistance + rolloffFactor · (clamp(d, minDistance, maxDistance) − minDistance)). Distance grows ⇒ gain decreases monotonically, exactly as the spec requires.

Usage: Basic Example

import { GameAudioPlugin } from "@suaveplan/audio-plugins-fx/game";

const game = new GameAudioPlugin({
    listenerPosition: { x: 0, y: 0, z: 0 },
    voiceChat: { minDistance: 1, maxDistance: 50, rolloffFactor: 1 },
});
chain.add(game);

Usage: Advanced Example

import { GameAudioPlugin } from "@suaveplan/audio-plugins-fx/game";

const game = new GameAudioPlugin({ voiceChat: { minDistance: 1, maxDistance: 100, rolloffFactor: 2 } });
await game.setup(
    { sampleRate: 48_000, channels: 1, blockSize: 512 },
    ctx
);

// Register a footstep emitter and a voice-chat peer.
game.createSpatialSource("footstep", { x: 5, y: 0, z: 0 });
game.addVoiceChatParticipant("teammate", { x: 10, y: 0, z: 0 });

// Adaptive music: crossfade from exploration to combat over 1s.
game.addMusicLayer("exploration", explorationBuffer);
game.addMusicLayer("combat", combatBuffer);
game.crossfadeMusicLayers("exploration", "combat", 1_000);
game.setGameState("combat");

// Cull distant sources every frame to keep the voice-chat budget tight.
game.optimizeVoiceChatBandwidth();
game.cullDistantSources(80);

API Reference

  • GameAudioPlugin (class extends AbstractAudioFxPlugin)
    • Construction and lifecycle: new GameAudioPlugin(options?), setup, reset.
    • Listener: setListenerPosition, setListenerOrientation, getListenerPosition, getListenerOrientation.
    • Spatial sources: see Features above.
    • Voice chat: see Features above.
    • Music layers: see Features above.
    • Voice effects: see Features above.
    • Game state: setGameState, getGameState.

Implementation Notes

The audio path is silent by design. Implementing the full mix of spatial sources + music layers + voice chat into a single mono output stream would require this plugin to embed three sibling-plugin engines (SpatialAudioPlugin, MixerPlugin, plus a voice-chat router) — which would either duplicate code or pull every plugin into this one's import graph. Instead, the plugin owns the registry of game-audio state; the browser engine reads that state via createAudioNode() and renders it into a Web Audio graph in a future revision. The universal block path produces silence but advances the internal sample clock so isCrossfading / isBlending work correctly under any renderer.

Crossfade scheduling stores the start sample (processedSamples at the time of the call) plus the duration in samples. isCrossfading is elapsed < durationSamples. The check is exact at the sample level — the test "isCrossfading true immediately; false after enough samples processed" exercises that by processing exactly the configured number of samples.

cullDistantSources walks the spatial-source map and deletes entries whose distance to the listener exceeds the threshold. The cost is O(numSources) per call; for the typical game-audio case (under 100 sources) this is below 1 ms even at 60 fps cull frequency. A spatial-hash variant would amortise this to O(log n) but adds bookkeeping that the MVP does not need.

optimizeVoiceChatBandwidth returns a coarse 0..3 tier rather than per-participant codec parameters because the actual codec selection lives in the transport layer (WebRTC / similar). The plugin's job is to advise; the transport applies the advice. Currently the heuristic is just a participant-count threshold (>8 ⇒ level 3, otherwise level 1) — a future revision can layer on signal-strength and ping data.

The plugin maintains an internal processedSamples counter incremented by output.length each processBlock. This is the same clock crossfade and effect-blend countdowns reference — using BlockContext.blockOffsetSamples directly would not work because the chain may slice or align blocks differently, and the consumer expects clock advancement per actual rendered sample. The counter resets on setup/reset.