1#![cfg_attr(not(feature = "audio"), allow(dead_code))]
12
13use crate::math::{Vec2, Vec2Ext, FloatExt};
14use std::collections::HashMap;
15
16#[derive(Debug, Clone)]
22pub struct Sound {
23 pub name: String,
25 pub is_music: bool,
27 pub volume: f32,
29 pub pitch: f32,
31 pub looping: bool,
33 pub spatial_min_distance: f32,
35 pub spatial_max_distance: f32,
37 pub position: Option<Vec2>,
39}
40
41impl Sound {
42 pub fn new(name: &str) -> Self {
44 Self {
45 name: name.to_string(),
46 is_music: false,
47 volume: 1.0,
48 pitch: 1.0,
49 looping: false,
50 spatial_min_distance: 50.0,
51 spatial_max_distance: 500.0,
52 position: None,
53 }
54 }
55
56 pub fn as_music(mut self) -> Self {
58 self.is_music = true;
59 self
60 }
61
62 pub fn with_volume(mut self, volume: f32) -> Self {
64 self.volume = volume.clamp(0.0, 1.0);
65 self
66 }
67
68 pub fn with_pitch(mut self, pitch: f32) -> Self {
70 self.pitch = pitch.clamp(0.1, 10.0);
71 self
72 }
73
74 pub fn with_looping(mut self, looping: bool) -> Self {
76 self.looping = looping;
77 self
78 }
79
80 pub fn with_position(mut self, pos: Vec2) -> Self {
82 self.position = Some(pos);
83 self
84 }
85
86 pub fn with_spatial_range(mut self, min: f32, max: f32) -> Self {
88 self.spatial_min_distance = min;
89 self.spatial_max_distance = max;
90 self
91 }
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
100pub enum AudioChannel {
101 Sfx,
103 Music,
105 Ui,
107 Ambient,
109}
110
111pub struct AudioManager {
130 master_volume: f32,
132 channel_volumes: HashMap<AudioChannel, f32>,
134 sounds: HashMap<String, Sound>,
136 instances: Vec<PlayingInstance>,
138 listener_position: Vec2,
140}
141
142#[derive(Debug, Clone)]
144struct PlayingInstance {
145 sound_name: String,
146 channel: AudioChannel,
147 current_volume: f32,
149 fade: Option<FadeState>,
151 alive: bool,
153}
154
155#[derive(Debug, Clone, Copy)]
157struct FadeState {
158 kind: FadeKind,
159 duration: f32,
160 elapsed: f32,
161 start_volume: f32,
162 target_volume: f32,
163}
164
165#[derive(Debug, Clone, Copy, PartialEq)]
166enum FadeKind {
167 In,
168 Out,
169}
170
171#[derive(Debug, Clone, Copy)]
173pub enum Fade {
174 In(f32),
176 Out(f32),
178 None,
180}
181
182impl Default for Fade {
183 fn default() -> Self {
184 Fade::None
185 }
186}
187
188impl AudioManager {
189 pub fn new() -> Self {
191 let mut channel_volumes = HashMap::new();
192 channel_volumes.insert(AudioChannel::Sfx, 1.0);
193 channel_volumes.insert(AudioChannel::Music, 0.8);
194 channel_volumes.insert(AudioChannel::Ui, 0.7);
195 channel_volumes.insert(AudioChannel::Ambient, 0.6);
196
197 Self {
198 master_volume: 1.0,
199 channel_volumes,
200 sounds: HashMap::new(),
201 instances: Vec::new(),
202 listener_position: Vec2::ZERO,
203 }
204 }
205
206 pub fn load(&mut self, name: &str, _path: &str) -> Result<(), String> {
208 let sound = Sound::new(name);
209 self.sounds.insert(name.to_string(), sound);
210 Ok(())
211 }
212
213 pub fn load_with_config(&mut self, sound: Sound) {
215 let name = sound.name.clone();
216 self.sounds.insert(name, sound);
217 }
218
219 pub fn play(&mut self, name: &str) {
221 self.play_with_fade(name, AudioChannel::Sfx, Fade::None);
222 }
223
224 pub fn play_on_channel(&mut self, name: &str, channel: AudioChannel) {
226 self.play_with_fade(name, channel, Fade::None);
227 }
228
229 pub fn play_music(&mut self, name: &str, fade: Fade) {
231 self.stop_channel(AudioChannel::Music);
233 self.play_with_fade(name, AudioChannel::Music, fade);
234 }
235
236 fn play_with_fade(&mut self, name: &str, channel: AudioChannel, fade: Fade) {
238 let sound = match self.sounds.get(name) {
239 Some(s) => s.clone(),
240 None => return,
241 };
242
243 let base_volume = sound.volume
244 * self.master_volume
245 * self.channel_volumes.get(&channel).copied().unwrap_or(1.0);
246
247 let spatial_volume = if let Some(sound_pos) = sound.position {
249 let dist = sound_pos.distance_to(self.listener_position);
250 if dist <= sound.spatial_min_distance {
251 1.0
252 } else if dist >= sound.spatial_max_distance {
253 0.0
254 } else {
255 1.0 - (dist - sound.spatial_min_distance)
256 / (sound.spatial_max_distance - sound.spatial_min_distance)
257 }
258 } else {
259 1.0
260 };
261
262 let start_volume = match fade {
263 Fade::In(duration) => {
264 Some(FadeState {
265 kind: FadeKind::In,
266 duration,
267 elapsed: 0.0,
268 start_volume: 0.0,
269 target_volume: base_volume * spatial_volume,
270 })
271 }
272 Fade::Out(duration) => {
273 Some(FadeState {
274 kind: FadeKind::Out,
275 duration,
276 elapsed: 0.0,
277 start_volume: base_volume * spatial_volume,
278 target_volume: 0.0,
279 })
280 }
281 Fade::None => None,
282 };
283
284 let current_volume = start_volume
285 .as_ref()
286 .map(|f| f.start_volume)
287 .unwrap_or(base_volume * spatial_volume);
288
289 self.instances.push(PlayingInstance {
290 sound_name: name.to_string(),
291 channel,
292 current_volume,
293 fade: start_volume,
294 alive: true,
295 });
296 }
297
298 pub fn stop(&mut self, name: &str) {
300 for instance in &mut self.instances {
301 if instance.sound_name == name {
302 instance.alive = false;
303 }
304 }
305 }
306
307 pub fn stop_channel(&mut self, channel: AudioChannel) {
309 for instance in &mut self.instances {
310 if instance.channel == channel {
311 instance.alive = false;
312 }
313 }
314 }
315
316 pub fn stop_all(&mut self) {
318 for instance in &mut self.instances {
319 instance.alive = false;
320 }
321 }
322
323 pub fn set_master_volume(&mut self, volume: f32) {
325 self.master_volume = volume.clamp(0.0, 1.0);
326 }
327
328 pub fn set_channel_volume(&mut self, channel: AudioChannel, volume: f32) {
330 self.channel_volumes.insert(channel, volume.clamp(0.0, 1.0));
331 }
332
333 pub fn set_listener_position(&mut self, pos: Vec2) {
335 self.listener_position = pos;
336 }
337
338 pub fn pause_all(&mut self) {
340 }
342
343 pub fn resume_all(&mut self) {
345 }
347
348 pub fn is_playing(&self, name: &str) -> bool {
350 self.instances.iter().any(|i| i.sound_name == name && i.alive)
351 }
352
353 pub fn update(&mut self, dt: f32) {
355 for instance in &mut self.instances {
356 if !instance.alive {
357 continue;
358 }
359
360 if let Some(fade) = &mut instance.fade {
362 fade.elapsed += dt;
363 let t = (fade.elapsed / fade.duration).clamp(0.0, 1.0);
364 instance.current_volume = fade.start_volume.lerp(fade.target_volume, t);
365
366 if t >= 1.0 {
367 match fade.kind {
368 FadeKind::Out => instance.alive = false,
369 FadeKind::In => instance.fade = None,
370 }
371 }
372 }
373 }
374
375 self.instances.retain(|i| i.alive);
377 }
378
379 pub fn playing_count(&self) -> usize {
381 self.instances.iter().filter(|i| i.alive).count()
382 }
383}
384
385impl Default for AudioManager {
386 fn default() -> Self {
387 Self::new()
388 }
389}