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