Skip to main content

gizmo_audio/
lib.rs

1use rodio::{Decoder, OutputStream, OutputStreamHandle, Sink, Source, SpatialSink};
2use std::collections::HashMap;
3use std::fs::File;
4use std::io::{Cursor, Read};
5use std::path::Path;
6use std::sync::Arc;
7
8// ======================== ERRORS ========================
9
10#[derive(Debug)]
11pub enum AudioError {
12    Io(std::io::Error),
13    NotFound(String),
14}
15
16impl std::fmt::Display for AudioError {
17    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18        match self {
19            AudioError::Io(err) => write!(f, "IO Error: {}", err),
20            AudioError::NotFound(path) => write!(f, "File not found: {}", path),
21        }
22    }
23}
24
25impl std::error::Error for AudioError {}
26
27// ======================== ECS COMPONENT ========================
28
29/// 3D veya 2D oynatılabilecek ses kaynağı bileşeni (ECS için)
30#[derive(Clone, serde::Serialize, serde::Deserialize)]
31pub struct AudioSource {
32    pub sound_name: String,
33    pub is_3d: bool,
34    pub volume: f32,
35    pub pitch: f32,
36    pub loop_sound: bool,
37    pub max_distance: f32, // Sesin zayıflayarak tamamen kısılacağı mesafe limiti
38    pub _internal_sink_id: Option<u64>,
39}
40
41impl Default for AudioSource {
42    fn default() -> Self {
43        Self::new("default")
44    }
45}
46
47impl AudioSource {
48    pub fn new(name: &str) -> Self {
49        Self {
50            sound_name: name.to_string(),
51            is_3d: true,
52            volume: 1.0,
53            pitch: 1.0,
54            loop_sound: false,
55            max_distance: 100.0, // Varsayılan değer
56            _internal_sink_id: None,
57        }
58    }
59
60    pub fn with_loop(mut self, l: bool) -> Self {
61        self.loop_sound = l;
62        self
63    }
64
65    pub fn with_max_distance(mut self, dist: f32) -> Self {
66        self.max_distance = dist;
67        self
68    }
69}
70
71// ======================== AUDIO MANAGER ========================
72
73pub struct AudioManager {
74    // OutputStream is kept alive so audio actually plays
75    _stream: OutputStream,
76    stream_handle: OutputStreamHandle,
77
78    // RAM'e (Memory) yüklenmiş ses dosyaları (Disk I/O darboğazını önler)
79    sound_buffers: HashMap<String, Arc<[u8]>>,
80
81    // Aktif SpatialSink'leri veya normal Sink'leri takip edip parametrelerini güncellemek için
82    active_spatial_sinks: HashMap<u64, SpatialSink>,
83    active_sinks: HashMap<u64, Sink>,
84    next_sink_id: u64,
85}
86
87impl AudioManager {
88    pub fn new() -> Option<Self> {
89        match OutputStream::try_default() {
90            Ok((stream, stream_handle)) => {
91                log::info!("Gizmo Audio: Ses cihazı başlatıldı! 3D Uzamsal (Spatial) Motor Aktif.");
92                Some(Self {
93                    _stream: stream,
94                    stream_handle,
95                    sound_buffers: HashMap::new(),
96                    active_spatial_sinks: HashMap::new(),
97                    active_sinks: HashMap::new(),
98                    next_sink_id: 1,
99                })
100            }
101            Err(e) => {
102                log::error!("Gizmo Audio Başarısız (Cihaz bulunamadı): {}", e);
103                None
104            }
105        }
106    }
107
108    /// Sesi diske gidip okuyarak byte array olarak RAM'e kaydeder
109    pub fn load_sound(&mut self, name: &str, path: &str) -> Result<(), AudioError> {
110        let mut file =
111            File::open(Path::new(path)).map_err(|_| AudioError::NotFound(path.to_string()))?;
112        let mut buffer = Vec::new();
113        file.read_to_end(&mut buffer).map_err(AudioError::Io)?;
114        self.sound_buffers.insert(name.to_string(), buffer.into());
115        Ok(())
116    }
117
118    /// Update çağrıldığında biten sesleri temizler
119    pub fn update(&mut self) {
120        self.clean_dead_sinks();
121    }
122
123    /// Normal (Global/Stereo) bir ses oynatır (tek seferlik)
124    pub fn play(&mut self, name: &str) -> Option<u64> {
125        self.play_internal(name, false)
126    }
127
128    /// Normal (Global/Stereo) bir sesi döngüsel oynatır
129    pub fn play_looped(&mut self, name: &str) -> Option<u64> {
130        self.play_internal(name, true)
131    }
132
133    fn play_internal(&mut self, name: &str, looped: bool) -> Option<u64> {
134        if let Some(bytes) = self.sound_buffers.get(name) {
135            let cursor = Cursor::new(Arc::clone(bytes));
136            if let Ok(decoder) = Decoder::new(cursor) {
137                if let Ok(sink) = Sink::try_new(&self.stream_handle) {
138                    if looped {
139                        sink.append(decoder.repeat_infinite());
140                    } else {
141                        sink.append(decoder);
142                    }
143                    let id = self.next_sink_id;
144                    self.next_sink_id = self.next_sink_id.wrapping_add(1);
145
146                    self.active_sinks.insert(id, sink);
147                    return Some(id);
148                }
149            }
150        } else {
151            log::error!("AudioManager: '{}' adlı ses bellekte yok!", name);
152        }
153        None
154    }
155
156    /// 3D Uzamsal (Spatial) bir ses oynatır (tek seferlik)
157    pub fn play_3d(
158        &mut self,
159        name: &str,
160        emitter_pos: [f32; 3],
161        left_ear: [f32; 3],
162        right_ear: [f32; 3],
163    ) -> Option<u64> {
164        self.play_3d_internal(name, emitter_pos, left_ear, right_ear, false)
165    }
166
167    /// 3D Uzamsal bir sesi döngüsel oynatır
168    pub fn play_3d_looped(
169        &mut self,
170        name: &str,
171        emitter_pos: [f32; 3],
172        left_ear: [f32; 3],
173        right_ear: [f32; 3],
174    ) -> Option<u64> {
175        self.play_3d_internal(name, emitter_pos, left_ear, right_ear, true)
176    }
177
178    fn play_3d_internal(
179        &mut self,
180        name: &str,
181        emitter_pos: [f32; 3],
182        left_ear: [f32; 3],
183        right_ear: [f32; 3],
184        looped: bool,
185    ) -> Option<u64> {
186        if let Some(bytes) = self.sound_buffers.get(name) {
187            let cursor = Cursor::new(Arc::clone(bytes));
188            if let Ok(decoder) = Decoder::new(cursor) {
189                if let Ok(sink) =
190                    SpatialSink::try_new(&self.stream_handle, emitter_pos, left_ear, right_ear)
191                {
192                    if looped {
193                        sink.append(decoder.repeat_infinite());
194                    } else {
195                        sink.append(decoder);
196                    }
197
198                    let id = self.next_sink_id;
199                    self.next_sink_id = self.next_sink_id.wrapping_add(1);
200
201                    self.active_spatial_sinks.insert(id, sink);
202                    return Some(id);
203                }
204            }
205        } else {
206            log::error!("AudioManager: '{}' adlı 3D ses bellekte yok!", name);
207        }
208        None
209    }
210
211    // ========== ECS SINK GÜNCELLEMELERİ ==========
212
213    pub fn update_spatial_sink(
214        &mut self,
215        id: u64,
216        emitter_pos: [f32; 3],
217        left_ear: [f32; 3],
218        right_ear: [f32; 3],
219        max_distance: f32,
220        base_volume: f32,
221    ) {
222        if let Some(sink) = self.active_spatial_sinks.get(&id) {
223            sink.set_emitter_position(emitter_pos);
224            sink.set_left_ear_position(left_ear);
225            sink.set_right_ear_position(right_ear);
226
227            let listener_pos = [
228                (left_ear[0] + right_ear[0]) / 2.0,
229                (left_ear[1] + right_ear[1]) / 2.0,
230                (left_ear[2] + right_ear[2]) / 2.0,
231            ];
232            let dx = emitter_pos[0] - listener_pos[0];
233            let dy = emitter_pos[1] - listener_pos[1];
234            let dz = emitter_pos[2] - listener_pos[2];
235            let distance = (dx * dx + dy * dy + dz * dz).sqrt();
236            let mut volume = if max_distance > 0.0 {
237                (1.0 - distance / max_distance).max(0.0)
238            } else {
239                1.0
240            };
241            volume *= base_volume;
242
243            sink.set_volume(volume);
244        }
245    }
246
247    pub fn set_volume(&mut self, id: u64, volume: f32) {
248        if let Some(sink) = self.active_spatial_sinks.get(&id) {
249            sink.set_volume(volume);
250        } else if let Some(sink) = self.active_sinks.get(&id) {
251            sink.set_volume(volume);
252        }
253    }
254
255    pub fn set_pitch(&mut self, id: u64, pitch: f32) {
256        if let Some(sink) = self.active_spatial_sinks.get(&id) {
257            sink.set_speed(pitch);
258        } else if let Some(sink) = self.active_sinks.get(&id) {
259            sink.set_speed(pitch);
260        }
261    }
262
263    pub fn stop(&mut self, id: u64) {
264        if let Some(sink) = self.active_spatial_sinks.get(&id) {
265            sink.stop();
266        } else if let Some(sink) = self.active_sinks.get(&id) {
267            sink.stop();
268        }
269    }
270
271    pub fn pause(&mut self, id: u64) {
272        if let Some(sink) = self.active_spatial_sinks.get(&id) {
273            sink.pause();
274        } else if let Some(sink) = self.active_sinks.get(&id) {
275            sink.pause();
276        }
277    }
278
279    pub fn resume(&mut self, id: u64) {
280        if let Some(sink) = self.active_spatial_sinks.get(&id) {
281            sink.play();
282        } else if let Some(sink) = self.active_sinks.get(&id) {
283            sink.play();
284        }
285    }
286
287    /// Çalan bitmiş sesleri (Sinks) Garbage Collector gibi temizler
288    pub fn clean_dead_sinks(&mut self) {
289        self.active_spatial_sinks.retain(|_, sink| !sink.empty());
290        self.active_sinks.retain(|_, sink| !sink.empty());
291    }
292
293    pub fn is_playing(&self, id: u64) -> bool {
294        if let Some(sink) = self.active_spatial_sinks.get(&id) {
295            !sink.empty() && !sink.is_paused()
296        } else if let Some(sink) = self.active_sinks.get(&id) {
297            !sink.empty() && !sink.is_paused()
298        } else {
299            false
300        }
301    }
302}
303
304gizmo_core::impl_component!(AudioSource);