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}