use crate::error::{PulseError, PulseResult};
const MAX_RHYTHM_STEPS: usize = 1_000_000;
pub fn euclidean(pulses: usize, steps: usize) -> Vec<usize> {
tunes::sequences::euclidean::generate(pulses, steps)
}
pub fn try_euclidean(pulses: usize, steps: usize) -> PulseResult<Vec<usize>> {
validate_rhythm_steps(steps)?;
Ok(euclidean(pulses, steps))
}
pub fn euclidean_pattern(pulses: usize, steps: usize) -> Vec<u32> {
tunes::sequences::euclidean::pattern(pulses, steps)
}
pub fn try_euclidean_pattern(pulses: usize, steps: usize) -> PulseResult<Vec<u32>> {
validate_rhythm_steps(steps)?;
Ok(euclidean_pattern(pulses, steps))
}
pub fn named_pattern(name: &str) -> PulseResult<Vec<usize>> {
match normalize_name(name).as_str() {
"fouronfloor" | "kickfourfloor" => Ok(tunes::sequences::euclidean::kick_four_floor()),
"tresillo" => Ok(tunes::sequences::euclidean::tresillo()),
"cinquillo" => Ok(tunes::sequences::euclidean::cinquillo()),
_ => Err(PulseError::InvalidRhythmPattern {
value: name.to_string(),
}),
}
}
pub fn steps_from_pattern_string(pattern: &str) -> Vec<usize> {
pattern
.chars()
.enumerate()
.filter_map(|(index, character)| {
matches!(character, 'x' | 'X' | '1' | '*').then_some(index)
})
.collect()
}
fn normalize_name(value: &str) -> String {
value
.trim()
.to_ascii_lowercase()
.replace(['_', '-', ' '], "")
}
fn validate_rhythm_steps(steps: usize) -> PulseResult<()> {
if steps <= MAX_RHYTHM_STEPS {
Ok(())
} else {
Err(PulseError::InvalidDrumGridSteps { steps })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wraps_euclidean_generation() {
assert_eq!(euclidean(4, 16), vec![0, 4, 8, 12]);
assert_eq!(euclidean_pattern(4, 8).len(), 8);
}
#[test]
fn try_euclidean_rejects_unrenderable_step_counts() {
let error = try_euclidean(4, usize::MAX)
.expect_err("oversized Euclidean rhythm should fail before allocation");
assert!(matches!(error, PulseError::InvalidDrumGridSteps { .. }));
}
#[test]
fn resolves_named_patterns() {
assert_eq!(named_pattern("four_on_floor").unwrap(), vec![0, 4, 8, 12]);
assert_eq!(named_pattern("tresillo").unwrap(), vec![0, 2, 5]);
assert_eq!(named_pattern("cinquillo").unwrap(), vec![0, 2, 3, 5, 7]);
}
#[test]
fn parses_pattern_strings_to_zero_based_steps() {
assert_eq!(steps_from_pattern_string("x---x---"), vec![0, 4]);
assert_eq!(steps_from_pattern_string("xX1*----"), vec![0, 1, 2, 3]);
assert!(steps_from_pattern_string("--------").is_empty());
}
}