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}