Skip to main content

goud_engine/ecs/components/audiosource/
source.rs

1//! AudioSource component for spatial audio playback.
2
3use crate::assets::loaders::audio::AudioAsset;
4use crate::assets::AssetHandle;
5use crate::ecs::Component;
6
7use super::attenuation::AttenuationModel;
8use super::channel::AudioChannel;
9
10/// AudioSource component for spatial audio playback.
11///
12/// Attach this component to an entity to enable audio playback. The audio system
13/// will automatically handle playback, looping, volume, pitch, and spatial audio
14/// based on the component's configuration.
15///
16/// # Fields
17///
18/// - `audio`: Reference to the audio asset to play
19/// - `playing`: Whether the audio is currently playing
20/// - `looping`: Whether the audio should loop when it finishes
21/// - `volume`: Volume multiplier (0.0 = silent, 1.0 = full volume)
22/// - `pitch`: Pitch multiplier (0.5 = half speed, 2.0 = double speed)
23/// - `channel`: Audio channel for grouping and mixing
24/// - `auto_play`: Whether to start playing automatically when spawned
25/// - `spatial`: Whether to apply spatial audio (requires Transform)
26/// - `max_distance`: Maximum distance for spatial audio attenuation
27/// - `attenuation`: Distance-based volume falloff model
28/// - `sink_id`: Internal audio sink ID (managed by audio system)
29///
30/// # Examples
31///
32/// See module-level documentation for usage examples.
33#[derive(Clone, Debug)]
34pub struct AudioSource {
35    /// Reference to the audio asset to play
36    pub audio: AssetHandle<AudioAsset>,
37    /// Whether the audio is currently playing
38    pub playing: bool,
39    /// Whether the audio should loop when it finishes
40    pub looping: bool,
41    /// Volume multiplier (0.0 = silent, 1.0 = full volume)
42    pub volume: f32,
43    /// Pitch multiplier (0.5 = half speed, 2.0 = double speed)
44    pub pitch: f32,
45    /// Audio channel for grouping and mixing
46    pub channel: AudioChannel,
47    /// Whether to start playing automatically when spawned
48    pub auto_play: bool,
49    /// Whether to apply spatial audio (requires Transform)
50    pub spatial: bool,
51    /// Maximum distance for spatial audio attenuation
52    pub max_distance: f32,
53    /// Distance-based volume falloff model
54    pub attenuation: AttenuationModel,
55    /// Internal audio sink ID (managed by audio system)
56    pub(crate) sink_id: Option<u64>,
57}
58
59impl AudioSource {
60    /// Creates a new AudioSource with default settings.
61    ///
62    /// # Arguments
63    ///
64    /// - `audio`: Reference to the audio asset to play
65    ///
66    /// # Default Values
67    ///
68    /// - playing: false (stopped)
69    /// - looping: false (one-shot)
70    /// - volume: 1.0 (full volume)
71    /// - pitch: 1.0 (normal speed)
72    /// - channel: SFX
73    /// - auto_play: false
74    /// - spatial: false (non-spatial)
75    /// - max_distance: 100.0
76    /// - attenuation: InverseDistance
77    /// - sink_id: None
78    ///
79    /// # Examples
80    ///
81    /// ```
82    /// use goud_engine::ecs::components::AudioSource;
83    /// use goud_engine::assets::AssetHandle;
84    /// use goud_engine::assets::loaders::audio::AudioAsset;
85    ///
86    /// let audio_handle: AssetHandle<AudioAsset> = AssetHandle::default();
87    /// let source = AudioSource::new(audio_handle);
88    ///
89    /// assert_eq!(source.playing, false);
90    /// assert_eq!(source.volume, 1.0);
91    /// assert_eq!(source.pitch, 1.0);
92    /// ```
93    pub fn new(audio: AssetHandle<AudioAsset>) -> Self {
94        Self {
95            audio,
96            playing: false,
97            looping: false,
98            volume: 1.0,
99            pitch: 1.0,
100            channel: AudioChannel::default(),
101            auto_play: false,
102            spatial: false,
103            max_distance: 100.0,
104            attenuation: AttenuationModel::default(),
105            sink_id: None,
106        }
107    }
108
109    /// Sets the volume (0.0-1.0, clamped).
110    ///
111    /// # Examples
112    ///
113    /// ```
114    /// use goud_engine::ecs::components::AudioSource;
115    /// use goud_engine::assets::AssetHandle;
116    /// use goud_engine::assets::loaders::audio::AudioAsset;
117    ///
118    /// let audio_handle: AssetHandle<AudioAsset> = AssetHandle::default();
119    /// let source = AudioSource::new(audio_handle).with_volume(0.5);
120    /// assert_eq!(source.volume, 0.5);
121    /// ```
122    pub fn with_volume(mut self, volume: f32) -> Self {
123        self.volume = volume.clamp(0.0, 1.0);
124        self
125    }
126
127    /// Sets the pitch (0.5-2.0, clamped).
128    ///
129    /// # Examples
130    ///
131    /// ```
132    /// use goud_engine::ecs::components::AudioSource;
133    /// use goud_engine::assets::AssetHandle;
134    /// use goud_engine::assets::loaders::audio::AudioAsset;
135    ///
136    /// let audio_handle: AssetHandle<AudioAsset> = AssetHandle::default();
137    /// let source = AudioSource::new(audio_handle).with_pitch(1.5);
138    /// assert_eq!(source.pitch, 1.5);
139    /// ```
140    pub fn with_pitch(mut self, pitch: f32) -> Self {
141        self.pitch = pitch.clamp(0.5, 2.0);
142        self
143    }
144
145    /// Sets whether the audio should loop.
146    pub fn with_looping(mut self, looping: bool) -> Self {
147        self.looping = looping;
148        self
149    }
150
151    /// Sets the audio channel.
152    pub fn with_channel(mut self, channel: AudioChannel) -> Self {
153        self.channel = channel;
154        self
155    }
156
157    /// Sets whether to start playing automatically when spawned.
158    pub fn with_auto_play(mut self, auto_play: bool) -> Self {
159        self.auto_play = auto_play;
160        self
161    }
162
163    /// Sets whether to apply spatial audio (requires Transform component).
164    pub fn with_spatial(mut self, spatial: bool) -> Self {
165        self.spatial = spatial;
166        self
167    }
168
169    /// Sets the maximum distance for spatial audio attenuation.
170    pub fn with_max_distance(mut self, max_distance: f32) -> Self {
171        self.max_distance = max_distance.max(0.1);
172        self
173    }
174
175    /// Sets the attenuation model for spatial audio.
176    pub fn with_attenuation(mut self, attenuation: AttenuationModel) -> Self {
177        self.attenuation = attenuation;
178        self
179    }
180
181    /// Starts playing the audio.
182    pub fn play(&mut self) {
183        self.playing = true;
184    }
185
186    /// Pauses the audio (retains playback position).
187    pub fn pause(&mut self) {
188        self.playing = false;
189    }
190
191    /// Stops the audio (resets playback position).
192    pub fn stop(&mut self) {
193        self.playing = false;
194        self.sink_id = None;
195    }
196
197    /// Returns whether the audio is currently playing.
198    pub fn is_playing(&self) -> bool {
199        self.playing
200    }
201
202    /// Returns whether the audio is spatial.
203    pub fn is_spatial(&self) -> bool {
204        self.spatial
205    }
206
207    /// Returns whether the audio has an active sink.
208    pub fn has_sink(&self) -> bool {
209        self.sink_id.is_some()
210    }
211
212    /// Sets the internal sink ID (managed by audio system).
213    #[cfg(test)]
214    pub(crate) fn set_sink_id(&mut self, id: Option<u64>) {
215        self.sink_id = id;
216    }
217
218    /// Returns the internal sink ID.
219    #[cfg(test)]
220    pub(crate) fn sink_id(&self) -> Option<u64> {
221        self.sink_id
222    }
223}
224
225impl Component for AudioSource {}
226
227impl Default for AudioSource {
228    fn default() -> Self {
229        Self::new(AssetHandle::default())
230    }
231}
232
233impl std::fmt::Display for AudioSource {
234    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235        write!(
236            f,
237            "AudioSource(playing={}, looping={}, volume={:.2}, pitch={:.2}, channel={}, spatial={})",
238            self.playing, self.looping, self.volume, self.pitch, self.channel, self.spatial
239        )
240    }
241}