#![no_std]
use smallvec::SmallVec;
#[derive(Debug, Clone)]
pub struct Pattern {
steps: SmallVec<[bool; 64]>,
length: usize,
pulses: usize,
rotation: isize,
cursor: usize,
}
impl Pattern {
pub fn new(length: usize, pulses: usize, rotation: isize) -> Self {
let mut pattern = Pattern::with_length(length);
pattern.pulses(pulses);
pattern.rotate(rotation);
pattern
}
pub fn with_length(length: usize) -> Self {
let mut steps = SmallVec::new();
for _ in 0..length {
steps.push(false);
}
Self {
steps,
length,
pulses: 0,
rotation: 0,
cursor: 0,
}
}
pub fn from_slice(slice: &[bool]) -> Self {
Self {
steps: SmallVec::from_slice(slice),
length: slice.len(),
cursor: 0,
pulses: 0,
rotation: 0,
}
}
pub fn pulses(&mut self, pulses: usize) {
self.pulses = if pulses > self.length {
self.length
} else {
pulses
};
self.steps.clear();
let mut bucket: usize = 0;
for _ in 0..self.length {
bucket += self.pulses;
if bucket >= self.length {
bucket -= self.length;
self.steps.push(true);
} else {
self.steps.push(false);
}
}
if self.length > 0 && self.pulses > 0 {
let offset = self.length / self.pulses - 1;
self.steps.rotate_right(offset);
}
}
pub fn rotate(&mut self, rotation: isize) {
self.rotation = rotation;
if rotation.is_positive() {
self.steps.rotate_right(rotation as usize);
} else if rotation.is_negative() {
self.steps.rotate_left(rotation.abs() as usize);
}
}
pub fn clear(&mut self) {
self.steps.clear();
for _ in 0..self.length {
self.steps.push(false);
}
}
pub fn resize(&mut self, length: usize) {
self.steps.resize(length, false);
self.length = length;
}
pub fn reset(&mut self) {
self.move_cursor(0);
}
pub fn move_cursor(&mut self, step: usize) {
self.cursor = if self.is_in_range(step) {
step
} else {
self.last_index()
};
}
pub fn step(&self, step: usize) -> Option<bool> {
if step < self.len() {
Some(self.steps[step])
} else {
None
}
}
pub fn len(&self) -> usize {
self.steps.len()
}
pub fn as_slice(&self) -> &[bool] {
self.steps.as_slice()
}
pub fn next_looped(&mut self) -> bool {
let step = self.steps[self.cursor];
if self.cursor == self.last_index() {
self.reset();
} else {
self.move_cursor(self.cursor + 1);
}
step
}
fn is_in_range(&self, step: usize) -> bool {
step < self.len()
}
fn last_index(&self) -> usize {
self.len() - 1
}
}
impl Iterator for Pattern {
type Item = bool;
fn next(&mut self) -> Option<bool> {
if self.is_in_range(self.cursor) {
let current = self.cursor;
self.cursor += 1;
Some(self.steps[current])
} else {
self.reset();
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bjorklund_example() {
let pattern = Pattern::new(13, 5, -3);
assert_eq!(
[true, false, false, true, false, true, false, false, true, false, true, false, false],
pattern.as_slice()
);
}
#[test]
fn ruchenitza() {
let pattern = Pattern::new(7, 3, -3);
assert_eq!(
[true, false, true, false, true, false, false],
pattern.as_slice()
);
}
#[test]
fn york_samai() {
let pattern = Pattern::new(6, 5, 1);
assert_eq!(
[true, false, true, true, true, true],
pattern.as_slice()
);
}
#[test]
fn cumbia() {
let pattern = Pattern::new(4, 3, 1);
assert_eq!(
[true, false, true, true],
pattern.as_slice()
);
}
#[test]
fn khafif_e_ramal() {
let pattern = Pattern::new(5, 2, -3);
assert_eq!(
[true, false, true, false, false],
pattern.as_slice()
);
}
#[test]
fn agsag_samai() {
let pattern = Pattern::new(9, 5, 1);
assert_eq!(
[true, false, true, false, true, false, true, false, true],
pattern.as_slice()
);
}
#[test]
fn venda() {
let pattern = Pattern::new(12, 5, 0);
assert_eq!(
[true, false, false, true, false, true, false, false, true, false, true, false],
pattern.as_slice()
);
}
#[test]
fn bendir() {
let pattern = Pattern::new(8, 7, 1);
assert_eq!(
[true, false, true, true, true, true, true, true],
pattern.as_slice()
);
}
#[test]
fn overflow() {
let pattern = Pattern::new(8, 9, 0);
assert_eq!(
[true, true, true, true, true, true, true, true],
pattern.as_slice()
);
}
#[test]
fn zero_length() {
let pattern = Pattern::with_length(0);
assert_eq!(
0,
pattern.len()
);
}
#[test]
fn zero_pulses() {
let mut pattern = Pattern::with_length(2);
pattern.pulses(0);
assert_eq!(
[false, false],
pattern.as_slice()
);
}
}