1use crate::config::SpatialConfig;
4use crate::hrtf::HrtfProcessor;
5use crate::memory::{MemoryConfig, MemoryManager};
6use crate::position::{Listener, SoundSource};
7use crate::room::RoomSimulator;
8use crate::types::{BinauraAudio, Position3D, SpatialEffect, SpatialRequest, SpatialResult};
9use scirs2_core::ndarray::{Array1, Array2};
10use std::collections::HashMap;
11use std::sync::Arc;
12use std::time::{Duration, Instant};
13use tokio::sync::RwLock;
14
15pub struct SpatialProcessor {
17 config: SpatialConfig,
19 hrtf_processor: Arc<RwLock<HrtfProcessor>>,
21 room_simulator: Arc<RwLock<RoomSimulator>>,
23 sound_sources: Arc<RwLock<HashMap<String, SoundSource>>>,
25 listener: Arc<RwLock<Listener>>,
27 processing_state: ProcessingState,
29 memory_manager: Arc<MemoryManager>,
31}
32
33pub struct SpatialProcessorBuilder {
35 config: Option<SpatialConfig>,
36 hrtf_database_path: Option<std::path::PathBuf>,
37 memory_config: Option<MemoryConfig>,
38}
39
40#[derive(Debug)]
42struct ProcessingState {
43 #[allow(dead_code)]
45 input_buffer: Array1<f32>,
46 #[allow(dead_code)]
47 output_buffer: Array2<f32>,
48 processed_frames: u64,
50 total_processing_time: Duration,
51}
52
53#[derive(Debug, Clone, Copy)]
55pub enum AttenuationModel {
56 Linear,
58 InverseDistance,
60 InverseSquare,
62 Exponential(f32),
64}
65
66#[allow(dead_code)]
68struct DopplerProcessor {
69 previous_positions: HashMap<String, (Position3D, Instant)>,
71 speed_of_sound: f32,
73 sample_rate: f32,
75}
76
77impl SpatialProcessor {
78 pub async fn new(config: SpatialConfig) -> crate::Result<Self> {
80 Self::with_memory_config(config, MemoryConfig::default()).await
81 }
82
83 pub async fn with_memory_config(
85 config: SpatialConfig,
86 memory_config: MemoryConfig,
87 ) -> crate::Result<Self> {
88 config.validate()?;
89
90 let hrtf_processor = Arc::new(RwLock::new(
91 HrtfProcessor::new(config.hrtf_database_path.clone()).await?,
92 ));
93
94 let room_simulator = Arc::new(RwLock::new(RoomSimulator::new(
95 config.room_dimensions,
96 config.reverb_time,
97 )?));
98
99 let processing_state = ProcessingState {
100 input_buffer: Array1::zeros(config.buffer_size),
101 output_buffer: Array2::zeros((2, config.buffer_size)),
102 processed_frames: 0,
103 total_processing_time: Duration::ZERO,
104 };
105
106 let memory_manager = Arc::new(MemoryManager::new(memory_config));
108
109 Ok(Self {
110 config,
111 hrtf_processor,
112 room_simulator,
113 sound_sources: Arc::new(RwLock::new(HashMap::new())),
114 listener: Arc::new(RwLock::new(Listener::default())),
115 processing_state,
116 memory_manager,
117 })
118 }
119
120 pub async fn process_request(
122 &mut self,
123 request: SpatialRequest,
124 ) -> crate::Result<SpatialResult> {
125 let start_time = Instant::now();
126
127 request.validate()?;
129
130 let source_position = request.source_position;
132 let effects = request.effects.clone();
133 let request_id = request.id.clone();
134 let sample_rate = request.sample_rate;
135
136 let input_audio = Array1::from_vec(request.audio);
138
139 let mut left_channel = Array1::zeros(input_audio.len());
141 let mut right_channel = Array1::zeros(input_audio.len());
142
143 let listener = self.listener.read().await;
145 let listener_position = listener.position();
146 let listener_orientation = listener.orientation();
147 drop(listener);
148
149 let relative_position = self.calculate_relative_position(
151 &source_position,
152 &listener_position,
153 &listener_orientation,
154 );
155
156 for effect in &effects {
158 match effect {
159 SpatialEffect::Hrtf => {
160 self.apply_hrtf(
161 &input_audio,
162 &mut left_channel,
163 &mut right_channel,
164 &relative_position,
165 )
166 .await?;
167 }
168 SpatialEffect::DistanceAttenuation => {
169 let distance = source_position.distance_to(&listener_position);
170 let attenuation = self.calculate_distance_attenuation(distance);
171 left_channel *= attenuation;
172 right_channel *= attenuation;
173 }
174 SpatialEffect::Reverb => {
175 self.apply_reverb(&mut left_channel, &mut right_channel, &source_position)
176 .await?;
177 }
178 SpatialEffect::Doppler => {
179 self.apply_doppler_effect(
180 &mut left_channel,
181 &mut right_channel,
182 &source_position,
183 )
184 .await?;
185 }
186 SpatialEffect::AirAbsorption => {
187 let distance = source_position.distance_to(&listener_position);
188 self.apply_air_absorption(&mut left_channel, &mut right_channel, distance);
189 }
190 }
191 }
192
193 let processing_time = start_time.elapsed();
195 self.processing_state.processed_frames += 1;
196 self.processing_state.total_processing_time += processing_time;
197
198 let binaural_audio =
199 BinauraAudio::new(left_channel.to_vec(), right_channel.to_vec(), sample_rate);
200
201 Ok(SpatialResult::success(
202 request_id,
203 binaural_audio,
204 processing_time,
205 effects,
206 ))
207 }
208
209 pub async fn add_sound_source(&self, id: String, source: SoundSource) {
211 let mut sources = self.sound_sources.write().await;
212 sources.insert(id, source);
213 }
214
215 pub async fn remove_sound_source(&self, id: &str) {
217 let mut sources = self.sound_sources.write().await;
218 sources.remove(id);
219 }
220
221 pub async fn update_listener(&self, position: Position3D, orientation: (f32, f32, f32)) {
223 let mut listener = self.listener.write().await;
224 listener.set_position(position);
225 listener.set_orientation(orientation);
226 }
227
228 pub fn get_stats(&self) -> ProcessingStats {
230 ProcessingStats {
231 processed_frames: self.processing_state.processed_frames,
232 total_processing_time: self.processing_state.total_processing_time,
233 average_processing_time: if self.processing_state.processed_frames > 0 {
234 self.processing_state.total_processing_time
235 / self.processing_state.processed_frames as u32
236 } else {
237 Duration::ZERO
238 },
239 }
240 }
241
242 fn calculate_relative_position(
244 &self,
245 source_pos: &Position3D,
246 listener_pos: &Position3D,
247 listener_orientation: &(f32, f32, f32),
248 ) -> Position3D {
249 let mut relative = Position3D::new(
251 source_pos.x - listener_pos.x,
252 source_pos.y - listener_pos.y,
253 source_pos.z - listener_pos.z,
254 );
255
256 let (yaw, _pitch, _roll) = *listener_orientation;
258
259 let cos_yaw = yaw.cos();
261 let sin_yaw = yaw.sin();
262
263 let rotated_x = relative.x * cos_yaw + relative.z * sin_yaw;
264 let rotated_z = -relative.x * sin_yaw + relative.z * cos_yaw;
265
266 relative.x = rotated_x;
267 relative.z = rotated_z;
268
269 relative
270 }
271
272 async fn apply_hrtf(
274 &self,
275 input: &Array1<f32>,
276 left_output: &mut Array1<f32>,
277 right_output: &mut Array1<f32>,
278 position: &Position3D,
279 ) -> crate::Result<()> {
280 let hrtf_processor = self.hrtf_processor.read().await;
281 hrtf_processor
282 .process_position(input, left_output, right_output, position)
283 .await
284 }
285
286 async fn apply_reverb(
288 &self,
289 left_channel: &mut Array1<f32>,
290 right_channel: &mut Array1<f32>,
291 source_position: &Position3D,
292 ) -> crate::Result<()> {
293 let room_simulator = self.room_simulator.read().await;
294 room_simulator
295 .process_reverb(left_channel, right_channel, source_position)
296 .await
297 }
298
299 async fn apply_doppler_effect(
301 &self,
302 left_channel: &mut Array1<f32>,
303 right_channel: &mut Array1<f32>,
304 _source_position: &Position3D,
305 ) -> crate::Result<()> {
306 let doppler_factor = 1.0; *left_channel *= doppler_factor;
311 *right_channel *= doppler_factor;
312
313 Ok(())
314 }
315
316 fn calculate_distance_attenuation(&self, distance: f32) -> f32 {
318 if !self.config.enable_distance_attenuation {
319 return 1.0;
320 }
321
322 if distance <= 1.0 {
323 return 1.0;
324 }
325
326 if distance >= self.config.max_distance {
327 return 0.0;
328 }
329
330 1.0 / distance.max(1.0)
332 }
333
334 fn apply_air_absorption(
336 &self,
337 left_channel: &mut Array1<f32>,
338 right_channel: &mut Array1<f32>,
339 distance: f32,
340 ) {
341 if !self.config.enable_air_absorption {
342 return;
343 }
344
345 let absorption_factor = (-0.01 * distance).exp();
348
349 *left_channel *= absorption_factor;
350 *right_channel *= absorption_factor;
351 }
352
353 pub fn memory_manager(&self) -> &Arc<MemoryManager> {
355 &self.memory_manager
356 }
357
358 pub async fn memory_stats(&self) -> crate::memory::MemoryStatistics {
360 self.memory_manager.get_memory_stats().await
361 }
362
363 pub async fn optimize_memory(&self) -> bool {
365 self.memory_manager.check_memory_pressure().await
366 }
367}
368
369impl SpatialProcessorBuilder {
370 pub fn new() -> Self {
372 Self {
373 config: None,
374 hrtf_database_path: None,
375 memory_config: None,
376 }
377 }
378
379 pub fn config(mut self, config: SpatialConfig) -> Self {
381 self.config = Some(config);
382 self
383 }
384
385 pub fn hrtf_database_path(mut self, path: std::path::PathBuf) -> Self {
387 self.hrtf_database_path = Some(path);
388 self
389 }
390
391 pub fn memory_config(mut self, config: MemoryConfig) -> Self {
393 self.memory_config = Some(config);
394 self
395 }
396
397 pub async fn build(self) -> crate::Result<SpatialProcessor> {
399 let mut config = self.config.unwrap_or_default();
400
401 if let Some(path) = self.hrtf_database_path {
402 config.hrtf_database_path = Some(path);
403 }
404
405 let memory_config = self.memory_config.unwrap_or_default();
406 SpatialProcessor::with_memory_config(config, memory_config).await
407 }
408}
409
410impl Default for SpatialProcessorBuilder {
411 fn default() -> Self {
412 Self::new()
413 }
414}
415
416#[derive(Debug, Clone)]
418pub struct ProcessingStats {
419 pub processed_frames: u64,
421 pub total_processing_time: Duration,
423 pub average_processing_time: Duration,
425}
426
427impl DopplerProcessor {
428 #[allow(dead_code)]
430 fn new(speed_of_sound: f32, sample_rate: f32) -> Self {
431 Self {
432 previous_positions: HashMap::new(),
433 speed_of_sound,
434 sample_rate,
435 }
436 }
437
438 #[allow(dead_code)]
440 fn calculate_doppler_factor(
441 &mut self,
442 source_id: &str,
443 current_position: Position3D,
444 listener_position: Position3D,
445 ) -> f32 {
446 let now = Instant::now();
447
448 if let Some((prev_pos, prev_time)) = self.previous_positions.get(source_id) {
449 let time_diff = now.duration_since(*prev_time).as_secs_f32();
450 if time_diff > 0.0 {
451 let prev_distance = prev_pos.distance_to(&listener_position);
453 let curr_distance = current_position.distance_to(&listener_position);
454 let radial_velocity = (curr_distance - prev_distance) / time_diff;
455
456 let factor = self.speed_of_sound / (self.speed_of_sound + radial_velocity);
458
459 self.previous_positions
460 .insert(source_id.to_string(), (current_position, now));
461 return factor.clamp(0.5, 2.0); }
463 }
464
465 self.previous_positions
466 .insert(source_id.to_string(), (current_position, now));
467 1.0
468 }
469}
470
471#[cfg(test)]
472mod tests {
473 use super::*;
474
475 #[tokio::test]
476 async fn test_spatial_processor_creation() {
477 let config = SpatialConfig::default();
478 let result = SpatialProcessor::new(config).await;
479 assert!(result.is_ok());
480 }
481
482 #[tokio::test]
483 async fn test_spatial_processor_builder() {
484 let processor = SpatialProcessorBuilder::new()
485 .config(SpatialConfig::default())
486 .build()
487 .await;
488 assert!(processor.is_ok());
489 }
490
491 #[tokio::test]
492 async fn test_distance_attenuation() {
493 let config = SpatialConfig::default();
494 let processor = SpatialProcessor {
495 config: config.clone(),
496 hrtf_processor: Arc::new(RwLock::new(HrtfProcessor::new(None).await.unwrap())),
497 room_simulator: Arc::new(RwLock::new(
498 RoomSimulator::new((10.0, 8.0, 3.0), 1.2).unwrap(),
499 )),
500 sound_sources: Arc::new(RwLock::new(HashMap::new())),
501 listener: Arc::new(RwLock::new(Listener::default())),
502 processing_state: ProcessingState {
503 input_buffer: Array1::zeros(1024),
504 output_buffer: Array2::zeros((2, 1024)),
505 processed_frames: 0,
506 total_processing_time: Duration::ZERO,
507 },
508 memory_manager: Arc::new(MemoryManager::new(MemoryConfig::default())),
509 };
510
511 assert_eq!(processor.calculate_distance_attenuation(1.0), 1.0);
513 assert!(processor.calculate_distance_attenuation(2.0) < 1.0);
514 assert!(
515 processor.calculate_distance_attenuation(10.0)
516 < processor.calculate_distance_attenuation(5.0)
517 );
518 }
519
520 #[tokio::test]
521 async fn test_relative_position_calculation() {
522 let config = SpatialConfig::default();
523 let processor = SpatialProcessor {
524 config,
525 hrtf_processor: Arc::new(RwLock::new(HrtfProcessor::new(None).await.unwrap())),
526 room_simulator: Arc::new(RwLock::new(
527 RoomSimulator::new((10.0, 8.0, 3.0), 1.2).unwrap(),
528 )),
529 sound_sources: Arc::new(RwLock::new(HashMap::new())),
530 listener: Arc::new(RwLock::new(Listener::default())),
531 processing_state: ProcessingState {
532 input_buffer: Array1::zeros(1024),
533 output_buffer: Array2::zeros((2, 1024)),
534 processed_frames: 0,
535 total_processing_time: Duration::ZERO,
536 },
537 memory_manager: Arc::new(MemoryManager::new(MemoryConfig::default())),
538 };
539
540 let source_pos = Position3D::new(5.0, 0.0, 0.0);
541 let listener_pos = Position3D::new(0.0, 0.0, 0.0);
542 let listener_orientation = (0.0, 0.0, 0.0);
543
544 let relative_pos = processor.calculate_relative_position(
545 &source_pos,
546 &listener_pos,
547 &listener_orientation,
548 );
549
550 assert_eq!(relative_pos.x, 5.0);
551 assert_eq!(relative_pos.y, 0.0);
552 assert_eq!(relative_pos.z, 0.0);
553 }
554}