Skip to main content

rill_patchbay/automaton/
sequencer.rs

1//! # Sequencers
2//!
3//! Automata for generating rhythmic patterns and sequences
4//! of values over time.
5
6use crate::engine::{Automaton, NoAction, Range, Time};
7use rill_core::traits::ParamValue;
8
9/// Sequencer step — duration of the step in beat fractions.
10///
11/// Values come from the Servo's value table, not from the automaton.
12/// The automaton only tracks step indices; the servo maps them to
13/// parameter values through a table lookup.
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15#[derive(Debug, Clone)]
16pub struct Step {
17    /// Duration in beat fractions (1.0 = quarter note at the given tempo).
18    pub duration: f64,
19}
20
21/// Sequencer playback mode
22#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23#[derive(Debug, Clone, Copy, PartialEq)]
24pub enum PlayMode {
25    /// One shot
26    OneShot,
27    /// Loop
28    Loop,
29    /// Ping-pong
30    PingPong,
31    /// Random
32    Random,
33    /// Brownian
34    Brownian,
35}
36
37/// Sequencer automaton
38#[derive(Debug, Clone)]
39pub struct SequencerAutomaton {
40    /// Automaton name
41    name: String,
42    /// Sequencer steps
43    steps: Vec<Step>,
44    /// Playback mode
45    mode: PlayMode,
46    /// Tempo (BPM)
47    tempo: f64,
48    /// Duration scale (1.0 = quarter note)
49    duration_scale: f64,
50    /// Whether to interpolate between steps
51    interpolate: bool,
52    /// Output value range
53    range: Range,
54}
55
56impl SequencerAutomaton {
57    /// Create a new sequencer
58    pub fn new(name: &str, steps: Vec<Step>) -> Self {
59        Self {
60            name: name.to_string(),
61            steps,
62            mode: PlayMode::Loop,
63            tempo: 120.0,
64            duration_scale: 1.0,
65            interpolate: false,
66            range: Range::unipolar(),
67        }
68    }
69
70    /// Set playback mode
71    pub fn with_mode(mut self, mode: PlayMode) -> Self {
72        self.mode = mode;
73        self
74    }
75
76    /// Set tempo
77    pub fn with_tempo(mut self, bpm: f64) -> Self {
78        self.tempo = bpm.max(1.0);
79        self
80    }
81
82    /// Enable/disable interpolation
83    pub fn with_interpolation(mut self, interpolate: bool) -> Self {
84        self.interpolate = interpolate;
85        self
86    }
87
88    /// Set the range
89    pub fn with_range(mut self, range: Range) -> Self {
90        self.range = range;
91        self
92    }
93
94    /// Get step duration in seconds.
95    ///
96    /// `step.duration` is in quarter-note beats (1.0 = one quarter note at the given tempo).
97    /// `duration_scale` allows global scaling (e.g. 0.5 = double tempo).
98    fn step_duration(&self, step: &Step) -> f64 {
99        step.duration * 60.0 / self.tempo * self.duration_scale
100    }
101
102    /// Xorshift PRNG
103    fn xorshift(&self, rng: &mut u64) -> u64 {
104        let mut x = *rng;
105        x ^= x << 13;
106        x ^= x >> 7;
107        x ^= x << 17;
108        *rng = x;
109        x
110    }
111
112    /// Random index
113    fn random_index(&self, rng: &mut u64) -> usize {
114        let x = self.xorshift(rng);
115        (x as usize) % self.steps.len()
116    }
117
118    /// Select the next step based on mode and current state
119    fn next_step(&self, current_step: usize, direction: i8, rng_state: &mut u64) -> (usize, i8) {
120        match self.mode {
121            PlayMode::OneShot => {
122                if current_step < self.steps.len() - 1 {
123                    (current_step + 1, direction)
124                } else {
125                    (current_step, direction)
126                }
127            }
128
129            PlayMode::Loop => ((current_step + 1) % self.steps.len(), direction),
130
131            PlayMode::PingPong => {
132                let next = current_step as i32 + direction as i32;
133                if next < 0 {
134                    (1, 1)
135                } else if next >= self.steps.len() as i32 {
136                    (self.steps.len() - 2, -1)
137                } else {
138                    (next as usize, direction)
139                }
140            }
141
142            PlayMode::Random => (self.random_index(rng_state), direction),
143
144            PlayMode::Brownian => {
145                let mut candidates = vec![current_step];
146                if current_step > 0 {
147                    candidates.push(current_step - 1);
148                }
149                if current_step < self.steps.len() - 1 {
150                    candidates.push(current_step + 1);
151                }
152                let idx = self.random_index(rng_state) % candidates.len();
153                (candidates[idx], direction)
154            }
155        }
156    }
157}
158
159impl Automaton for SequencerAutomaton {
160    type Internal = (usize, f64, i8, u64);
161    type Action = NoAction;
162
163    fn step(
164        &self,
165        internal: &mut Self::Internal,
166        _current: &ParamValue,
167        time: Time,
168        _action: &Self::Action,
169    ) -> ParamValue {
170        let (current_step, step_start_time, direction, mut rng_state) = *internal;
171
172        if self.steps.is_empty() {
173            return ParamValue::Int(0);
174        }
175
176        let current_step_data = &self.steps[current_step];
177        let step_dur = self.step_duration(current_step_data);
178        let elapsed = time - step_start_time;
179
180        if elapsed >= step_dur {
181            let (next, new_dir) = self.next_step(current_step, direction, &mut rng_state);
182            *internal = (next, time, new_dir, rng_state);
183            ParamValue::Int(next as i32)
184        } else {
185            ParamValue::Int(current_step as i32)
186        }
187    }
188
189    fn initial_internal(&self) -> Self::Internal {
190        (0, 0.0, 1, 123456789)
191    }
192
193    fn name(&self) -> &str {
194        &self.name
195    }
196}
197
198/// Create a sequence of `count` equal-duration steps
199pub fn simple_sequence(count: usize, duration: f64) -> Vec<Step> {
200    (0..count).map(|_| Step { duration }).collect()
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206
207    #[test]
208    fn test_sequencer() {
209        let steps = simple_sequence(4, 1.0);
210        let seq = SequencerAutomaton::new("Test", steps);
211        let mut internal = seq.initial_internal();
212        let current = ParamValue::Float(0.0);
213
214        assert_eq!(internal.0, 0);
215
216        let _value = seq.step(&mut internal, &current, 0.6, &NoAction);
217        assert_eq!(internal.0, 1);
218    }
219}