pub const STEPS: u32 = 16;
pub fn euclidean_bits(hits: u32, rotation: u32) -> u32 {
let steps = STEPS;
let hits = hits.min(steps);
if hits == 0 {
return 0;
}
let mut bits = 0u32;
for i in 0..hits {
let idx = (i * steps) / hits;
let rotated = (idx + rotation) % steps;
bits |= 1 << rotated;
}
bits
}
#[inline]
pub fn step_position(t: f64, bpm: f64, steps_per_beat: f64) -> (u64, f64) {
let pos = t * bpm / 60.0 * steps_per_beat;
(pos as u64, pos.fract())
}
#[inline]
pub fn step_is_active(bits: u32, t: f64, bpm: f64) -> (bool, f64) {
let (idx, phi) = step_position(t, bpm, 4.0);
let step = (idx % STEPS as u64) as u32;
let active = (bits >> step) & 1 == 1;
(active, phi)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn four_on_the_floor() {
let bits = euclidean_bits(4, 0);
assert_eq!(bits, 0b0001_0001_0001_0001);
}
#[test]
fn empty_pattern_when_no_hits() {
assert_eq!(euclidean_bits(0, 0), 0);
}
#[test]
fn rotation_shifts() {
let base = euclidean_bits(4, 0);
let rotated = euclidean_bits(4, 2);
let expected = ((base << 2) | (base >> 14)) & 0xFFFF;
assert_eq!(rotated, expected);
}
#[test]
fn hits_count_matches() {
for h in 0..=16 {
let bits = euclidean_bits(h, 0);
assert_eq!(bits.count_ones(), h);
}
}
}