#[must_use = "euclidean rhythm pattern should be used"]
pub fn euclidean(steps: u8, pulses: u8, rotation: u8) -> Vec<bool> {
if steps == 0 {
panic!("steps == 0");
}
if pulses > steps {
panic!("pulses > steps");
}
if pulses == 0 {
return vec![false; steps as usize];
}
if pulses == steps {
return vec![true; steps as usize];
}
let mut pattern = bjorklund(steps, pulses);
let rot = (rotation % steps) as usize;
if rot > 0 {
pattern.rotate_left(rot);
}
pattern
}
pub fn pattern_to_string(pattern: &[bool], pulse_char: char, rest_char: char) -> String {
pattern
.iter()
.map(|&b| if b { pulse_char } else { rest_char })
.collect()
}
pub fn rotate_pattern(pattern: &[bool], rotation: i32) -> Vec<bool> {
if pattern.is_empty() {
return Vec::new();
}
let len = pattern.len() as i32;
let normalized_rotation = ((rotation % len) + len) % len;
let mut result = Vec::with_capacity(pattern.len());
result.extend_from_slice(&pattern[normalized_rotation as usize..]);
result.extend_from_slice(&pattern[..normalized_rotation as usize]);
result
}
#[inline]
fn bjorklund(steps: u8, pulses: u8) -> Vec<bool> {
let steps = steps as usize;
let pulses = pulses as usize;
if pulses == 1 {
let mut pattern = vec![false; steps];
pattern[0] = true;
return pattern;
}
if pulses == 0 || pulses == steps {
return Vec::new();
}
let mut pattern: Vec<Vec<bool>> = Vec::new();
for _ in 0..pulses {
pattern.push(vec![true]);
}
for _ in 0..(steps - pulses) {
pattern.push(vec![false]);
}
let mut split_idx = pulses;
loop {
let num_left = split_idx;
let num_right = pattern.len() - split_idx;
if num_right <= 1 {
break;
}
let num_pairs = num_left.min(num_right);
for i in 0..num_pairs {
let right_pattern = pattern[split_idx + i].clone();
pattern[i].extend(right_pattern);
}
pattern.drain(split_idx..split_idx + num_pairs);
split_idx = num_pairs;
}
pattern.into_iter().flatten().collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tresillo() {
let pattern = euclidean(8, 3, 0);
assert_eq!(
pattern,
vec![true, false, false, true, false, false, true, false]
);
}
#[test]
fn west_african_bell() {
let pattern = euclidean(8, 5, 0);
assert_eq!(
pattern,
vec![true, false, true, true, false, true, true, false]
);
}
#[test]
fn persian() {
let pattern = euclidean(12, 5, 0);
assert_eq!(
pattern,
vec![
true, false, false, true, false, true, false, false, true, false, true, false
]
);
}
#[test]
fn bossa_nova() {
let pattern = euclidean(16, 7, 0);
assert_eq!(
pattern,
vec![
true, false, false, true, false, true, false, true, false, false, true, false,
true, false, true, false
]
);
}
#[test]
fn all_pulses() {
let pattern = euclidean(8, 8, 0);
assert_eq!(pattern, vec![true; 8]);
}
#[test]
fn all_rests() {
let pattern = euclidean(8, 0, 0);
assert_eq!(pattern, vec![false; 8]);
}
#[test]
fn single_pulse() {
let pattern = euclidean(8, 1, 0);
assert_eq!(
pattern,
vec![true, false, false, false, false, false, false, false]
);
}
#[test]
#[should_panic]
fn pulses_gt_steps() {
let _ = euclidean(8, 9, 0);
}
#[test]
#[should_panic]
fn steps_zero() {
let _ = euclidean(0, 0, 0);
}
#[test]
fn rotation_wraps() {
let pattern = euclidean(8, 3, 10); assert_eq!(
pattern,
vec![false, true, false, false, true, false, true, false]
);
}
#[test]
fn large_steps() {
let pattern = euclidean(64, 5, 0);
assert_eq!(pattern.len(), 64);
assert_eq!(pattern.iter().filter(|&&x| x).count(), 5);
}
#[test]
fn pattern_length_always_matches_steps() {
for steps in 1..=32 {
for pulses in 0..=steps {
let pattern = euclidean(steps, pulses, 0);
assert_eq!(
pattern.len(),
steps as usize,
"E({},{}) length mismatch",
pulses,
steps
);
}
}
}
#[test]
fn pulse_count_always_matches() {
for steps in 1..=32 {
for pulses in 0..=steps {
let pattern = euclidean(steps, pulses, 0);
let actual_pulses = pattern.iter().filter(|&&x| x).count();
assert_eq!(
actual_pulses, pulses as usize,
"E({},{}) pulse count mismatch",
pulses, steps
);
}
}
}
#[test]
fn rotation_by_one() {
let original = euclidean(8, 3, 0);
let rotated = euclidean(8, 3, 1);
assert_eq!(rotated[0], original[1]);
assert_eq!(rotated[7], original[0]);
}
#[test]
fn pattern_to_string_works() {
let pattern = euclidean(8, 3, 0);
assert_eq!(pattern_to_string(&pattern, 'x', '.'), "x..x..x.");
assert_eq!(pattern_to_string(&pattern, '1', '0'), "10010010");
}
#[test]
fn rotate_pattern_works() {
let pattern = vec![true, false, false, true];
let rotated = rotate_pattern(&pattern, 1);
assert_eq!(rotated, vec![false, false, true, true]);
let rotated = rotate_pattern(&pattern, -1);
assert_eq!(rotated, vec![true, true, false, false]);
let rotated = rotate_pattern(&pattern, 5); assert_eq!(rotated, vec![false, false, true, true]);
let rotated = rotate_pattern(&pattern, 0);
assert_eq!(rotated, pattern);
let empty: Vec<bool> = vec![];
assert_eq!(rotate_pattern(&empty, 1), empty);
}
}