Skip to main content

rill_patchbay/sequencer/
step.rs

1use super::snapshot::ParameterTarget;
2
3/// A single step in a sequencer pattern.
4///
5/// Each step carries zero or more *parameter locks* (p-locks): specific
6/// parameter values that are sent when the step becomes active.  Parameters
7/// not listed in `parameters` keep their current value — they are *not*
8/// reset or cleared.
9///
10/// # Duration
11///
12/// `duration_notes` expresses the step length in quarter-note units at the
13/// current tempo:
14///
15/// | `duration_notes` | Musical duration |
16/// |---|---|
17/// | 4.0  | whole note      |
18/// | 2.0  | half note       |
19/// | 1.0  | quarter note    |
20/// | 0.5  | eighth note     |
21/// | 0.25 | sixteenth note  |
22/// | 1.5  | dotted quarter  |
23/// | …    | any value >= 0  |
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25#[derive(Debug, Clone)]
26pub struct SequenceStep {
27    /// Parameter target locks for this step.
28    pub parameters: Vec<ParameterTarget>,
29    /// Step duration in quarter-note units.
30    pub duration_notes: f64,
31}
32
33impl SequenceStep {
34    /// Create a new step with the given p-locks and duration.
35    pub fn new(parameters: Vec<ParameterTarget>, duration_notes: f64) -> Self {
36        Self {
37            parameters,
38            duration_notes: duration_notes.max(0.0),
39        }
40    }
41
42    /// Create a single-parameter step (convenience constructor).
43    pub fn single(
44        node_id: impl Into<rill_core::NodeId>,
45        param: impl Into<String>,
46        value: f32,
47        duration_notes: f64,
48    ) -> Self {
49        Self {
50            parameters: vec![ParameterTarget::new(node_id.into(), param, value)],
51            duration_notes: duration_notes.max(0.0),
52        }
53    }
54
55    /// Return the step duration in audio samples for the given tempo and
56    /// sample rate.
57    ///
58    /// `quarter_note_samples = (60.0 / tempo) * sample_rate`
59    pub fn duration_samples(&self, tempo: f32, sample_rate: f32) -> u64 {
60        let qn = (60.0 / tempo.max(1.0)) * sample_rate;
61        (qn * self.duration_notes as f32) as u64
62    }
63}