rustmotion_core/engine/renderer/
audio_analysis.rs1use dashmap::DashMap;
2use std::sync::{Arc, OnceLock};
3
4#[derive(Debug, Clone)]
6pub struct AudioAnalysis {
7 pub frame_rate: u32,
9 pub amplitude: Vec<f32>,
11 pub bands: Vec<[f32; 16]>,
13 pub start: f64,
22 pub end: Option<f64>,
25}
26
27impl AudioAnalysis {
28 fn track_time(&self, time: f64) -> Option<f64> {
32 if time < self.start {
33 return None;
34 }
35 if let Some(end) = self.end {
36 if time >= end {
37 return None;
38 }
39 }
40 Some(time - self.start)
41 }
42
43 pub fn amplitude_at(&self, time: f64) -> f32 {
45 let Some(time) = self.track_time(time) else {
46 return 0.0;
47 };
48 let idx = (time * self.frame_rate as f64) as usize;
49 self.amplitude
50 .get(idx.min(self.amplitude.len().saturating_sub(1)))
51 .copied()
52 .unwrap_or(0.0)
53 }
54
55 pub fn band_at(&self, time: f64, band: u8) -> f32 {
57 let Some(time) = self.track_time(time) else {
58 return 0.0;
59 };
60 let idx = (time * self.frame_rate as f64) as usize;
61 let idx = idx.min(self.bands.len().saturating_sub(1));
62 self.bands
63 .get(idx)
64 .map(|b| b[band.min(15) as usize])
65 .unwrap_or(0.0)
66 }
67
68 pub fn amplitude_smoothed(&self, time: f64, smoothing_frames: u32) -> f32 {
70 if smoothing_frames == 0 || self.amplitude.is_empty() {
71 return self.amplitude_at(time);
72 }
73 let Some(time) = self.track_time(time) else {
74 return 0.0;
75 };
76 let end_idx = (time * self.frame_rate as f64) as usize;
77 let end_idx = end_idx.min(self.amplitude.len().saturating_sub(1));
78 let start_idx = end_idx.saturating_sub(smoothing_frames as usize);
79 let window = &self.amplitude[start_idx..=end_idx];
80 if window.is_empty() {
81 return 0.0;
82 }
83 window.iter().sum::<f32>() / window.len() as f32
84 }
85
86 pub fn band_smoothed(&self, time: f64, band: u8, smoothing_frames: u32) -> f32 {
88 if smoothing_frames == 0 || self.bands.is_empty() {
89 return self.band_at(time, band);
90 }
91 let Some(time) = self.track_time(time) else {
92 return 0.0;
93 };
94 let end_idx = (time * self.frame_rate as f64) as usize;
95 let end_idx = end_idx.min(self.bands.len().saturating_sub(1));
96 let start_idx = end_idx.saturating_sub(smoothing_frames as usize);
97 let window = &self.bands[start_idx..=end_idx];
98 if window.is_empty() {
99 return 0.0;
100 }
101 window.iter().map(|b| b[band.min(15) as usize]).sum::<f32>() / window.len() as f32
102 }
103}
104
105type AudioCacheMap = Arc<DashMap<String, Arc<AudioAnalysis>>>;
106
107static AUDIO_ANALYSIS_CACHE: OnceLock<AudioCacheMap> = OnceLock::new();
108
109pub fn audio_analysis_cache() -> &'static AudioCacheMap {
111 AUDIO_ANALYSIS_CACHE.get_or_init(|| Arc::new(DashMap::new()))
112}