Skip to main content

voirs_spatial/
room.rs

1//! Room acoustics simulation and reverberation processing
2
3pub mod adaptive_acoustics;
4pub mod simulation;
5
6use crate::types::Position3D;
7use scirs2_core::ndarray::{Array1, Array2};
8use serde::{Deserialize, Serialize};
9pub use simulation::{
10    AcousticResponse, AdvancedRoomSimulator, DiffractionProcessor, DynamicEnvironmentManager,
11    FrequencyDependentProperty, Material as SimMaterial, MaterialDatabase, RayTracingEngine,
12    RoomGeometry, RoomSimulationConfig,
13};
14use std::collections::{HashMap, VecDeque};
15
16/// Room acoustics simulator
17#[derive(Debug, Clone)]
18pub struct RoomSimulator {
19    /// Room configuration
20    config: RoomConfig,
21    /// Reverberation processor
22    reverb_processor: ReverbProcessor,
23    /// Early reflection processor
24    early_reflections: EarlyReflectionProcessor,
25    /// Late reverberation processor
26    late_reverb: LateReverbProcessor,
27    /// Room impulse response
28    room_ir: Option<RoomImpulseResponse>,
29}
30
31/// Room acoustics configuration
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct RoomConfig {
34    /// Room dimensions (width, height, depth) in meters
35    pub dimensions: (f32, f32, f32),
36    /// Wall materials and absorption coefficients
37    pub wall_materials: WallMaterials,
38    /// Reverberation time (RT60) in seconds
39    pub reverb_time: f32,
40    /// Room volume in cubic meters
41    pub volume: f32,
42    /// Surface area in square meters
43    pub surface_area: f32,
44    /// Temperature in Celsius
45    pub temperature: f32,
46    /// Humidity percentage
47    pub humidity: f32,
48    /// Air absorption enabled
49    pub enable_air_absorption: bool,
50}
51
52/// Wall materials and their acoustic properties
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct WallMaterials {
55    /// Floor material
56    pub floor: Material,
57    /// Ceiling material
58    pub ceiling: Material,
59    /// Left wall material
60    pub left_wall: Material,
61    /// Right wall material
62    pub right_wall: Material,
63    /// Front wall material
64    pub front_wall: Material,
65    /// Back wall material
66    pub back_wall: Material,
67}
68
69/// Acoustic material properties
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct Material {
72    /// Material name
73    pub name: String,
74    /// Absorption coefficients by frequency band
75    pub absorption_coefficients: Vec<FrequencyBandAbsorption>,
76    /// Scattering coefficient (0.0 = pure reflection, 1.0 = pure diffusion)
77    pub scattering_coefficient: f32,
78    /// Transmission coefficient
79    pub transmission_coefficient: f32,
80}
81
82/// Frequency band absorption
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct FrequencyBandAbsorption {
85    /// Center frequency in Hz
86    pub frequency: f32,
87    /// Absorption coefficient (0.0 = no absorption, 1.0 = full absorption)
88    pub coefficient: f32,
89}
90
91/// Room impulse response
92#[derive(Debug, Clone)]
93pub struct RoomImpulseResponse {
94    /// Early reflections
95    pub early_reflections: Array1<f32>,
96    /// Late reverberation
97    pub late_reverb: Array1<f32>,
98    /// Combined impulse response
99    pub combined_ir: Array1<f32>,
100    /// Sample rate
101    pub sample_rate: u32,
102}
103
104/// Early reflection processor
105#[derive(Debug, Clone)]
106pub struct EarlyReflectionProcessor {
107    /// Reflection paths
108    #[allow(dead_code)]
109    reflection_paths: Vec<ReflectionPath>,
110    /// Maximum reflection order
111    #[allow(dead_code)]
112    max_order: usize,
113    /// Speed of sound
114    #[allow(dead_code)]
115    speed_of_sound: f32,
116    /// Sample rate
117    sample_rate: f32,
118}
119
120/// Late reverberation processor
121#[derive(Debug, Clone)]
122pub struct LateReverbProcessor {
123    /// Feedback delay networks
124    #[allow(dead_code)]
125    feedback_networks: Vec<FeedbackDelayNetwork>,
126    /// Diffusion all-pass filters
127    #[allow(dead_code)]
128    diffusion_filters: Vec<AllPassFilter>,
129    /// Reverb time
130    reverb_time: f32,
131    /// Diffusion amount
132    #[allow(dead_code)]
133    diffusion: f32,
134}
135
136/// Combined reverberation processor
137#[derive(Debug, Clone)]
138pub struct ReverbProcessor {
139    /// Early reflections
140    #[allow(dead_code)]
141    early_processor: EarlyReflectionProcessor,
142    /// Late reverb
143    #[allow(dead_code)]
144    late_processor: LateReverbProcessor,
145    /// Crossover frequency between early and late
146    #[allow(dead_code)]
147    crossover_frequency: f32,
148    /// Mix levels
149    #[allow(dead_code)]
150    dry_level: f32,
151    #[allow(dead_code)]
152    early_level: f32,
153    #[allow(dead_code)]
154    late_level: f32,
155}
156
157/// Reflection path in the room
158#[derive(Debug, Clone)]
159pub struct ReflectionPath {
160    /// Path from source to reflection point to listener
161    pub path: Vec<Position3D>,
162    /// Total path length
163    pub length: f32,
164    /// Delay in samples
165    pub delay_samples: usize,
166    /// Attenuation factor
167    pub attenuation: f32,
168    /// Reflection surfaces
169    pub surfaces: Vec<SurfaceReflection>,
170}
171
172/// Surface reflection information
173#[derive(Debug, Clone)]
174pub struct SurfaceReflection {
175    /// Surface position
176    pub position: Position3D,
177    /// Surface normal
178    pub normal: Position3D,
179    /// Material properties
180    pub material: Material,
181    /// Incident angle
182    pub incident_angle: f32,
183}
184
185/// Feedback delay network for late reverberation
186#[derive(Debug, Clone)]
187pub struct FeedbackDelayNetwork {
188    /// Delay lines
189    #[allow(dead_code)]
190    delay_lines: Vec<DelayLine>,
191    /// Feedback matrix
192    #[allow(dead_code)]
193    feedback_matrix: Array2<f32>,
194    /// Input gains
195    #[allow(dead_code)]
196    input_gains: Array1<f32>,
197    /// Output gains
198    #[allow(dead_code)]
199    output_gains: Array1<f32>,
200}
201
202/// All-pass filter for diffusion
203#[derive(Debug, Clone)]
204pub struct AllPassFilter {
205    /// Delay line
206    #[allow(dead_code)]
207    delay_line: DelayLine,
208    /// Feedback coefficient
209    #[allow(dead_code)]
210    feedback: f32,
211    /// Feed-forward coefficient
212    #[allow(dead_code)]
213    feedforward: f32,
214}
215
216/// Delay line with interpolation
217#[derive(Debug, Clone)]
218pub struct DelayLine {
219    /// Buffer
220    buffer: VecDeque<f32>,
221    /// Delay in samples
222    delay_samples: f32,
223    /// Maximum delay
224    max_delay: usize,
225}
226
227/// Wall structure for ray tracing
228#[derive(Debug, Clone)]
229pub struct Wall {
230    /// Wall normal vector
231    pub normal: Position3D,
232    /// Point on the wall surface
233    pub point: Position3D,
234    /// Wall material properties
235    pub material: Material,
236}
237
238impl RoomSimulator {
239    /// Create new room simulator
240    pub fn new(dimensions: (f32, f32, f32), reverb_time: f32) -> crate::Result<Self> {
241        let config = RoomConfig::new(dimensions, reverb_time);
242        let reverb_processor = ReverbProcessor::new(&config)?;
243        let early_reflections = EarlyReflectionProcessor::new(&config)?;
244        let late_reverb = LateReverbProcessor::new(&config)?;
245
246        Ok(Self {
247            config,
248            reverb_processor,
249            early_reflections,
250            late_reverb,
251            room_ir: None,
252        })
253    }
254
255    /// Create room simulator with custom configuration
256    pub fn with_config(config: RoomConfig) -> crate::Result<Self> {
257        let reverb_processor = ReverbProcessor::new(&config)?;
258        let early_reflections = EarlyReflectionProcessor::new(&config)?;
259        let late_reverb = LateReverbProcessor::new(&config)?;
260
261        Ok(Self {
262            config,
263            reverb_processor,
264            early_reflections,
265            late_reverb,
266            room_ir: None,
267        })
268    }
269
270    /// Process audio with room reverb
271    pub async fn process_reverb(
272        &self,
273        left_channel: &mut Array1<f32>,
274        right_channel: &mut Array1<f32>,
275        source_position: &Position3D,
276    ) -> crate::Result<()> {
277        // Apply early reflections
278        self.early_reflections
279            .process(left_channel, right_channel, source_position)
280            .await?;
281
282        // Apply late reverberation
283        self.late_reverb
284            .process(left_channel, right_channel)
285            .await?;
286
287        Ok(())
288    }
289
290    /// Calculate room impulse response for a specific source-listener pair
291    pub fn calculate_room_ir(
292        &mut self,
293        source_position: Position3D,
294        listener_position: Position3D,
295        sample_rate: u32,
296    ) -> crate::Result<RoomImpulseResponse> {
297        let ir_length = (self.config.reverb_time * sample_rate as f32) as usize;
298        let mut early_reflections = Array1::zeros(ir_length);
299        let mut late_reverb = Array1::zeros(ir_length);
300
301        // Calculate early reflections using image source method
302        let reflection_paths =
303            self.calculate_reflection_paths(source_position, listener_position, 3)?;
304
305        for path in &reflection_paths {
306            if path.delay_samples < early_reflections.len() {
307                early_reflections[path.delay_samples] += path.attenuation;
308            }
309        }
310
311        // Generate late reverberation using statistical model
312        self.generate_late_reverb(&mut late_reverb, sample_rate)?;
313
314        // Combine early and late
315        let mut combined_ir = Array1::zeros(ir_length);
316        for i in 0..ir_length {
317            combined_ir[i] = early_reflections[i] + late_reverb[i];
318        }
319
320        let room_ir = RoomImpulseResponse {
321            early_reflections,
322            late_reverb,
323            combined_ir,
324            sample_rate,
325        };
326
327        self.room_ir = Some(room_ir.clone());
328        Ok(room_ir)
329    }
330
331    /// Calculate reflection paths using enhanced ray tracing method
332    fn calculate_reflection_paths(
333        &self,
334        source: Position3D,
335        listener: Position3D,
336        max_order: usize,
337    ) -> crate::Result<Vec<ReflectionPath>> {
338        let mut paths = Vec::new();
339        let (width, height, depth) = self.config.dimensions;
340
341        // Direct path
342        let direct_path = ReflectionPath {
343            path: vec![source, listener],
344            length: source.distance_to(&listener),
345            delay_samples: (source.distance_to(&listener) / 343.0 * 44100.0) as usize,
346            attenuation: 1.0 / (1.0 + source.distance_to(&listener)),
347            surfaces: Vec::new(),
348        };
349        paths.push(direct_path);
350
351        // Enhanced ray tracing with multiple orders
352        self.calculate_ray_traced_paths(&mut paths, source, listener, max_order)?;
353
354        Ok(paths)
355    }
356
357    /// Enhanced ray tracing algorithm for multiple reflection orders
358    fn calculate_ray_traced_paths(
359        &self,
360        paths: &mut Vec<ReflectionPath>,
361        source: Position3D,
362        listener: Position3D,
363        max_order: usize,
364    ) -> crate::Result<()> {
365        let (width, height, depth) = self.config.dimensions;
366
367        // First, add deterministic first-order reflections for compatibility
368        if max_order >= 1 {
369            self.add_wall_reflections(paths, source, listener, width, height, depth);
370        }
371
372        // Then add enhanced ray tracing for higher order reflections
373        if max_order >= 2 {
374            self.add_ray_traced_reflections(paths, source, listener, max_order)?;
375        }
376
377        Ok(())
378    }
379
380    /// Add enhanced ray-traced reflections for higher order paths
381    fn add_ray_traced_reflections(
382        &self,
383        paths: &mut Vec<ReflectionPath>,
384        source: Position3D,
385        listener: Position3D,
386        max_order: usize,
387    ) -> crate::Result<()> {
388        let walls = self.get_room_walls(
389            self.config.dimensions.0,
390            self.config.dimensions.1,
391            self.config.dimensions.2,
392        );
393
394        // Use deterministic ray directions based on listener position
395        let to_listener = Position3D::new(
396            listener.x - source.x,
397            listener.y - source.y,
398            listener.z - source.z,
399        )
400        .normalized();
401
402        // Generate rays in directions likely to reach listener after reflections
403        let base_directions = vec![
404            to_listener,
405            Position3D::new(to_listener.x, -to_listener.y, to_listener.z).normalized(),
406            Position3D::new(-to_listener.x, to_listener.y, to_listener.z).normalized(),
407            Position3D::new(to_listener.x, to_listener.y, -to_listener.z).normalized(),
408        ];
409
410        for base_direction in base_directions {
411            let mut current_pos = source;
412            let mut ray_direction = base_direction;
413            let mut current_path = vec![source];
414            let mut total_attenuation = 1.0;
415            let mut total_length = 0.0;
416            let mut surface_reflections = Vec::new();
417
418            for order in 1..=max_order {
419                if let Some((intersection_point, wall_normal, material)) =
420                    self.find_nearest_wall_intersection(current_pos, ray_direction, &walls)?
421                {
422                    let distance = current_pos.distance_to(&intersection_point);
423                    total_length += distance;
424                    current_path.push(intersection_point);
425
426                    // Apply material-dependent attenuation
427                    let frequency_attenuation = self.calculate_frequency_attenuation(&material);
428                    total_attenuation *= frequency_attenuation;
429
430                    // Record surface reflection
431                    surface_reflections.push(SurfaceReflection {
432                        position: intersection_point,
433                        normal: wall_normal,
434                        material: material.clone(),
435                        incident_angle: self.calculate_incident_angle(ray_direction, wall_normal),
436                    });
437
438                    // Calculate reflected ray direction (perfect specular reflection)
439                    let dot = ray_direction.dot(&wall_normal);
440                    ray_direction = Position3D::new(
441                        ray_direction.x - 2.0 * dot * wall_normal.x,
442                        ray_direction.y - 2.0 * dot * wall_normal.y,
443                        ray_direction.z - 2.0 * dot * wall_normal.z,
444                    )
445                    .normalized();
446
447                    current_pos = intersection_point;
448
449                    // For higher order reflections, check if we can reach listener
450                    if order >= 2 {
451                        let listener_distance = intersection_point.distance_to(&listener);
452                        if listener_distance < 2.0 && total_attenuation > 0.01 {
453                            current_path.push(listener);
454                            total_length += listener_distance;
455
456                            let reflection_path = ReflectionPath {
457                                path: current_path.clone(),
458                                length: total_length,
459                                delay_samples: (total_length / 343.0 * 44100.0) as usize,
460                                attenuation: total_attenuation / (1.0 + total_length),
461                                surfaces: surface_reflections.clone(),
462                            };
463
464                            paths.push(reflection_path);
465                            break;
466                        }
467                    }
468                } else {
469                    break; // No intersection found
470                }
471            }
472        }
473
474        Ok(())
475    }
476
477    /// Generate random ray direction in 3D space
478    fn random_ray_direction(
479        &self,
480        rng: &mut scirs2_core::random::prelude::ThreadRng,
481    ) -> Position3D {
482        use scirs2_core::random::Rng;
483
484        let theta = rng.random_range(0.0..std::f32::consts::PI * 2.0);
485        let phi = rng.random_range(0.0..std::f32::consts::PI);
486
487        Position3D::new(phi.sin() * theta.cos(), phi.sin() * theta.sin(), phi.cos())
488    }
489
490    /// Get room walls as geometric surfaces
491    fn get_room_walls(&self, width: f32, height: f32, depth: f32) -> Vec<Wall> {
492        vec![
493            Wall {
494                normal: Position3D::new(-1.0, 0.0, 0.0),
495                point: Position3D::new(0.0, 0.0, 0.0),
496                material: self.config.wall_materials.left_wall.clone(),
497            },
498            Wall {
499                normal: Position3D::new(1.0, 0.0, 0.0),
500                point: Position3D::new(width, 0.0, 0.0),
501                material: self.config.wall_materials.right_wall.clone(),
502            },
503            Wall {
504                normal: Position3D::new(0.0, -1.0, 0.0),
505                point: Position3D::new(0.0, 0.0, 0.0),
506                material: self.config.wall_materials.floor.clone(),
507            },
508            Wall {
509                normal: Position3D::new(0.0, 1.0, 0.0),
510                point: Position3D::new(0.0, height, 0.0),
511                material: self.config.wall_materials.ceiling.clone(),
512            },
513            Wall {
514                normal: Position3D::new(0.0, 0.0, -1.0),
515                point: Position3D::new(0.0, 0.0, 0.0),
516                material: self.config.wall_materials.front_wall.clone(),
517            },
518            Wall {
519                normal: Position3D::new(0.0, 0.0, 1.0),
520                point: Position3D::new(0.0, 0.0, depth),
521                material: self.config.wall_materials.back_wall.clone(),
522            },
523        ]
524    }
525
526    /// Find nearest wall intersection with ray
527    fn find_nearest_wall_intersection(
528        &self,
529        ray_origin: Position3D,
530        ray_direction: Position3D,
531        walls: &[Wall],
532    ) -> crate::Result<Option<(Position3D, Position3D, Material)>> {
533        let mut nearest_intersection = None;
534        let mut nearest_distance = f32::INFINITY;
535
536        for wall in walls {
537            if let Some((intersection_point, distance)) =
538                self.ray_plane_intersection(ray_origin, ray_direction, &wall.point, &wall.normal)?
539            {
540                if distance > 0.001 && distance < nearest_distance {
541                    // Check if intersection is within room bounds
542                    if self.is_within_room_bounds(intersection_point) {
543                        nearest_distance = distance;
544                        nearest_intersection =
545                            Some((intersection_point, wall.normal, wall.material.clone()));
546                    }
547                }
548            }
549        }
550
551        Ok(nearest_intersection)
552    }
553
554    /// Calculate ray-plane intersection
555    fn ray_plane_intersection(
556        &self,
557        ray_origin: Position3D,
558        ray_direction: Position3D,
559        plane_point: &Position3D,
560        plane_normal: &Position3D,
561    ) -> crate::Result<Option<(Position3D, f32)>> {
562        let denominator = ray_direction.dot(plane_normal);
563
564        if denominator.abs() < 1e-6 {
565            return Ok(None); // Ray parallel to plane
566        }
567
568        let diff = Position3D::new(
569            plane_point.x - ray_origin.x,
570            plane_point.y - ray_origin.y,
571            plane_point.z - ray_origin.z,
572        );
573
574        let t = diff.dot(plane_normal) / denominator;
575
576        if t >= 0.0 {
577            let intersection = Position3D::new(
578                ray_origin.x + t * ray_direction.x,
579                ray_origin.y + t * ray_direction.y,
580                ray_origin.z + t * ray_direction.z,
581            );
582            Ok(Some((intersection, t)))
583        } else {
584            Ok(None)
585        }
586    }
587
588    /// Check if point is within room bounds
589    fn is_within_room_bounds(&self, point: Position3D) -> bool {
590        let (width, height, depth) = self.config.dimensions;
591        point.x >= 0.0
592            && point.x <= width
593            && point.y >= 0.0
594            && point.y <= height
595            && point.z >= 0.0
596            && point.z <= depth
597    }
598
599    /// Calculate frequency-dependent attenuation based on material
600    fn calculate_frequency_attenuation(&self, material: &Material) -> f32 {
601        // Simplified frequency attenuation - average across all bands
602        1.0 - material.average_absorption()
603    }
604
605    /// Calculate incident angle between ray and surface normal
606    fn calculate_incident_angle(&self, ray_direction: Position3D, normal: Position3D) -> f32 {
607        let dot_product = ray_direction.dot(&normal).abs();
608        dot_product.acos()
609    }
610
611    /// Calculate reflection direction with specular and diffuse components
612    fn calculate_reflection_direction(
613        &self,
614        incident: Position3D,
615        normal: Position3D,
616        scattering_coeff: f32,
617        rng: &mut scirs2_core::random::prelude::ThreadRng,
618    ) -> Position3D {
619        use scirs2_core::random::Rng;
620
621        // Specular reflection component
622        let dot = incident.dot(&normal);
623        let specular = Position3D::new(
624            incident.x - 2.0 * dot * normal.x,
625            incident.y - 2.0 * dot * normal.y,
626            incident.z - 2.0 * dot * normal.z,
627        );
628
629        // Diffuse reflection component (Lambert's cosine law)
630        let diffuse = self.random_ray_direction(rng);
631
632        // Mix specular and diffuse based on scattering coefficient
633        let mix_factor = 1.0 - scattering_coeff;
634        Position3D::new(
635            mix_factor * specular.x + scattering_coeff * diffuse.x,
636            mix_factor * specular.y + scattering_coeff * diffuse.y,
637            mix_factor * specular.z + scattering_coeff * diffuse.z,
638        )
639        .normalized()
640    }
641
642    /// Add first-order wall reflections
643    fn add_wall_reflections(
644        &self,
645        paths: &mut Vec<ReflectionPath>,
646        source: Position3D,
647        listener: Position3D,
648        width: f32,
649        height: f32,
650        depth: f32,
651    ) {
652        let walls = [
653            // Left wall (x = 0)
654            (
655                Position3D::new(0.0, source.y, source.z),
656                Position3D::new(-1.0, 0.0, 0.0),
657            ),
658            // Right wall (x = width)
659            (
660                Position3D::new(width, source.y, source.z),
661                Position3D::new(1.0, 0.0, 0.0),
662            ),
663            // Floor (y = 0)
664            (
665                Position3D::new(source.x, 0.0, source.z),
666                Position3D::new(0.0, -1.0, 0.0),
667            ),
668            // Ceiling (y = height)
669            (
670                Position3D::new(source.x, height, source.z),
671                Position3D::new(0.0, 1.0, 0.0),
672            ),
673            // Front wall (z = 0)
674            (
675                Position3D::new(source.x, source.y, 0.0),
676                Position3D::new(0.0, 0.0, -1.0),
677            ),
678            // Back wall (z = depth)
679            (
680                Position3D::new(source.x, source.y, depth),
681                Position3D::new(0.0, 0.0, 1.0),
682            ),
683        ];
684
685        for (reflection_point, normal) in walls {
686            // Calculate reflected path
687            let source_to_reflection = reflection_point.distance_to(&source);
688            let reflection_to_listener = reflection_point.distance_to(&listener);
689            let total_length = source_to_reflection + reflection_to_listener;
690
691            let path = ReflectionPath {
692                path: vec![source, reflection_point, listener],
693                length: total_length,
694                delay_samples: (total_length / 343.0 * 44100.0) as usize,
695                attenuation: 0.7 / (1.0 + total_length), // Simplified attenuation
696                surfaces: vec![SurfaceReflection {
697                    position: reflection_point,
698                    normal,
699                    material: Material::default(),
700                    incident_angle: 0.0, // Simplified
701                }],
702            };
703
704            paths.push(path);
705        }
706    }
707
708    /// Generate late reverberation using statistical model
709    fn generate_late_reverb(
710        &self,
711        buffer: &mut Array1<f32>,
712        sample_rate: u32,
713    ) -> crate::Result<()> {
714        let decay_rate = -60.0 / (self.config.reverb_time * sample_rate as f32); // dB per sample
715
716        for (i, sample) in buffer.iter_mut().enumerate() {
717            let time = i as f32 / sample_rate as f32;
718            let amplitude = (decay_rate * time / 20.0).exp(); // Convert dB to linear
719            *sample = amplitude * (scirs2_core::random::random::<f32>() - 0.5) * 2.0;
720            // Noise with decay
721        }
722
723        Ok(())
724    }
725
726    /// Get room configuration
727    pub fn config(&self) -> &RoomConfig {
728        &self.config
729    }
730
731    /// Set room configuration
732    pub fn set_config(&mut self, config: RoomConfig) -> crate::Result<()> {
733        self.config = config;
734        self.reverb_processor = ReverbProcessor::new(&self.config)?;
735        self.early_reflections = EarlyReflectionProcessor::new(&self.config)?;
736        self.late_reverb = LateReverbProcessor::new(&self.config)?;
737        Ok(())
738    }
739}
740
741/// Trait for room acoustics processing
742pub trait RoomAcoustics {
743    /// Process audio with room acoustics
744    fn process_acoustics(
745        &mut self,
746        input: &Array1<f32>,
747        output: &mut Array1<f32>,
748        source_position: Position3D,
749        listener_position: Position3D,
750    ) -> crate::Result<()>;
751
752    /// Calculate reverberation time
753    fn calculate_reverb_time(&self) -> f32;
754
755    /// Get room properties
756    fn room_properties(&self) -> RoomProperties;
757}
758
759/// Room acoustic properties
760#[derive(Debug, Clone)]
761pub struct RoomProperties {
762    /// Volume in cubic meters
763    pub volume: f32,
764    /// Surface area in square meters
765    pub surface_area: f32,
766    /// Average absorption coefficient
767    pub average_absorption: f32,
768    /// Critical distance
769    pub critical_distance: f32,
770    /// Reverberation time
771    pub reverb_time: f32,
772}
773
774impl RoomConfig {
775    /// Create new room configuration
776    pub fn new(dimensions: (f32, f32, f32), reverb_time: f32) -> Self {
777        let (width, height, depth) = dimensions;
778        let volume = width * height * depth;
779        let surface_area = 2.0 * (width * height + width * depth + height * depth);
780
781        Self {
782            dimensions,
783            wall_materials: WallMaterials::default(),
784            reverb_time,
785            volume,
786            surface_area,
787            temperature: 20.0,
788            humidity: 50.0,
789            enable_air_absorption: true,
790        }
791    }
792
793    /// Calculate average absorption coefficient
794    pub fn average_absorption(&self) -> f32 {
795        // Simplified calculation - would need frequency-dependent calculation in practice
796        let materials = [
797            &self.wall_materials.floor,
798            &self.wall_materials.ceiling,
799            &self.wall_materials.left_wall,
800            &self.wall_materials.right_wall,
801            &self.wall_materials.front_wall,
802            &self.wall_materials.back_wall,
803        ];
804
805        let total_absorption: f32 = materials
806            .iter()
807            .map(|material| material.average_absorption())
808            .sum();
809
810        total_absorption / materials.len() as f32
811    }
812}
813
814impl Material {
815    /// Get average absorption coefficient
816    pub fn average_absorption(&self) -> f32 {
817        if self.absorption_coefficients.is_empty() {
818            return 0.1; // Default
819        }
820
821        let sum: f32 = self
822            .absorption_coefficients
823            .iter()
824            .map(|band| band.coefficient)
825            .sum();
826
827        sum / self.absorption_coefficients.len() as f32
828    }
829
830    /// Create concrete material
831    pub fn concrete() -> Self {
832        Self {
833            name: "Concrete".to_string(),
834            absorption_coefficients: vec![
835                FrequencyBandAbsorption {
836                    frequency: 125.0,
837                    coefficient: 0.01,
838                },
839                FrequencyBandAbsorption {
840                    frequency: 250.0,
841                    coefficient: 0.01,
842                },
843                FrequencyBandAbsorption {
844                    frequency: 500.0,
845                    coefficient: 0.02,
846                },
847                FrequencyBandAbsorption {
848                    frequency: 1000.0,
849                    coefficient: 0.02,
850                },
851                FrequencyBandAbsorption {
852                    frequency: 2000.0,
853                    coefficient: 0.02,
854                },
855                FrequencyBandAbsorption {
856                    frequency: 4000.0,
857                    coefficient: 0.02,
858                },
859            ],
860            scattering_coefficient: 0.1,
861            transmission_coefficient: 0.01,
862        }
863    }
864
865    /// Create carpet material
866    pub fn carpet() -> Self {
867        Self {
868            name: "Carpet".to_string(),
869            absorption_coefficients: vec![
870                FrequencyBandAbsorption {
871                    frequency: 125.0,
872                    coefficient: 0.08,
873                },
874                FrequencyBandAbsorption {
875                    frequency: 250.0,
876                    coefficient: 0.24,
877                },
878                FrequencyBandAbsorption {
879                    frequency: 500.0,
880                    coefficient: 0.57,
881                },
882                FrequencyBandAbsorption {
883                    frequency: 1000.0,
884                    coefficient: 0.69,
885                },
886                FrequencyBandAbsorption {
887                    frequency: 2000.0,
888                    coefficient: 0.71,
889                },
890                FrequencyBandAbsorption {
891                    frequency: 4000.0,
892                    coefficient: 0.73,
893                },
894            ],
895            scattering_coefficient: 0.3,
896            transmission_coefficient: 0.05,
897        }
898    }
899}
900
901impl Default for Material {
902    fn default() -> Self {
903        Self::concrete()
904    }
905}
906
907impl Default for WallMaterials {
908    fn default() -> Self {
909        Self {
910            floor: Material::carpet(),
911            ceiling: Material::concrete(),
912            left_wall: Material::concrete(),
913            right_wall: Material::concrete(),
914            front_wall: Material::concrete(),
915            back_wall: Material::concrete(),
916        }
917    }
918}
919
920impl EarlyReflectionProcessor {
921    /// Create new early reflection processor
922    pub fn new(_config: &RoomConfig) -> crate::Result<Self> {
923        Ok(Self {
924            reflection_paths: Vec::new(),
925            max_order: 3,
926            speed_of_sound: 343.0,
927            sample_rate: 44100.0,
928        })
929    }
930
931    /// Process early reflections
932    pub async fn process(
933        &self,
934        left_channel: &mut Array1<f32>,
935        right_channel: &mut Array1<f32>,
936        _source_position: &Position3D,
937    ) -> crate::Result<()> {
938        // Simplified early reflection processing
939        // Apply a simple delay and attenuation
940        let delay_samples = (0.02 * self.sample_rate) as usize; // 20ms delay
941        let attenuation = 0.3;
942
943        if delay_samples < left_channel.len() {
944            for i in delay_samples..left_channel.len() {
945                left_channel[i] += left_channel[i - delay_samples] * attenuation;
946                right_channel[i] += right_channel[i - delay_samples] * attenuation;
947            }
948        }
949
950        Ok(())
951    }
952}
953
954impl LateReverbProcessor {
955    /// Create new late reverb processor
956    pub fn new(config: &RoomConfig) -> crate::Result<Self> {
957        let feedback_networks = vec![FeedbackDelayNetwork::new(&[0.03, 0.032, 0.034, 0.036])?];
958
959        let diffusion_filters = vec![
960            AllPassFilter::new(0.005, 0.7)?,
961            AllPassFilter::new(0.012, 0.5)?,
962        ];
963
964        Ok(Self {
965            feedback_networks,
966            diffusion_filters,
967            reverb_time: config.reverb_time,
968            diffusion: 0.7,
969        })
970    }
971
972    /// Process late reverberation
973    pub async fn process(
974        &self,
975        left_channel: &mut Array1<f32>,
976        right_channel: &mut Array1<f32>,
977    ) -> crate::Result<()> {
978        // Simplified late reverb processing
979        // Apply exponential decay
980        let decay_rate = (-60.0 / (self.reverb_time * 44100.0)).exp();
981
982        for i in 1..left_channel.len() {
983            left_channel[i] += left_channel[i - 1] * decay_rate * 0.1;
984            right_channel[i] += right_channel[i - 1] * decay_rate * 0.1;
985        }
986
987        Ok(())
988    }
989}
990
991impl ReverbProcessor {
992    /// Create new reverb processor
993    pub fn new(config: &RoomConfig) -> crate::Result<Self> {
994        Ok(Self {
995            early_processor: EarlyReflectionProcessor::new(config)?,
996            late_processor: LateReverbProcessor::new(config)?,
997            crossover_frequency: 500.0,
998            dry_level: 0.7,
999            early_level: 0.3,
1000            late_level: 0.4,
1001        })
1002    }
1003}
1004
1005impl FeedbackDelayNetwork {
1006    /// Create new feedback delay network
1007    pub fn new(delays: &[f32]) -> crate::Result<Self> {
1008        let mut delay_lines = Vec::new();
1009        for &delay in delays {
1010            delay_lines.push(DelayLine::new(delay, 44100.0)?);
1011        }
1012
1013        let size = delays.len();
1014        let feedback_matrix = Array2::eye(size) * 0.7; // Simplified feedback matrix
1015        let input_gains = Array1::ones(size);
1016        let output_gains = Array1::ones(size);
1017
1018        Ok(Self {
1019            delay_lines,
1020            feedback_matrix,
1021            input_gains,
1022            output_gains,
1023        })
1024    }
1025}
1026
1027impl AllPassFilter {
1028    /// Create new all-pass filter
1029    pub fn new(delay: f32, feedback: f32) -> crate::Result<Self> {
1030        Ok(Self {
1031            delay_line: DelayLine::new(delay, 44100.0)?,
1032            feedback,
1033            feedforward: -feedback,
1034        })
1035    }
1036}
1037
1038impl DelayLine {
1039    /// Create new delay line
1040    pub fn new(delay_time: f32, sample_rate: f32) -> crate::Result<Self> {
1041        let delay_samples = delay_time * sample_rate;
1042        let max_delay = delay_samples.ceil() as usize + 1;
1043        let buffer = VecDeque::with_capacity(max_delay);
1044
1045        Ok(Self {
1046            buffer,
1047            delay_samples,
1048            max_delay,
1049        })
1050    }
1051
1052    /// Process sample through delay line
1053    pub fn process(&mut self, input: f32) -> f32 {
1054        // Add input to buffer
1055        self.buffer.push_back(input);
1056
1057        // Remove old samples if buffer is too large
1058        while self.buffer.len() > self.max_delay {
1059            self.buffer.pop_front();
1060        }
1061
1062        // Get delayed output (simplified - no interpolation)
1063        let delay_index = self.delay_samples as usize;
1064        if self.buffer.len() > delay_index {
1065            self.buffer[self.buffer.len() - 1 - delay_index]
1066        } else {
1067            0.0
1068        }
1069    }
1070}
1071
1072/// Multi-room environment system
1073pub struct MultiRoomEnvironment {
1074    /// Individual rooms in the environment
1075    pub rooms: HashMap<String, Room>,
1076    /// Connections between rooms (doors, openings, etc.)
1077    pub connections: Vec<RoomConnection>,
1078    /// Global acoustic properties
1079    pub global_config: GlobalAcousticConfig,
1080    /// Inter-room sound propagation cache
1081    propagation_cache: HashMap<(String, String), PropagationPath>,
1082}
1083
1084/// Individual room in a multi-room environment
1085#[derive(Debug, Clone)]
1086pub struct Room {
1087    /// Room identifier
1088    pub id: String,
1089    /// Room simulator
1090    pub simulator: RoomSimulator,
1091    /// Room position in global coordinate system
1092    pub position: Position3D,
1093    /// Room orientation (yaw, pitch, roll)
1094    pub orientation: (f32, f32, f32),
1095    /// Room volume level adjustment
1096    pub volume_adjustment: f32,
1097}
1098
1099/// Connection between rooms (doors, openings, vents)
1100#[derive(Debug, Clone, Serialize, Deserialize)]
1101pub struct RoomConnection {
1102    /// Connection ID
1103    pub id: String,
1104    /// Source room ID
1105    pub from_room: String,
1106    /// Target room ID
1107    pub to_room: String,
1108    /// Connection type
1109    pub connection_type: ConnectionType,
1110    /// Position of connection in from_room
1111    pub from_position: Position3D,
1112    /// Position of connection in to_room
1113    pub to_position: Position3D,
1114    /// Opening dimensions (width, height)
1115    pub dimensions: (f32, f32),
1116    /// Acoustic properties
1117    pub acoustic_properties: ConnectionAcousticProperties,
1118    /// Current state (open, closed, partially open)
1119    pub state: ConnectionState,
1120}
1121
1122/// Type of connection between rooms
1123#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1124pub enum ConnectionType {
1125    /// Standard door
1126    Door,
1127    /// Open doorway/archway
1128    Doorway,
1129    /// Window
1130    Window,
1131    /// Air vent
1132    Vent,
1133    /// Large opening
1134    Opening,
1135    /// Sound isolation barrier
1136    SoundBarrier,
1137}
1138
1139/// Connection state
1140#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
1141pub enum ConnectionState {
1142    /// Fully open
1143    Open,
1144    /// Fully closed
1145    Closed,
1146    /// Partially open with specified percentage (0.0-1.0)
1147    PartiallyOpen(f32),
1148}
1149
1150/// Acoustic properties of room connections
1151#[derive(Debug, Clone, Serialize, Deserialize)]
1152pub struct ConnectionAcousticProperties {
1153    /// Sound transmission coefficient (0.0-1.0)
1154    pub transmission_coefficient: f32,
1155    /// Frequency-dependent transmission
1156    pub frequency_transmission: Vec<FrequencyBandAbsorption>,
1157    /// Attenuation through the connection (dB)
1158    pub attenuation_db: f32,
1159    /// Reflection coefficient at the opening
1160    pub reflection_coefficient: f32,
1161    /// Diffraction around edges
1162    pub diffraction_enabled: bool,
1163}
1164
1165/// Global acoustic configuration for multi-room environment
1166#[derive(Debug, Clone, Serialize, Deserialize)]
1167pub struct GlobalAcousticConfig {
1168    /// Speed of sound (m/s)
1169    pub speed_of_sound: f32,
1170    /// Global temperature (°C)
1171    pub temperature: f32,
1172    /// Global humidity (%)
1173    pub humidity: f32,
1174    /// Air absorption enabled globally
1175    pub enable_air_absorption: bool,
1176    /// Maximum propagation distance between rooms
1177    pub max_propagation_distance: f32,
1178    /// Inter-room delay processing
1179    pub enable_inter_room_delays: bool,
1180}
1181
1182/// Sound propagation path between rooms
1183#[derive(Debug, Clone)]
1184pub struct PropagationPath {
1185    /// Sequence of rooms the sound passes through
1186    pub room_sequence: Vec<String>,
1187    /// Connection IDs used in the path
1188    pub connections: Vec<String>,
1189    /// Total attenuation along the path
1190    pub total_attenuation: f32,
1191    /// Total delay in samples
1192    pub total_delay_samples: usize,
1193    /// Frequency response of the path
1194    pub frequency_response: Vec<f32>,
1195}
1196
1197impl Default for MultiRoomEnvironment {
1198    fn default() -> Self {
1199        Self::new()
1200    }
1201}
1202
1203impl MultiRoomEnvironment {
1204    /// Create new multi-room environment
1205    pub fn new() -> Self {
1206        Self {
1207            rooms: HashMap::new(),
1208            connections: Vec::new(),
1209            global_config: GlobalAcousticConfig::default(),
1210            propagation_cache: HashMap::new(),
1211        }
1212    }
1213
1214    /// Add room to the environment
1215    pub fn add_room(&mut self, room: Room) -> crate::Result<()> {
1216        if self.rooms.contains_key(&room.id) {
1217            return Err(crate::Error::LegacyRoom(format!(
1218                "Room with ID '{}' already exists",
1219                room.id
1220            )));
1221        }
1222        self.rooms.insert(room.id.clone(), room);
1223        Ok(())
1224    }
1225
1226    /// Add connection between rooms
1227    pub fn add_connection(&mut self, connection: RoomConnection) -> crate::Result<()> {
1228        // Validate that both rooms exist
1229        if !self.rooms.contains_key(&connection.from_room) {
1230            return Err(crate::Error::LegacyRoom(format!(
1231                "Source room '{}' does not exist",
1232                connection.from_room
1233            )));
1234        }
1235        if !self.rooms.contains_key(&connection.to_room) {
1236            return Err(crate::Error::LegacyRoom(format!(
1237                "Target room '{}' does not exist",
1238                connection.to_room
1239            )));
1240        }
1241
1242        self.connections.push(connection);
1243        self.invalidate_propagation_cache();
1244        Ok(())
1245    }
1246
1247    /// Calculate multi-room acoustic propagation
1248    pub async fn process_multi_room_audio(
1249        &mut self,
1250        source_room_id: &str,
1251        source_position: Position3D,
1252        listener_room_id: &str,
1253        listener_position: Position3D,
1254        input_audio: &Array1<f32>,
1255        sample_rate: u32,
1256    ) -> crate::Result<(Array1<f32>, Array1<f32>)> {
1257        let mut left_output = Array1::zeros(input_audio.len());
1258        let mut right_output = Array1::zeros(input_audio.len());
1259
1260        if source_room_id == listener_room_id {
1261            // Same room - use standard room acoustics
1262            let room = self.rooms.get(source_room_id).ok_or_else(|| {
1263                crate::Error::LegacyRoom(format!("Room '{source_room_id}' not found"))
1264            })?;
1265
1266            // Process with room acoustics
1267            self.apply_room_acoustics(
1268                &room.simulator,
1269                input_audio,
1270                &mut left_output,
1271                &mut right_output,
1272                source_position,
1273                listener_position,
1274            )
1275            .await?;
1276        } else {
1277            // Different rooms - calculate inter-room propagation
1278            let propagation_paths = self
1279                .find_propagation_paths(source_room_id, listener_room_id)
1280                .await?;
1281
1282            for path in &propagation_paths {
1283                let mut path_left = input_audio.clone();
1284                let mut path_right = input_audio.clone();
1285
1286                // Apply path-specific processing
1287                self.apply_propagation_path(path, &mut path_left, &mut path_right, sample_rate)
1288                    .await?;
1289
1290                // Add to output
1291                for i in 0..left_output.len() {
1292                    left_output[i] += path_left[i];
1293                    right_output[i] += path_right[i];
1294                }
1295            }
1296        }
1297
1298        Ok((left_output, right_output))
1299    }
1300
1301    /// Apply room acoustics to audio
1302    async fn apply_room_acoustics(
1303        &self,
1304        room_simulator: &RoomSimulator,
1305        input: &Array1<f32>,
1306        left_output: &mut Array1<f32>,
1307        right_output: &mut Array1<f32>,
1308        source_position: Position3D,
1309        _listener_position: Position3D,
1310    ) -> crate::Result<()> {
1311        // Copy input to outputs
1312        for i in 0..input.len() {
1313            left_output[i] = input[i];
1314            right_output[i] = input[i];
1315        }
1316
1317        // Apply room reverb
1318        room_simulator
1319            .process_reverb(left_output, right_output, &source_position)
1320            .await?;
1321
1322        Ok(())
1323    }
1324
1325    /// Find all possible propagation paths between rooms
1326    async fn find_propagation_paths(
1327        &mut self,
1328        source_room: &str,
1329        target_room: &str,
1330    ) -> crate::Result<Vec<PropagationPath>> {
1331        // Check cache first
1332        let cache_key = (source_room.to_string(), target_room.to_string());
1333        if let Some(cached_path) = self.propagation_cache.get(&cache_key) {
1334            return Ok(vec![cached_path.clone()]);
1335        }
1336
1337        // Use breadth-first search to find shortest paths
1338        let mut paths = Vec::new();
1339        let mut visited = std::collections::HashSet::new();
1340        let mut queue = std::collections::VecDeque::new();
1341
1342        // Initialize with source room
1343        queue.push_back(PropagationPath {
1344            room_sequence: vec![source_room.to_string()],
1345            connections: Vec::new(),
1346            total_attenuation: 1.0,
1347            total_delay_samples: 0,
1348            frequency_response: vec![1.0; 10], // Simplified frequency bands
1349        });
1350
1351        while let Some(current_path) = queue.pop_front() {
1352            // Get current room - room_sequence is guaranteed non-empty by algorithm invariant
1353            // (initialized with source_room and always extended, never truncated)
1354            let current_room = current_path.room_sequence.last().ok_or_else(|| {
1355                crate::Error::LegacyProcessing(
1356                    "Internal error: room sequence unexpectedly empty in path finding".to_string(),
1357                )
1358            })?;
1359
1360            if current_room == target_room {
1361                paths.push(current_path.clone());
1362                continue;
1363            }
1364
1365            if visited.contains(current_room) || current_path.room_sequence.len() > 5 {
1366                continue; // Avoid cycles and limit depth
1367            }
1368
1369            visited.insert(current_room.clone());
1370
1371            // Find connections from current room
1372            for connection in &self.connections {
1373                if connection.from_room == *current_room
1374                    && connection.state != ConnectionState::Closed
1375                {
1376                    let mut new_path = current_path.clone();
1377                    new_path.room_sequence.push(connection.to_room.clone());
1378                    new_path.connections.push(connection.id.clone());
1379
1380                    // Calculate additional attenuation and delay
1381                    let connection_attenuation = self.calculate_connection_attenuation(connection);
1382                    new_path.total_attenuation *= connection_attenuation;
1383
1384                    // Estimate delay based on distance (simplified)
1385                    let distance = connection
1386                        .from_position
1387                        .distance_to(&connection.to_position);
1388                    let delay_samples =
1389                        (distance / self.global_config.speed_of_sound * 44100.0) as usize;
1390                    new_path.total_delay_samples += delay_samples;
1391
1392                    queue.push_back(new_path);
1393                }
1394            }
1395        }
1396
1397        // Cache the result
1398        if !paths.is_empty() {
1399            self.propagation_cache.insert(cache_key, paths[0].clone());
1400        }
1401
1402        Ok(paths)
1403    }
1404
1405    /// Calculate attenuation through a connection
1406    fn calculate_connection_attenuation(&self, connection: &RoomConnection) -> f32 {
1407        let base_attenuation = match connection.state {
1408            ConnectionState::Open => connection.acoustic_properties.transmission_coefficient,
1409            ConnectionState::Closed => 0.01, // Very little transmission when closed
1410            ConnectionState::PartiallyOpen(ratio) => {
1411                connection.acoustic_properties.transmission_coefficient * ratio
1412            }
1413        };
1414
1415        // Apply frequency-independent attenuation for simplification
1416        base_attenuation * 10_f32.powf(-connection.acoustic_properties.attenuation_db / 20.0)
1417    }
1418
1419    /// Apply propagation path effects to audio
1420    async fn apply_propagation_path(
1421        &self,
1422        path: &PropagationPath,
1423        left_audio: &mut Array1<f32>,
1424        right_audio: &mut Array1<f32>,
1425        _sample_rate: u32,
1426    ) -> crate::Result<()> {
1427        // Apply total attenuation
1428        for sample in left_audio.iter_mut() {
1429            *sample *= path.total_attenuation;
1430        }
1431        for sample in right_audio.iter_mut() {
1432            *sample *= path.total_attenuation;
1433        }
1434
1435        // Apply delay (simplified - would need proper delay line in production)
1436        if path.total_delay_samples > 0 && path.total_delay_samples < left_audio.len() {
1437            // Shift samples to apply delay
1438            for i in (path.total_delay_samples..left_audio.len()).rev() {
1439                left_audio[i] = left_audio[i - path.total_delay_samples];
1440                right_audio[i] = right_audio[i - path.total_delay_samples];
1441            }
1442            for i in 0..path.total_delay_samples {
1443                left_audio[i] = 0.0;
1444                right_audio[i] = 0.0;
1445            }
1446        }
1447
1448        Ok(())
1449    }
1450
1451    /// Invalidate propagation cache when rooms or connections change
1452    fn invalidate_propagation_cache(&mut self) {
1453        self.propagation_cache.clear();
1454    }
1455
1456    /// Get room by ID
1457    pub fn get_room(&self, room_id: &str) -> Option<&Room> {
1458        self.rooms.get(room_id)
1459    }
1460
1461    /// Get room by ID (mutable)
1462    pub fn get_room_mut(&mut self, room_id: &str) -> Option<&mut Room> {
1463        self.rooms.get_mut(room_id)
1464    }
1465
1466    /// Update connection state
1467    pub fn set_connection_state(
1468        &mut self,
1469        connection_id: &str,
1470        state: ConnectionState,
1471    ) -> crate::Result<()> {
1472        if let Some(connection) = self.connections.iter_mut().find(|c| c.id == connection_id) {
1473            connection.state = state;
1474            self.invalidate_propagation_cache();
1475            Ok(())
1476        } else {
1477            Err(crate::Error::LegacyRoom(format!(
1478                "Connection '{connection_id}' not found"
1479            )))
1480        }
1481    }
1482}
1483
1484impl Room {
1485    /// Create new room
1486    pub fn new(
1487        id: String,
1488        dimensions: (f32, f32, f32),
1489        reverb_time: f32,
1490        position: Position3D,
1491    ) -> crate::Result<Self> {
1492        Ok(Self {
1493            id,
1494            simulator: RoomSimulator::new(dimensions, reverb_time)?,
1495            position,
1496            orientation: (0.0, 0.0, 0.0),
1497            volume_adjustment: 1.0,
1498        })
1499    }
1500
1501    /// Create room with custom configuration
1502    pub fn with_config(
1503        id: String,
1504        config: RoomConfig,
1505        position: Position3D,
1506    ) -> crate::Result<Self> {
1507        Ok(Self {
1508            id,
1509            simulator: RoomSimulator::with_config(config)?,
1510            position,
1511            orientation: (0.0, 0.0, 0.0),
1512            volume_adjustment: 1.0,
1513        })
1514    }
1515}
1516
1517impl Default for GlobalAcousticConfig {
1518    fn default() -> Self {
1519        Self {
1520            speed_of_sound: 343.0,
1521            temperature: 20.0,
1522            humidity: 50.0,
1523            enable_air_absorption: true,
1524            max_propagation_distance: 100.0,
1525            enable_inter_room_delays: true,
1526        }
1527    }
1528}
1529
1530impl Default for ConnectionAcousticProperties {
1531    fn default() -> Self {
1532        Self {
1533            transmission_coefficient: 0.3,
1534            frequency_transmission: vec![
1535                FrequencyBandAbsorption {
1536                    frequency: 125.0,
1537                    coefficient: 0.2,
1538                },
1539                FrequencyBandAbsorption {
1540                    frequency: 500.0,
1541                    coefficient: 0.3,
1542                },
1543                FrequencyBandAbsorption {
1544                    frequency: 2000.0,
1545                    coefficient: 0.4,
1546                },
1547                FrequencyBandAbsorption {
1548                    frequency: 8000.0,
1549                    coefficient: 0.2,
1550                },
1551            ],
1552            attenuation_db: 3.0,
1553            reflection_coefficient: 0.1,
1554            diffraction_enabled: true,
1555        }
1556    }
1557}
1558
1559#[cfg(test)]
1560mod tests {
1561    use super::*;
1562
1563    #[test]
1564    fn test_room_simulator_creation() {
1565        let simulator = RoomSimulator::new((10.0, 8.0, 3.0), 1.2);
1566        assert!(simulator.is_ok());
1567    }
1568
1569    #[test]
1570    fn test_room_config() {
1571        let config = RoomConfig::new((5.0, 4.0, 3.0), 1.0);
1572        assert_eq!(config.dimensions, (5.0, 4.0, 3.0));
1573        assert_eq!(config.volume, 60.0);
1574        assert!(config.average_absorption() > 0.0);
1575    }
1576
1577    #[test]
1578    fn test_material_properties() {
1579        let concrete = Material::concrete();
1580        let carpet = Material::carpet();
1581
1582        assert!(carpet.average_absorption() > concrete.average_absorption());
1583    }
1584
1585    #[test]
1586    fn test_delay_line() {
1587        let mut delay_line =
1588            DelayLine::new(0.001, 44100.0).expect("Should successfully create delay line"); // 1ms delay
1589
1590        // Feed impulse
1591        let output1 = delay_line.process(1.0);
1592        assert_eq!(output1, 0.0); // Should be silent initially
1593
1594        // Process enough samples for delay
1595        for _ in 0..50 {
1596            delay_line.process(0.0);
1597        }
1598
1599        let _output2 = delay_line.process(0.0);
1600        // Should now output the delayed impulse (simplified test)
1601    }
1602
1603    #[test]
1604    fn test_reflection_path_calculation() {
1605        let simulator = RoomSimulator::new((10.0, 8.0, 3.0), 1.2)
1606            .expect("Should successfully create room simulator");
1607        let source = Position3D::new(2.0, 1.0, 1.0);
1608        let listener = Position3D::new(8.0, 1.0, 2.0);
1609
1610        let paths = simulator
1611            .calculate_reflection_paths(source, listener, 1)
1612            .expect("Should successfully calculate reflection paths");
1613        assert!(!paths.is_empty());
1614
1615        // Should have direct path plus wall reflections
1616        assert!(paths.len() >= 7); // Direct + 6 walls
1617    }
1618
1619    #[test]
1620    fn test_multi_room_environment_creation() {
1621        let mut env = MultiRoomEnvironment::new();
1622        assert_eq!(env.rooms.len(), 0);
1623        assert_eq!(env.connections.len(), 0);
1624    }
1625
1626    #[test]
1627    fn test_room_creation() {
1628        let room = Room::new(
1629            "living_room".to_string(),
1630            (5.0, 4.0, 3.0),
1631            1.2,
1632            Position3D::new(0.0, 0.0, 0.0),
1633        )
1634        .expect("Should successfully create room");
1635
1636        assert_eq!(room.id, "living_room");
1637        assert_eq!(room.position, Position3D::new(0.0, 0.0, 0.0));
1638    }
1639
1640    #[test]
1641    fn test_multi_room_environment_add_room() {
1642        let mut env = MultiRoomEnvironment::new();
1643
1644        let room = Room::new(
1645            "kitchen".to_string(),
1646            (4.0, 3.0, 2.5),
1647            0.8,
1648            Position3D::new(5.0, 0.0, 0.0),
1649        )
1650        .expect("Should successfully create kitchen");
1651
1652        env.add_room(room)
1653            .expect("Should successfully add room to environment");
1654        assert_eq!(env.rooms.len(), 1);
1655        assert!(env.get_room("kitchen").is_some());
1656    }
1657
1658    #[test]
1659    fn test_room_connection() {
1660        let mut env = MultiRoomEnvironment::new();
1661
1662        // Add two rooms
1663        let living_room = Room::new(
1664            "living_room".to_string(),
1665            (5.0, 4.0, 3.0),
1666            1.2,
1667            Position3D::new(0.0, 0.0, 0.0),
1668        )
1669        .expect("Should successfully create living room");
1670
1671        let kitchen = Room::new(
1672            "kitchen".to_string(),
1673            (4.0, 3.0, 2.5),
1674            0.8,
1675            Position3D::new(5.0, 0.0, 0.0),
1676        )
1677        .expect("Should successfully create kitchen");
1678
1679        env.add_room(living_room)
1680            .expect("Should successfully add living room");
1681        env.add_room(kitchen)
1682            .expect("Should successfully add kitchen");
1683
1684        // Create connection between rooms
1685        let connection = RoomConnection {
1686            id: "door_1".to_string(),
1687            from_room: "living_room".to_string(),
1688            to_room: "kitchen".to_string(),
1689            connection_type: ConnectionType::Door,
1690            from_position: Position3D::new(5.0, 2.0, 1.0),
1691            to_position: Position3D::new(0.0, 2.0, 1.0),
1692            dimensions: (0.8, 2.0),
1693            acoustic_properties: ConnectionAcousticProperties::default(),
1694            state: ConnectionState::Open,
1695        };
1696
1697        env.add_connection(connection)
1698            .expect("Should successfully add connection");
1699        assert_eq!(env.connections.len(), 1);
1700    }
1701
1702    #[test]
1703    fn test_connection_state_changes() {
1704        let mut env = MultiRoomEnvironment::new();
1705
1706        // Add rooms and connection
1707        let living_room = Room::new(
1708            "living_room".to_string(),
1709            (5.0, 4.0, 3.0),
1710            1.2,
1711            Position3D::new(0.0, 0.0, 0.0),
1712        )
1713        .expect("Should successfully create living room");
1714        let kitchen = Room::new(
1715            "kitchen".to_string(),
1716            (4.0, 3.0, 2.5),
1717            0.8,
1718            Position3D::new(5.0, 0.0, 0.0),
1719        )
1720        .expect("Should successfully create kitchen");
1721
1722        env.add_room(living_room)
1723            .expect("Should successfully add living room");
1724        env.add_room(kitchen)
1725            .expect("Should successfully add kitchen");
1726
1727        let connection = RoomConnection {
1728            id: "door_1".to_string(),
1729            from_room: "living_room".to_string(),
1730            to_room: "kitchen".to_string(),
1731            connection_type: ConnectionType::Door,
1732            from_position: Position3D::new(5.0, 2.0, 1.0),
1733            to_position: Position3D::new(0.0, 2.0, 1.0),
1734            dimensions: (0.8, 2.0),
1735            acoustic_properties: ConnectionAcousticProperties::default(),
1736            state: ConnectionState::Open,
1737        };
1738
1739        env.add_connection(connection)
1740            .expect("Should successfully add connection");
1741
1742        // Test state changes
1743        env.set_connection_state("door_1", ConnectionState::Closed)
1744            .expect("Should successfully set connection state to closed");
1745        assert_eq!(env.connections[0].state, ConnectionState::Closed);
1746
1747        env.set_connection_state("door_1", ConnectionState::PartiallyOpen(0.5))
1748            .expect("Should successfully set connection state to partially open");
1749        assert_eq!(
1750            env.connections[0].state,
1751            ConnectionState::PartiallyOpen(0.5)
1752        );
1753    }
1754
1755    #[tokio::test]
1756    async fn test_multi_room_audio_processing() {
1757        let mut env = MultiRoomEnvironment::new();
1758
1759        // Add rooms
1760        let living_room = Room::new(
1761            "living_room".to_string(),
1762            (5.0, 4.0, 3.0),
1763            1.2,
1764            Position3D::new(0.0, 0.0, 0.0),
1765        )
1766        .expect("Should successfully create living room");
1767        let kitchen = Room::new(
1768            "kitchen".to_string(),
1769            (4.0, 3.0, 2.5),
1770            0.8,
1771            Position3D::new(5.0, 0.0, 0.0),
1772        )
1773        .expect("Should successfully create kitchen");
1774
1775        env.add_room(living_room)
1776            .expect("Should successfully add living room");
1777        env.add_room(kitchen)
1778            .expect("Should successfully add kitchen");
1779
1780        // Add connection
1781        let connection = RoomConnection {
1782            id: "door_1".to_string(),
1783            from_room: "living_room".to_string(),
1784            to_room: "kitchen".to_string(),
1785            connection_type: ConnectionType::Door,
1786            from_position: Position3D::new(5.0, 2.0, 1.0),
1787            to_position: Position3D::new(0.0, 2.0, 1.0),
1788            dimensions: (0.8, 2.0),
1789            acoustic_properties: ConnectionAcousticProperties::default(),
1790            state: ConnectionState::Open,
1791        };
1792
1793        env.add_connection(connection)
1794            .expect("Should successfully add connection");
1795
1796        // Test audio processing
1797        let input_audio = Array1::from_vec(vec![0.5; 1000]);
1798        let source_pos = Position3D::new(2.0, 2.0, 1.5);
1799        let listener_pos = Position3D::new(2.0, 1.5, 1.5);
1800
1801        // Same room processing
1802        let result = env
1803            .process_multi_room_audio(
1804                "living_room",
1805                source_pos,
1806                "living_room",
1807                listener_pos,
1808                &input_audio,
1809                44100,
1810            )
1811            .await;
1812
1813        assert!(result.is_ok());
1814        let (left, right) = result.expect("Should successfully process multi-room audio");
1815        assert_eq!(left.len(), input_audio.len());
1816        assert_eq!(right.len(), input_audio.len());
1817    }
1818}