Spatial Audio
Learn how aether-audio delivers 3D spatial sound with HRTF processing, configurable attenuation, acoustic zones, Opus voice codec, and microphone capture.
Overview
The aether-audio crate provides the spatial audio subsystem for the Aether engine. It handles 3D sound positioning, HRTF-based binaural rendering, voice chat encoding with Opus, and microphone capture. Audio sources are positioned in world space and processed through attenuation, occlusion, and reverb models before reaching the listener.
Listeners and Sources
Every audio scene has a listener (the player's head position) and positioned audio sources:
use aether_audio::{ListenerState, AudioSource, AudioId, Vec3};
let listener = ListenerState {
position: Vec3 { x: 0.0, y: 1.7, z: 0.0 },
forward: Vec3 { x: 0.0, y: 0.0, z: -1.0 },
up: Vec3 { x: 0.0, y: 1.0, z: 0.0 },
};
let source = AudioSource {
id: AudioId(42),
position: Vec3 { x: 5.0, y: 1.0, z: -3.0 },
volume: 1.0, world_id: 1,
};
Sources are classified into LOD tiers by distance: Near (< 2m, full HRTF), Mid (2-8m), Far (8-16m, simplified panning), and Distant (> 16m, mono mix). Distant sources receive reduced processing to stay within the audio thread's time budget.
HRTF Processing
Head-Related Transfer Functions model how sound interacts with the listener's head and ears, providing 3D spatial cues through headphones -- essential for VR immersion.
use aether_audio::{HrtfProfile, HrtfSample};
let sample = HrtfSample::for_profile(HrtfProfile::Generic, 45.0);
// Per-ear delay, gain, and crosstalk reduction
println!("L delay: {}ms, R delay: {}ms", sample.left_delay_ms, sample.right_delay_ms);
println!("Crosstalk reduction: {}dB", sample.crosstalk_reduction_db);
Four profiles are available: Generic (most listeners), and EarShapeA/B/C (tuned for specific ear geometries). Head tracking from the VR headset updates listener orientation each frame.
Attenuation Models
Distance attenuation controls how volume decreases with distance. Three curves are available:
- Linear -- Volume decreases linearly. Simple and predictable.
- Inverse -- Follows
1 / (1 + 4t). Natural-sounding with steep near-field drop-off. - Exponential -- Follows
(1 - t)^2. Aggressive, making sounds feel localized.
use aether_audio::{AttenuationModel, AttenuationCurve};
let model = AttenuationModel {
curve: AttenuationCurve::Inverse,
min_gain: 0.02, max_gain: 1.0, max_distance: 200.0,
};
let gain = model.gain(50.0); // Value between min_gain and max_gain
// Built-in presets
let linear = AttenuationModel::from_preset_linear();
let inverse = AttenuationModel::from_preset_inverse();
The band() method classifies distances into discrete gain bands for coarser LOD-based processing.
Acoustic Zones
Acoustic zones model reverb and occlusion in enclosed spaces:
use aether_audio::{RoomAcoustics, AcousticsProfile, OcclusionState};
let hall = RoomAcoustics {
reverb_mix: 0.15, occlusion: 0.2,
early_reflection_gain: 0.05, late_reverb_gain: 0.08, room_size_m2: 32.0,
};
let dry = hall.dry_gain(); // 0.85 (automatically computed)
Two built-in profiles: AcousticsProfile::offline() for general rooms (moderate reverb, 12 reflections) and AcousticsProfile::voice_mode() for clear speech (minimal reverb, 4 reflections).
Occlusion states (None, Thin, Thick) reduce high frequencies and volume when walls block line-of-sight between source and listener.
Opus Voice Codec
Voice chat uses Opus for low-latency encoding with built-in forward error correction:
use aether_audio::{OpusConfig, BitRateKbps, OpusPacket};
let config = OpusConfig::opus_voice_default();
// 48kHz, 20ms frames, 32kbps, FEC enabled
let pps = config.packets_per_second(); // 50.0
let max_size = OpusPacket::packet_size_limit(&config); // 350 bytes
Bitrate options: 16 kbps (low bandwidth), 24 kbps (balanced), 32 kbps (default, clear voice), 64 kbps (high quality). In-band FEC embeds redundancy for packet loss recovery without retransmission.
Audio Capture
CaptureStream manages microphone input with a thread-safe ring buffer:
use aether_audio::{CaptureConfig, CaptureStream};
let mut stream = CaptureStream::new(CaptureConfig::default());
// Defaults: 48kHz, mono, 1-second ring buffer
stream.start()?;
stream.poll(); // Transfer OS samples to ring buffer
let samples = stream.read_samples(1024);
// Or encode directly: stream.read_encoded_frame(&mut codec)
stream.stop();
The ring buffer discards oldest samples when full, ensuring the capture pipeline never blocks.
Voice Channels
The channel system manages routing for proximity chat, private channels, and broadcasts:
use aether_audio::{VoiceChannelManager, ChannelConfig, ChannelKind, RoutingPolicy};
let mut channels = VoiceChannelManager::new();
channels.create_channel(ChannelConfig {
kind: ChannelKind::Proximity,
routing: RoutingPolicy::DistanceBased { max_range: 20.0 },
..Default::default()
});
Voice zones can trigger automatic channel switching when players enter specific areas.
Audio Runtime
The AudioRuntime orchestrates the full pipeline -- spatialization, mixing, and output:
use aether_audio::{AudioRuntime, AudioRuntimeConfig, AudioRuntimeInput};
let runtime = AudioRuntime::new(AudioRuntimeConfig::default());
let output = runtime.step(AudioRuntimeInput {
listener, sources: &active_sources, delta_time: 0.011,
});
// output.mix_instructions: per-source gain, panning, and effects
Key Types Reference
| Type | Module | Purpose |
|---|---|---|
ListenerState | types | Listener position and orientation |
AudioSource | types | Positioned sound source |
AudioLod | types | Distance-based processing tier |
HrtfProfile | hrtf | Ear shape selection for HRTF |
HrtfSample | hrtf | Per-ear delay, gain, crosstalk |
AttenuationModel | attenuation | Distance falloff curve and bounds |
RoomAcoustics | acoustics | Reverb, occlusion, reflection settings |
AcousticsProfile | acoustics | Preset acoustic zone config |
OpusConfig | opus | Codec sample rate, bitrate, FEC |
CaptureStream | capture | Microphone input with ring buffer |
VoiceChannelManager | channel | Voice routing and zone management |
AudioRuntime | runtime | Top-level audio pipeline orchestrator |