1use std::time::{Duration, Instant};
4
5#[derive(Debug, Clone, PartialEq)]
7pub enum BeatDetectionMode {
8 Off,
10
11 HardCut1,
13
14 HardCut2,
16
17 HardCut3,
19
20 HardCut4,
23
24 HardCut5,
26
27 HardCut6 { special_preset: String },
30}
31
32impl BeatDetectionMode {
33 pub fn name(&self) -> &'static str {
35 match self {
36 Self::Off => "Off",
37 Self::HardCut1 => "HardCut1",
38 Self::HardCut2 => "HardCut2",
39 Self::HardCut3 => "HardCut3",
40 Self::HardCut4 => "HardCut4",
41 Self::HardCut5 => "HardCut5",
42 Self::HardCut6 { .. } => "HardCut6",
43 }
44 }
45
46 pub fn next(&self) -> Self {
48 match self {
49 Self::Off => Self::HardCut1,
50 Self::HardCut1 => Self::HardCut2,
51 Self::HardCut2 => Self::HardCut3,
52 Self::HardCut3 => Self::HardCut4,
53 Self::HardCut4 => Self::HardCut5,
54 Self::HardCut5 => Self::HardCut6 {
55 special_preset: "Bass/WHITE.milk".to_string(),
56 },
57 Self::HardCut6 { .. } => Self::Off,
58 }
59 }
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
64pub enum PresetChange {
65 Random,
67
68 Specific(String),
70}
71
72#[derive(Debug, Clone)]
74pub struct BeatDetector {
75 mode: BeatDetectionMode,
77
78 last_trigger: Option<Instant>,
80
81 enabled: bool,
83}
84
85impl BeatDetector {
86 pub fn new() -> Self {
88 Self {
89 mode: BeatDetectionMode::Off,
90 last_trigger: None,
91 enabled: false,
92 }
93 }
94
95 pub fn with_mode(mode: BeatDetectionMode) -> Self {
97 let enabled = mode != BeatDetectionMode::Off;
98 Self {
99 mode,
100 last_trigger: None,
101 enabled,
102 }
103 }
104
105 pub fn set_mode(&mut self, mode: BeatDetectionMode) {
107 self.enabled = mode != BeatDetectionMode::Off;
108 self.mode = mode;
109 }
110
111 pub fn mode(&self) -> &BeatDetectionMode {
113 &self.mode
114 }
115
116 pub fn next_mode(&mut self) {
118 self.mode = self.mode.next();
119 self.enabled = self.mode != BeatDetectionMode::Off;
120 }
121
122 pub fn enable(&mut self) {
124 self.enabled = true;
125 }
126
127 pub fn disable(&mut self) {
129 self.enabled = false;
130 }
131
132 pub fn is_enabled(&self) -> bool {
134 self.enabled
135 }
136
137 pub fn should_change_preset(
139 &mut self,
140 bass: f32,
141 _mid: f32,
142 treb: f32,
143 ) -> Option<PresetChange> {
144 if !self.enabled || self.mode == BeatDetectionMode::Off {
145 return None;
146 }
147
148 let now = Instant::now();
149
150 let can_trigger = match self.last_trigger {
152 None => true,
153 Some(last) => {
154 let min_delay = self.get_min_delay();
155 now.duration_since(last) >= min_delay
156 }
157 };
158
159 let change = match self.mode {
161 BeatDetectionMode::Off => None,
162
163 BeatDetectionMode::HardCut1 => {
164 if bass > 1.5 && can_trigger {
165 Some(PresetChange::Random)
166 } else {
167 None
168 }
169 }
170
171 BeatDetectionMode::HardCut2 => {
172 if treb > 2.9 && can_trigger {
173 Some(PresetChange::Random)
174 } else {
175 None
176 }
177 }
178
179 BeatDetectionMode::HardCut3 => {
180 if treb > 2.9 && can_trigger {
181 Some(PresetChange::Random)
182 } else {
183 None
184 }
185 }
186
187 BeatDetectionMode::HardCut4 => {
188 if treb > 8.0 {
189 Some(PresetChange::Random)
191 } else if treb > 2.9 && can_trigger {
192 Some(PresetChange::Random)
193 } else {
194 None
195 }
196 }
197
198 BeatDetectionMode::HardCut5 => {
199 if treb > 2.9 && can_trigger {
200 Some(PresetChange::Random)
201 } else {
202 None
203 }
204 }
205
206 BeatDetectionMode::HardCut6 { ref special_preset } => {
207 if bass > 4.90 {
208 Some(PresetChange::Specific(special_preset.clone()))
210 } else if bass > 1.5 && can_trigger {
211 Some(PresetChange::Random)
212 } else {
213 None
214 }
215 }
216 };
217
218 if change.is_some() {
220 self.last_trigger = Some(now);
221 }
222
223 change
224 }
225
226 fn get_min_delay(&self) -> Duration {
228 match self.mode {
229 BeatDetectionMode::Off => Duration::from_secs(0),
230 BeatDetectionMode::HardCut1 => Duration::from_millis(200),
231 BeatDetectionMode::HardCut2 => Duration::from_millis(500),
232 BeatDetectionMode::HardCut3 => Duration::from_secs(1),
233 BeatDetectionMode::HardCut4 => Duration::from_secs(3),
234 BeatDetectionMode::HardCut5 => Duration::from_secs(5),
235 BeatDetectionMode::HardCut6 { .. } => Duration::from_millis(200),
236 }
237 }
238}
239
240impl Default for BeatDetector {
241 fn default() -> Self {
242 Self::new()
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249 use std::thread;
250
251 #[test]
252 fn test_beat_detector_off() {
253 let mut detector = BeatDetector::new();
254
255 assert_eq!(*detector.mode(), BeatDetectionMode::Off);
256 assert!(!detector.is_enabled());
257
258 let change = detector.should_change_preset(2.0, 1.0, 3.0);
259 assert_eq!(change, None);
260 }
261
262 #[test]
263 fn test_hardcut1_bass_threshold() {
264 let mut detector = BeatDetector::with_mode(BeatDetectionMode::HardCut1);
265
266 let change = detector.should_change_preset(1.0, 0.5, 0.5);
268 assert_eq!(change, None);
269
270 let change = detector.should_change_preset(2.0, 0.5, 0.5);
272 assert_eq!(change, Some(PresetChange::Random));
273 }
274
275 #[test]
276 fn test_hardcut2_treb_threshold() {
277 let mut detector = BeatDetector::with_mode(BeatDetectionMode::HardCut2);
278
279 let change = detector.should_change_preset(0.5, 0.5, 2.0);
281 assert_eq!(change, None);
282
283 let change = detector.should_change_preset(0.5, 0.5, 3.5);
285 assert_eq!(change, Some(PresetChange::Random));
286 }
287
288 #[test]
289 fn test_hardcut4_immediate_trigger() {
290 let mut detector = BeatDetector::with_mode(BeatDetectionMode::HardCut4);
291
292 let change = detector.should_change_preset(0.5, 0.5, 3.0);
294 assert_eq!(change, Some(PresetChange::Random));
295
296 let change = detector.should_change_preset(0.5, 0.5, 3.0);
298 assert_eq!(change, None);
299
300 let change = detector.should_change_preset(0.5, 0.5, 9.0);
302 assert_eq!(change, Some(PresetChange::Random));
303 }
304
305 #[test]
306 fn test_hardcut6_special_preset() {
307 let mut detector = BeatDetector::with_mode(BeatDetectionMode::HardCut6 {
308 special_preset: "Bass/WHITE.milk".to_string(),
309 });
310
311 let change = detector.should_change_preset(2.0, 0.5, 0.5);
313 assert_eq!(change, Some(PresetChange::Random));
314
315 let change = detector.should_change_preset(5.0, 0.5, 0.5);
317 assert_eq!(
318 change,
319 Some(PresetChange::Specific("Bass/WHITE.milk".to_string()))
320 );
321 }
322
323 #[test]
324 fn test_min_delay() {
325 let mut detector = BeatDetector::with_mode(BeatDetectionMode::HardCut1);
326
327 let change = detector.should_change_preset(2.0, 0.5, 0.5);
329 assert_eq!(change, Some(PresetChange::Random));
330
331 let change = detector.should_change_preset(2.0, 0.5, 0.5);
333 assert_eq!(change, None);
334
335 thread::sleep(Duration::from_millis(250));
337
338 let change = detector.should_change_preset(2.0, 0.5, 0.5);
340 assert_eq!(change, Some(PresetChange::Random));
341 }
342
343 #[test]
344 fn test_mode_cycling() {
345 let mut detector = BeatDetector::new();
346
347 assert_eq!(*detector.mode(), BeatDetectionMode::Off);
348
349 detector.next_mode();
350 assert_eq!(*detector.mode(), BeatDetectionMode::HardCut1);
351
352 detector.next_mode();
353 assert_eq!(*detector.mode(), BeatDetectionMode::HardCut2);
354
355 detector.next_mode();
356 assert_eq!(*detector.mode(), BeatDetectionMode::HardCut3);
357
358 detector.next_mode();
359 assert_eq!(*detector.mode(), BeatDetectionMode::HardCut4);
360
361 detector.next_mode();
362 assert_eq!(*detector.mode(), BeatDetectionMode::HardCut5);
363
364 detector.next_mode();
365 assert!(matches!(
366 detector.mode(),
367 BeatDetectionMode::HardCut6 { .. }
368 ));
369
370 detector.next_mode();
371 assert_eq!(*detector.mode(), BeatDetectionMode::Off);
372 }
373}