sim-lib-music-serial 0.1.1

Immutable serial plans with stable row and event identity, explicit provenance, and validated partial temporal order.
Documentation
use sim_lib_music_core::{Articulation, Time};
use sim_lib_serial_core::SeriesTransform;

use crate::{
    ArticulationTrack, DurationTrack, DynamicsTrack, Exhaustion, IntegralPlan, ParameterTrack,
    RegisterTrack, TimbreTrack,
};

#[test]
fn parameter_series_bindings_keep_generic_ledgers() {
    let mut plan = IntegralPlan::new(12);
    let custom = ParameterTrack::try_new("bow-pressure", vec![1_u8, 3, 2, 4], Exhaustion::Cycle)
        .expect("custom track");
    plan.bind_parameter(custom).expect("bind custom");

    let bound = plan
        .typed_parameter::<u8>("bow-pressure")
        .expect("typed custom parameter");
    let ordinals = bound
        .ordinal_ledger()
        .into_iter()
        .map(|entry| (entry.plan_ordinal, entry.parameter_ordinal, entry.cycle))
        .collect::<Vec<_>>();
    assert_eq!(
        ordinals[..6],
        [
            (0, 0, 0),
            (1, 1, 0),
            (2, 2, 0),
            (3, 3, 0),
            (4, 0, 1),
            (5, 1, 1)
        ]
    );
    assert_eq!(
        bound.projection().steps()[..4]
            .iter()
            .map(|step| step.value)
            .collect::<Vec<_>>(),
        vec![1, 3, 2, 4]
    );
}

#[test]
fn integral_plan_accepts_builtin_and_custom_parameter_tracks_through_one_path() {
    let mut plan = IntegralPlan::new(12);
    plan.bind_parameter(
        DurationTrack::try_new(
            "duration",
            vec![
                Time::new(1, 8),
                Time::new(1, 4),
                Time::new(3, 8),
                Time::new(1, 2),
            ],
            Exhaustion::Cycle,
        )
        .expect("duration track"),
    )
    .expect("bind duration");
    plan.bind_parameter(
        DynamicsTrack::try_new("dynamics", vec![48, 64, 96, 112], Exhaustion::Cycle)
            .expect("dynamics track"),
    )
    .expect("bind dynamics");
    plan.bind_parameter(
        RegisterTrack::try_new("register", vec![3, 4, 5], Exhaustion::Cycle)
            .expect("register track"),
    )
    .expect("bind register");
    plan.bind_parameter(
        ArticulationTrack::try_new(
            "articulation",
            vec![
                Articulation::Normal,
                Articulation::Accent,
                Articulation::Marcato,
            ],
            Exhaustion::Cycle,
        )
        .expect("articulation track"),
    )
    .expect("bind articulation");
    plan.bind_parameter(
        TimbreTrack::try_new(
            "timbre",
            vec!["clarinet".to_owned(), "oboe".to_owned(), "horn".to_owned()],
            Exhaustion::Cycle,
        )
        .expect("timbre track"),
    )
    .expect("bind timbre");
    plan.bind_parameter(
        ParameterTrack::try_new("bow-pressure", vec![1_u8, 3, 2, 4], Exhaustion::Cycle)
            .expect("custom track"),
    )
    .expect("bind custom");

    assert!(plan.typed_parameter::<Time>("duration").is_some());
    assert!(plan.typed_parameter::<u8>("dynamics").is_some());
    assert!(plan.typed_parameter::<i8>("register").is_some());
    assert!(
        plan.typed_parameter::<Articulation>("articulation")
            .is_some()
    );
    assert!(plan.typed_parameter::<String>("timbre").is_some());
    assert!(plan.typed_parameter::<u8>("bow-pressure").is_some());
    assert_eq!(
        plan.parameter("timbre")
            .expect("timbre binding")
            .debug_values()[..3],
        ["\"clarinet\"", "\"oboe\"", "\"horn\""]
    );
}

#[test]
fn parameter_tracks_support_independent_phasing_exhaustion_and_transforms() {
    let duration = DurationTrack::try_new(
        "duration",
        vec![
            Time::new(1, 8),
            Time::new(1, 4),
            Time::new(3, 8),
            Time::new(1, 2),
        ],
        Exhaustion::Cycle,
    )
    .expect("duration")
    .with_phase(1);
    let register = RegisterTrack::try_new("register", vec![3, 4, 5], Exhaustion::Truncate)
        .expect("register")
        .with_phase(1);
    let articulation = ArticulationTrack::try_new(
        "articulation",
        vec![
            Articulation::Normal,
            Articulation::Accent,
            Articulation::Marcato,
        ],
        Exhaustion::OneShot,
    )
    .expect("articulation")
    .with_phase(1);
    let dynamics = DynamicsTrack::try_new("dynamics", vec![40, 60, 80, 100], Exhaustion::Cycle)
        .expect("dynamics");
    let transformed = dynamics
        .transformed(&SeriesTransform::retrograde(4))
        .expect("transform");

    let duration_projection = duration.project(12);
    let register_projection = register.project(6);
    let articulation_projection = articulation.project(6);
    let transformed_projection = transformed.project(4);
    let original_projection = dynamics.project(4);

    assert_eq!(
        duration_projection
            .steps()
            .iter()
            .map(|step| step.parameter_ordinal)
            .collect::<Vec<_>>(),
        vec![1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0]
    );
    assert_eq!(
        register_projection
            .steps()
            .iter()
            .map(|step| (step.plan_ordinal, step.parameter_ordinal, step.value))
            .collect::<Vec<_>>(),
        vec![(0, 1, 4), (1, 2, 5)]
    );
    assert_eq!(
        articulation_projection
            .steps()
            .iter()
            .map(|step| step.parameter_ordinal)
            .collect::<Vec<_>>(),
        vec![1, 2]
    );
    assert_eq!(
        articulation_projection.omitted_plan_ordinals(),
        &[2, 3, 4, 5]
    );
    assert_eq!(
        original_projection
            .steps()
            .iter()
            .map(|step| step.value)
            .collect::<Vec<_>>(),
        vec![40, 60, 80, 100]
    );
    assert_eq!(
        transformed_projection
            .steps()
            .iter()
            .map(|step| step.value)
            .collect::<Vec<_>>(),
        vec![100, 80, 60, 40]
    );
}