Skip to main content

voirs_spatial/
haptic.rs

1//! # Haptic Integration for Spatial Audio
2//!
3//! This module provides tactile feedback integration with spatial audio systems,
4//! enabling immersive multi-sensory experiences that combine audio positioning
5//! with tactile sensations.
6
7use crate::{types::AudioChannel, Position3D, Result};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::sync::{Arc, RwLock};
11use std::time::{Duration, Instant};
12
13/// Haptic device interface for tactile feedback
14pub trait HapticDevice: Send + Sync {
15    /// Send a haptic pattern to the device
16    fn send_pattern(&mut self, pattern: &HapticPattern) -> Result<()>;
17
18    /// Stop all haptic feedback
19    fn stop(&mut self) -> Result<()>;
20
21    /// Check if the device is connected and ready
22    fn is_ready(&self) -> bool;
23
24    /// Get device capabilities
25    fn capabilities(&self) -> HapticCapabilities;
26
27    /// Get device identifier
28    fn device_id(&self) -> String;
29}
30
31/// Haptic feedback pattern with timing and intensity control
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct HapticPattern {
34    /// Pattern identifier
35    pub id: String,
36
37    /// Pattern name
38    pub name: String,
39
40    /// Pattern elements with timing
41    pub elements: Vec<HapticElement>,
42
43    /// Total pattern duration
44    pub duration: Duration,
45
46    /// Whether pattern should loop
47    pub looping: bool,
48
49    /// Pattern priority (higher numbers take precedence)
50    pub priority: u8,
51
52    /// Spatial positioning for the haptic effect
53    pub spatial_position: Option<Position3D>,
54}
55
56/// Individual haptic element within a pattern
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct HapticElement {
59    /// Start time within the pattern
60    pub start_time: Duration,
61
62    /// Element duration
63    pub duration: Duration,
64
65    /// Haptic effect type
66    pub effect_type: HapticEffectType,
67
68    /// Intensity (0.0 to 1.0)
69    pub intensity: f32,
70
71    /// Frequency for vibration effects (Hz)
72    pub frequency: Option<f32>,
73
74    /// Spatial attenuation based on audio source distance
75    pub spatial_attenuation: f32,
76}
77
78/// Types of haptic effects
79#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
80pub enum HapticEffectType {
81    /// Simple vibration
82    Vibration,
83
84    /// Pulsed vibration
85    Pulse,
86
87    /// Continuous buzz
88    Buzz,
89
90    /// Sharp click/tap
91    Click,
92
93    /// Rumble effect
94    Rumble,
95
96    /// Directional force
97    DirectionalForce,
98
99    /// Temperature change
100    Thermal,
101
102    /// Custom effect
103    Custom(String),
104}
105
106/// Haptic device capabilities
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct HapticCapabilities {
109    /// Maximum simultaneous effects
110    pub max_concurrent_effects: usize,
111
112    /// Supported effect types
113    pub supported_effects: Vec<HapticEffectType>,
114
115    /// Intensity resolution (steps between 0.0 and 1.0)
116    pub intensity_resolution: u32,
117
118    /// Frequency range for vibration (Hz)
119    pub frequency_range: Option<(f32, f32)>,
120
121    /// Spatial positioning support
122    pub spatial_support: bool,
123
124    /// Latency compensation in milliseconds
125    pub latency_compensation: f32,
126
127    /// Device-specific features
128    pub features: HashMap<String, String>,
129}
130
131/// Configuration for haptic-audio integration
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct HapticAudioConfig {
134    /// Enable haptic feedback
135    pub enabled: bool,
136
137    /// Master haptic intensity multiplier
138    pub master_intensity: f32,
139
140    /// Distance-based attenuation curve
141    pub distance_attenuation: DistanceAttenuation,
142
143    /// Audio-to-haptic mapping settings
144    pub audio_mapping: AudioHapticMapping,
145
146    /// Synchronization settings
147    pub sync_settings: SyncSettings,
148
149    /// Pattern preferences
150    pub pattern_preferences: PatternPreferences,
151}
152
153/// Distance-based attenuation configuration
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct DistanceAttenuation {
156    /// Minimum distance for full intensity
157    pub min_distance: f32,
158
159    /// Maximum distance for zero intensity
160    pub max_distance: f32,
161
162    /// Attenuation curve type
163    pub curve_type: AttenuationCurve,
164
165    /// Custom curve parameters
166    pub curve_parameters: Vec<f32>,
167}
168
169/// Attenuation curve types
170#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
171pub enum AttenuationCurve {
172    /// Linear attenuation
173    Linear,
174
175    /// Inverse square law (realistic)
176    InverseSquare,
177
178    /// Exponential decay
179    Exponential,
180
181    /// Logarithmic curve
182    Logarithmic,
183
184    /// Custom curve with parameters
185    Custom,
186}
187
188/// Audio-to-haptic mapping configuration
189#[derive(Debug, Clone, Serialize, Deserialize)]
190pub struct AudioHapticMapping {
191    /// Bass frequency haptic mapping
192    pub bass_mapping: FrequencyMapping,
193
194    /// Mid-range frequency haptic mapping
195    pub mid_mapping: FrequencyMapping,
196
197    /// High frequency haptic mapping
198    pub high_mapping: FrequencyMapping,
199
200    /// Transient detection settings
201    pub transient_settings: TransientSettings,
202
203    /// Rhythm extraction settings
204    pub rhythm_settings: RhythmSettings,
205}
206
207/// Frequency band to haptic mapping
208#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct FrequencyMapping {
210    /// Frequency range (Hz)
211    pub frequency_range: (f32, f32),
212
213    /// Haptic effect type for this range
214    pub effect_type: HapticEffectType,
215
216    /// Intensity scaling factor
217    pub intensity_scale: f32,
218
219    /// Frequency to haptic frequency mapping
220    pub frequency_scale: f32,
221
222    /// Threshold for activation
223    pub activation_threshold: f32,
224}
225
226/// Transient detection for haptic triggers
227#[derive(Debug, Clone, Serialize, Deserialize)]
228pub struct TransientSettings {
229    /// Enable transient detection
230    pub enabled: bool,
231
232    /// Detection sensitivity
233    pub sensitivity: f32,
234
235    /// Minimum time between transients (ms)
236    pub min_interval: f32,
237
238    /// Haptic effect for transients
239    pub transient_effect: HapticEffectType,
240}
241
242/// Rhythm extraction for haptic patterns
243#[derive(Debug, Clone, Serialize, Deserialize)]
244pub struct RhythmSettings {
245    /// Enable rhythm-based haptics
246    pub enabled: bool,
247
248    /// Tempo detection range (BPM)
249    pub tempo_range: (f32, f32),
250
251    /// Beat emphasis intensity
252    pub beat_emphasis: f32,
253
254    /// Downbeat special effect
255    pub downbeat_effect: Option<HapticEffectType>,
256}
257
258/// Synchronization settings for audio-haptic alignment
259#[derive(Debug, Clone, Serialize, Deserialize)]
260pub struct SyncSettings {
261    /// Audio-haptic latency compensation (ms)
262    pub latency_compensation: f32,
263
264    /// Synchronization tolerance (ms)
265    pub sync_tolerance: f32,
266
267    /// Prediction lookahead (ms)
268    pub prediction_lookahead: f32,
269
270    /// Buffer size for processing
271    pub buffer_size: usize,
272}
273
274/// Pattern preference settings
275#[derive(Debug, Clone, Serialize, Deserialize)]
276pub struct PatternPreferences {
277    /// Preferred pattern styles
278    pub preferred_styles: Vec<PatternStyle>,
279
280    /// Pattern complexity level (0-10)
281    pub complexity_level: u8,
282
283    /// Adaptive pattern learning
284    pub adaptive_learning: bool,
285
286    /// User comfort settings
287    pub comfort_settings: HapticComfortSettings,
288}
289
290/// Haptic pattern style categories
291#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
292pub enum PatternStyle {
293    /// Minimal, subtle feedback
294    Subtle,
295
296    /// Moderate intensity
297    Moderate,
298
299    /// Strong, pronounced effects
300    Intense,
301
302    /// Musical rhythm-based
303    Musical,
304
305    /// Environmental effects
306    Environmental,
307
308    /// Narrative/storytelling effects
309    Narrative,
310}
311
312/// User comfort and safety settings for haptic feedback
313#[derive(Debug, Clone, Serialize, Deserialize)]
314pub struct HapticComfortSettings {
315    /// Maximum session duration (minutes)
316    pub max_session_duration: u32,
317
318    /// Rest interval frequency (minutes)
319    pub rest_interval: u32,
320
321    /// Intensity fade during long sessions
322    pub session_fade: bool,
323
324    /// Accessibility accommodations
325    pub accessibility: HapticAccessibilitySettings,
326}
327
328/// Accessibility settings for haptic feedback
329#[derive(Debug, Clone, Serialize, Deserialize)]
330pub struct HapticAccessibilitySettings {
331    /// Support for users with limited tactile sensitivity
332    pub enhanced_intensity: bool,
333
334    /// Visual feedback substitution
335    pub visual_substitution: bool,
336
337    /// Simplified pattern mode
338    pub simplified_patterns: bool,
339
340    /// One-handed operation support
341    pub one_handed_mode: bool,
342}
343
344/// Main haptic integration processor
345pub struct HapticAudioProcessor {
346    /// Configuration
347    config: HapticAudioConfig,
348
349    /// Connected haptic devices
350    devices: Arc<RwLock<HashMap<String, Box<dyn HapticDevice>>>>,
351
352    /// Active patterns
353    active_patterns: Arc<RwLock<HashMap<String, ActivePattern>>>,
354
355    /// Audio analysis state
356    audio_analyzer: AudioAnalyzer,
357
358    /// Pattern library
359    pattern_library: PatternLibrary,
360
361    /// Synchronization state
362    sync_state: SyncState,
363
364    /// Performance metrics
365    metrics: HapticMetrics,
366}
367
368/// Active pattern tracking
369#[derive(Debug)]
370struct ActivePattern {
371    /// Pattern definition
372    pattern: HapticPattern,
373
374    /// Start time
375    start_time: Instant,
376
377    /// Current element index
378    current_element: usize,
379
380    /// Associated audio source
381    audio_source_id: Option<String>,
382
383    /// Current spatial position
384    current_position: Option<Position3D>,
385
386    /// Intensity scaling factor
387    intensity_scale: f32,
388}
389
390/// Audio analysis for haptic generation
391struct AudioAnalyzer {
392    /// FFT analysis window
393    fft_window: Vec<f32>,
394
395    /// Previous audio frame for comparison
396    previous_frame: Vec<f32>,
397
398    /// Beat detection state
399    beat_detector: BeatDetector,
400
401    /// Transient detection state
402    transient_detector: TransientDetector,
403
404    /// Frequency analysis bins
405    frequency_bins: Vec<f32>,
406}
407
408/// Beat detection for rhythm-based haptics
409struct BeatDetector {
410    /// Energy history for beat detection
411    energy_history: Vec<f32>,
412
413    /// Current tempo estimate (BPM)
414    current_tempo: f32,
415
416    /// Beat phase tracking
417    beat_phase: f32,
418
419    /// Last beat time
420    last_beat: Instant,
421}
422
423/// Transient detection for haptic triggers
424struct TransientDetector {
425    /// Spectral flux history
426    flux_history: Vec<Vec<f32>>,
427
428    /// Detection threshold
429    threshold: f32,
430
431    /// Last transient time
432    last_transient: Instant,
433}
434
435/// Pattern library management
436struct PatternLibrary {
437    /// Built-in patterns
438    builtin_patterns: HashMap<String, HapticPattern>,
439
440    /// User-created patterns
441    user_patterns: HashMap<String, HapticPattern>,
442
443    /// Pattern usage statistics
444    usage_stats: HashMap<String, PatternUsageStats>,
445}
446
447/// Pattern usage statistics for adaptive learning
448#[derive(Debug, Clone, Serialize, Deserialize)]
449struct PatternUsageStats {
450    /// Number of times used
451    usage_count: u32,
452
453    /// Average user rating
454    average_rating: f32,
455
456    /// Contexts where pattern was effective
457    effective_contexts: Vec<String>,
458
459    /// Last used timestamp (as seconds since epoch)
460    #[serde(skip)]
461    last_used: Option<Instant>,
462}
463
464/// Synchronization state management
465struct SyncState {
466    /// Audio timeline reference
467    audio_timeline: Instant,
468
469    /// Haptic timeline reference
470    haptic_timeline: Instant,
471
472    /// Measured latency offset
473    measured_latency: Duration,
474
475    /// Prediction buffer
476    prediction_buffer: Vec<PredictedHapticEvent>,
477}
478
479/// Predicted haptic event for synchronization
480#[derive(Debug)]
481struct PredictedHapticEvent {
482    /// Event timestamp
483    timestamp: Instant,
484
485    /// Haptic pattern to trigger
486    pattern_id: String,
487
488    /// Spatial position
489    position: Option<Position3D>,
490
491    /// Intensity scaling
492    intensity: f32,
493}
494
495/// Performance metrics for haptic processing
496#[derive(Debug, Clone, Serialize, Deserialize)]
497pub struct HapticMetrics {
498    /// Processing latency (ms)
499    pub processing_latency: f32,
500
501    /// Synchronization accuracy (ms RMS error)
502    pub sync_accuracy: f32,
503
504    /// Active patterns count
505    pub active_patterns: usize,
506
507    /// Device utilization percentage
508    pub device_utilization: f32,
509
510    /// Pattern cache hit rate
511    pub cache_hit_rate: f32,
512
513    /// User satisfaction rating
514    pub user_satisfaction: f32,
515
516    /// System resource usage
517    pub resource_usage: ResourceUsage,
518}
519
520/// Resource usage tracking
521#[derive(Debug, Clone, Serialize, Deserialize)]
522pub struct ResourceUsage {
523    /// CPU usage percentage
524    pub cpu_usage: f32,
525
526    /// Memory usage (MB)
527    pub memory_usage: f32,
528
529    /// Pattern library size
530    pub pattern_library_size: usize,
531
532    /// Active device count
533    pub active_devices: usize,
534}
535
536impl Default for HapticAudioConfig {
537    fn default() -> Self {
538        Self {
539            enabled: true,
540            master_intensity: 0.7,
541            distance_attenuation: DistanceAttenuation {
542                min_distance: 0.5,
543                max_distance: 10.0,
544                curve_type: AttenuationCurve::InverseSquare,
545                curve_parameters: vec![],
546            },
547            audio_mapping: AudioHapticMapping {
548                bass_mapping: FrequencyMapping {
549                    frequency_range: (20.0, 250.0),
550                    effect_type: HapticEffectType::Rumble,
551                    intensity_scale: 1.2,
552                    frequency_scale: 0.1,
553                    activation_threshold: 0.3,
554                },
555                mid_mapping: FrequencyMapping {
556                    frequency_range: (250.0, 4000.0),
557                    effect_type: HapticEffectType::Vibration,
558                    intensity_scale: 1.0,
559                    frequency_scale: 0.5,
560                    activation_threshold: 0.2,
561                },
562                high_mapping: FrequencyMapping {
563                    frequency_range: (4000.0, 20000.0),
564                    effect_type: HapticEffectType::Click,
565                    intensity_scale: 0.8,
566                    frequency_scale: 1.0,
567                    activation_threshold: 0.4,
568                },
569                transient_settings: TransientSettings {
570                    enabled: true,
571                    sensitivity: 0.7,
572                    min_interval: 50.0,
573                    transient_effect: HapticEffectType::Click,
574                },
575                rhythm_settings: RhythmSettings {
576                    enabled: true,
577                    tempo_range: (60.0, 180.0),
578                    beat_emphasis: 1.5,
579                    downbeat_effect: Some(HapticEffectType::Pulse),
580                },
581            },
582            sync_settings: SyncSettings {
583                latency_compensation: 15.0,
584                sync_tolerance: 5.0,
585                prediction_lookahead: 100.0,
586                buffer_size: 1024,
587            },
588            pattern_preferences: PatternPreferences {
589                preferred_styles: vec![PatternStyle::Moderate, PatternStyle::Musical],
590                complexity_level: 5,
591                adaptive_learning: true,
592                comfort_settings: HapticComfortSettings {
593                    max_session_duration: 60,
594                    rest_interval: 15,
595                    session_fade: true,
596                    accessibility: HapticAccessibilitySettings {
597                        enhanced_intensity: false,
598                        visual_substitution: false,
599                        simplified_patterns: false,
600                        one_handed_mode: false,
601                    },
602                },
603            },
604        }
605    }
606}
607
608impl HapticAudioProcessor {
609    /// Create new haptic audio processor
610    pub fn new(config: HapticAudioConfig) -> Self {
611        Self {
612            config,
613            devices: Arc::new(RwLock::new(HashMap::new())),
614            active_patterns: Arc::new(RwLock::new(HashMap::new())),
615            audio_analyzer: AudioAnalyzer::new(),
616            pattern_library: PatternLibrary::new(),
617            sync_state: SyncState::new(),
618            metrics: HapticMetrics::default(),
619        }
620    }
621
622    /// Add haptic device
623    pub fn add_device(&mut self, device: Box<dyn HapticDevice>) -> Result<()> {
624        let device_id = device.device_id();
625        let mut devices = self.devices.write().map_err(|_| {
626            crate::Error::LegacyProcessing("Haptic devices lock poisoned".to_string())
627        })?;
628        devices.insert(device_id, device);
629        Ok(())
630    }
631
632    /// Remove haptic device
633    pub fn remove_device(&mut self, device_id: &str) -> Result<()> {
634        let mut devices = self.devices.write().map_err(|_| {
635            crate::Error::LegacyProcessing("Haptic devices lock poisoned".to_string())
636        })?;
637        devices.remove(device_id);
638        Ok(())
639    }
640
641    /// Process audio frame and generate haptic feedback
642    pub fn process_audio_frame(
643        &mut self,
644        audio_samples: &[f32],
645        audio_channel_type: AudioChannel,
646        spatial_positions: &[(String, Position3D)],
647        listener_position: Position3D,
648    ) -> Result<()> {
649        // Analyze audio for haptic generation
650        let haptic_events = self.audio_analyzer.analyze_frame(
651            audio_samples,
652            audio_channel_type,
653            &self.config.audio_mapping,
654        )?;
655
656        // Process spatial positioning
657        let spatial_haptic_events =
658            self.apply_spatial_processing(haptic_events, spatial_positions, listener_position)?;
659
660        // Generate haptic patterns
661        for event in spatial_haptic_events {
662            self.trigger_haptic_event(event)?;
663        }
664
665        // Update active patterns
666        self.update_active_patterns()?;
667
668        // Update metrics
669        self.update_metrics();
670
671        Ok(())
672    }
673
674    /// Manually trigger haptic pattern
675    pub fn trigger_pattern(
676        &mut self,
677        pattern_id: &str,
678        position: Option<Position3D>,
679        intensity_scale: f32,
680    ) -> Result<()> {
681        if let Some(pattern) = self.pattern_library.get_pattern(pattern_id) {
682            let active_pattern = ActivePattern {
683                pattern: pattern.clone(),
684                start_time: Instant::now(),
685                current_element: 0,
686                audio_source_id: None,
687                current_position: position,
688                intensity_scale,
689            };
690
691            let mut active_patterns = self.active_patterns.write().map_err(|_| {
692                crate::Error::LegacyProcessing("Active patterns lock poisoned".to_string())
693            })?;
694            active_patterns.insert(pattern_id.to_string(), active_pattern);
695        }
696
697        Ok(())
698    }
699
700    /// Stop all haptic feedback
701    pub fn stop_all(&mut self) -> Result<()> {
702        // Stop all devices
703        let mut devices = self.devices.write().map_err(|_| {
704            crate::Error::LegacyProcessing("Haptic devices lock poisoned".to_string())
705        })?;
706        for device in devices.values_mut() {
707            device.stop()?;
708        }
709
710        // Clear active patterns
711        let mut active_patterns = self.active_patterns.write().map_err(|_| {
712            crate::Error::LegacyProcessing("Active patterns lock poisoned".to_string())
713        })?;
714        active_patterns.clear();
715
716        Ok(())
717    }
718
719    /// Get current metrics
720    pub fn metrics(&self) -> &HapticMetrics {
721        &self.metrics
722    }
723
724    /// Update configuration
725    pub fn update_config(&mut self, config: HapticAudioConfig) {
726        self.config = config;
727    }
728
729    // Private helper methods
730
731    fn apply_spatial_processing(
732        &self,
733        events: Vec<HapticEvent>,
734        spatial_positions: &[(String, Position3D)],
735        listener_position: Position3D,
736    ) -> Result<Vec<SpatialHapticEvent>> {
737        let mut spatial_events = Vec::new();
738
739        for event in events {
740            // Find corresponding spatial position
741            if let Some((_, source_position)) = spatial_positions
742                .iter()
743                .find(|(id, _)| id == &event.source_id)
744            {
745                // Calculate distance and attenuation
746                let distance = calculate_distance(listener_position, *source_position);
747                let attenuation = self.calculate_distance_attenuation(distance);
748
749                let spatial_event = SpatialHapticEvent {
750                    base_event: event,
751                    position: *source_position,
752                    distance,
753                    attenuation,
754                };
755
756                spatial_events.push(spatial_event);
757            }
758        }
759
760        Ok(spatial_events)
761    }
762
763    fn calculate_distance_attenuation(&self, distance: f32) -> f32 {
764        let attenuation = &self.config.distance_attenuation;
765
766        if distance <= attenuation.min_distance {
767            return 1.0;
768        }
769
770        if distance >= attenuation.max_distance {
771            return 0.0;
772        }
773
774        let normalized_distance = (distance - attenuation.min_distance)
775            / (attenuation.max_distance - attenuation.min_distance);
776
777        match attenuation.curve_type {
778            AttenuationCurve::Linear => 1.0 - normalized_distance,
779            AttenuationCurve::InverseSquare => {
780                1.0 / (1.0 + normalized_distance * normalized_distance)
781            }
782            AttenuationCurve::Exponential => (-normalized_distance * 2.0).exp(),
783            AttenuationCurve::Logarithmic => (1.0 - normalized_distance).ln().abs().min(1.0),
784            AttenuationCurve::Custom => {
785                // Custom curve implementation would go here
786                1.0 - normalized_distance
787            }
788        }
789    }
790
791    fn trigger_haptic_event(&mut self, event: SpatialHapticEvent) -> Result<()> {
792        // Find appropriate pattern for the event
793        let pattern = self.pattern_library.select_pattern_for_event(&event)?;
794
795        // Apply spatial and intensity scaling
796        let mut scaled_pattern = pattern;
797        for element in &mut scaled_pattern.elements {
798            element.intensity *= event.attenuation * self.config.master_intensity;
799            element.spatial_attenuation = event.attenuation;
800        }
801
802        // Set spatial position
803        scaled_pattern.spatial_position = Some(event.position);
804
805        // Send to devices
806        let devices = self.devices.read().map_err(|_| {
807            crate::Error::LegacyProcessing("Haptic devices lock poisoned".to_string())
808        })?;
809        for device in devices.values() {
810            if device.is_ready() {
811                // Create device-specific pattern based on capabilities
812                let device_pattern =
813                    self.adapt_pattern_for_device(&scaled_pattern, device.capabilities())?;
814                // Note: would need to make device mutable or use interior mutability
815            }
816        }
817
818        Ok(())
819    }
820
821    fn adapt_pattern_for_device(
822        &self,
823        pattern: &HapticPattern,
824        capabilities: HapticCapabilities,
825    ) -> Result<HapticPattern> {
826        let mut adapted_pattern = pattern.clone();
827
828        // Filter unsupported effects
829        adapted_pattern.elements.retain(|element| {
830            capabilities
831                .supported_effects
832                .contains(&element.effect_type)
833        });
834
835        // Adjust intensity resolution
836        for element in &mut adapted_pattern.elements {
837            let resolution_step = 1.0 / capabilities.intensity_resolution as f32;
838            element.intensity = (element.intensity / resolution_step).round() * resolution_step;
839        }
840
841        // Limit concurrent effects
842        if adapted_pattern.elements.len() > capabilities.max_concurrent_effects {
843            adapted_pattern
844                .elements
845                .truncate(capabilities.max_concurrent_effects);
846        }
847
848        Ok(adapted_pattern)
849    }
850
851    fn update_active_patterns(&mut self) -> Result<()> {
852        let mut active_patterns = self.active_patterns.write().map_err(|_| {
853            crate::Error::LegacyProcessing("Active patterns lock poisoned".to_string())
854        })?;
855        let current_time = Instant::now();
856
857        // Remove completed patterns
858        active_patterns.retain(|_, pattern| {
859            let elapsed = current_time.duration_since(pattern.start_time);
860            elapsed < pattern.pattern.duration || pattern.pattern.looping
861        });
862
863        // Update pattern states
864        for pattern in active_patterns.values_mut() {
865            let elapsed = current_time.duration_since(pattern.start_time);
866
867            // Update current element index
868            while pattern.current_element < pattern.pattern.elements.len() {
869                let element = &pattern.pattern.elements[pattern.current_element];
870                if elapsed >= element.start_time {
871                    pattern.current_element += 1;
872                } else {
873                    break;
874                }
875            }
876        }
877
878        Ok(())
879    }
880
881    fn update_metrics(&mut self) {
882        let active_pattern_count = match self.active_patterns.read() {
883            Ok(patterns) => patterns.len(),
884            Err(_) => {
885                tracing::warn!("Active patterns lock poisoned, using 0");
886                0
887            }
888        };
889
890        let device_count = match self.devices.read() {
891            Ok(devices) => devices.len(),
892            Err(_) => {
893                tracing::warn!("Haptic devices lock poisoned, using 0");
894                0
895            }
896        };
897
898        self.metrics.active_patterns = active_pattern_count;
899        self.metrics.resource_usage.active_devices = device_count;
900        self.metrics.resource_usage.pattern_library_size = self.pattern_library.size();
901
902        // Update other metrics (would be implemented with actual measurements)
903        self.metrics.processing_latency = 5.0; // Placeholder
904        self.metrics.sync_accuracy = 2.0; // Placeholder
905        self.metrics.device_utilization = 65.0; // Placeholder
906        self.metrics.cache_hit_rate = 85.0; // Placeholder
907    }
908}
909
910// Helper structs for internal processing
911
912#[derive(Debug)]
913struct HapticEvent {
914    source_id: String,
915    effect_type: HapticEffectType,
916    intensity: f32,
917    frequency: Option<f32>,
918    timestamp: Instant,
919}
920
921#[derive(Debug)]
922struct SpatialHapticEvent {
923    base_event: HapticEvent,
924    position: Position3D,
925    distance: f32,
926    attenuation: f32,
927}
928
929impl Default for HapticMetrics {
930    fn default() -> Self {
931        Self {
932            processing_latency: 0.0,
933            sync_accuracy: 0.0,
934            active_patterns: 0,
935            device_utilization: 0.0,
936            cache_hit_rate: 0.0,
937            user_satisfaction: 5.0,
938            resource_usage: ResourceUsage {
939                cpu_usage: 0.0,
940                memory_usage: 0.0,
941                pattern_library_size: 0,
942                active_devices: 0,
943            },
944        }
945    }
946}
947
948impl AudioAnalyzer {
949    fn new() -> Self {
950        Self {
951            fft_window: vec![0.0; 1024],
952            previous_frame: vec![0.0; 1024],
953            beat_detector: BeatDetector::new(),
954            transient_detector: TransientDetector::new(),
955            frequency_bins: vec![0.0; 512],
956        }
957    }
958
959    fn analyze_frame(
960        &mut self,
961        audio_samples: &[f32],
962        audio_channel_type: AudioChannel,
963        mapping: &AudioHapticMapping,
964    ) -> Result<Vec<HapticEvent>> {
965        let mut events = Vec::new();
966
967        // Perform FFT analysis
968        self.perform_fft_analysis(audio_samples)?;
969
970        // Detect transients
971        if mapping.transient_settings.enabled {
972            if let Some(transient) = self.transient_detector.detect(&self.frequency_bins)? {
973                events.push(HapticEvent {
974                    source_id: format!("audio_{audio_channel_type:?}"),
975                    effect_type: mapping.transient_settings.transient_effect.clone(),
976                    intensity: transient.intensity,
977                    frequency: None,
978                    timestamp: Instant::now(),
979                });
980            }
981        }
982
983        // Detect beats
984        if mapping.rhythm_settings.enabled {
985            if let Some(beat) = self.beat_detector.detect(&self.frequency_bins)? {
986                let effect_type = if beat.is_downbeat {
987                    mapping
988                        .rhythm_settings
989                        .downbeat_effect
990                        .clone()
991                        .unwrap_or(HapticEffectType::Pulse)
992                } else {
993                    HapticEffectType::Pulse
994                };
995
996                events.push(HapticEvent {
997                    source_id: format!("audio_{audio_channel_type:?}"),
998                    effect_type,
999                    intensity: beat.intensity * mapping.rhythm_settings.beat_emphasis,
1000                    frequency: Some(beat.tempo / 60.0), // Convert BPM to Hz
1001                    timestamp: Instant::now(),
1002                });
1003            }
1004        }
1005
1006        // Analyze frequency bands
1007        self.analyze_frequency_bands(mapping, &audio_channel_type, &mut events)?;
1008
1009        Ok(events)
1010    }
1011
1012    fn perform_fft_analysis(&mut self, samples: &[f32]) -> Result<()> {
1013        // Copy samples to FFT window (simplified implementation)
1014        let copy_len = samples.len().min(self.fft_window.len());
1015        self.fft_window[..copy_len].copy_from_slice(&samples[..copy_len]);
1016
1017        // Perform FFT (simplified - would use proper FFT library)
1018        for (i, bin) in self.frequency_bins.iter_mut().enumerate() {
1019            *bin = self.fft_window[i * 2].abs(); // Simplified magnitude calculation
1020        }
1021
1022        Ok(())
1023    }
1024
1025    fn analyze_frequency_bands(
1026        &self,
1027        mapping: &AudioHapticMapping,
1028        audio_channel_type: &AudioChannel,
1029        events: &mut Vec<HapticEvent>,
1030    ) -> Result<()> {
1031        let mappings = [
1032            &mapping.bass_mapping,
1033            &mapping.mid_mapping,
1034            &mapping.high_mapping,
1035        ];
1036
1037        for frequency_mapping in mappings {
1038            let band_energy = self.calculate_band_energy(&frequency_mapping.frequency_range);
1039
1040            if band_energy > frequency_mapping.activation_threshold {
1041                events.push(HapticEvent {
1042                    source_id: format!("audio_{audio_channel_type:?}"),
1043                    effect_type: frequency_mapping.effect_type.clone(),
1044                    intensity: band_energy * frequency_mapping.intensity_scale,
1045                    frequency: Some(
1046                        (frequency_mapping.frequency_range.0 + frequency_mapping.frequency_range.1)
1047                            / 2.0
1048                            * frequency_mapping.frequency_scale,
1049                    ),
1050                    timestamp: Instant::now(),
1051                });
1052            }
1053        }
1054
1055        Ok(())
1056    }
1057
1058    fn calculate_band_energy(&self, frequency_range: &(f32, f32)) -> f32 {
1059        // Simplified band energy calculation
1060        let start_bin = (frequency_range.0 / 20000.0 * self.frequency_bins.len() as f32) as usize;
1061        let end_bin = (frequency_range.1 / 20000.0 * self.frequency_bins.len() as f32) as usize;
1062
1063        self.frequency_bins[start_bin..end_bin.min(self.frequency_bins.len())]
1064            .iter()
1065            .sum::<f32>()
1066            / (end_bin - start_bin) as f32
1067    }
1068}
1069
1070impl BeatDetector {
1071    fn new() -> Self {
1072        Self {
1073            energy_history: Vec::with_capacity(100),
1074            current_tempo: 120.0,
1075            beat_phase: 0.0,
1076            last_beat: Instant::now(),
1077        }
1078    }
1079
1080    fn detect(&mut self, frequency_bins: &[f32]) -> Result<Option<BeatEvent>> {
1081        // Calculate current energy
1082        let current_energy = frequency_bins.iter().sum::<f32>() / frequency_bins.len() as f32;
1083        self.energy_history.push(current_energy);
1084
1085        if self.energy_history.len() > 100 {
1086            self.energy_history.remove(0);
1087        }
1088
1089        // Simple beat detection based on energy peaks
1090        if self.energy_history.len() >= 3 {
1091            let len = self.energy_history.len();
1092            let current = self.energy_history[len - 1];
1093            let previous = self.energy_history[len - 2];
1094            let prev_prev = self.energy_history[len - 3];
1095
1096            // Detect peak
1097            if current > previous && previous > prev_prev && current > 0.5 {
1098                let now = Instant::now();
1099                let interval = now.duration_since(self.last_beat).as_secs_f32();
1100
1101                if interval > 0.3 {
1102                    // Minimum 200 BPM
1103                    self.last_beat = now;
1104                    self.current_tempo = 60.0 / interval;
1105
1106                    return Ok(Some(BeatEvent {
1107                        intensity: current,
1108                        tempo: self.current_tempo,
1109                        is_downbeat: self.beat_phase < 0.1, // First beat in measure
1110                    }));
1111                }
1112            }
1113        }
1114
1115        Ok(None)
1116    }
1117}
1118
1119impl TransientDetector {
1120    fn new() -> Self {
1121        Self {
1122            flux_history: Vec::with_capacity(10),
1123            threshold: 0.1,
1124            last_transient: Instant::now(),
1125        }
1126    }
1127
1128    fn detect(&mut self, frequency_bins: &[f32]) -> Result<Option<TransientEvent>> {
1129        // Calculate spectral flux (rate of change in spectrum)
1130        if !self.flux_history.is_empty() {
1131            let previous_bins = &self.flux_history[self.flux_history.len() - 1];
1132            let flux: f32 = frequency_bins
1133                .iter()
1134                .zip(previous_bins.iter())
1135                .map(|(current, previous)| (current - previous).max(0.0))
1136                .sum();
1137
1138            if flux > self.threshold {
1139                let now = Instant::now();
1140                let interval = now.duration_since(self.last_transient).as_millis() as f32;
1141
1142                if interval > 50.0 {
1143                    // Minimum 50ms between transients
1144                    self.last_transient = now;
1145                    return Ok(Some(TransientEvent {
1146                        intensity: flux.min(1.0),
1147                        flux_value: flux,
1148                    }));
1149                }
1150            }
1151        }
1152
1153        // Store current bins for next comparison
1154        self.flux_history.push(frequency_bins.to_vec());
1155        if self.flux_history.len() > 10 {
1156            self.flux_history.remove(0);
1157        }
1158
1159        Ok(None)
1160    }
1161}
1162
1163impl PatternLibrary {
1164    fn new() -> Self {
1165        let mut builtin_patterns = HashMap::new();
1166
1167        // Add some built-in patterns
1168        builtin_patterns.insert(
1169            "bass_rumble".to_string(),
1170            Self::create_bass_rumble_pattern(),
1171        );
1172        builtin_patterns.insert("click_feedback".to_string(), Self::create_click_pattern());
1173        builtin_patterns.insert("heartbeat".to_string(), Self::create_heartbeat_pattern());
1174
1175        Self {
1176            builtin_patterns,
1177            user_patterns: HashMap::new(),
1178            usage_stats: HashMap::new(),
1179        }
1180    }
1181
1182    fn get_pattern(&self, pattern_id: &str) -> Option<&HapticPattern> {
1183        self.builtin_patterns
1184            .get(pattern_id)
1185            .or_else(|| self.user_patterns.get(pattern_id))
1186    }
1187
1188    fn select_pattern_for_event(&self, event: &SpatialHapticEvent) -> Result<HapticPattern> {
1189        // Simple pattern selection logic
1190        let pattern_id = match event.base_event.effect_type {
1191            HapticEffectType::Rumble => "bass_rumble",
1192            HapticEffectType::Click => "click_feedback",
1193            HapticEffectType::Pulse => "heartbeat",
1194            _ => "click_feedback",
1195        };
1196
1197        self.get_pattern(pattern_id)
1198            .cloned()
1199            .ok_or_else(|| crate::Error::LegacyProcessing("Pattern not found".to_string()))
1200    }
1201
1202    fn size(&self) -> usize {
1203        self.builtin_patterns.len() + self.user_patterns.len()
1204    }
1205
1206    fn create_bass_rumble_pattern() -> HapticPattern {
1207        HapticPattern {
1208            id: "bass_rumble".to_string(),
1209            name: "Bass Rumble".to_string(),
1210            elements: vec![HapticElement {
1211                start_time: Duration::from_millis(0),
1212                duration: Duration::from_millis(200),
1213                effect_type: HapticEffectType::Rumble,
1214                intensity: 0.8,
1215                frequency: Some(40.0),
1216                spatial_attenuation: 1.0,
1217            }],
1218            duration: Duration::from_millis(200),
1219            looping: false,
1220            priority: 5,
1221            spatial_position: None,
1222        }
1223    }
1224
1225    fn create_click_pattern() -> HapticPattern {
1226        HapticPattern {
1227            id: "click_feedback".to_string(),
1228            name: "Click Feedback".to_string(),
1229            elements: vec![HapticElement {
1230                start_time: Duration::from_millis(0),
1231                duration: Duration::from_millis(50),
1232                effect_type: HapticEffectType::Click,
1233                intensity: 1.0,
1234                frequency: None,
1235                spatial_attenuation: 1.0,
1236            }],
1237            duration: Duration::from_millis(50),
1238            looping: false,
1239            priority: 8,
1240            spatial_position: None,
1241        }
1242    }
1243
1244    fn create_heartbeat_pattern() -> HapticPattern {
1245        HapticPattern {
1246            id: "heartbeat".to_string(),
1247            name: "Heartbeat".to_string(),
1248            elements: vec![
1249                HapticElement {
1250                    start_time: Duration::from_millis(0),
1251                    duration: Duration::from_millis(100),
1252                    effect_type: HapticEffectType::Pulse,
1253                    intensity: 0.9,
1254                    frequency: Some(2.0),
1255                    spatial_attenuation: 1.0,
1256                },
1257                HapticElement {
1258                    start_time: Duration::from_millis(150),
1259                    duration: Duration::from_millis(80),
1260                    effect_type: HapticEffectType::Pulse,
1261                    intensity: 0.7,
1262                    frequency: Some(2.0),
1263                    spatial_attenuation: 1.0,
1264                },
1265            ],
1266            duration: Duration::from_millis(800),
1267            looping: true,
1268            priority: 3,
1269            spatial_position: None,
1270        }
1271    }
1272}
1273
1274impl SyncState {
1275    fn new() -> Self {
1276        let now = Instant::now();
1277        Self {
1278            audio_timeline: now,
1279            haptic_timeline: now,
1280            measured_latency: Duration::from_millis(15),
1281            prediction_buffer: Vec::new(),
1282        }
1283    }
1284}
1285
1286// Helper structs for audio analysis
1287
1288#[derive(Debug)]
1289struct BeatEvent {
1290    intensity: f32,
1291    tempo: f32,
1292    is_downbeat: bool,
1293}
1294
1295#[derive(Debug)]
1296struct TransientEvent {
1297    intensity: f32,
1298    flux_value: f32,
1299}
1300
1301// Utility functions
1302
1303fn calculate_distance(pos1: Position3D, pos2: Position3D) -> f32 {
1304    let dx = pos1.x - pos2.x;
1305    let dy = pos1.y - pos2.y;
1306    let dz = pos1.z - pos2.z;
1307    (dx * dx + dy * dy + dz * dz).sqrt()
1308}
1309
1310#[cfg(test)]
1311mod tests {
1312    use super::*;
1313
1314    #[test]
1315    fn test_haptic_config_creation() {
1316        let config = HapticAudioConfig::default();
1317        assert!(config.enabled);
1318        assert_eq!(config.master_intensity, 0.7);
1319    }
1320
1321    #[test]
1322    fn test_pattern_creation() {
1323        let pattern = HapticPattern {
1324            id: "test".to_string(),
1325            name: "Test Pattern".to_string(),
1326            elements: vec![],
1327            duration: Duration::from_millis(100),
1328            looping: false,
1329            priority: 5,
1330            spatial_position: None,
1331        };
1332        assert_eq!(pattern.id, "test");
1333        assert_eq!(pattern.duration, Duration::from_millis(100));
1334    }
1335
1336    #[test]
1337    fn test_distance_calculation() {
1338        let pos1 = Position3D {
1339            x: 0.0,
1340            y: 0.0,
1341            z: 0.0,
1342        };
1343        let pos2 = Position3D {
1344            x: 3.0,
1345            y: 4.0,
1346            z: 0.0,
1347        };
1348        let distance = calculate_distance(pos1, pos2);
1349        assert_eq!(distance, 5.0);
1350    }
1351
1352    #[test]
1353    fn test_haptic_processor_creation() {
1354        let config = HapticAudioConfig::default();
1355        let processor = HapticAudioProcessor::new(config);
1356        assert_eq!(processor.metrics().active_patterns, 0);
1357    }
1358
1359    #[test]
1360    fn test_pattern_library() {
1361        let library = PatternLibrary::new();
1362        assert!(library.get_pattern("bass_rumble").is_some());
1363        assert!(library.get_pattern("click_feedback").is_some());
1364        assert!(library.get_pattern("heartbeat").is_some());
1365        assert!(library.get_pattern("nonexistent").is_none());
1366    }
1367
1368    #[test]
1369    fn test_distance_attenuation() {
1370        let config = HapticAudioConfig::default();
1371        let processor = HapticAudioProcessor::new(config);
1372
1373        // Test linear attenuation
1374        let attenuation_close = processor.calculate_distance_attenuation(1.0);
1375        let attenuation_far = processor.calculate_distance_attenuation(8.0);
1376
1377        assert!(attenuation_close > attenuation_far);
1378        assert!(attenuation_close <= 1.0);
1379        assert!(attenuation_far >= 0.0);
1380    }
1381
1382    #[test]
1383    fn test_effect_type_serialization() {
1384        let effect = HapticEffectType::Vibration;
1385        let serialized =
1386            serde_json::to_string(&effect).expect("Failed to serialize effect type in test");
1387        let deserialized: HapticEffectType =
1388            serde_json::from_str(&serialized).expect("Failed to deserialize effect type in test");
1389        assert_eq!(effect, deserialized);
1390    }
1391
1392    #[test]
1393    fn test_capabilities_structure() {
1394        let capabilities = HapticCapabilities {
1395            max_concurrent_effects: 4,
1396            supported_effects: vec![HapticEffectType::Vibration, HapticEffectType::Click],
1397            intensity_resolution: 256,
1398            frequency_range: Some((10.0, 1000.0)),
1399            spatial_support: true,
1400            latency_compensation: 15.0,
1401            features: HashMap::new(),
1402        };
1403
1404        assert_eq!(capabilities.max_concurrent_effects, 4);
1405        assert!(capabilities.spatial_support);
1406        assert_eq!(capabilities.supported_effects.len(), 2);
1407    }
1408}