rust_synth/math/
rhythm.rs1pub const STEPS: u32 = 16;
9
10pub fn euclidean_bits(hits: u32, rotation: u32) -> u32 {
12 let steps = STEPS;
13 let hits = hits.min(steps);
14 if hits == 0 {
15 return 0;
16 }
17 let mut bits = 0u32;
22 for i in 0..hits {
23 let idx = (i * steps) / hits;
24 let rotated = (idx + rotation) % steps;
25 bits |= 1 << rotated;
26 }
27 bits
28}
29
30#[inline]
32pub fn step_position(t: f64, bpm: f64, steps_per_beat: f64) -> (u64, f64) {
33 let pos = t * bpm / 60.0 * steps_per_beat;
34 (pos as u64, pos.fract())
35}
36
37#[inline]
39pub fn step_is_active(bits: u32, t: f64, bpm: f64) -> (bool, f64) {
40 let (idx, phi) = step_position(t, bpm, 4.0);
41 let step = (idx % STEPS as u64) as u32;
42 let active = (bits >> step) & 1 == 1;
43 (active, phi)
44}
45
46#[cfg(test)]
47mod tests {
48 use super::*;
49
50 #[test]
51 fn four_on_the_floor() {
52 let bits = euclidean_bits(4, 0);
53 assert_eq!(bits, 0b0001_0001_0001_0001);
55 }
56
57 #[test]
58 fn empty_pattern_when_no_hits() {
59 assert_eq!(euclidean_bits(0, 0), 0);
60 }
61
62 #[test]
63 fn rotation_shifts() {
64 let base = euclidean_bits(4, 0);
65 let rotated = euclidean_bits(4, 2);
66 let expected = ((base << 2) | (base >> 14)) & 0xFFFF;
68 assert_eq!(rotated, expected);
69 }
70
71 #[test]
72 fn hits_count_matches() {
73 for h in 0..=16 {
74 let bits = euclidean_bits(h, 0);
75 assert_eq!(bits.count_ones(), h);
76 }
77 }
78}