Skip to main content

voirs_spatial/visual_audio/
processor.rs

1//! Main visual audio processor implementation
2
3use super::analyzer::VisualAudioAnalyzer;
4use super::config::VisualAudioConfig;
5use super::effects::VisualEffectLibrary;
6use super::mapping::ScalingCurve;
7use super::sync::VisualSyncState;
8use super::types::{
9    DirectionZone, SpatialVisualEvent, VisualAudioMetrics, VisualDisplay, VisualEffect, VisualEvent,
10};
11use crate::{types::AudioChannel, Position3D, Result};
12use std::collections::HashMap;
13use std::sync::{Arc, RwLock};
14use std::time::Instant;
15
16/// Main visual audio integration processor
17pub struct VisualAudioProcessor {
18    /// Configuration
19    config: VisualAudioConfig,
20
21    /// Connected visual displays
22    displays: Arc<RwLock<HashMap<String, Box<dyn VisualDisplay>>>>,
23
24    /// Active visual effects
25    active_effects: Arc<RwLock<HashMap<String, ActiveVisualEffect>>>,
26
27    /// Audio analysis for visual generation
28    audio_analyzer: VisualAudioAnalyzer,
29
30    /// Effect library
31    effect_library: VisualEffectLibrary,
32
33    /// Synchronization state
34    sync_state: VisualSyncState,
35
36    /// Performance metrics
37    metrics: VisualAudioMetrics,
38}
39
40/// Active visual effect tracking
41#[derive(Debug)]
42struct ActiveVisualEffect {
43    /// Effect definition
44    effect: VisualEffect,
45
46    /// Start time
47    start_time: Instant,
48
49    /// Current element index
50    current_element: usize,
51
52    /// Associated audio source
53    audio_source_id: Option<String>,
54
55    /// Current 3D position
56    current_position: Position3D,
57
58    /// Intensity scaling factor
59    intensity_scale: f32,
60
61    /// Distance from listener
62    distance: f32,
63}
64
65impl VisualAudioProcessor {
66    /// Create new visual audio processor
67    pub fn new(config: VisualAudioConfig) -> Self {
68        Self {
69            config,
70            displays: Arc::new(RwLock::new(HashMap::new())),
71            active_effects: Arc::new(RwLock::new(HashMap::new())),
72            audio_analyzer: VisualAudioAnalyzer::new(),
73            effect_library: VisualEffectLibrary::new(),
74            sync_state: VisualSyncState::new(),
75            metrics: VisualAudioMetrics::default(),
76        }
77    }
78
79    /// Add visual display
80    pub fn add_display(&mut self, display: Box<dyn VisualDisplay>) -> Result<()> {
81        let display_id = display.display_id();
82        let mut displays = self.displays.write().map_err(|e| {
83            crate::Error::LegacyProcessing(format!(
84                "Failed to acquire write lock on displays: {}",
85                e
86            ))
87        })?;
88        displays.insert(display_id, display);
89        Ok(())
90    }
91
92    /// Remove visual display
93    pub fn remove_display(&mut self, display_id: &str) -> Result<()> {
94        let mut displays = self.displays.write().map_err(|e| {
95            crate::Error::LegacyProcessing(format!(
96                "Failed to acquire write lock on displays: {}",
97                e
98            ))
99        })?;
100        displays.remove(display_id);
101        Ok(())
102    }
103
104    /// Process audio frame and generate visual effects
105    pub fn process_audio_frame(
106        &mut self,
107        audio_samples: &[f32],
108        audio_channel_type: AudioChannel,
109        spatial_positions: &[(String, Position3D)],
110        listener_position: Position3D,
111    ) -> Result<()> {
112        if !self.config.enabled {
113            return Ok(());
114        }
115
116        // Analyze audio for visual generation
117        let visual_events = self.audio_analyzer.analyze_frame(
118            audio_samples,
119            audio_channel_type,
120            &self.config.audio_mapping,
121        )?;
122
123        // Process spatial positioning for visual effects
124        let spatial_visual_events =
125            self.apply_spatial_processing(visual_events, spatial_positions, listener_position)?;
126
127        // Generate and trigger visual effects
128        for event in spatial_visual_events {
129            self.trigger_visual_event(event)?;
130        }
131
132        // Update active effects
133        self.update_active_effects()?;
134
135        // Render effects to displays
136        self.render_to_displays()?;
137
138        // Update metrics
139        self.update_metrics();
140
141        Ok(())
142    }
143
144    /// Manually trigger visual effect
145    pub fn trigger_effect(
146        &mut self,
147        effect_id: &str,
148        position: Position3D,
149        intensity_scale: f32,
150    ) -> Result<()> {
151        if let Some(effect) = self.effect_library.get_effect(effect_id) {
152            let active_effect = ActiveVisualEffect {
153                effect: effect.clone(),
154                start_time: Instant::now(),
155                current_element: 0,
156                audio_source_id: None,
157                current_position: position,
158                intensity_scale,
159                distance: calculate_distance(
160                    position,
161                    Position3D {
162                        x: 0.0,
163                        y: 0.0,
164                        z: 0.0,
165                    },
166                ),
167            };
168
169            let mut active_effects = self.active_effects.write().map_err(|e| {
170                crate::Error::LegacyProcessing(format!(
171                    "Failed to acquire write lock on active_effects: {}",
172                    e
173                ))
174            })?;
175            active_effects.insert(effect_id.to_string(), active_effect);
176        }
177
178        Ok(())
179    }
180
181    /// Clear all visual effects
182    pub fn clear_all_effects(&mut self) -> Result<()> {
183        // Clear active effects
184        let mut active_effects = self.active_effects.write().map_err(|e| {
185            crate::Error::LegacyProcessing(format!(
186                "Failed to acquire write lock on active_effects: {}",
187                e
188            ))
189        })?;
190        active_effects.clear();
191
192        // Clear all displays
193        let mut displays = self.displays.write().map_err(|e| {
194            crate::Error::LegacyProcessing(format!(
195                "Failed to acquire write lock on displays: {}",
196                e
197            ))
198        })?;
199        for display in displays.values_mut() {
200            display.clear_all()?;
201        }
202
203        Ok(())
204    }
205
206    /// Get current metrics
207    pub fn metrics(&self) -> &VisualAudioMetrics {
208        &self.metrics
209    }
210
211    /// Update configuration
212    pub fn update_config(&mut self, config: VisualAudioConfig) {
213        self.config = config;
214    }
215
216    // Private helper methods
217
218    fn apply_spatial_processing(
219        &self,
220        events: Vec<VisualEvent>,
221        spatial_positions: &[(String, Position3D)],
222        listener_position: Position3D,
223    ) -> Result<Vec<SpatialVisualEvent>> {
224        let mut spatial_events = Vec::new();
225
226        for event in events {
227            // Find corresponding spatial position or use default
228            let source_position = spatial_positions
229                .iter()
230                .find(|(id, _)| id == &event.source_id)
231                .map(|(_, pos)| *pos)
232                .unwrap_or(Position3D {
233                    x: 0.0,
234                    y: 0.0,
235                    z: 0.0,
236                });
237
238            // Calculate distance and attenuation
239            let distance = calculate_distance(listener_position, source_position);
240            let attenuation = self.calculate_visual_distance_attenuation(distance);
241
242            let spatial_event = SpatialVisualEvent {
243                base_event: event,
244                position: source_position,
245                distance,
246                attenuation,
247                direction_zone: self.calculate_direction_zone(listener_position, source_position),
248            };
249
250            spatial_events.push(spatial_event);
251        }
252
253        Ok(spatial_events)
254    }
255
256    pub(crate) fn calculate_visual_distance_attenuation(&self, distance: f32) -> f32 {
257        let attenuation = &self.config.distance_attenuation;
258
259        if distance <= attenuation.min_distance {
260            return 1.0;
261        }
262
263        if distance >= attenuation.max_distance {
264            return 0.0;
265        }
266
267        let normalized_distance = (distance - attenuation.min_distance)
268            / (attenuation.max_distance - attenuation.min_distance);
269
270        match attenuation.curve_type {
271            ScalingCurve::Linear => 1.0 - normalized_distance,
272            ScalingCurve::Logarithmic => (1.0 - normalized_distance).ln().abs().min(1.0),
273            ScalingCurve::Exponential => (-normalized_distance * 2.0).exp(),
274            ScalingCurve::Power(p) => (1.0 - normalized_distance).powf(p),
275            ScalingCurve::Custom => 1.0 - normalized_distance, // Fallback
276        }
277    }
278
279    pub(crate) fn calculate_direction_zone(
280        &self,
281        listener_pos: Position3D,
282        source_pos: Position3D,
283    ) -> DirectionZone {
284        let dx = source_pos.x - listener_pos.x;
285        let dy = source_pos.y - listener_pos.y;
286        let dz = source_pos.z - listener_pos.z;
287
288        // Calculate azimuth angle (Y is forward, X is right)
289        let azimuth = dx.atan2(dy).to_degrees();
290        let normalized_azimuth = if azimuth < 0.0 {
291            azimuth + 360.0
292        } else {
293            azimuth
294        };
295
296        // Check elevation first
297        let elevation = dz.atan2((dx * dx + dy * dy).sqrt()).to_degrees();
298        if elevation > 45.0 {
299            return DirectionZone::Above;
300        } else if elevation < -45.0 {
301            return DirectionZone::Below;
302        }
303
304        // Determine horizontal zone
305        match normalized_azimuth {
306            a if a >= 315.0 || a < 45.0 => DirectionZone::Front,
307            a if a >= 45.0 && a < 135.0 => DirectionZone::Right,
308            a if a >= 135.0 && a < 225.0 => DirectionZone::Back,
309            a if a >= 225.0 && a < 315.0 => DirectionZone::Left,
310            _ => DirectionZone::Front,
311        }
312    }
313
314    fn trigger_visual_event(&mut self, event: SpatialVisualEvent) -> Result<()> {
315        // Select appropriate visual effect for the event
316        let effect = self.effect_library.select_effect_for_event(&event)?;
317
318        // Apply spatial and intensity scaling
319        let mut scaled_effect = effect;
320        for element in &mut scaled_effect.elements {
321            element.intensity *= event.attenuation * self.config.master_intensity;
322            element.distance_attenuation = event.attenuation;
323
324            // Apply directional color coding if enabled
325            if self.config.audio_mapping.directional_cues.enabled {
326                if let Some(direction_color) = self
327                    .config
328                    .audio_mapping
329                    .directional_cues
330                    .direction_colors
331                    .get(&event.direction_zone)
332                {
333                    // Blend with original color
334                    element.color.r = (element.color.r + direction_color.r) * 0.5;
335                    element.color.g = (element.color.g + direction_color.g) * 0.5;
336                    element.color.b = (element.color.b + direction_color.b) * 0.5;
337                }
338            }
339        }
340
341        // Set spatial position
342        scaled_effect.position = event.position;
343
344        // Create active effect
345        let active_effect = ActiveVisualEffect {
346            effect: scaled_effect,
347            start_time: Instant::now(),
348            current_element: 0,
349            audio_source_id: Some(event.base_event.source_id.clone()),
350            current_position: event.position,
351            intensity_scale: event.attenuation * self.config.master_intensity,
352            distance: event.distance,
353        };
354
355        // Add to active effects
356        let mut active_effects = self.active_effects.write().map_err(|e| {
357            crate::Error::LegacyProcessing(format!(
358                "Failed to acquire write lock on active_effects: {}",
359                e
360            ))
361        })?;
362        let effect_id = format!(
363            "{}_{}",
364            event.base_event.source_id,
365            active_effect.start_time.elapsed().as_millis()
366        );
367        active_effects.insert(effect_id, active_effect);
368
369        Ok(())
370    }
371
372    fn update_active_effects(&mut self) -> Result<()> {
373        let mut active_effects = self.active_effects.write().map_err(|e| {
374            crate::Error::LegacyProcessing(format!(
375                "Failed to acquire write lock on active_effects: {}",
376                e
377            ))
378        })?;
379        let current_time = Instant::now();
380
381        // Remove completed effects
382        active_effects.retain(|_, effect| {
383            let elapsed = current_time.duration_since(effect.start_time);
384            elapsed < effect.effect.duration || effect.effect.looping
385        });
386
387        // Update effect states
388        for effect in active_effects.values_mut() {
389            let elapsed = current_time.duration_since(effect.start_time);
390
391            // Update current element index
392            while effect.current_element < effect.effect.elements.len() {
393                let element = &effect.effect.elements[effect.current_element];
394                if elapsed >= element.start_time {
395                    effect.current_element += 1;
396                } else {
397                    break;
398                }
399            }
400        }
401
402        Ok(())
403    }
404
405    fn render_to_displays(&mut self) -> Result<()> {
406        let active_effects = self.active_effects.read().map_err(|e| {
407            crate::Error::LegacyProcessing(format!(
408                "Failed to acquire read lock on active_effects: {}",
409                e
410            ))
411        })?;
412        let mut displays = self.displays.write().map_err(|e| {
413            crate::Error::LegacyProcessing(format!(
414                "Failed to acquire write lock on displays: {}",
415                e
416            ))
417        })?;
418
419        // Render to each display
420        for display in displays.values_mut() {
421            if !display.is_ready() {
422                continue;
423            }
424
425            // Clear previous frame
426            display.clear_all()?;
427
428            // Render active effects
429            for effect in active_effects.values() {
430                display.render_effect(&effect.effect)?;
431            }
432
433            // Update display
434            display.update()?;
435        }
436
437        Ok(())
438    }
439
440    fn update_metrics(&mut self) {
441        if let Ok(active_effects) = self.active_effects.read() {
442            if let Ok(displays) = self.displays.read() {
443                self.metrics.active_effects = active_effects.len();
444                self.metrics.resource_usage.active_displays = displays.len();
445                self.metrics.resource_usage.effect_library_size = self.effect_library.size();
446
447                // Update other metrics (would be implemented with actual measurements)
448                self.metrics.processing_latency = 8.0; // Placeholder
449                self.metrics.sync_accuracy = 3.0; // Placeholder
450                self.metrics.frame_rate = 60.0; // Placeholder
451                self.metrics.gpu_utilization = 45.0; // Placeholder
452                self.metrics.cache_hit_rate = 90.0; // Placeholder
453            }
454        }
455    }
456}
457
458// Utility functions
459
460/// Calculate distance between two 3D positions
461pub(crate) fn calculate_distance(pos1: Position3D, pos2: Position3D) -> f32 {
462    let dx = pos1.x - pos2.x;
463    let dy = pos1.y - pos2.y;
464    let dz = pos1.z - pos2.z;
465    (dx * dx + dy * dy + dz * dz).sqrt()
466}