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#[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#[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, 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, _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
71pub struct AudioManager {
74 _stream: OutputStream,
76 stream_handle: OutputStreamHandle,
77
78 sound_buffers: HashMap<String, Arc<[u8]>>,
80
81 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 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 pub fn update(&mut self) {
120 self.clean_dead_sinks();
121 }
122
123 pub fn play(&mut self, name: &str) -> Option<u64> {
125 self.play_internal(name, false)
126 }
127
128 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 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 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 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 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);