terminals_core/audio/
bands.rs1#[derive(Debug, Clone, Copy, Default)]
13pub struct BandAnalysis {
14 pub sub_bass: f32,
15 pub bass: f32,
16 pub mids: f32,
17 pub highs: f32,
18 pub rms: f32,
19 pub spectral_centroid: f32,
20 pub beat: bool,
22}
23
24pub fn analyze_bands(bins: &[u8], sample_rate: u32, prev_energy: f32) -> BandAnalysis {
32 if bins.is_empty() || sample_rate == 0 {
33 return BandAnalysis::default();
34 }
35
36 let n = bins.len();
37 let bin_width = sample_rate as f32 / (2.0 * n as f32);
38
39 let sub_bass_end = ((60.0 / bin_width) as usize).min(n);
41 let bass_start = ((60.0 / bin_width) as usize).min(n);
42 let bass_end = ((250.0 / bin_width) as usize).min(n);
43 let mid_start = ((250.0 / bin_width) as usize).min(n);
44 let mid_end = ((2000.0 / bin_width) as usize).min(n);
45 let high_start = ((2000.0 / bin_width) as usize).min(n);
46 let high_end = ((20000.0 / bin_width) as usize).min(n);
47
48 let sub_bass = band_energy(&bins[..sub_bass_end]);
50 let bass = band_energy(&bins[bass_start..bass_end]);
51 let mids = band_energy(&bins[mid_start..mid_end]);
52 let highs = band_energy(&bins[high_start..high_end]);
53
54 let total_energy: f32 = bins.iter().map(|&b| (b as f32) * (b as f32)).sum();
56 let rms = (total_energy / n as f32).sqrt() / 255.0;
57
58 let mut weighted_sum = 0.0f32;
60 let mut mag_sum = 0.0f32;
61 for (i, &b) in bins.iter().enumerate() {
62 let mag = b as f32;
63 weighted_sum += mag * (i as f32 * bin_width);
64 mag_sum += mag;
65 }
66 let centroid = if mag_sum > 0.0 {
67 (weighted_sum / mag_sum / (sample_rate as f32 / 2.0)).min(1.0)
68 } else {
69 0.0
70 };
71
72 let current_energy = rms;
74 let beat = current_energy > 0.15 && current_energy > prev_energy * 1.4;
75
76 BandAnalysis {
77 sub_bass,
78 bass,
79 mids,
80 highs,
81 rms,
82 spectral_centroid: centroid,
83 beat,
84 }
85}
86
87fn band_energy(bins: &[u8]) -> f32 {
90 if bins.is_empty() {
91 return 0.0;
92 }
93 let sum: f32 = bins.iter().map(|&b| (b as f32) * (b as f32)).sum();
94 let rms = (sum / bins.len() as f32).sqrt();
95 (rms / 255.0).min(1.0)
96}
97
98pub fn analyze_bands_pcm(samples: &[f32], sample_rate: u32) -> BandAnalysis {
101 if samples.is_empty() || sample_rate == 0 {
102 return BandAnalysis::default();
103 }
104
105 let rms: f32 = (samples.iter().map(|s| s * s).sum::<f32>() / samples.len() as f32).sqrt();
107
108 BandAnalysis {
110 sub_bass: 0.0,
111 bass: rms, mids: 0.0,
113 highs: 0.0,
114 rms,
115 spectral_centroid: 0.0,
116 beat: false,
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn test_analyze_empty() {
126 let result = analyze_bands(&[], 48000, 0.0);
127 assert_eq!(result.rms, 0.0);
128 assert!(!result.beat);
129 }
130
131 #[test]
132 fn test_analyze_silence() {
133 let bins = vec![0u8; 4096];
134 let result = analyze_bands(&bins, 48000, 0.0);
135 assert_eq!(result.sub_bass, 0.0);
136 assert_eq!(result.bass, 0.0);
137 assert_eq!(result.mids, 0.0);
138 assert_eq!(result.highs, 0.0);
139 assert_eq!(result.rms, 0.0);
140 assert!(!result.beat);
141 }
142
143 #[test]
144 fn test_analyze_bass_heavy() {
145 let mut bins = vec![0u8; 4096];
146 for bin in &mut bins[10..43] {
150 *bin = 200;
151 }
152 let result = analyze_bands(&bins, 48000, 0.0);
153 assert!(result.bass > 0.5, "bass = {}", result.bass);
154 assert!(result.bass > result.highs, "bass should > highs");
155 }
156
157 #[test]
158 fn test_beat_detection() {
159 let bins = vec![128u8; 4096]; let result = analyze_bands(&bins, 48000, 0.1); assert!(result.beat, "Should detect beat on energy spike");
162
163 let result2 = analyze_bands(&bins, 48000, 0.9); assert!(!result2.beat, "Should not detect beat without spike");
165 }
166
167 #[test]
168 fn test_band_values_in_range() {
169 let bins: Vec<u8> = (0..4096).map(|i| (i % 256) as u8).collect();
170 let result = analyze_bands(&bins, 48000, 0.0);
171 assert!(result.sub_bass >= 0.0 && result.sub_bass <= 1.0);
172 assert!(result.bass >= 0.0 && result.bass <= 1.0);
173 assert!(result.mids >= 0.0 && result.mids <= 1.0);
174 assert!(result.highs >= 0.0 && result.highs <= 1.0);
175 assert!(result.rms >= 0.0 && result.rms <= 1.0);
176 assert!(result.spectral_centroid >= 0.0 && result.spectral_centroid <= 1.0);
177 }
178}