Skip to main content

voirs_spatial/
core.rs

1//! Core spatial audio processing functionality
2
3use 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
15/// Main spatial audio processor
16pub struct SpatialProcessor {
17    /// Configuration
18    config: SpatialConfig,
19    /// HRTF processor
20    hrtf_processor: Arc<RwLock<HrtfProcessor>>,
21    /// Room simulator
22    room_simulator: Arc<RwLock<RoomSimulator>>,
23    /// Active sound sources
24    sound_sources: Arc<RwLock<HashMap<String, SoundSource>>>,
25    /// Listener
26    listener: Arc<RwLock<Listener>>,
27    /// Processing state
28    processing_state: ProcessingState,
29    /// Memory manager for optimization
30    memory_manager: Arc<MemoryManager>,
31}
32
33/// Builder for SpatialProcessor
34pub struct SpatialProcessorBuilder {
35    config: Option<SpatialConfig>,
36    hrtf_database_path: Option<std::path::PathBuf>,
37    memory_config: Option<MemoryConfig>,
38}
39
40/// Internal processing state
41#[derive(Debug)]
42struct ProcessingState {
43    /// Sample buffers
44    #[allow(dead_code)]
45    input_buffer: Array1<f32>,
46    #[allow(dead_code)]
47    output_buffer: Array2<f32>,
48    /// Processing statistics
49    processed_frames: u64,
50    total_processing_time: Duration,
51}
52
53/// Distance attenuation model
54#[derive(Debug, Clone, Copy)]
55pub enum AttenuationModel {
56    /// Linear attenuation
57    Linear,
58    /// Inverse distance law
59    InverseDistance,
60    /// Inverse square law
61    InverseSquare,
62    /// Custom exponential model
63    Exponential(f32),
64}
65
66/// Doppler effect processor
67#[allow(dead_code)]
68struct DopplerProcessor {
69    /// Previous positions for velocity calculation
70    previous_positions: HashMap<String, (Position3D, Instant)>,
71    /// Speed of sound
72    speed_of_sound: f32,
73    /// Sample rate
74    sample_rate: f32,
75}
76
77impl SpatialProcessor {
78    /// Create new spatial processor with configuration
79    pub async fn new(config: SpatialConfig) -> crate::Result<Self> {
80        Self::with_memory_config(config, MemoryConfig::default()).await
81    }
82
83    /// Create new spatial processor with spatial and memory configurations
84    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        // Initialize memory manager with provided configuration
107        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    /// Process spatial audio request
121    pub async fn process_request(
122        &mut self,
123        request: SpatialRequest,
124    ) -> crate::Result<SpatialResult> {
125        let start_time = Instant::now();
126
127        // Validate request
128        request.validate()?;
129
130        // Store needed values before moving request.audio
131        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        // Convert input audio to Array1
137        let input_audio = Array1::from_vec(request.audio);
138
139        // Initialize output channels
140        let mut left_channel = Array1::zeros(input_audio.len());
141        let mut right_channel = Array1::zeros(input_audio.len());
142
143        // Get listener information
144        let listener = self.listener.read().await;
145        let listener_position = listener.position();
146        let listener_orientation = listener.orientation();
147        drop(listener);
148
149        // Calculate relative position
150        let relative_position = self.calculate_relative_position(
151            &source_position,
152            &listener_position,
153            &listener_orientation,
154        );
155
156        // Apply requested effects
157        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        // Create result
194        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    /// Add sound source
210    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    /// Remove sound source
216    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    /// Update listener position and orientation
222    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    /// Get processing statistics
229    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    /// Calculate relative position from listener perspective
243    fn calculate_relative_position(
244        &self,
245        source_pos: &Position3D,
246        listener_pos: &Position3D,
247        listener_orientation: &(f32, f32, f32),
248    ) -> Position3D {
249        // Calculate position relative to listener
250        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        // Apply listener orientation rotation
257        let (yaw, _pitch, _roll) = *listener_orientation;
258
259        // Simplified rotation (yaw only for now)
260        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    /// Apply HRTF processing
273    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    /// Apply reverb effect
287    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    /// Apply Doppler effect
300    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        // Simplified Doppler effect implementation
307        // In a real implementation, this would require velocity tracking
308        let doppler_factor = 1.0; // Placeholder
309
310        *left_channel *= doppler_factor;
311        *right_channel *= doppler_factor;
312
313        Ok(())
314    }
315
316    /// Calculate distance attenuation
317    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        // Inverse distance law with minimum distance
331        1.0 / distance.max(1.0)
332    }
333
334    /// Apply air absorption
335    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        // Simplified air absorption model
346        // Higher frequencies are absorbed more
347        let absorption_factor = (-0.01 * distance).exp();
348
349        *left_channel *= absorption_factor;
350        *right_channel *= absorption_factor;
351    }
352
353    /// Get memory manager for advanced memory operations
354    pub fn memory_manager(&self) -> &Arc<MemoryManager> {
355        &self.memory_manager
356    }
357
358    /// Get memory usage statistics
359    pub async fn memory_stats(&self) -> crate::memory::MemoryStatistics {
360        self.memory_manager.get_memory_stats().await
361    }
362
363    /// Check for memory pressure and clean up if needed
364    pub async fn optimize_memory(&self) -> bool {
365        self.memory_manager.check_memory_pressure().await
366    }
367}
368
369impl SpatialProcessorBuilder {
370    /// Create new builder
371    pub fn new() -> Self {
372        Self {
373            config: None,
374            hrtf_database_path: None,
375            memory_config: None,
376        }
377    }
378
379    /// Set configuration
380    pub fn config(mut self, config: SpatialConfig) -> Self {
381        self.config = Some(config);
382        self
383    }
384
385    /// Set HRTF database path
386    pub fn hrtf_database_path(mut self, path: std::path::PathBuf) -> Self {
387        self.hrtf_database_path = Some(path);
388        self
389    }
390
391    /// Set memory configuration
392    pub fn memory_config(mut self, config: MemoryConfig) -> Self {
393        self.memory_config = Some(config);
394        self
395    }
396
397    /// Build the spatial processor
398    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/// Processing statistics
417#[derive(Debug, Clone)]
418pub struct ProcessingStats {
419    /// Total processed frames
420    pub processed_frames: u64,
421    /// Total processing time
422    pub total_processing_time: Duration,
423    /// Average processing time per frame
424    pub average_processing_time: Duration,
425}
426
427impl DopplerProcessor {
428    /// Create new Doppler processor
429    #[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    /// Calculate Doppler factor for a moving source
439    #[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                // Calculate velocity towards listener
452                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                // Apply Doppler formula
457                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); // Limit extreme values
462            }
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        // Test distance attenuation
512        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}