1use crate::BeatAnalysis;
2
3pub fn beat_counts(analysis: &BeatAnalysis) -> Vec<i32> {
13 infer_beat_numbers(&analysis.beats, &analysis.downbeats)
14}
15
16fn infer_beat_numbers(beats: &[f32], downbeats: &[f32]) -> Vec<i32> {
19 let start_counter: i32 = if downbeats.len() >= 2 {
21 let first = beats.partition_point(|&b| b < downbeats[0]) as i32;
23 let second = beats.partition_point(|&b| b < downbeats[1]) as i32;
24 let beats_in_first_measure = second - first;
25 let pickup_beats = first;
26 if pickup_beats < beats_in_first_measure {
27 beats_in_first_measure - pickup_beats
28 } else {
29 1
31 }
32 } else {
33 1
35 };
36
37 let mut numbers = Vec::with_capacity(beats.len());
40 let mut counter = start_counter;
41 let mut db_idx = 0usize;
42 for &beat in beats {
43 match downbeats.get(db_idx) {
44 Some(&db) if (beat - db).abs() < 0.001 => {
45 counter = 1;
46 db_idx += 1;
47 }
48 _ => counter += 1,
49 }
50 numbers.push(counter);
51 }
52 numbers
53}
54
55pub fn calculate_bpm(analysis: &BeatAnalysis) -> Option<f32> {
60 if analysis.beats.len() < 2 {
61 return None;
62 }
63
64 let mut intervals: Vec<f32> = analysis
65 .beats
66 .windows(2)
67 .map(|w| w[1] - w[0])
68 .filter(|&iv| iv > 0.1 && iv < 3.0)
69 .collect();
70
71 if intervals.is_empty() {
72 return None;
73 }
74
75 intervals.sort_by(|a, b| a.total_cmp(b));
76 let n = intervals.len();
77 let median = if n.is_multiple_of(2) {
78 (intervals[n / 2 - 1] + intervals[n / 2]) / 2.0
79 } else {
80 intervals[n / 2]
81 };
82
83 Some(60.0 / median)
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use crate::runtime::Tensor;
90
91 fn make_analysis(beats: Vec<f32>, downbeats: Vec<f32>) -> BeatAnalysis {
92 BeatAnalysis {
93 beats,
94 downbeats,
95 mel: Tensor {
96 shape: vec![1, 0, 128],
97 data: vec![],
98 },
99 beat_logits: vec![],
100 downbeat_logits: vec![],
101 }
102 }
103
104 #[test]
105 fn test_beat_counts_basic() {
106 let analysis = make_analysis(vec![0.5, 1.0, 1.5, 2.0], vec![0.5]);
107 let counts = beat_counts(&analysis);
108 assert_eq!(counts, vec![1, 2, 3, 4]);
109 }
110
111 #[test]
112 fn test_beat_counts_multiple_downbeats() {
113 let analysis = make_analysis(vec![0.5, 1.0, 1.5, 2.0, 2.5, 3.0], vec![0.5, 2.0]);
114 let counts = beat_counts(&analysis);
115 assert_eq!(counts, vec![1, 2, 3, 1, 2, 3]);
116 }
117
118 #[test]
119 fn test_beat_counts_no_downbeats() {
120 let analysis = make_analysis(vec![0.5, 1.0, 1.5], vec![]);
123 let counts = beat_counts(&analysis);
124 assert_eq!(counts, vec![2, 3, 4]);
125 }
126
127 #[test]
128 fn test_beat_counts_beats_before_first_downbeat() {
129 let analysis = make_analysis(vec![0.5, 1.0, 1.5, 2.0], vec![1.5]);
132 let counts = beat_counts(&analysis);
133 assert_eq!(counts, vec![2, 3, 1, 2]);
134 }
135
136 #[test]
137 fn test_beat_counts_pickup_measure() {
138 let analysis = make_analysis(vec![0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5], vec![1.0, 3.0]);
140 assert_eq!(beat_counts(&analysis), vec![3, 4, 1, 2, 3, 4, 1, 2]);
142 }
143
144 #[test]
145 fn test_beat_counts_pickup_longer_than_first_measure() {
146 let analysis = make_analysis(vec![0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0], vec![1.5, 2.5]);
148 assert_eq!(beat_counts(&analysis), vec![2, 3, 4, 1, 2, 1, 2]);
150 }
151
152 #[test]
153 fn test_beat_counts_clean_start_single_downbeat() {
154 let analysis = make_analysis(vec![0.5, 1.0, 1.5, 2.0], vec![0.5]);
156 assert_eq!(beat_counts(&analysis), vec![1, 2, 3, 4]);
157 }
158
159 #[test]
160 fn test_calculate_bpm_120() {
161 let analysis = make_analysis(vec![0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0], vec![0.0]);
162 let bpm = calculate_bpm(&analysis).unwrap();
163 assert!((bpm - 120.0).abs() < 0.1, "Expected ~120 BPM, got {}", bpm);
164 }
165
166 #[test]
167 fn test_calculate_bpm_too_few_beats() {
168 let analysis = make_analysis(vec![0.5], vec![]);
169 assert!(calculate_bpm(&analysis).is_none());
170 }
171
172 #[test]
173 fn test_calculate_bpm_empty() {
174 let analysis = make_analysis(vec![], vec![]);
175 assert!(calculate_bpm(&analysis).is_none());
176 }
177}