use crate::types::Position3D;
use serde::{Deserialize, Serialize};
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Listener {
position: Position3D,
orientation: (f32, f32, f32),
velocity: Position3D,
head_radius: f32,
interaural_distance: f32,
movement_history: Vec<PositionSnapshot>,
#[serde(skip)]
last_update: Option<Instant>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SoundSource {
pub id: String,
position: Position3D,
velocity: Position3D,
orientation: Option<(f32, f32, f32)>,
source_type: SourceType,
attenuation: AttenuationParams,
directivity: Option<DirectivityPattern>,
movement_history: Vec<PositionSnapshot>,
is_active: bool,
#[serde(skip)]
last_update: Option<Instant>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PositionSnapshot {
pub position: Position3D,
pub timestamp: f64, pub velocity: Position3D,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum SourceType {
Point,
Directional,
Area {
width: f32,
height: f32,
},
Line {
length: f32,
},
Ambient,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AttenuationParams {
pub reference_distance: f32,
pub max_distance: f32,
pub rolloff_factor: f32,
pub model: AttenuationModel,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum AttenuationModel {
None,
Linear,
Inverse,
InverseSquare,
Exponential,
Custom,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DirectivityPattern {
pub front_gain: f32,
pub back_gain: f32,
pub side_gain: f32,
pub directivity_index: f32,
pub frequency_response: Vec<FrequencyGain>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FrequencyGain {
pub frequency: f32,
pub gain: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrientationSnapshot {
pub orientation: (f32, f32, f32),
pub timestamp: f64,
pub angular_velocity: (f32, f32, f32),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Box3D {
pub min: Position3D,
pub max: Position3D,
pub material_id: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum NavigationMode {
FreeFlight,
Walking,
Seated,
Teleport,
Vehicle,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComfortSettings {
pub motion_sickness_reduction: f32,
pub snap_turn: bool,
pub snap_turn_degrees: f32,
pub movement_vignetting: bool,
pub ground_reference: bool,
pub speed_multiplier: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MovementConstraints {
pub boundary: Option<Box3D>,
pub max_speed: f32,
pub max_acceleration: f32,
pub ground_height: Option<f32>,
pub ceiling_height: Option<f32>,
}
#[derive(Debug, Clone, Default)]
pub struct MovementMetrics {
pub total_distance: f32,
pub average_speed: f32,
pub peak_speed: f32,
pub movement_duration: Duration,
pub update_count: usize,
pub prediction_accuracy: f32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum PlatformType {
Generic,
Oculus,
SteamVR,
ARKit,
ARCore,
WMR,
Custom,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlatformData {
pub device_id: String,
pub pose_data: Vec<f32>,
pub tracking_confidence: f32,
pub platform_timestamp: u64,
pub properties: std::collections::HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CalibrationData {
pub head_circumference: Option<f32>,
pub ipd: Option<f32>,
pub height_offset: f32,
pub forward_offset: f32,
pub custom_hrtf_profile: Option<String>,
}
impl Listener {
pub fn new() -> Self {
Self {
position: Position3D::default(),
orientation: (0.0, 0.0, 0.0),
velocity: Position3D::default(),
head_radius: 0.0875, interaural_distance: 0.175, movement_history: Vec::new(),
last_update: None,
}
}
pub fn at_position(position: Position3D) -> Self {
let mut listener = Self::new();
listener.position = position;
listener
}
pub fn position(&self) -> Position3D {
self.position
}
pub fn set_position(&mut self, position: Position3D) {
let now = Instant::now();
if let Some(last_time) = self.last_update {
let time_delta = now.duration_since(last_time).as_secs_f32();
if time_delta > 0.0 {
self.velocity = Position3D::new(
(position.x - self.position.x) / time_delta,
(position.y - self.position.y) / time_delta,
(position.z - self.position.z) / time_delta,
);
}
}
self.movement_history.push(PositionSnapshot {
position: self.position,
timestamp: now.elapsed().as_secs_f64(),
velocity: self.velocity,
});
if self.movement_history.len() > 100 {
self.movement_history.remove(0);
}
self.position = position;
self.last_update = Some(now);
}
pub fn orientation(&self) -> (f32, f32, f32) {
self.orientation
}
pub fn set_orientation(&mut self, orientation: (f32, f32, f32)) {
self.orientation = orientation;
}
pub fn velocity(&self) -> Position3D {
self.velocity
}
pub fn head_radius(&self) -> f32 {
self.head_radius
}
pub fn set_head_radius(&mut self, radius: f32) {
self.head_radius = radius;
}
pub fn interaural_distance(&self) -> f32 {
self.interaural_distance
}
pub fn set_interaural_distance(&mut self, distance: f32) {
self.interaural_distance = distance;
}
pub fn movement_history(&self) -> &[PositionSnapshot] {
&self.movement_history
}
pub fn predict_position(&self, time_ahead: Duration) -> Position3D {
let delta_time = time_ahead.as_secs_f32();
Position3D::new(
self.position.x + self.velocity.x * delta_time,
self.position.y + self.velocity.y * delta_time,
self.position.z + self.velocity.z * delta_time,
)
}
pub fn left_ear_position(&self) -> Position3D {
let (yaw, _pitch, _roll) = self.orientation;
let offset_x = -self.interaural_distance / 2.0 * yaw.cos();
let offset_z = -self.interaural_distance / 2.0 * yaw.sin();
Position3D::new(
self.position.x + offset_x,
self.position.y,
self.position.z + offset_z,
)
}
pub fn right_ear_position(&self) -> Position3D {
let (yaw, _pitch, _roll) = self.orientation;
let offset_x = self.interaural_distance / 2.0 * yaw.cos();
let offset_z = self.interaural_distance / 2.0 * yaw.sin();
Position3D::new(
self.position.x + offset_x,
self.position.y,
self.position.z + offset_z,
)
}
}
impl Default for Listener {
fn default() -> Self {
Self::new()
}
}
impl SoundSource {
pub fn new_point(id: String, position: Position3D) -> Self {
Self {
id,
position,
velocity: Position3D::default(),
orientation: None,
source_type: SourceType::Point,
attenuation: AttenuationParams::default(),
directivity: None,
movement_history: Vec::new(),
is_active: true,
last_update: None,
}
}
pub fn new_directional(
id: String,
position: Position3D,
orientation: (f32, f32, f32),
directivity: DirectivityPattern,
) -> Self {
Self {
id,
position,
velocity: Position3D::default(),
orientation: Some(orientation),
source_type: SourceType::Directional,
attenuation: AttenuationParams::default(),
directivity: Some(directivity),
movement_history: Vec::new(),
is_active: true,
last_update: None,
}
}
pub fn position(&self) -> Position3D {
self.position
}
pub fn set_position(&mut self, position: Position3D) {
let now = Instant::now();
if let Some(last_time) = self.last_update {
let time_delta = now.duration_since(last_time).as_secs_f32();
if time_delta > 0.0 {
self.velocity = Position3D::new(
(position.x - self.position.x) / time_delta,
(position.y - self.position.y) / time_delta,
(position.z - self.position.z) / time_delta,
);
}
}
self.movement_history.push(PositionSnapshot {
position: self.position,
timestamp: now.elapsed().as_secs_f64(),
velocity: self.velocity,
});
if self.movement_history.len() > 100 {
self.movement_history.remove(0);
}
self.position = position;
self.last_update = Some(now);
}
pub fn velocity(&self) -> Position3D {
self.velocity
}
pub fn orientation(&self) -> Option<(f32, f32, f32)> {
self.orientation
}
pub fn set_orientation(&mut self, orientation: (f32, f32, f32)) {
self.orientation = Some(orientation);
}
pub fn source_type(&self) -> SourceType {
self.source_type
}
pub fn attenuation(&self) -> &AttenuationParams {
&self.attenuation
}
pub fn set_attenuation(&mut self, attenuation: AttenuationParams) {
self.attenuation = attenuation;
}
pub fn directivity(&self) -> Option<&DirectivityPattern> {
self.directivity.as_ref()
}
pub fn set_directivity(&mut self, directivity: DirectivityPattern) {
self.directivity = Some(directivity);
}
pub fn is_active(&self) -> bool {
self.is_active
}
pub fn set_active(&mut self, active: bool) {
self.is_active = active;
}
pub fn calculate_directional_gain(&self, listener_position: Position3D) -> f32 {
if let (Some(orientation), Some(directivity)) = (&self.orientation, &self.directivity) {
let direction = Position3D::new(
listener_position.x - self.position.x,
listener_position.y - self.position.y,
listener_position.z - self.position.z,
);
let distance = self.position.distance_to(&listener_position);
if distance == 0.0 {
return 1.0;
}
let normalized_dir = Position3D::new(
direction.x / distance,
direction.y / distance,
direction.z / distance,
);
let (yaw, _pitch, _roll) = *orientation;
let source_forward = Position3D::new(yaw.cos(), 0.0, yaw.sin());
let dot_product =
source_forward.x * normalized_dir.x + source_forward.z * normalized_dir.z;
let angle = dot_product.acos();
let angle_degrees = angle.to_degrees();
if angle_degrees <= 45.0 {
directivity.front_gain
} else if angle_degrees <= 135.0 {
directivity.side_gain
} else {
directivity.back_gain
}
} else {
1.0 }
}
pub fn predict_position(&self, time_ahead: Duration) -> Position3D {
let delta_time = time_ahead.as_secs_f32();
Position3D::new(
self.position.x + self.velocity.x * delta_time,
self.position.y + self.velocity.y * delta_time,
self.position.z + self.velocity.z * delta_time,
)
}
}
impl Default for AttenuationParams {
fn default() -> Self {
Self {
reference_distance: 1.0,
max_distance: 100.0,
rolloff_factor: 1.0,
model: AttenuationModel::Inverse,
}
}
}
impl DirectivityPattern {
pub fn omnidirectional() -> Self {
Self {
front_gain: 1.0,
back_gain: 1.0,
side_gain: 1.0,
directivity_index: 0.0,
frequency_response: Vec::new(),
}
}
pub fn cardioid() -> Self {
Self {
front_gain: 1.0,
back_gain: 0.0,
side_gain: 0.5,
directivity_index: 3.0,
frequency_response: Vec::new(),
}
}
pub fn hypercardioid() -> Self {
Self {
front_gain: 1.0,
back_gain: 0.25,
side_gain: 0.375,
directivity_index: 6.0,
frequency_response: Vec::new(),
}
}
}
impl Default for ComfortSettings {
fn default() -> Self {
Self {
motion_sickness_reduction: 0.3,
snap_turn: false,
snap_turn_degrees: 30.0,
movement_vignetting: false,
ground_reference: true,
speed_multiplier: 1.0,
}
}
}
impl Default for MovementConstraints {
fn default() -> Self {
Self {
boundary: None,
max_speed: 10.0, max_acceleration: 20.0, ground_height: Some(0.0),
ceiling_height: None,
}
}
}