1use rodio::{Decoder, OutputStream, OutputStreamHandle, Sink, Source, SpatialSink};
17use std::collections::HashMap;
18use std::fs::File;
19use std::io::{Cursor, Read};
20use std::path::Path;
21use std::sync::Arc;
22
23#[derive(Debug)]
28#[non_exhaustive]
29pub enum AudioError {
30 Io(std::io::Error),
32 NotFound(String),
34 Backend(String),
36 NotLoaded(String),
39 Decode(String),
41}
42
43impl std::fmt::Display for AudioError {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 match self {
46 AudioError::Io(err) => write!(f, "IO Error: {}", err),
47 AudioError::NotFound(path) => write!(f, "File not found: {}", path),
48 AudioError::Backend(msg) => write!(f, "Audio backend error: {}", msg),
49 AudioError::NotLoaded(name) => {
50 write!(f, "Sound '{}' is not loaded into memory", name)
51 }
52 AudioError::Decode(msg) => write!(f, "Failed to decode sound: {}", msg),
53 }
54 }
55}
56
57impl std::error::Error for AudioError {
58 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
59 match self {
60 AudioError::Io(err) => Some(err),
61 _ => None,
62 }
63 }
64}
65
66impl From<std::io::Error> for AudioError {
67 fn from(err: std::io::Error) -> Self {
68 AudioError::Io(err)
69 }
70}
71
72#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
76#[non_exhaustive]
77pub struct AudioSource {
78 pub sound_name: String,
80 pub is_3d: bool,
82 pub volume: f32,
84 pub pitch: f32,
86 pub loop_sound: bool,
88 pub max_distance: f32,
90 pub _internal_sink_id: Option<u64>,
92 #[serde(skip)]
97 pub has_played: bool,
98}
99
100impl Default for AudioSource {
101 fn default() -> Self {
102 Self::new("default")
103 }
104}
105
106impl AudioSource {
107 pub fn new(name: &str) -> Self {
109 Self {
110 sound_name: name.to_string(),
111 is_3d: true,
112 volume: 1.0,
113 pitch: 1.0,
114 loop_sound: false,
115 max_distance: 100.0, _internal_sink_id: None,
117 has_played: false,
118 }
119 }
120
121 pub fn with_loop(mut self, l: bool) -> Self {
123 self.loop_sound = l;
124 self
125 }
126
127 pub fn with_max_distance(mut self, dist: f32) -> Self {
129 self.max_distance = dist;
130 self
131 }
132}
133
134pub struct AudioManager {
139 _stream: OutputStream,
141 stream_handle: OutputStreamHandle,
142
143 sound_buffers: HashMap<String, Arc<[u8]>>,
145
146 active_spatial_sinks: HashMap<u64, SpatialSink>,
148 active_sinks: HashMap<u64, Sink>,
149 next_sink_id: u64,
150
151 underwater: bool,
153}
154
155#[cfg(all(target_arch = "wasm32", not(target_feature = "atomics")))]
163unsafe impl Send for AudioManager {}
164#[cfg(all(target_arch = "wasm32", not(target_feature = "atomics")))]
165unsafe impl Sync for AudioManager {}
166
167impl std::fmt::Debug for AudioManager {
168 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169 f.debug_struct("AudioManager")
170 .field("loaded_sounds", &self.sound_buffers.len())
171 .field("active_spatial_sinks", &self.active_spatial_sinks.len())
172 .field("active_sinks", &self.active_sinks.len())
173 .field("next_sink_id", &self.next_sink_id)
174 .finish_non_exhaustive()
175 }
176}
177
178pub(crate) fn sanitize_playback_speed(pitch: f32) -> f32 {
185 if pitch.is_finite() {
186 pitch.max(0.01)
187 } else {
188 1.0
189 }
190}
191
192impl AudioManager {
193 pub fn new() -> Result<Self, AudioError> {
208 match OutputStream::try_default() {
209 Ok((stream, stream_handle)) => {
210 log::info!("Gizmo Audio: Ses cihazı başlatıldı! 3D Uzamsal (Spatial) Motor Aktif.");
211 Ok(Self {
212 _stream: stream,
213 stream_handle,
214 sound_buffers: HashMap::new(),
215 active_spatial_sinks: HashMap::new(),
216 active_sinks: HashMap::new(),
217 next_sink_id: 1,
218 underwater: false,
219 })
220 }
221 Err(e) => {
222 log::error!("Gizmo Audio Başarısız (Cihaz bulunamadı): {}", e);
223 Err(AudioError::Backend(e.to_string()))
224 }
225 }
226 }
227
228 pub fn load_sound(&mut self, name: &str, path: &str) -> Result<(), AudioError> {
230 let mut file =
231 File::open(Path::new(path)).map_err(|_| AudioError::NotFound(path.to_string()))?;
232 let mut buffer = Vec::new();
233 file.read_to_end(&mut buffer).map_err(AudioError::Io)?;
234 self.sound_buffers.insert(name.to_string(), buffer.into());
235 Ok(())
236 }
237
238 pub fn load_sound_bytes(&mut self, name: &str, bytes: impl Into<Arc<[u8]>>) {
247 self.sound_buffers.insert(name.to_string(), bytes.into());
248 }
249
250 pub fn update(&mut self) {
252 self.clean_dead_sinks();
253 }
254
255 const UW_VOLUME_MUL: f32 = 0.4;
258 const UW_SPEED: f32 = 0.85;
260
261 pub fn set_underwater(&mut self, on: bool) {
268 if on == self.underwater {
269 return;
270 }
271 self.underwater = on;
272 let (vol_mul, speed) = if on {
273 (Self::UW_VOLUME_MUL, Self::UW_SPEED)
274 } else {
275 (1.0 / Self::UW_VOLUME_MUL, 1.0)
276 };
277 for sink in self.active_sinks.values() {
278 sink.set_volume(sink.volume() * vol_mul);
279 sink.set_speed(speed);
280 }
281 for sink in self.active_spatial_sinks.values() {
282 sink.set_volume(sink.volume() * vol_mul);
283 sink.set_speed(speed);
284 }
285 }
286
287 #[inline]
289 pub fn is_underwater(&self) -> bool {
290 self.underwater
291 }
292
293 fn apply_underwater_to(sink: &Sink, underwater: bool) {
295 if underwater {
296 sink.set_volume(sink.volume() * Self::UW_VOLUME_MUL);
297 sink.set_speed(Self::UW_SPEED);
298 }
299 }
300
301 pub fn play(&mut self, name: &str) -> Result<u64, AudioError> {
309 self.play_internal(name, false)
310 }
311
312 pub fn play_looped(&mut self, name: &str) -> Result<u64, AudioError> {
320 self.play_internal(name, true)
321 }
322
323 fn play_internal(&mut self, name: &str, looped: bool) -> Result<u64, AudioError> {
324 let bytes = self.sound_buffers.get(name).ok_or_else(|| {
325 log::error!("AudioManager: '{}' adlı ses bellekte yok!", name);
326 AudioError::NotLoaded(name.to_string())
327 })?;
328 let cursor = Cursor::new(Arc::clone(bytes));
329 let decoder = Decoder::new(cursor).map_err(|e| AudioError::Decode(e.to_string()))?;
330 let sink = Sink::try_new(&self.stream_handle).map_err(|e| AudioError::Backend(e.to_string()))?;
331 if looped {
332 sink.append(decoder.repeat_infinite());
333 } else {
334 sink.append(decoder);
335 }
336 let id = self.next_sink_id;
337 self.next_sink_id = self.next_sink_id.wrapping_add(1);
338
339 Self::apply_underwater_to(&sink, self.underwater);
341 self.active_sinks.insert(id, sink);
342 Ok(id)
343 }
344
345 pub fn play_3d(
353 &mut self,
354 name: &str,
355 emitter_pos: [f32; 3],
356 left_ear: [f32; 3],
357 right_ear: [f32; 3],
358 ) -> Result<u64, AudioError> {
359 self.play_3d_internal(name, emitter_pos, left_ear, right_ear, false)
360 }
361
362 pub fn play_3d_looped(
370 &mut self,
371 name: &str,
372 emitter_pos: [f32; 3],
373 left_ear: [f32; 3],
374 right_ear: [f32; 3],
375 ) -> Result<u64, AudioError> {
376 self.play_3d_internal(name, emitter_pos, left_ear, right_ear, true)
377 }
378
379 fn play_3d_internal(
380 &mut self,
381 name: &str,
382 emitter_pos: [f32; 3],
383 left_ear: [f32; 3],
384 right_ear: [f32; 3],
385 looped: bool,
386 ) -> Result<u64, AudioError> {
387 let bytes = self.sound_buffers.get(name).ok_or_else(|| {
388 log::error!("AudioManager: '{}' adlı 3D ses bellekte yok!", name);
389 AudioError::NotLoaded(name.to_string())
390 })?;
391 let cursor = Cursor::new(Arc::clone(bytes));
392 let decoder = Decoder::new(cursor).map_err(|e| AudioError::Decode(e.to_string()))?;
393 let sink = SpatialSink::try_new(&self.stream_handle, emitter_pos, left_ear, right_ear)
394 .map_err(|e| AudioError::Backend(e.to_string()))?;
395 if looped {
396 sink.append(decoder.repeat_infinite());
397 } else {
398 sink.append(decoder);
399 }
400
401 let id = self.next_sink_id;
402 self.next_sink_id = self.next_sink_id.wrapping_add(1);
403
404 if self.underwater {
405 sink.set_volume(sink.volume() * Self::UW_VOLUME_MUL);
406 sink.set_speed(Self::UW_SPEED);
407 }
408 self.active_spatial_sinks.insert(id, sink);
409 Ok(id)
410 }
411
412 pub fn update_spatial_sink(
417 &mut self,
418 id: u64,
419 emitter_pos: [f32; 3],
420 left_ear: [f32; 3],
421 right_ear: [f32; 3],
422 max_distance: f32,
423 base_volume: f32,
424 ) {
425 if let Some(sink) = self.active_spatial_sinks.get(&id) {
426 sink.set_emitter_position(emitter_pos);
427 sink.set_left_ear_position(left_ear);
428 sink.set_right_ear_position(right_ear);
429
430 let listener_pos = [
431 (left_ear[0] + right_ear[0]) / 2.0,
432 (left_ear[1] + right_ear[1]) / 2.0,
433 (left_ear[2] + right_ear[2]) / 2.0,
434 ];
435 let dx = emitter_pos[0] - listener_pos[0];
436 let dy = emitter_pos[1] - listener_pos[1];
437 let dz = emitter_pos[2] - listener_pos[2];
438 let distance = (dx * dx + dy * dy + dz * dz).sqrt();
439 let mut volume = if max_distance > 0.0 {
440 (1.0 - distance / max_distance).max(0.0)
441 } else {
442 1.0
443 };
444 volume *= base_volume;
445
446 sink.set_volume(volume);
447 }
448 }
449
450 pub fn set_volume(&mut self, id: u64, volume: f32) {
452 if let Some(sink) = self.active_spatial_sinks.get(&id) {
453 sink.set_volume(volume);
454 } else if let Some(sink) = self.active_sinks.get(&id) {
455 sink.set_volume(volume);
456 }
457 }
458
459 pub fn set_pitch(&mut self, id: u64, pitch: f32) {
461 let pitch = sanitize_playback_speed(pitch);
462 if let Some(sink) = self.active_spatial_sinks.get(&id) {
463 sink.set_speed(pitch);
464 } else if let Some(sink) = self.active_sinks.get(&id) {
465 sink.set_speed(pitch);
466 }
467 }
468
469 pub fn stop(&mut self, id: u64) {
471 if let Some(sink) = self.active_spatial_sinks.get(&id) {
472 sink.stop();
473 } else if let Some(sink) = self.active_sinks.get(&id) {
474 sink.stop();
475 }
476 }
477
478 pub fn pause(&mut self, id: u64) {
480 if let Some(sink) = self.active_spatial_sinks.get(&id) {
481 sink.pause();
482 } else if let Some(sink) = self.active_sinks.get(&id) {
483 sink.pause();
484 }
485 }
486
487 pub fn resume(&mut self, id: u64) {
489 if let Some(sink) = self.active_spatial_sinks.get(&id) {
490 sink.play();
491 } else if let Some(sink) = self.active_sinks.get(&id) {
492 sink.play();
493 }
494 }
495
496 pub fn clean_dead_sinks(&mut self) {
498 self.active_spatial_sinks.retain(|_, sink| !sink.empty());
499 self.active_sinks.retain(|_, sink| !sink.empty());
500 }
501
502 pub fn is_playing(&self, id: u64) -> bool {
504 if let Some(sink) = self.active_spatial_sinks.get(&id) {
505 !sink.empty() && !sink.is_paused()
506 } else if let Some(sink) = self.active_sinks.get(&id) {
507 !sink.empty() && !sink.is_paused()
508 } else {
509 false
510 }
511 }
512}
513
514#[cfg(test)]
515mod tests {
516 use super::sanitize_playback_speed;
517
518 #[test]
519 fn playback_speed_never_reaches_zero() {
520 assert!(sanitize_playback_speed(0.0) >= 0.01);
524 assert!(sanitize_playback_speed(-2.0) >= 0.01);
525 assert_eq!(sanitize_playback_speed(f32::NAN), 1.0);
526 assert_eq!(sanitize_playback_speed(f32::INFINITY), 1.0);
527 assert_eq!(sanitize_playback_speed(1.5), 1.5);
529 assert_eq!(sanitize_playback_speed(0.5), 0.5);
530 }
531}
532
533gizmo_core::impl_component!(AudioSource);