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