1use super::occlusion::{OcclusionDetector, OcclusionResult};
4use super::prediction::{MotionPredictor, MotionSnapshot};
5use super::spatial_grid::SpatialGrid;
6use super::types::SoundSource;
7use crate::types::Position3D;
8use std::collections::{HashMap, VecDeque};
9use std::time::{Duration, Instant};
10
11pub struct SpatialSourceManager {
13 pub sources: HashMap<String, SoundSource>,
15 spatial_grid: SpatialGrid,
17 occlusion_detector: OcclusionDetector,
19 max_sources: usize,
21 pub culling_distance: f32,
23 update_frequency: f32,
25}
26
27#[derive(Debug, Clone)]
29pub struct DopplerProcessor {
30 speed_of_sound: f32,
32 sample_rate: f32,
34 max_doppler_factor: f32,
36 smoothing_factor: f32,
38}
39
40#[derive(Debug, Clone)]
42pub struct DynamicSourceManager {
43 sources: HashMap<String, DynamicSource>,
45 doppler_processor: DopplerProcessor,
47 motion_predictor: MotionPredictor,
49}
50
51#[derive(Debug, Clone)]
53pub struct DynamicSource {
54 pub base_source: SoundSource,
56 pub velocity: Position3D,
58 pub acceleration: Position3D,
60 pub motion_history: VecDeque<MotionSnapshot>,
62 pub doppler_factor: f32,
64 pub smoothed_doppler_factor: f32,
66 pub last_doppler_update: Option<Instant>,
68}
69
70impl SpatialSourceManager {
71 pub fn new(bounds: (Position3D, Position3D), cell_size: f32) -> Self {
73 Self {
74 sources: HashMap::new(),
75 spatial_grid: SpatialGrid::new(bounds, cell_size),
76 occlusion_detector: OcclusionDetector::new(),
77 max_sources: 64,
78 culling_distance: 100.0,
79 update_frequency: 60.0,
80 }
81 }
82
83 pub fn add_source(&mut self, source: SoundSource) -> crate::Result<()> {
85 if self.sources.len() >= self.max_sources {
86 return Err(crate::Error::LegacyPosition(
87 "Maximum sources exceeded".to_string(),
88 ));
89 }
90
91 let source_id = source.id.clone();
92 let position = source.position();
93
94 self.spatial_grid.add_source(&source_id, position);
96
97 self.sources.insert(source_id, source);
99
100 Ok(())
101 }
102
103 pub fn remove_source(&mut self, source_id: &str) -> Option<SoundSource> {
105 if let Some(source) = self.sources.remove(source_id) {
106 self.spatial_grid.remove_source(source_id);
107 Some(source)
108 } else {
109 None
110 }
111 }
112
113 pub fn update_source_position(
115 &mut self,
116 source_id: &str,
117 position: Position3D,
118 ) -> crate::Result<()> {
119 if let Some(source) = self.sources.get_mut(source_id) {
120 let old_position = source.position();
121 source.set_position(position);
122
123 self.spatial_grid
125 .move_source(source_id, old_position, position);
126
127 Ok(())
128 } else {
129 Err(crate::Error::LegacyPosition(format!(
130 "Source not found: {source_id}"
131 )))
132 }
133 }
134
135 pub fn get_nearby_sources(
137 &self,
138 listener_position: Position3D,
139 radius: f32,
140 ) -> Vec<&SoundSource> {
141 let nearby_ids = self.spatial_grid.query_sphere(listener_position, radius);
142 nearby_ids
143 .iter()
144 .filter_map(|id| self.sources.get(id))
145 .collect()
146 }
147
148 pub fn check_occlusion(
150 &self,
151 source_position: Position3D,
152 listener_position: Position3D,
153 ) -> OcclusionResult {
154 self.occlusion_detector
155 .check_occlusion(source_position, listener_position)
156 }
157
158 pub fn cull_distant_sources(&mut self, listener_position: Position3D) {
160 let culling_distance_sq = self.culling_distance * self.culling_distance;
161 let distant_sources: Vec<String> = self
162 .sources
163 .iter()
164 .filter(|(_, source)| {
165 let distance_sq = listener_position.distance_to(&source.position()).powi(2);
166 distance_sq > culling_distance_sq
167 })
168 .map(|(id, _)| id.clone())
169 .collect();
170
171 for source_id in distant_sources {
172 self.remove_source(&source_id);
173 }
174 }
175
176 pub fn get_active_sources(&self) -> Vec<&SoundSource> {
178 self.sources.values().filter(|s| s.is_active()).collect()
179 }
180}
181
182impl DopplerProcessor {
183 pub fn new(sample_rate: f32) -> Self {
185 Self {
186 speed_of_sound: 343.0, sample_rate,
188 max_doppler_factor: 2.0, smoothing_factor: 0.95, }
191 }
192
193 pub fn with_speed_of_sound(sample_rate: f32, speed_of_sound: f32) -> Self {
195 Self {
196 speed_of_sound,
197 sample_rate,
198 max_doppler_factor: 2.0,
199 smoothing_factor: 0.95,
200 }
201 }
202
203 pub fn calculate_doppler_factor(
205 &self,
206 source_position: Position3D,
207 source_velocity: Position3D,
208 listener_position: Position3D,
209 listener_velocity: Position3D,
210 ) -> f32 {
211 let source_to_listener = Position3D::new(
213 listener_position.x - source_position.x,
214 listener_position.y - source_position.y,
215 listener_position.z - source_position.z,
216 );
217
218 let distance = source_to_listener.magnitude();
219 if distance < 0.001 {
220 return 1.0; }
222
223 let direction = source_to_listener.normalized();
225
226 let source_radial_velocity = source_velocity.dot(&direction); let listener_radial_velocity = -listener_velocity.dot(&direction); let numerator = self.speed_of_sound + listener_radial_velocity;
234 let denominator = self.speed_of_sound - source_radial_velocity;
235
236 if denominator.abs() < 0.001 {
237 return 1.0; }
239
240 let doppler_factor = numerator / denominator;
241
242 doppler_factor.clamp(1.0 / self.max_doppler_factor, self.max_doppler_factor)
244 }
245
246 pub fn process_doppler_effect(
248 &self,
249 input: &[f32],
250 output: &mut [f32],
251 doppler_factor: f32,
252 ) -> crate::Result<()> {
253 if input.len() != output.len() {
254 return Err(crate::Error::LegacyProcessing(
255 "Input and output buffers must have the same length".to_string(),
256 ));
257 }
258
259 if (doppler_factor - 1.0).abs() < 0.001 {
260 output.copy_from_slice(input);
262 return Ok(());
263 }
264
265 let pitch_ratio = doppler_factor;
268 let mut read_pos = 0.0;
269
270 for i in 0..output.len() {
271 let read_index = read_pos as usize;
272 let read_frac = read_pos - read_index as f32;
273
274 if read_index + 1 < input.len() {
275 let sample1 = input[read_index];
277 let sample2 = input[read_index + 1];
278 output[i] = sample1 + read_frac * (sample2 - sample1);
279 } else if read_index < input.len() {
280 output[i] = input[read_index];
281 } else {
282 output[i] = 0.0;
283 }
284
285 read_pos += pitch_ratio;
286 if read_pos >= input.len() as f32 {
287 output[i + 1..].fill(0.0);
289 break;
290 }
291 }
292
293 Ok(())
294 }
295
296 pub fn smooth_doppler_factor(&self, current: f32, target: f32) -> f32 {
298 current * self.smoothing_factor + target * (1.0 - self.smoothing_factor)
299 }
300
301 pub fn set_speed_of_sound(&mut self, speed: f32) {
303 self.speed_of_sound = speed;
304 }
305
306 pub fn speed_of_sound(&self) -> f32 {
308 self.speed_of_sound
309 }
310}
311
312impl DynamicSourceManager {
313 pub fn new(sample_rate: f32) -> Self {
315 Self {
316 sources: HashMap::new(),
317 doppler_processor: DopplerProcessor::new(sample_rate),
318 motion_predictor: MotionPredictor::new(),
319 }
320 }
321
322 pub fn add_source(&mut self, source: SoundSource) -> crate::Result<()> {
324 let dynamic_source = DynamicSource::new(source);
325 self.sources
326 .insert(dynamic_source.base_source.id.clone(), dynamic_source);
327 Ok(())
328 }
329
330 pub fn remove_source(&mut self, source_id: &str) -> Option<DynamicSource> {
332 self.sources.remove(source_id)
333 }
334
335 pub fn update_source_motion(
337 &mut self,
338 source_id: &str,
339 position: Position3D,
340 velocity: Position3D,
341 acceleration: Position3D,
342 ) -> crate::Result<()> {
343 if let Some(source) = self.sources.get_mut(source_id) {
344 source.update_motion(position, velocity, acceleration);
345 Ok(())
346 } else {
347 Err(crate::Error::LegacyPosition(format!(
348 "Source '{source_id}' not found"
349 )))
350 }
351 }
352
353 pub async fn process_dynamic_sources(
355 &mut self,
356 listener_position: Position3D,
357 listener_velocity: Position3D,
358 ) -> crate::Result<()> {
359 for source in self.sources.values_mut() {
360 let doppler_factor = self.doppler_processor.calculate_doppler_factor(
362 source.base_source.position(),
363 source.velocity,
364 listener_position,
365 listener_velocity,
366 );
367
368 source.smoothed_doppler_factor = self
370 .doppler_processor
371 .smooth_doppler_factor(source.smoothed_doppler_factor, doppler_factor);
372
373 source.doppler_factor = doppler_factor;
374 source.last_doppler_update = Some(Instant::now());
375 }
376
377 Ok(())
378 }
379
380 pub fn sources(&self) -> &HashMap<String, DynamicSource> {
382 &self.sources
383 }
384
385 pub fn get_source(&self, source_id: &str) -> Option<&DynamicSource> {
387 self.sources.get(source_id)
388 }
389
390 pub fn get_source_mut(&mut self, source_id: &str) -> Option<&mut DynamicSource> {
392 self.sources.get_mut(source_id)
393 }
394
395 pub fn predict_source_positions(
397 &self,
398 prediction_time: Duration,
399 ) -> HashMap<String, Position3D> {
400 let mut predictions = HashMap::new();
401
402 for (id, source) in &self.sources {
403 if let Some(predicted_pos) = self
404 .motion_predictor
405 .predict_position(&source.motion_history, prediction_time)
406 {
407 predictions.insert(id.clone(), predicted_pos);
408 }
409 }
410
411 predictions
412 }
413}
414
415impl DynamicSource {
416 pub fn new(base_source: SoundSource) -> Self {
418 Self {
419 base_source,
420 velocity: Position3D::default(),
421 acceleration: Position3D::default(),
422 motion_history: VecDeque::with_capacity(100),
423 doppler_factor: 1.0,
424 smoothed_doppler_factor: 1.0,
425 last_doppler_update: None,
426 }
427 }
428
429 pub fn update_motion(
431 &mut self,
432 position: Position3D,
433 velocity: Position3D,
434 acceleration: Position3D,
435 ) {
436 self.velocity = velocity;
437 self.acceleration = acceleration;
438
439 self.base_source.set_position(position);
441
442 let snapshot = MotionSnapshot {
444 position,
445 velocity,
446 acceleration,
447 timestamp: std::time::SystemTime::now()
448 .duration_since(std::time::UNIX_EPOCH)
449 .unwrap_or_default()
450 .as_secs_f64(),
451 };
452
453 self.motion_history.push_back(snapshot);
454
455 while self.motion_history.len() > 100 {
457 self.motion_history.pop_front();
458 }
459 }
460
461 pub fn predict_position(&self, time_delta: Duration) -> Position3D {
463 let dt = time_delta.as_secs_f32();
464 let current_pos = self.base_source.position();
465
466 Position3D::new(
468 current_pos.x + self.velocity.x * dt + 0.5 * self.acceleration.x * dt * dt,
469 current_pos.y + self.velocity.y * dt + 0.5 * self.acceleration.y * dt * dt,
470 current_pos.z + self.velocity.z * dt + 0.5 * self.acceleration.z * dt * dt,
471 )
472 }
473
474 pub fn is_moving(&self) -> bool {
476 self.velocity.magnitude() > 0.1 }
478}