1use anyhow::{ensure, Result};
2
3const FPS: f32 = 50.0;
5
6#[derive(Debug, Clone)]
8pub struct BeatResult {
9 pub beats: Vec<f32>,
11 pub downbeats: Vec<f32>,
13}
14
15pub struct PostProcessor {
21 fps: f32,
22}
23
24impl Default for PostProcessor {
25 fn default() -> Self {
26 Self { fps: FPS }
27 }
28}
29
30impl PostProcessor {
31 pub fn new(fps: f32) -> Self {
33 Self { fps }
34 }
35
36 pub fn process(&self, beat_logits: &[f32], downbeat_logits: &[f32]) -> Result<BeatResult> {
41 ensure!(
42 beat_logits.len() == downbeat_logits.len(),
43 "beat_logits length ({}) != downbeat_logits length ({})",
44 beat_logits.len(),
45 downbeat_logits.len()
46 );
47
48 let beat_frames = find_peaks(beat_logits);
49 let downbeat_frames = find_peaks(downbeat_logits);
50
51 let beats: Vec<f32> = beat_frames.iter().map(|&f| f as f32 / self.fps).collect();
52 let mut downbeats: Vec<f32> = downbeat_frames
53 .iter()
54 .map(|&f| f as f32 / self.fps)
55 .collect();
56
57 snap_downbeats_to_beats(&beats, &mut downbeats);
58
59 Ok(BeatResult { beats, downbeats })
60 }
61}
62
63fn find_peaks(logits: &[f32]) -> Vec<usize> {
68 let len = logits.len();
69 let mut peaks = Vec::new();
70
71 for i in 0..len {
72 if logits[i] <= 0.0 {
74 continue;
75 }
76
77 let start = i.saturating_sub(3);
79 let end = (i + 4).min(len);
80
81 let mut is_max = true;
82 for j in start..end {
83 if logits[j] > logits[i] {
84 is_max = false;
85 break;
86 }
87 }
88
89 if is_max {
90 peaks.push(i);
91 }
92 }
93
94 deduplicate_peaks(&peaks, 1)
95}
96
97fn deduplicate_peaks(peaks: &[usize], width: usize) -> Vec<usize> {
102 if peaks.is_empty() {
103 return Vec::new();
104 }
105
106 let mut result = Vec::new();
107 let mut p = peaks[0] as f64;
108 let mut c = 1.0_f64;
109
110 for &p2_usize in &peaks[1..] {
111 let p2 = p2_usize as f64;
112 if p2 - p <= width as f64 {
113 c += 1.0;
114 p += (p2 - p) / c;
115 } else {
116 result.push(p.round() as usize);
117 p = p2;
118 c = 1.0;
119 }
120 }
121 result.push(p.round() as usize);
122
123 result
124}
125
126fn snap_downbeats_to_beats(beat_times: &[f32], downbeat_times: &mut Vec<f32>) {
128 if beat_times.is_empty() || downbeat_times.is_empty() {
129 return;
130 }
131
132 for d_time in downbeat_times.iter_mut() {
133 let pos = beat_times.partition_point(|&b| b < *d_time);
135
136 let best = match (pos.checked_sub(1), beat_times.get(pos)) {
137 (Some(before), Some(&after)) => {
138 if (*d_time - beat_times[before]).abs() <= (after - *d_time).abs() {
139 beat_times[before]
140 } else {
141 after
142 }
143 }
144 (Some(before), None) => beat_times[before],
145 (None, Some(&after)) => after,
146 (None, None) => continue,
147 };
148
149 *d_time = best;
150 }
151
152 downbeat_times.sort_by(|a, b| a.total_cmp(b));
154 downbeat_times.dedup();
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
162 fn test_find_peaks_single_peak() {
163 let logits = [0.0, 0.0, 0.5, 1.0, 0.5, 0.0, 0.0];
164 let peaks = find_peaks(&logits);
165 assert_eq!(peaks, vec![3]);
166 }
167
168 #[test]
169 fn test_find_peaks_below_threshold() {
170 let logits = [-1.0, -0.5, -2.0, -0.1];
171 let peaks = find_peaks(&logits);
172 assert!(peaks.is_empty());
173 }
174
175 #[test]
176 fn test_find_peaks_multiple_peaks() {
177 let mut logits = vec![0.0; 20];
179 logits[3] = 2.0;
180 logits[15] = 1.5;
181 let peaks = find_peaks(&logits);
182 assert_eq!(peaks, vec![3, 15]);
183 }
184
185 #[test]
186 fn test_find_peaks_adjacent_equal() {
187 let logits = [0.0, 1.0, 1.0, 0.0];
189 let peaks = find_peaks(&logits);
190 assert_eq!(peaks.len(), 1);
192 assert_eq!(peaks[0], 2);
194 }
195
196 #[test]
197 fn test_deduplicate_peaks_empty() {
198 let peaks = deduplicate_peaks(&[], 1);
199 assert!(peaks.is_empty());
200 }
201
202 #[test]
203 fn test_deduplicate_peaks_no_adjacent() {
204 let peaks = deduplicate_peaks(&[5, 10, 20], 1);
205 assert_eq!(peaks, vec![5, 10, 20]);
206 }
207
208 #[test]
209 fn test_deduplicate_peaks_merge() {
210 let peaks = deduplicate_peaks(&[10, 11, 12, 20], 1);
212 assert_eq!(peaks, vec![11, 12, 20]);
213
214 let peaks = deduplicate_peaks(&[10, 11, 11, 20], 1);
216 assert_eq!(peaks, vec![11, 20]);
217 }
218
219 #[test]
220 fn test_deduplicate_peaks_single() {
221 let peaks = deduplicate_peaks(&[42], 1);
222 assert_eq!(peaks, vec![42]);
223 }
224
225 #[test]
226 fn test_snap_downbeats() {
227 let beats = vec![1.0, 2.0, 3.0];
228 let mut downbeats = vec![1.1, 2.8];
229 snap_downbeats_to_beats(&beats, &mut downbeats);
230 assert_eq!(downbeats, vec![1.0, 3.0]);
231 }
232
233 #[test]
234 fn test_snap_downbeats_dedup() {
235 let beats = vec![1.0, 2.0, 3.0];
236 let mut downbeats = vec![1.8, 2.1];
238 snap_downbeats_to_beats(&beats, &mut downbeats);
239 assert_eq!(downbeats, vec![2.0]);
240 }
241
242 #[test]
243 fn test_snap_downbeats_empty_beats() {
244 let beats: Vec<f32> = vec![];
245 let mut downbeats = vec![1.0, 2.0];
246 snap_downbeats_to_beats(&beats, &mut downbeats);
247 assert_eq!(downbeats, vec![1.0, 2.0]);
249 }
250
251 #[test]
252 fn test_snap_downbeats_empty_downbeats() {
253 let beats = vec![1.0, 2.0];
254 let mut downbeats: Vec<f32> = vec![];
255 snap_downbeats_to_beats(&beats, &mut downbeats);
256 assert!(downbeats.is_empty());
257 }
258
259 #[test]
260 fn test_process_full() {
261 let mut beat_logits = vec![-5.0; 200];
263 let mut downbeat_logits = vec![-5.0; 200];
264
265 beat_logits[50] = 3.0;
267 beat_logits[100] = 2.5;
268 beat_logits[150] = 4.0;
269
270 downbeat_logits[51] = 2.0;
272
273 let pp = PostProcessor::new(50.0);
274 let result = pp.process(&beat_logits, &downbeat_logits).unwrap();
275
276 assert_eq!(result.beats, vec![1.0, 2.0, 3.0]); assert_eq!(result.downbeats, vec![1.0]); }
279
280 #[test]
281 fn test_process_empty_logits() {
282 let pp = PostProcessor::default();
283 let result = pp.process(&[], &[]).unwrap();
284 assert!(result.beats.is_empty());
285 assert!(result.downbeats.is_empty());
286 }
287
288 #[test]
289 fn test_process_mismatched_lengths() {
290 let pp = PostProcessor::default();
291 let err = pp.process(&[1.0, 2.0], &[1.0]);
292 assert!(err.is_err());
293 }
294}