goud_engine/assets/audio_manager/mod.rs
1//! Audio manager resource for centralized audio playback control.
2//!
3//! The `AudioManager` is an ECS resource that provides a high-level interface
4//! for playing, pausing, stopping, and controlling audio in the game engine.
5//! It wraps the rodio audio backend and provides a clean API for audio operations.
6//!
7//! # Features
8//!
9//! - Play audio from loaded assets
10//! - Control global volume
11//! - Play, pause, stop, resume audio
12//! - Check playback state
13//! - Thread-safe for use in parallel systems
14//!
15//! # Usage
16//!
17//! ```no_run
18//! use goud_engine::ecs::{World, Resource};
19//! use goud_engine::assets::AudioManager;
20//!
21//! let mut world = World::new();
22//! let audio_manager = AudioManager::new().expect("Failed to initialize audio");
23//! world.insert_resource(audio_manager);
24//!
25//! // In a system:
26//! // fn play_sound_system(audio: ResMut<AudioManager>, assets: Res<AssetServer>) {
27//! // let sound_handle = assets.load("sounds/jump.wav");
28//! // audio.play(sound_handle);
29//! // }
30//! ```
31
32pub(super) mod spatial;
33
34mod controls;
35mod playback;
36mod spatial_playback;
37#[cfg(test)]
38mod tests;
39
40use crate::core::error::{GoudError, GoudResult};
41use rodio::{DeviceSinkBuilder, MixerDeviceSink, Player};
42use std::collections::HashMap;
43use std::sync::{Arc, Mutex};
44
45/// Central audio playback manager resource.
46///
47/// `AudioManager` is an ECS resource that manages audio playback using the
48/// rodio audio library. It maintains the audio output stream and provides
49/// methods for playing, pausing, stopping, and controlling audio.
50///
51/// # Thread Safety
52///
53/// The AudioManager is thread-safe (`Send + Sync`) and can be used in parallel
54/// systems. Internal state is protected by a Mutex.
55///
56/// # Example
57///
58/// ```no_run
59/// use goud_engine::assets::AudioManager;
60///
61/// // Create audio manager
62/// let mut audio_manager = AudioManager::new().expect("Failed to initialize audio");
63///
64/// // Set global volume (0.0 to 1.0)
65/// audio_manager.set_global_volume(0.5);
66///
67/// // Get global volume
68/// assert_eq!(audio_manager.global_volume(), 0.5);
69/// ```
70pub struct AudioManager {
71 /// Audio device sink (must be kept alive for playback).
72 pub(super) device_sink: MixerDeviceSink,
73
74 /// Global volume (0.0 to 1.0).
75 pub(super) global_volume: Arc<Mutex<f32>>,
76
77 /// Active audio players (for controlling playback).
78 pub(super) players: Arc<Mutex<HashMap<u64, Player>>>,
79
80 /// Next player ID for tracking.
81 pub(super) next_player_id: Arc<Mutex<u64>>,
82}
83
84impl AudioManager {
85 /// Creates a new AudioManager instance.
86 ///
87 /// Initializes the audio output stream. Returns an error if audio
88 /// output is not available (e.g., no audio device found).
89 ///
90 /// # Errors
91 ///
92 /// Returns `AudioInitFailed` if the audio system cannot be initialized.
93 ///
94 /// # Example
95 ///
96 /// ```no_run
97 /// use goud_engine::assets::AudioManager;
98 ///
99 /// let audio_manager = AudioManager::new().expect("Failed to initialize audio");
100 /// ```
101 pub fn new() -> GoudResult<Self> {
102 let mut device_sink = DeviceSinkBuilder::open_default_sink().map_err(|e| {
103 GoudError::AudioInitFailed(format!("Failed to create audio output stream: {}", e))
104 })?;
105 // Suppress the log message rodio prints when the device sink is dropped.
106 device_sink.log_on_drop(false);
107
108 Ok(Self {
109 device_sink,
110 global_volume: Arc::new(Mutex::new(1.0)),
111 players: Arc::new(Mutex::new(HashMap::new())),
112 next_player_id: Arc::new(Mutex::new(0)),
113 })
114 }
115}
116
117// SAFETY: AudioManager is Send + Sync because all internal state is protected by Mutex.
118// MixerDeviceSink contains a cpal::Stream which is Send but not Sync by default.
119// We ensure thread safety by only accessing it through our Mutex-protected methods.
120unsafe impl Send for AudioManager {}
121unsafe impl Sync for AudioManager {}
122
123impl std::fmt::Debug for AudioManager {
124 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125 f.debug_struct("AudioManager")
126 .field("global_volume", &self.global_volume())
127 .field("active_players", &self.active_count())
128 .finish()
129 }
130}