#[derive(Debug, Clone)]
pub struct Step {
pub active: bool,
pub note_hz: f64,
pub velocity: u8,
pub duration_beats: f64,
pub probability: f64,
}
impl Default for Step {
fn default() -> Self {
Self { active: true, note_hz: 440.0, velocity: 100, duration_beats: 0.25, probability: 1.0 }
}
}
#[derive(Debug, Clone)]
pub struct SequencerPattern {
pub steps: Vec<Step>,
pub length: usize,
pub loop_count: Option<u32>,
}
#[derive(Debug, Clone, Default)]
pub struct SequencerState {
pub current_step: usize,
pub loop_iteration: u32,
pub total_steps_elapsed: u64,
}
fn lcg_rand(seed: u64) -> f64 {
let x = seed.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
(x >> 33) as f64 / (u32::MAX as f64)
}
pub struct Sequencer {
pub pattern: SequencerPattern,
pub bpm: f64,
pub state: SequencerState,
}
impl Sequencer {
pub fn new(pattern: SequencerPattern, bpm: f64) -> Self {
Self { pattern, bpm, state: SequencerState::default() }
}
pub fn tick(&mut self, seed: u64) -> Option<Step> {
let len = self.pattern.length.min(self.pattern.steps.len());
if len == 0 { return None; }
let idx = self.state.current_step % len;
let step = self.pattern.steps[idx].clone();
self.state.total_steps_elapsed += 1;
self.state.current_step += 1;
if self.state.current_step >= len {
self.state.current_step = 0;
self.state.loop_iteration += 1;
}
if !step.active { return None; }
let r = lcg_rand(seed ^ self.state.total_steps_elapsed);
if r > step.probability { return None; }
Some(step)
}
pub fn reset(&mut self) {
self.state = SequencerState::default();
}
pub fn set_bpm(&mut self, bpm: f64) {
self.bpm = bpm;
}
pub fn step_duration_ms(&self) -> f64 {
(60_000.0 / self.bpm) / 4.0
}
pub fn swing(&mut self, amount: f64, seed: u64) -> Option<Step> {
let _is_odd = self.state.current_step % 2 == 1;
let _ = amount; self.tick(seed)
}
pub fn pattern_duration_ms(&self) -> f64 {
self.step_duration_ms() * self.pattern.length as f64
}
}
pub struct PolySequencer {
pub tracks: Vec<Sequencer>,
}
impl PolySequencer {
pub fn new(tracks: Vec<Sequencer>) -> Self {
assert!(tracks.len() <= 8, "PolySequencer supports at most 8 tracks");
Self { tracks }
}
pub fn tick_all(&mut self, seed: u64) -> Vec<(usize, Option<Step>)> {
self.tracks
.iter_mut()
.enumerate()
.map(|(i, seq)| (i, seq.tick(seed ^ (i as u64 * 0xdeadbeef))))
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn basic_pattern(n: usize) -> SequencerPattern {
SequencerPattern {
steps: (0..n).map(|i| Step {
active: true,
note_hz: 220.0 * (i + 1) as f64,
velocity: 80,
duration_beats: 0.25,
probability: 1.0,
}).collect(),
length: n,
loop_count: None,
}
}
#[test]
fn test_tick_returns_step_when_active() {
let mut seq = Sequencer::new(basic_pattern(4), 120.0);
let step = seq.tick(42);
assert!(step.is_some());
assert_eq!(step.unwrap().velocity, 80);
}
#[test]
fn test_tick_skips_inactive_step() {
let mut pat = basic_pattern(4);
pat.steps[0].active = false;
let mut seq = Sequencer::new(pat, 120.0);
assert!(seq.tick(0).is_none());
}
#[test]
fn test_tick_respects_zero_probability() {
let mut pat = basic_pattern(4);
pat.steps[0].probability = 0.0;
let mut seq = Sequencer::new(pat, 120.0);
for seed in 0..100u64 {
assert!(seq.tick(seed).is_none(), "step should not fire at probability=0");
seq.reset();
}
}
#[test]
fn test_step_duration_ms() {
let seq = Sequencer::new(basic_pattern(4), 120.0);
assert!((seq.step_duration_ms() - 125.0).abs() < 1e-9);
}
#[test]
fn test_pattern_duration_ms() {
let seq = Sequencer::new(basic_pattern(16), 120.0);
assert!((seq.pattern_duration_ms() - 2000.0).abs() < 1e-9);
}
#[test]
fn test_reset() {
let mut seq = Sequencer::new(basic_pattern(4), 120.0);
seq.tick(1); seq.tick(2); seq.tick(3);
seq.reset();
assert_eq!(seq.state.current_step, 0);
assert_eq!(seq.state.total_steps_elapsed, 0);
}
#[test]
fn test_wraps_around() {
let mut seq = Sequencer::new(basic_pattern(4), 120.0);
for _ in 0..4 { seq.tick(99); }
assert_eq!(seq.state.current_step, 0);
assert_eq!(seq.state.loop_iteration, 1);
}
#[test]
fn test_poly_sequencer_tick_all() {
let tracks = vec![
Sequencer::new(basic_pattern(4), 120.0),
Sequencer::new(basic_pattern(8), 120.0),
];
let mut poly = PolySequencer::new(tracks);
let results = poly.tick_all(7);
assert_eq!(results.len(), 2);
assert_eq!(results[0].0, 0);
assert_eq!(results[1].0, 1);
}
#[test]
fn test_set_bpm() {
let mut seq = Sequencer::new(basic_pattern(4), 120.0);
seq.set_bpm(60.0);
assert!((seq.step_duration_ms() - 250.0).abs() < 1e-9);
}
}