sim-lib-music-serial 0.1.0

Immutable serial plans with stable row and event identity, explicit provenance, and validated partial temporal order.
Documentation

sim-lib-music-serial

Immutable serial plans with stable row and event identity, explicit role and origin provenance, and validated partial temporal order.

sim-lib-music-serial keeps the structural source honest before and during realization. Rows remain immutable RowForm values from sim-lib-pitch-serial; events cite them through stable OrdinalRef values rather than flattening them into a fake score order. Equal-onset chords are expressed through simultaneous groups, while independent temporal requirements stay in a validated precedence DAG. Roles and origins stay explicit, every event declares at least one structural-reading license for downstream reports, and every non-structural event carries parent evidence.

Strict realization adds explicit register, duration, velocity, articulation, voice, rest, tie, and simultaneity choices without mutating the source plan. Rendered serial notes retain their planned event and ordinal provenance, then pass through the canonical sim-lib-music-core staff, piano-roll, and score forms so equal-pitch occurrences, voice identity, and the structural readings that license each note survive rendering.

When one composition needs inspectable serial policy rather than a hidden style switch, compose a SerialPractice from open PracticeRule components and evaluate a named SerialReading: StructuralPlan, DeclaredRoles, or AllSounding. Built-in rules cover aggregate, order, repeats, doublings, simultaneity, row mixing, foreign material, and parameter exhaustion. The result is an explicit invariant ledger with the rule id, expected fact, observed fact, evidence ids, and any declared waiver for every preserved, relaxed, or violated policy.

When one composition needs inspectable deployment rather than a privileged style mode, compose a TechniquePlan from open SerialDeployer components. The built-in deployers cover complete horizontal statements, motivic partitions, chordal vertical blocks, interlocking partitions, melody/accompaniment distribution, aggregate rotation, and simultaneous forms. schoenberg_partitioned() is one ordinary public composition of those parts.

use std::collections::BTreeMap;

use sim_lib_music_core::ObjectId;
use sim_lib_music_serial::{
    EventPlacement, OrdinalRef, PlannedSerialEvent, RowInstanceId, SerialEventId,
    SerialOrigin, SerialPlan, SerialRole, SimultaneousGroupId, StructuralLicense,
    StructuralReadingId,
};
use sim_lib_pitch_core::PitchClass;
use sim_lib_pitch_serial::{RowFamily, RowOperation, ToneRow};

let row = ToneRow::try_from_classes([
    PitchClass::E, PitchClass::F, PitchClass::G, PitchClass::CS,
    PitchClass::FS, PitchClass::DS, PitchClass::GS, PitchClass::D,
    PitchClass::B, PitchClass::C, PitchClass::A, PitchClass::AS,
])?;
let row_form = row.apply(RowOperation::new(RowFamily::P, 0));
let row_id = RowInstanceId::new("row/op25/p0")?;
let group = SimultaneousGroupId::new("simul/opening")?;
let license = StructuralLicense::new(
    StructuralReadingId::new("reading/opening")?,
    "opening sonority reading",
)?;

let mut rows = BTreeMap::new();
rows.insert(row_id.clone(), row_form);

let opening = PlannedSerialEvent {
    id: SerialEventId::new("event/opening")?,
    ordinals: vec![
        OrdinalRef::new(row_id.clone(), 0),
        OrdinalRef::new(row_id.clone(), 4),
    ],
    role: SerialRole::Structural,
    origin: SerialOrigin::Structural {
        rationale: "opening statement".to_owned(),
    },
    voice: ObjectId::new("voice/soprano")?,
    placement: EventPlacement::simultaneous(group),
    parents: vec![],
    licenses: vec![license.clone()],
};

let answer = PlannedSerialEvent {
    id: SerialEventId::new("event/answer")?,
    ordinals: vec![
        OrdinalRef::new(row_id.clone(), 1),
        OrdinalRef::new(row_id.clone(), 2),
        OrdinalRef::new(row_id, 3),
    ],
    role: SerialRole::Structural,
    origin: SerialOrigin::Structural {
        rationale: "continuation".to_owned(),
    },
    voice: ObjectId::new("voice/alto")?,
    placement: EventPlacement::independent(),
    parents: vec![],
    licenses: vec![license],
};

let plan = SerialPlan::try_new(
    rows,
    [opening, answer]
        .into_iter()
        .map(|event| (event.id.clone(), event))
        .collect(),
    [(
        SerialEventId::new("event/opening")?,
        SerialEventId::new("event/answer")?,
    )],
)?;

assert_eq!(plan.simultaneous_groups().len(), 1);
# Ok::<(), Box<dyn std::error::Error>>(())

Construction fails closed when row or event ids are malformed, ordinal references leave the row, structural coverage is incomplete, parent evidence is missing or cyclic, or precedence fabricates an order inside one simultaneous group.