1use crate::types::Position3D;
4use serde::{Deserialize, Serialize};
5use std::time::{Duration, Instant};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct Listener {
10 position: Position3D,
12 orientation: (f32, f32, f32),
14 velocity: Position3D,
16 head_radius: f32,
18 interaural_distance: f32,
20 movement_history: Vec<PositionSnapshot>,
22 #[serde(skip)]
24 last_update: Option<Instant>,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct SoundSource {
30 pub id: String,
32 position: Position3D,
34 velocity: Position3D,
36 orientation: Option<(f32, f32, f32)>,
38 source_type: SourceType,
40 attenuation: AttenuationParams,
42 directivity: Option<DirectivityPattern>,
44 movement_history: Vec<PositionSnapshot>,
46 is_active: bool,
48 #[serde(skip)]
50 last_update: Option<Instant>,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct PositionSnapshot {
56 pub position: Position3D,
58 pub timestamp: f64, pub velocity: Position3D,
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
66pub enum SourceType {
67 Point,
69 Directional,
71 Area {
73 width: f32,
75 height: f32,
77 },
78 Line {
80 length: f32,
82 },
83 Ambient,
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct AttenuationParams {
90 pub reference_distance: f32,
92 pub max_distance: f32,
94 pub rolloff_factor: f32,
96 pub model: AttenuationModel,
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
102pub enum AttenuationModel {
103 None,
105 Linear,
107 Inverse,
109 InverseSquare,
111 Exponential,
113 Custom,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct DirectivityPattern {
120 pub front_gain: f32,
122 pub back_gain: f32,
124 pub side_gain: f32,
126 pub directivity_index: f32,
128 pub frequency_response: Vec<FrequencyGain>,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct FrequencyGain {
135 pub frequency: f32,
137 pub gain: f32,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct OrientationSnapshot {
144 pub orientation: (f32, f32, f32),
146 pub timestamp: f64,
148 pub angular_velocity: (f32, f32, f32),
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize)]
154pub struct Box3D {
155 pub min: Position3D,
157 pub max: Position3D,
159 pub material_id: String,
161}
162
163#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
165pub enum NavigationMode {
166 FreeFlight,
168 Walking,
170 Seated,
172 Teleport,
174 Vehicle,
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct ComfortSettings {
181 pub motion_sickness_reduction: f32,
183 pub snap_turn: bool,
185 pub snap_turn_degrees: f32,
187 pub movement_vignetting: bool,
189 pub ground_reference: bool,
191 pub speed_multiplier: f32,
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
197pub struct MovementConstraints {
198 pub boundary: Option<Box3D>,
200 pub max_speed: f32,
202 pub max_acceleration: f32,
204 pub ground_height: Option<f32>,
206 pub ceiling_height: Option<f32>,
208}
209
210#[derive(Debug, Clone, Default)]
212pub struct MovementMetrics {
213 pub total_distance: f32,
215 pub average_speed: f32,
217 pub peak_speed: f32,
219 pub movement_duration: Duration,
221 pub update_count: usize,
223 pub prediction_accuracy: f32,
225}
226
227#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
229pub enum PlatformType {
230 Generic,
232 Oculus,
234 SteamVR,
236 ARKit,
238 ARCore,
240 WMR,
242 Custom,
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize)]
248pub struct PlatformData {
249 pub device_id: String,
251 pub pose_data: Vec<f32>,
253 pub tracking_confidence: f32,
255 pub platform_timestamp: u64,
257 pub properties: std::collections::HashMap<String, String>,
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize)]
263pub struct CalibrationData {
264 pub head_circumference: Option<f32>,
266 pub ipd: Option<f32>,
268 pub height_offset: f32,
270 pub forward_offset: f32,
272 pub custom_hrtf_profile: Option<String>,
274}
275
276impl Listener {
279 pub fn new() -> Self {
281 Self {
282 position: Position3D::default(),
283 orientation: (0.0, 0.0, 0.0),
284 velocity: Position3D::default(),
285 head_radius: 0.0875, interaural_distance: 0.175, movement_history: Vec::new(),
288 last_update: None,
289 }
290 }
291
292 pub fn at_position(position: Position3D) -> Self {
294 let mut listener = Self::new();
295 listener.position = position;
296 listener
297 }
298
299 pub fn position(&self) -> Position3D {
301 self.position
302 }
303
304 pub fn set_position(&mut self, position: Position3D) {
306 let now = Instant::now();
307
308 if let Some(last_time) = self.last_update {
310 let time_delta = now.duration_since(last_time).as_secs_f32();
311 if time_delta > 0.0 {
312 self.velocity = Position3D::new(
313 (position.x - self.position.x) / time_delta,
314 (position.y - self.position.y) / time_delta,
315 (position.z - self.position.z) / time_delta,
316 );
317 }
318 }
319
320 self.movement_history.push(PositionSnapshot {
322 position: self.position,
323 timestamp: now.elapsed().as_secs_f64(),
324 velocity: self.velocity,
325 });
326
327 if self.movement_history.len() > 100 {
329 self.movement_history.remove(0);
330 }
331
332 self.position = position;
333 self.last_update = Some(now);
334 }
335
336 pub fn orientation(&self) -> (f32, f32, f32) {
338 self.orientation
339 }
340
341 pub fn set_orientation(&mut self, orientation: (f32, f32, f32)) {
343 self.orientation = orientation;
344 }
345
346 pub fn velocity(&self) -> Position3D {
348 self.velocity
349 }
350
351 pub fn head_radius(&self) -> f32 {
353 self.head_radius
354 }
355
356 pub fn set_head_radius(&mut self, radius: f32) {
358 self.head_radius = radius;
359 }
360
361 pub fn interaural_distance(&self) -> f32 {
363 self.interaural_distance
364 }
365
366 pub fn set_interaural_distance(&mut self, distance: f32) {
368 self.interaural_distance = distance;
369 }
370
371 pub fn movement_history(&self) -> &[PositionSnapshot] {
373 &self.movement_history
374 }
375
376 pub fn predict_position(&self, time_ahead: Duration) -> Position3D {
378 let delta_time = time_ahead.as_secs_f32();
379 Position3D::new(
380 self.position.x + self.velocity.x * delta_time,
381 self.position.y + self.velocity.y * delta_time,
382 self.position.z + self.velocity.z * delta_time,
383 )
384 }
385
386 pub fn left_ear_position(&self) -> Position3D {
388 let (yaw, _pitch, _roll) = self.orientation;
389 let offset_x = -self.interaural_distance / 2.0 * yaw.cos();
390 let offset_z = -self.interaural_distance / 2.0 * yaw.sin();
391
392 Position3D::new(
393 self.position.x + offset_x,
394 self.position.y,
395 self.position.z + offset_z,
396 )
397 }
398
399 pub fn right_ear_position(&self) -> Position3D {
401 let (yaw, _pitch, _roll) = self.orientation;
402 let offset_x = self.interaural_distance / 2.0 * yaw.cos();
403 let offset_z = self.interaural_distance / 2.0 * yaw.sin();
404
405 Position3D::new(
406 self.position.x + offset_x,
407 self.position.y,
408 self.position.z + offset_z,
409 )
410 }
411}
412
413impl Default for Listener {
414 fn default() -> Self {
415 Self::new()
416 }
417}
418
419impl SoundSource {
420 pub fn new_point(id: String, position: Position3D) -> Self {
422 Self {
423 id,
424 position,
425 velocity: Position3D::default(),
426 orientation: None,
427 source_type: SourceType::Point,
428 attenuation: AttenuationParams::default(),
429 directivity: None,
430 movement_history: Vec::new(),
431 is_active: true,
432 last_update: None,
433 }
434 }
435
436 pub fn new_directional(
438 id: String,
439 position: Position3D,
440 orientation: (f32, f32, f32),
441 directivity: DirectivityPattern,
442 ) -> Self {
443 Self {
444 id,
445 position,
446 velocity: Position3D::default(),
447 orientation: Some(orientation),
448 source_type: SourceType::Directional,
449 attenuation: AttenuationParams::default(),
450 directivity: Some(directivity),
451 movement_history: Vec::new(),
452 is_active: true,
453 last_update: None,
454 }
455 }
456
457 pub fn position(&self) -> Position3D {
459 self.position
460 }
461
462 pub fn set_position(&mut self, position: Position3D) {
464 let now = Instant::now();
465
466 if let Some(last_time) = self.last_update {
468 let time_delta = now.duration_since(last_time).as_secs_f32();
469 if time_delta > 0.0 {
470 self.velocity = Position3D::new(
471 (position.x - self.position.x) / time_delta,
472 (position.y - self.position.y) / time_delta,
473 (position.z - self.position.z) / time_delta,
474 );
475 }
476 }
477
478 self.movement_history.push(PositionSnapshot {
480 position: self.position,
481 timestamp: now.elapsed().as_secs_f64(),
482 velocity: self.velocity,
483 });
484
485 if self.movement_history.len() > 100 {
487 self.movement_history.remove(0);
488 }
489
490 self.position = position;
491 self.last_update = Some(now);
492 }
493
494 pub fn velocity(&self) -> Position3D {
496 self.velocity
497 }
498
499 pub fn orientation(&self) -> Option<(f32, f32, f32)> {
501 self.orientation
502 }
503
504 pub fn set_orientation(&mut self, orientation: (f32, f32, f32)) {
506 self.orientation = Some(orientation);
507 }
508
509 pub fn source_type(&self) -> SourceType {
511 self.source_type
512 }
513
514 pub fn attenuation(&self) -> &AttenuationParams {
516 &self.attenuation
517 }
518
519 pub fn set_attenuation(&mut self, attenuation: AttenuationParams) {
521 self.attenuation = attenuation;
522 }
523
524 pub fn directivity(&self) -> Option<&DirectivityPattern> {
526 self.directivity.as_ref()
527 }
528
529 pub fn set_directivity(&mut self, directivity: DirectivityPattern) {
531 self.directivity = Some(directivity);
532 }
533
534 pub fn is_active(&self) -> bool {
536 self.is_active
537 }
538
539 pub fn set_active(&mut self, active: bool) {
541 self.is_active = active;
542 }
543
544 pub fn calculate_directional_gain(&self, listener_position: Position3D) -> f32 {
546 if let (Some(orientation), Some(directivity)) = (&self.orientation, &self.directivity) {
547 let direction = Position3D::new(
549 listener_position.x - self.position.x,
550 listener_position.y - self.position.y,
551 listener_position.z - self.position.z,
552 );
553
554 let distance = self.position.distance_to(&listener_position);
556 if distance == 0.0 {
557 return 1.0;
558 }
559
560 let normalized_dir = Position3D::new(
561 direction.x / distance,
562 direction.y / distance,
563 direction.z / distance,
564 );
565
566 let (yaw, _pitch, _roll) = *orientation;
568 let source_forward = Position3D::new(yaw.cos(), 0.0, yaw.sin());
569
570 let dot_product =
572 source_forward.x * normalized_dir.x + source_forward.z * normalized_dir.z;
573 let angle = dot_product.acos();
574
575 let angle_degrees = angle.to_degrees();
577 if angle_degrees <= 45.0 {
578 directivity.front_gain
579 } else if angle_degrees <= 135.0 {
580 directivity.side_gain
581 } else {
582 directivity.back_gain
583 }
584 } else {
585 1.0 }
587 }
588
589 pub fn predict_position(&self, time_ahead: Duration) -> Position3D {
591 let delta_time = time_ahead.as_secs_f32();
592 Position3D::new(
593 self.position.x + self.velocity.x * delta_time,
594 self.position.y + self.velocity.y * delta_time,
595 self.position.z + self.velocity.z * delta_time,
596 )
597 }
598}
599
600impl Default for AttenuationParams {
601 fn default() -> Self {
602 Self {
603 reference_distance: 1.0,
604 max_distance: 100.0,
605 rolloff_factor: 1.0,
606 model: AttenuationModel::Inverse,
607 }
608 }
609}
610
611impl DirectivityPattern {
612 pub fn omnidirectional() -> Self {
614 Self {
615 front_gain: 1.0,
616 back_gain: 1.0,
617 side_gain: 1.0,
618 directivity_index: 0.0,
619 frequency_response: Vec::new(),
620 }
621 }
622
623 pub fn cardioid() -> Self {
625 Self {
626 front_gain: 1.0,
627 back_gain: 0.0,
628 side_gain: 0.5,
629 directivity_index: 3.0,
630 frequency_response: Vec::new(),
631 }
632 }
633
634 pub fn hypercardioid() -> Self {
636 Self {
637 front_gain: 1.0,
638 back_gain: 0.25,
639 side_gain: 0.375,
640 directivity_index: 6.0,
641 frequency_response: Vec::new(),
642 }
643 }
644}
645
646impl Default for ComfortSettings {
647 fn default() -> Self {
648 Self {
649 motion_sickness_reduction: 0.3,
650 snap_turn: false,
651 snap_turn_degrees: 30.0,
652 movement_vignetting: false,
653 ground_reference: true,
654 speed_multiplier: 1.0,
655 }
656 }
657}
658
659impl Default for MovementConstraints {
660 fn default() -> Self {
661 Self {
662 boundary: None,
663 max_speed: 10.0, max_acceleration: 20.0, ground_height: Some(0.0),
666 ceiling_height: None,
667 }
668 }
669}