Skip to main content

voirs_emotion/
debug.rs

1//! Debugging tools for emotion state visualization and analysis
2//!
3//! This module provides utilities for debugging emotion processing,
4//! visualizing emotion states, and analyzing emotion transitions.
5
6use crate::{
7    core::EmotionProcessor,
8    history::{EmotionHistory, EmotionHistoryEntry},
9    types::{Emotion, EmotionParameters, EmotionVector},
10    Error, Result,
11};
12
13use serde::{Deserialize, Serialize};
14use std::{
15    collections::HashMap,
16    time::{Duration, SystemTime},
17};
18use tracing::{debug, info, warn};
19
20/// Emotion state debugger for visualization and analysis
21#[derive(Debug)]
22pub struct EmotionDebugger {
23    /// Debug session configuration
24    config: DebugConfig,
25    /// Captured emotion states
26    captured_states: Vec<EmotionStateSnapshot>,
27    /// Performance metrics
28    performance_metrics: DebugPerformanceMetrics,
29    /// Recent activity window for CPU estimation (last N operations)
30    recent_operations: Vec<(std::time::Instant, Duration)>,
31    /// CPU estimation window size
32    cpu_window_size: usize,
33}
34
35/// Configuration for emotion debugging
36#[derive(Debug, Clone)]
37pub struct DebugConfig {
38    /// Enable detailed state capture
39    pub capture_detailed_states: bool,
40    /// Enable performance tracking
41    pub track_performance: bool,
42    /// Capture interval for continuous monitoring
43    pub capture_interval: Duration,
44    /// Maximum number of snapshots to keep
45    pub max_snapshots: usize,
46    /// Enable audio characteristic analysis
47    pub analyze_audio: bool,
48    /// Output format for debug data
49    pub output_format: DebugOutputFormat,
50}
51
52/// Debug output format options
53#[derive(Debug, Clone)]
54pub enum DebugOutputFormat {
55    /// Human-readable text format
56    Text,
57    /// JSON format for programmatic access
58    Json,
59    /// CSV format for spreadsheet analysis
60    Csv,
61    /// HTML format with visualization
62    Html,
63}
64
65/// Snapshot of emotion state at a specific time
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct EmotionStateSnapshot {
68    /// Timestamp of the snapshot
69    pub timestamp: SystemTime,
70    /// Current emotion parameters
71    pub emotion_parameters: EmotionParameters,
72    /// Dominant emotion and intensity
73    pub dominant_emotion: (String, f32),
74    /// All emotion values in the vector
75    pub emotion_vector_values: HashMap<String, f32>,
76    /// Processing performance metrics
77    pub performance: Option<SnapshotPerformanceMetrics>,
78    /// Audio characteristics (if available)
79    pub audio_characteristics: Option<AudioCharacteristics>,
80    /// Metadata and context
81    pub metadata: HashMap<String, String>,
82}
83
84/// Performance metrics for a single snapshot
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct SnapshotPerformanceMetrics {
87    /// Processing time in microseconds
88    pub processing_time_us: u64,
89    /// Memory usage in bytes
90    pub memory_usage_bytes: usize,
91    /// CPU usage percentage
92    pub cpu_usage_percent: f32,
93    /// Number of active interpolations
94    pub active_interpolations: usize,
95}
96
97/// Audio characteristics for debugging
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct AudioCharacteristics {
100    /// RMS energy level
101    pub rms_energy: f32,
102    /// Spectral centroid
103    pub spectral_centroid: f32,
104    /// Zero crossing rate
105    pub zero_crossing_rate: f32,
106    /// Estimated pitch
107    pub estimated_pitch: Option<f32>,
108}
109
110/// Overall performance metrics for debugging session
111#[derive(Debug, Clone)]
112pub struct DebugPerformanceMetrics {
113    /// Total processing time
114    pub total_processing_time: Duration,
115    /// Average processing time per operation
116    pub average_processing_time: Duration,
117    /// Peak memory usage
118    pub peak_memory_usage: usize,
119    /// Total number of operations
120    pub operation_count: usize,
121    /// Error count
122    pub error_count: usize,
123}
124
125impl EmotionDebugger {
126    /// Create new emotion debugger
127    pub fn new(config: DebugConfig) -> Self {
128        Self {
129            config,
130            captured_states: Vec::new(),
131            performance_metrics: DebugPerformanceMetrics::default(),
132            recent_operations: Vec::with_capacity(100),
133            cpu_window_size: 100, // Track last 100 operations for CPU estimation
134        }
135    }
136
137    /// Create debugger with default configuration
138    #[allow(clippy::should_implement_trait)]
139    pub fn default() -> Self {
140        Self::new(DebugConfig::default())
141    }
142
143    /// Capture current emotion state
144    pub async fn capture_state(
145        &mut self,
146        processor: &EmotionProcessor,
147        context: Option<&str>,
148    ) -> Result<()> {
149        let start_time = std::time::Instant::now();
150
151        debug!("Capturing emotion state for debugging");
152
153        // Get current emotion parameters
154        // Note: This is a placeholder - the actual method might be different
155        let emotion_params = EmotionParameters::neutral(); // placeholder
156        let dominant_emotion = emotion_params
157            .emotion_vector
158            .dominant_emotion()
159            .map(|(e, i)| (e.as_str().to_string(), i.value()))
160            .unwrap_or(("neutral".to_string(), 0.0));
161
162        // Extract emotion vector values
163        let emotion_vector_values =
164            self.extract_emotion_vector_values(&emotion_params.emotion_vector);
165
166        // Capture performance metrics if enabled
167        let performance = if self.config.track_performance {
168            Some(SnapshotPerformanceMetrics {
169                processing_time_us: start_time.elapsed().as_micros() as u64,
170                memory_usage_bytes: self.estimate_memory_usage(),
171                cpu_usage_percent: self.estimate_cpu_usage(),
172                active_interpolations: 0, // Placeholder - would need processor access
173            })
174        } else {
175            None
176        };
177
178        // Create metadata
179        let mut metadata = HashMap::new();
180        metadata.insert("context".to_string(), context.unwrap_or("none").to_string());
181        metadata.insert("capture_method".to_string(), "manual".to_string());
182
183        // Create snapshot
184        let snapshot = EmotionStateSnapshot {
185            timestamp: SystemTime::now(),
186            emotion_parameters: emotion_params,
187            dominant_emotion,
188            emotion_vector_values,
189            performance,
190            audio_characteristics: None, // Would be filled with actual audio analysis
191            metadata,
192        };
193
194        // Add to captured states
195        self.captured_states.push(snapshot);
196
197        // Limit snapshot count
198        if self.captured_states.len() > self.config.max_snapshots {
199            self.captured_states.remove(0);
200        }
201
202        // Update performance metrics
203        let processing_time = start_time.elapsed();
204        self.performance_metrics.operation_count += 1;
205        self.performance_metrics.total_processing_time += processing_time;
206
207        // Record operation for CPU usage tracking
208        self.record_operation(processing_time);
209
210        debug!("Emotion state captured successfully");
211        Ok(())
212    }
213
214    /// Start continuous state monitoring
215    pub async fn start_continuous_monitoring(
216        &mut self,
217        processor: &EmotionProcessor,
218    ) -> Result<()> {
219        info!("Starting continuous emotion state monitoring");
220
221        // This would run in a background task in a real implementation
222        // For now, we'll just capture the current state
223        self.capture_state(processor, Some("continuous_monitoring"))
224            .await?;
225
226        Ok(())
227    }
228
229    /// Analyze emotion state transitions
230    pub fn analyze_transitions(&self) -> EmotionTransitionAnalysis {
231        debug!("Analyzing emotion transitions");
232
233        let mut transitions = Vec::new();
234        let mut emotion_durations = HashMap::new();
235        let mut transition_frequencies = HashMap::new();
236
237        for window in self.captured_states.windows(2) {
238            let prev_state = &window[0];
239            let curr_state = &window[1];
240
241            let prev_emotion = &prev_state.dominant_emotion.0;
242            let curr_emotion = &curr_state.dominant_emotion.0;
243
244            if prev_emotion != curr_emotion {
245                let transition = EmotionTransition {
246                    from_emotion: prev_emotion.clone(),
247                    to_emotion: curr_emotion.clone(),
248                    from_intensity: prev_state.dominant_emotion.1,
249                    to_intensity: curr_state.dominant_emotion.1,
250                    duration: curr_state
251                        .timestamp
252                        .duration_since(prev_state.timestamp)
253                        .unwrap_or(Duration::from_millis(0)),
254                    timestamp: curr_state.timestamp,
255                };
256
257                transitions.push(transition);
258
259                // Track transition frequency
260                let transition_key = format!("{}->{}", prev_emotion, curr_emotion);
261                *transition_frequencies.entry(transition_key).or_insert(0) += 1;
262            }
263
264            // Track emotion duration
265            let duration = curr_state
266                .timestamp
267                .duration_since(prev_state.timestamp)
268                .unwrap_or(Duration::from_millis(0));
269            emotion_durations
270                .entry(prev_emotion.clone())
271                .and_modify(|d: &mut Duration| *d += duration)
272                .or_insert(duration);
273        }
274
275        EmotionTransitionAnalysis {
276            transitions,
277            emotion_durations,
278            transition_frequencies,
279            total_transitions: self.captured_states.len().saturating_sub(1),
280            analysis_timestamp: SystemTime::now(),
281        }
282    }
283
284    /// Generate debug report
285    pub fn generate_debug_report(&self) -> Result<String> {
286        match self.config.output_format {
287            DebugOutputFormat::Text => self.generate_text_report(),
288            DebugOutputFormat::Json => self.generate_json_report(),
289            DebugOutputFormat::Csv => self.generate_csv_report(),
290            DebugOutputFormat::Html => self.generate_html_report(),
291        }
292    }
293
294    /// Clear captured states
295    pub fn clear_captured_states(&mut self) {
296        self.captured_states.clear();
297        debug!("Captured states cleared");
298    }
299
300    /// Get captured states count
301    pub fn captured_states_count(&self) -> usize {
302        self.captured_states.len()
303    }
304
305    /// Get performance metrics
306    pub fn get_performance_metrics(&self) -> &DebugPerformanceMetrics {
307        &self.performance_metrics
308    }
309
310    // Helper methods
311
312    fn extract_emotion_vector_values(&self, vector: &EmotionVector) -> HashMap<String, f32> {
313        let mut values = HashMap::new();
314
315        // Get all emotion values from the vector
316        // This is a simplified implementation - real implementation would
317        // iterate through all emotions in the vector
318        if let Some((emotion, intensity)) = vector.dominant_emotion() {
319            values.insert(emotion.as_str().to_string(), intensity.value());
320        }
321
322        values
323    }
324
325    fn estimate_memory_usage(&self) -> usize {
326        // Rough estimate of memory usage
327        std::mem::size_of::<EmotionDebugger>()
328            + self.captured_states.len() * std::mem::size_of::<EmotionStateSnapshot>()
329    }
330
331    fn estimate_cpu_usage(&self) -> f32 {
332        // Estimate CPU usage based on recent processing activity
333        // This is a relative metric: (time spent processing / wall clock time) * 100
334
335        if self.recent_operations.is_empty() {
336            return 0.0;
337        }
338
339        // Calculate time window
340        let now = std::time::Instant::now();
341        let window_start = self
342            .recent_operations
343            .first()
344            .expect("recent_operations should not be empty at this point")
345            .0;
346        let wall_clock_time = now.duration_since(window_start);
347
348        // Sum up processing time in window
349        let total_processing_time: Duration = self
350            .recent_operations
351            .iter()
352            .map(|(_, duration)| *duration)
353            .sum();
354
355        // Calculate percentage (capped at 100%)
356        if wall_clock_time.as_secs_f32() > 0.0 {
357            (total_processing_time.as_secs_f32() / wall_clock_time.as_secs_f32() * 100.0).min(100.0)
358        } else {
359            0.0
360        }
361    }
362
363    /// Record an operation for CPU usage tracking
364    fn record_operation(&mut self, processing_time: Duration) {
365        let now = std::time::Instant::now();
366        self.recent_operations.push((now, processing_time));
367
368        // Keep only recent operations within window
369        if self.recent_operations.len() > self.cpu_window_size {
370            self.recent_operations.remove(0);
371        }
372    }
373
374    fn generate_text_report(&self) -> Result<String> {
375        let mut report = String::new();
376
377        report.push_str("=== EMOTION DEBUG REPORT ===\n\n");
378
379        // Summary
380        report.push_str(&format!(
381            "Captured States: {}\n",
382            self.captured_states.len()
383        ));
384        report.push_str(&format!(
385            "Total Operations: {}\n",
386            self.performance_metrics.operation_count
387        ));
388        report.push_str(&format!(
389            "Average Processing Time: {:?}\n",
390            self.performance_metrics.average_processing_time
391        ));
392        report.push('\n');
393
394        // Recent states
395        report.push_str("Recent Emotion States:\n");
396        for (i, state) in self.captured_states.iter().rev().take(10).enumerate() {
397            report.push_str(&format!(
398                "  {}: {} ({:.2}) at {:?}\n",
399                i + 1,
400                state.dominant_emotion.0,
401                state.dominant_emotion.1,
402                state.timestamp
403            ));
404        }
405
406        // Transition analysis
407        let transition_analysis = self.analyze_transitions();
408        report.push_str("\nTransition Analysis:\n");
409        report.push_str(&format!(
410            "  Total Transitions: {}\n",
411            transition_analysis.total_transitions
412        ));
413
414        for (transition_key, frequency) in transition_analysis.transition_frequencies.iter().take(5)
415        {
416            report.push_str(&format!("  {}: {} times\n", transition_key, frequency));
417        }
418
419        Ok(report)
420    }
421
422    fn generate_json_report(&self) -> Result<String> {
423        let report_data = serde_json::json!({
424            "summary": {
425                "captured_states": self.captured_states.len(),
426                "operation_count": self.performance_metrics.operation_count,
427                "total_processing_time_ms": self.performance_metrics.total_processing_time.as_millis(),
428            },
429            "captured_states": self.captured_states,
430            "transition_analysis": self.analyze_transitions(),
431            "performance_metrics": {
432                "peak_memory_usage": self.performance_metrics.peak_memory_usage,
433                "error_count": self.performance_metrics.error_count,
434            }
435        });
436
437        serde_json::to_string_pretty(&report_data).map_err(Error::Serialization)
438    }
439
440    fn generate_csv_report(&self) -> Result<String> {
441        let mut csv = String::new();
442
443        // CSV header
444        csv.push_str("timestamp,emotion,intensity,pitch_shift,tempo_scale,energy_scale\n");
445
446        // CSV data
447        for state in &self.captured_states {
448            csv.push_str(&format!(
449                "{:?},{},{},{},{},{}\n",
450                state.timestamp,
451                state.dominant_emotion.0,
452                state.dominant_emotion.1,
453                state.emotion_parameters.pitch_shift,
454                state.emotion_parameters.tempo_scale,
455                state.emotion_parameters.energy_scale
456            ));
457        }
458
459        Ok(csv)
460    }
461
462    fn generate_html_report(&self) -> Result<String> {
463        let mut html = String::new();
464
465        html.push_str(
466            "<!DOCTYPE html><html><head><title>Emotion Debug Report</title></head><body>",
467        );
468        html.push_str("<h1>Emotion Debug Report</h1>");
469
470        // Summary section
471        html.push_str("<h2>Summary</h2>");
472        html.push_str(&format!(
473            "<p>Captured States: {}</p>",
474            self.captured_states.len()
475        ));
476        html.push_str(&format!(
477            "<p>Total Operations: {}</p>",
478            self.performance_metrics.operation_count
479        ));
480
481        // States table
482        html.push_str("<h2>Recent Emotion States</h2>");
483        html.push_str(
484            "<table border='1'><tr><th>Timestamp</th><th>Emotion</th><th>Intensity</th></tr>",
485        );
486
487        for state in self.captured_states.iter().rev().take(20) {
488            html.push_str(&format!(
489                "<tr><td>{:?}</td><td>{}</td><td>{:.2}</td></tr>",
490                state.timestamp, state.dominant_emotion.0, state.dominant_emotion.1
491            ));
492        }
493
494        html.push_str("</table>");
495        html.push_str("</body></html>");
496
497        Ok(html)
498    }
499}
500
501/// Analysis of emotion transitions
502#[derive(Debug, Clone, Serialize, Deserialize)]
503pub struct EmotionTransitionAnalysis {
504    /// List of all transitions
505    pub transitions: Vec<EmotionTransition>,
506    /// Duration spent in each emotion
507    pub emotion_durations: HashMap<String, Duration>,
508    /// Frequency of each transition type
509    pub transition_frequencies: HashMap<String, usize>,
510    /// Total number of transitions
511    pub total_transitions: usize,
512    /// When the analysis was performed
513    pub analysis_timestamp: SystemTime,
514}
515
516/// Individual emotion transition
517#[derive(Debug, Clone, Serialize, Deserialize)]
518pub struct EmotionTransition {
519    /// Source emotion
520    pub from_emotion: String,
521    /// Target emotion
522    pub to_emotion: String,
523    /// Source intensity
524    pub from_intensity: f32,
525    /// Target intensity
526    pub to_intensity: f32,
527    /// Transition duration
528    #[serde(with = "duration_serde")]
529    pub duration: Duration,
530    /// When the transition occurred
531    pub timestamp: SystemTime,
532}
533
534/// Serde helper for Duration serialization
535mod duration_serde {
536    use serde::{Deserialize, Deserializer, Serializer};
537    use std::time::Duration;
538
539    pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
540    where
541        S: Serializer,
542    {
543        serializer.serialize_u64(duration.as_millis() as u64)
544    }
545
546    pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
547    where
548        D: Deserializer<'de>,
549    {
550        let millis = u64::deserialize(deserializer)?;
551        Ok(Duration::from_millis(millis))
552    }
553}
554
555impl Default for DebugConfig {
556    fn default() -> Self {
557        Self {
558            capture_detailed_states: true,
559            track_performance: true,
560            capture_interval: Duration::from_secs(1),
561            max_snapshots: 1000,
562            analyze_audio: false,
563            output_format: DebugOutputFormat::Text,
564        }
565    }
566}
567
568impl Default for DebugPerformanceMetrics {
569    fn default() -> Self {
570        Self {
571            total_processing_time: Duration::from_secs(0),
572            average_processing_time: Duration::from_secs(0),
573            peak_memory_usage: 0,
574            operation_count: 0,
575            error_count: 0,
576        }
577    }
578}
579
580#[cfg(test)]
581mod tests {
582    use super::*;
583
584    #[tokio::test]
585    async fn test_debugger_creation() {
586        let debugger = EmotionDebugger::default();
587        assert_eq!(debugger.captured_states_count(), 0);
588        assert_eq!(debugger.get_performance_metrics().operation_count, 0);
589    }
590
591    #[tokio::test]
592    async fn test_state_capture() {
593        let mut debugger = EmotionDebugger::default();
594        let processor = crate::core::EmotionProcessor::new().unwrap();
595
596        // Capture initial state
597        debugger
598            .capture_state(&processor, Some("test"))
599            .await
600            .unwrap();
601        assert_eq!(debugger.captured_states_count(), 1);
602
603        // Capture another state
604        debugger
605            .capture_state(&processor, Some("test2"))
606            .await
607            .unwrap();
608        assert_eq!(debugger.captured_states_count(), 2);
609    }
610
611    #[tokio::test]
612    async fn test_debug_config_default() {
613        let config = DebugConfig::default();
614        assert!(config.capture_detailed_states);
615        assert!(config.track_performance);
616        assert_eq!(config.max_snapshots, 1000);
617    }
618
619    #[tokio::test]
620    async fn test_transition_analysis() {
621        let mut debugger = EmotionDebugger::default();
622        let processor = crate::core::EmotionProcessor::new().unwrap();
623
624        // Capture several states
625        for _ in 0..5 {
626            debugger
627                .capture_state(&processor, Some("test"))
628                .await
629                .unwrap();
630        }
631
632        let analysis = debugger.analyze_transitions();
633        assert!(analysis.total_transitions <= 4); // At most 4 transitions for 5 states
634    }
635
636    #[test]
637    fn test_debug_output_formats() {
638        let debugger = EmotionDebugger::default();
639
640        // Test text report generation
641        let text_report = debugger.generate_text_report().unwrap();
642        assert!(text_report.contains("EMOTION DEBUG REPORT"));
643
644        // Test JSON report generation
645        let json_report = debugger.generate_json_report().unwrap();
646        assert!(json_report.contains("captured_states"));
647
648        // Test CSV report generation
649        let csv_report = debugger.generate_csv_report().unwrap();
650        assert!(csv_report.contains("timestamp,emotion,intensity"));
651
652        // Test HTML report generation
653        let html_report = debugger.generate_html_report().unwrap();
654        assert!(html_report.contains("<html>"));
655        assert!(html_report.contains("Emotion Debug Report"));
656    }
657
658    #[test]
659    fn test_clear_captured_states() {
660        let mut debugger = EmotionDebugger::default();
661
662        // Manually add a state for testing
663        let snapshot = EmotionStateSnapshot {
664            timestamp: SystemTime::now(),
665            emotion_parameters: crate::types::EmotionParameters::neutral(),
666            dominant_emotion: ("neutral".to_string(), 0.5),
667            emotion_vector_values: HashMap::new(),
668            performance: None,
669            audio_characteristics: None,
670            metadata: HashMap::new(),
671        };
672
673        debugger.captured_states.push(snapshot);
674        assert_eq!(debugger.captured_states_count(), 1);
675
676        debugger.clear_captured_states();
677        assert_eq!(debugger.captured_states_count(), 0);
678    }
679
680    #[test]
681    fn test_memory_and_cpu_estimation() {
682        let debugger = EmotionDebugger::default();
683
684        let memory_usage = debugger.estimate_memory_usage();
685        assert!(memory_usage > 0);
686
687        let cpu_usage = debugger.estimate_cpu_usage();
688        assert!(cpu_usage >= 0.0 && cpu_usage <= 100.0);
689    }
690}