use super::occlusion::{OcclusionDetector, OcclusionResult};
use super::prediction::{MotionPredictor, MotionSnapshot};
use super::spatial_grid::SpatialGrid;
use super::types::SoundSource;
use crate::types::Position3D;
use std::collections::{HashMap, VecDeque};
use std::time::{Duration, Instant};
pub struct SpatialSourceManager {
pub sources: HashMap<String, SoundSource>,
spatial_grid: SpatialGrid,
occlusion_detector: OcclusionDetector,
max_sources: usize,
pub culling_distance: f32,
update_frequency: f32,
}
#[derive(Debug, Clone)]
pub struct DopplerProcessor {
speed_of_sound: f32,
sample_rate: f32,
max_doppler_factor: f32,
smoothing_factor: f32,
}
#[derive(Debug, Clone)]
pub struct DynamicSourceManager {
sources: HashMap<String, DynamicSource>,
doppler_processor: DopplerProcessor,
motion_predictor: MotionPredictor,
}
#[derive(Debug, Clone)]
pub struct DynamicSource {
pub base_source: SoundSource,
pub velocity: Position3D,
pub acceleration: Position3D,
pub motion_history: VecDeque<MotionSnapshot>,
pub doppler_factor: f32,
pub smoothed_doppler_factor: f32,
pub last_doppler_update: Option<Instant>,
}
impl SpatialSourceManager {
pub fn new(bounds: (Position3D, Position3D), cell_size: f32) -> Self {
Self {
sources: HashMap::new(),
spatial_grid: SpatialGrid::new(bounds, cell_size),
occlusion_detector: OcclusionDetector::new(),
max_sources: 64,
culling_distance: 100.0,
update_frequency: 60.0,
}
}
pub fn add_source(&mut self, source: SoundSource) -> crate::Result<()> {
if self.sources.len() >= self.max_sources {
return Err(crate::Error::LegacyPosition(
"Maximum sources exceeded".to_string(),
));
}
let source_id = source.id.clone();
let position = source.position();
self.spatial_grid.add_source(&source_id, position);
self.sources.insert(source_id, source);
Ok(())
}
pub fn remove_source(&mut self, source_id: &str) -> Option<SoundSource> {
if let Some(source) = self.sources.remove(source_id) {
self.spatial_grid.remove_source(source_id);
Some(source)
} else {
None
}
}
pub fn update_source_position(
&mut self,
source_id: &str,
position: Position3D,
) -> crate::Result<()> {
if let Some(source) = self.sources.get_mut(source_id) {
let old_position = source.position();
source.set_position(position);
self.spatial_grid
.move_source(source_id, old_position, position);
Ok(())
} else {
Err(crate::Error::LegacyPosition(format!(
"Source not found: {source_id}"
)))
}
}
pub fn get_nearby_sources(
&self,
listener_position: Position3D,
radius: f32,
) -> Vec<&SoundSource> {
let nearby_ids = self.spatial_grid.query_sphere(listener_position, radius);
nearby_ids
.iter()
.filter_map(|id| self.sources.get(id))
.collect()
}
pub fn check_occlusion(
&self,
source_position: Position3D,
listener_position: Position3D,
) -> OcclusionResult {
self.occlusion_detector
.check_occlusion(source_position, listener_position)
}
pub fn cull_distant_sources(&mut self, listener_position: Position3D) {
let culling_distance_sq = self.culling_distance * self.culling_distance;
let distant_sources: Vec<String> = self
.sources
.iter()
.filter(|(_, source)| {
let distance_sq = listener_position.distance_to(&source.position()).powi(2);
distance_sq > culling_distance_sq
})
.map(|(id, _)| id.clone())
.collect();
for source_id in distant_sources {
self.remove_source(&source_id);
}
}
pub fn get_active_sources(&self) -> Vec<&SoundSource> {
self.sources.values().filter(|s| s.is_active()).collect()
}
}
impl DopplerProcessor {
pub fn new(sample_rate: f32) -> Self {
Self {
speed_of_sound: 343.0, sample_rate,
max_doppler_factor: 2.0, smoothing_factor: 0.95, }
}
pub fn with_speed_of_sound(sample_rate: f32, speed_of_sound: f32) -> Self {
Self {
speed_of_sound,
sample_rate,
max_doppler_factor: 2.0,
smoothing_factor: 0.95,
}
}
pub fn calculate_doppler_factor(
&self,
source_position: Position3D,
source_velocity: Position3D,
listener_position: Position3D,
listener_velocity: Position3D,
) -> f32 {
let source_to_listener = Position3D::new(
listener_position.x - source_position.x,
listener_position.y - source_position.y,
listener_position.z - source_position.z,
);
let distance = source_to_listener.magnitude();
if distance < 0.001 {
return 1.0; }
let direction = source_to_listener.normalized();
let source_radial_velocity = source_velocity.dot(&direction); let listener_radial_velocity = -listener_velocity.dot(&direction);
let numerator = self.speed_of_sound + listener_radial_velocity;
let denominator = self.speed_of_sound - source_radial_velocity;
if denominator.abs() < 0.001 {
return 1.0; }
let doppler_factor = numerator / denominator;
doppler_factor.clamp(1.0 / self.max_doppler_factor, self.max_doppler_factor)
}
pub fn process_doppler_effect(
&self,
input: &[f32],
output: &mut [f32],
doppler_factor: f32,
) -> crate::Result<()> {
if input.len() != output.len() {
return Err(crate::Error::LegacyProcessing(
"Input and output buffers must have the same length".to_string(),
));
}
if (doppler_factor - 1.0).abs() < 0.001 {
output.copy_from_slice(input);
return Ok(());
}
let pitch_ratio = doppler_factor;
let mut read_pos = 0.0;
for i in 0..output.len() {
let read_index = read_pos as usize;
let read_frac = read_pos - read_index as f32;
if read_index + 1 < input.len() {
let sample1 = input[read_index];
let sample2 = input[read_index + 1];
output[i] = sample1 + read_frac * (sample2 - sample1);
} else if read_index < input.len() {
output[i] = input[read_index];
} else {
output[i] = 0.0;
}
read_pos += pitch_ratio;
if read_pos >= input.len() as f32 {
output[i + 1..].fill(0.0);
break;
}
}
Ok(())
}
pub fn smooth_doppler_factor(&self, current: f32, target: f32) -> f32 {
current * self.smoothing_factor + target * (1.0 - self.smoothing_factor)
}
pub fn set_speed_of_sound(&mut self, speed: f32) {
self.speed_of_sound = speed;
}
pub fn speed_of_sound(&self) -> f32 {
self.speed_of_sound
}
}
impl DynamicSourceManager {
pub fn new(sample_rate: f32) -> Self {
Self {
sources: HashMap::new(),
doppler_processor: DopplerProcessor::new(sample_rate),
motion_predictor: MotionPredictor::new(),
}
}
pub fn add_source(&mut self, source: SoundSource) -> crate::Result<()> {
let dynamic_source = DynamicSource::new(source);
self.sources
.insert(dynamic_source.base_source.id.clone(), dynamic_source);
Ok(())
}
pub fn remove_source(&mut self, source_id: &str) -> Option<DynamicSource> {
self.sources.remove(source_id)
}
pub fn update_source_motion(
&mut self,
source_id: &str,
position: Position3D,
velocity: Position3D,
acceleration: Position3D,
) -> crate::Result<()> {
if let Some(source) = self.sources.get_mut(source_id) {
source.update_motion(position, velocity, acceleration);
Ok(())
} else {
Err(crate::Error::LegacyPosition(format!(
"Source '{source_id}' not found"
)))
}
}
pub async fn process_dynamic_sources(
&mut self,
listener_position: Position3D,
listener_velocity: Position3D,
) -> crate::Result<()> {
for source in self.sources.values_mut() {
let doppler_factor = self.doppler_processor.calculate_doppler_factor(
source.base_source.position(),
source.velocity,
listener_position,
listener_velocity,
);
source.smoothed_doppler_factor = self
.doppler_processor
.smooth_doppler_factor(source.smoothed_doppler_factor, doppler_factor);
source.doppler_factor = doppler_factor;
source.last_doppler_update = Some(Instant::now());
}
Ok(())
}
pub fn sources(&self) -> &HashMap<String, DynamicSource> {
&self.sources
}
pub fn get_source(&self, source_id: &str) -> Option<&DynamicSource> {
self.sources.get(source_id)
}
pub fn get_source_mut(&mut self, source_id: &str) -> Option<&mut DynamicSource> {
self.sources.get_mut(source_id)
}
pub fn predict_source_positions(
&self,
prediction_time: Duration,
) -> HashMap<String, Position3D> {
let mut predictions = HashMap::new();
for (id, source) in &self.sources {
if let Some(predicted_pos) = self
.motion_predictor
.predict_position(&source.motion_history, prediction_time)
{
predictions.insert(id.clone(), predicted_pos);
}
}
predictions
}
}
impl DynamicSource {
pub fn new(base_source: SoundSource) -> Self {
Self {
base_source,
velocity: Position3D::default(),
acceleration: Position3D::default(),
motion_history: VecDeque::with_capacity(100),
doppler_factor: 1.0,
smoothed_doppler_factor: 1.0,
last_doppler_update: None,
}
}
pub fn update_motion(
&mut self,
position: Position3D,
velocity: Position3D,
acceleration: Position3D,
) {
self.velocity = velocity;
self.acceleration = acceleration;
self.base_source.set_position(position);
let snapshot = MotionSnapshot {
position,
velocity,
acceleration,
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs_f64(),
};
self.motion_history.push_back(snapshot);
while self.motion_history.len() > 100 {
self.motion_history.pop_front();
}
}
pub fn predict_position(&self, time_delta: Duration) -> Position3D {
let dt = time_delta.as_secs_f32();
let current_pos = self.base_source.position();
Position3D::new(
current_pos.x + self.velocity.x * dt + 0.5 * self.acceleration.x * dt * dt,
current_pos.y + self.velocity.y * dt + 0.5 * self.acceleration.y * dt * dt,
current_pos.z + self.velocity.z * dt + 0.5 * self.acceleration.z * dt * dt,
)
}
pub fn is_moving(&self) -> bool {
self.velocity.magnitude() > 0.1 }
}