Skip to main content

sim_lib_music_serial/
cycle.rs

1//! Cyclic ordering and rotation for serial parameter tracks.
2
3/// Track categories that can reuse cyclic order/rotation projections.
4#[derive(Copy, Clone, Debug, PartialEq, Eq)]
5pub enum ParameterTrackKind {
6    /// Pitch-class or register parameters.
7    Pitch,
8    /// Rhythmic values such as durations or attack groups.
9    Rhythm,
10    /// Dynamic values such as accents or layers.
11    Dynamics,
12    /// Timbral values such as mute or synthesis patches.
13    Timbre,
14    /// Orchestration values such as instrument assignments.
15    Orchestration,
16    /// Harmonic values such as chord colors or voicing states.
17    Harmonic,
18}
19
20/// One named cyclic source order over a parameter track.
21#[derive(Clone, Debug, PartialEq, Eq)]
22pub struct CyclicOrder<T> {
23    /// The track the cycle controls.
24    pub track: ParameterTrackKind,
25    /// Source values in declared cyclic order.
26    pub values: Vec<T>,
27}
28
29/// Projection settings for one cyclic order.
30#[derive(Clone, Debug, PartialEq, Eq)]
31pub struct CyclicProjectionSpec {
32    /// Source-order indices visited before rotation.
33    pub order: Vec<usize>,
34    /// Left rotation applied to the ordered projection.
35    pub rotation: usize,
36}
37
38/// Materialized cyclic projection with retained provenance.
39#[derive(Clone, Debug, PartialEq, Eq)]
40pub struct CyclicProjection<T> {
41    /// The originating track.
42    pub track: ParameterTrackKind,
43    /// The requested source-order indices.
44    pub order: Vec<usize>,
45    /// The applied rotation amount modulo the projection length.
46    pub rotation: usize,
47    /// The projected values after rotation.
48    pub values: Vec<T>,
49}
50
51/// Projects one cyclic order through an explicit index order and rotation.
52pub fn project_cyclic_order<T: Clone>(
53    cycle: &CyclicOrder<T>,
54    spec: &CyclicProjectionSpec,
55) -> Result<CyclicProjection<T>, String> {
56    if cycle.values.is_empty() {
57        return Err("cyclic source cannot be empty".to_owned());
58    }
59    if spec.order.is_empty() {
60        return Err("cyclic order cannot be empty".to_owned());
61    }
62    let mut values = Vec::with_capacity(spec.order.len());
63    for &index in &spec.order {
64        let Some(value) = cycle.values.get(index).cloned() else {
65            return Err(format!(
66                "cyclic order index {index} is outside source length {}",
67                cycle.values.len()
68            ));
69        };
70        values.push(value);
71    }
72    let rotation = spec.rotation % values.len();
73    values.rotate_left(rotation);
74    Ok(CyclicProjection {
75        track: cycle.track,
76        order: spec.order.clone(),
77        rotation,
78        values,
79    })
80}