Back to Mirador Blog

Audio Management With Kira

2025-07-07
9 min read
Audio
miradorgame-developmentrust

Game audio is deceptively complex. Unlike a music player that simply plays tracks (although we do need that too), games need audio that responds to player actions, enemy movements, and environmental changes in real-time. When a player walks through a maze, they should hear their footsteps change from walking to sprinting. When an enemy approaches, the audio should come from the correct direction and distance. And when they hit a wall, there should be immediate audio feedback. These relatively complicated and specific set of requirements led me to [Kira](https://github.com/tesselode/kira), a backend-agnostic audio library that promised the spatial audio capabilities I needed, as well a host of other useful audio tools that seem perfect for my use case.

Understanding Kira's Design

Kira is designed around a few core principles that make it ideal for game audio. First, it's backend-agnostic, meaning the same audio code works across different platforms while leveraging the most efficient audio APIs available. On Windows, it uses DirectSound or WASAPI. On Linux, it uses ALSA or PulseAudio. The library handles these platform differences automatically, allowing developers to focus on audio logic rather than hardware specifics. The library provides several key abstractions that form the foundation of our audio system:

AudioManager - The central controller that manages all audio resources and coordinates between the game thread and audio thread. It handles the multi-threaded architecture where the main game thread sends commands to a dedicated high-priority audio thread. StaticSoundData - Pre-loaded audio files that are entirely loaded into memory. Perfect for short sound effects like footsteps, wall hits, and UI sounds. Kira's efficient memory management means multiple instances can share the same audio data without duplicating it in memory. StreamingSoundData - Audio files that are read from disk in chunks as needed. Ideal for longer audio content like background music or enemy ambient sounds that need to loop continuously. Streaming prevents large audio files from consuming excessive memory while still providing high-quality playback. Spatial Tracks - Specialized audio tracks that handle 3D positioning, distance attenuation, and spatial effects. These are crucial for creating immersive audio where sounds appear to come from specific locations in 3D space. Kira allows us to apply whatever effect we would like based on the player's distance from its source, whether that be volume dampening, increasing the reverb, both, or any of the other audio effects that kira provides. Tweens - Smooth interpolations for audio properties. Instead of instantly changing volume or position (which creates audio artifacts), tweens gradually transition values over time. This creates natural-sounding audio changes that enhance the player experience. All audio effects in kira are applied via tweens in order to prevent harsh jumps in the sound scape.

The command-based communication between threads uses lock-free data structures and atomic operations, ensuring that audio operations are both thread-safe and real-time safe. When we call manager.play(), it doesn't immediately play the sound. Instead, it queues a command that gets processed on the audio thread during the next audio callback, typically running at 44,100 Hz. Kira uses a multi-threaded architecture to allow several tracks to be layered on top of one another without hogging cpu time. The main game thread handles logic and sends commands to a dedicated audio thread, which processes all the mixing and effects. This separation ensures that audio processing never blocks the game loop, maintaining smooth 60 FPS gameplay even with complex spatial audio calculations. In our implementation, we create a GameAudioManager that wraps Kira's AudioManager and adds game-specific functionality. This wrapper handles the initialization of all our audio assets, manages spatial audio for enemies, controls movement-based footstep sounds, and provides methods for different game states. The beauty of Kira's design is that our game-specific audio logic can focus on high-level concepts like "spawn enemy audio" or "start walking footsteps" while Kira handles all the low-level audio processing, threading, and platform-specific details.

Choosing Between Static and Streaming Sounds

Our audio system uses both static and streaming sounds based on their intended use. For short, frequently-played sound effects like footsteps, wall hits, UI selections, and upgrade sounds, we use StaticSoundData. These sounds are loaded entirely into memory at startup, allowing for instant playback without any disk I/O during gameplay. This is crucial for responsive audio feedback where even a small delay would be noticeable. For longer audio content that needs to loop continuously, we use StreamingSoundData. The background music track and enemy ambient sounds fall into this category. These sounds are read from disk in chunks as needed, which prevents large audio files from consuming excessive memory. The streaming approach is particularly important for the background music, which is several minutes long and would otherwise consume significant memory if loaded entirely. The enemy audio system demonstrates this distinction well. When an enemy spawns, we create a spatial track that plays a streaming sound loop. This allows the enemy to have continuous ambient audio (like slime movement sounds) without memory concerns, while still maintaining the spatial positioning and distance attenuation that makes the audio feel realistic. The streaming ensures that even if we had many enemies, the memory usage would remain reasonable.

Spatial Audio: Hearing in Three Dimensions

The core of Mirador's audio system is spatial audio. Every sound source in the game world has a 3D position, and the player acts as an audio listener. As the player moves through the maze, the audio system continuously recalculates how sounds should be heard based on distance and direction.

pub fn spawn_enemy(&mut self, enemy_id: String, position: [f32; 3]) -> Result<(), Box<dyn Error>> {

let mut spatial_track = self.audio_manager.add_spatial_sub_track( &self.listener, position, SpatialTrackBuilder::new() .spatialization_strength(1.0) .distances(SpatialTrackDistances { min_distance: 1.0, max_distance: 3200.0, }) .with_effect(ReverbBuilder::new().mix(Value::Fixed(0.3.into()))) .with_effect(VolumeControlBuilder::new(Value::FromListenerDistance( Mapping { input_range: (5.0, 3200.0), output_range: ((20.0).into(), (-50.0).into()), easing: Easing::OutPowi(3), }, ))), )?;

When an enemy spawns, the system creates a spatial audio track positioned at the enemy's location. The track includes distance-based volume attenuation that makes distant enemies quieter, reverb effects for environmental realism, and 3D positioning that makes the audio appear to come from the correct direction. As the enemy moves through the maze, the audio smoothly follows their position.

Dynamic Movement Audio

Footsteps are a fundamental part of the player's audio experience. The system tracks three movement states: idle, walking, and sprinting. Each state has different audio characteristics that help players understand their movement without looking at the screen.

pub enum MovementState {

Idle, // No footstep audio Walking, // Normal pace - 0.5 second loop Sprinting, // Fast pace - 0.25 second loop }

When the player starts walking, the system begins looping a footstep sound every 0.5 seconds. When they sprint, the loop time drops to 0.25 seconds, creating a sense of urgency. The audio automatically stops when the player stops moving, providing immediate feedback about their movement state.

Environmental Audio Feedback

Collision detection in games often feels disconnected from the audio experience. In Mirador, when the player hits a wall, they immediately hear a collision sound. But to prevent audio spam during rapid collisions, the system includes a 330ms cooldown between wall hit sounds.

pub fn wall_hit(&mut self) -> Result<(), Box<dyn Error>> {

let now = Instant::now(); if let Some(last_hit) = self.last_wall_hit { if now.duration_since(last_hit) < self.wall_hit_cooldown { return Ok(()); // Skip playing sound } } self.play_with_volume(self.wall_hit_data.clone(), 0.0001)?; self.last_wall_hit = Some(now); Ok(()) }

The wall hit sound plays at a very low volume (0.0001) to provide subtle tactile feedback without overwhelming the player. This creates a more immersive experience where the audio feels like a natural extension of the collision system.

Background Music Integration

The background music system demonstrates another example of how Kira handles long-form audio content using streaming. The main music track is loaded as StreamingSoundData and loops continuously, adjusting its volume based on the game context. During gameplay, it plays at -10dB to avoid interfering with spatial audio. On the title screen, it increases to -5dB to be more prominent, and in the pause menu, it drops to -15dB to indicate the paused state. The streaming approach is essential here because the background music is several minutes long. Loading it entirely into memory would consume significant RAM, especially on lower-end systems. Instead, Kira reads the audio data in chunks as needed, maintaining high-quality playback while keeping memory usage minimal. The music system uses Kira's tweening capabilities for smooth volume transitions. Instead of instantly changing volume levels (which would create audio artifacts), the system gradually interpolates between volume levels over specified durations. This creates natural-sounding audio transitions that enhance the player experience.

Real-Time Audio Processing

The audio system runs every frame as part of the main game loop. Each frame, the system updates the listener position (player position), updates enemy audio positions, and processes any pending audio commands. The spatial calculations happen automatically in the background, ensuring that audio always matches the visual experience.

// Update listener position each frame

audio_manager.set_listener_position(player.position)?; // Update enemy audio positions audio_manager.update_enemy_position("enemy", enemy.position)?; // Process audio updates audio_manager.update()?;

This tight integration ensures that audio and visuals stay perfectly synchronized, creating a cohesive experience where players can rely on audio cues for navigation and awareness.

Performance Considerations

Audio processing happens thousands of times per second, so performance is critical. Kira's backend-agnostic design means the same audio code works across different platforms while leveraging the most efficient audio APIs available. On Windows, it uses DirectSound or WASAPI. On Linux, it uses ALSA or PulseAudio. The library handles the platform differences automatically, allowing the game to focus on audio logic rather than hardware specifics. The system also includes performance monitoring. During initialization, the audio manager creation is benchmarked to ensure it doesn't impact game startup times. The spatial audio calculations are optimized to minimize CPU usage while maintaining high-quality audio output.