Skip to main content

sim_lib_music_serial/
allowance.rs

1//! Explicit serial allowances used to admit generic completion candidates.
2
3use std::collections::BTreeSet;
4
5use sim_lib_pitch_core::PitchClass;
6
7use crate::OrdinalRef;
8
9/// One caller-declared referential subset that may license non-structural additions.
10#[derive(Clone, Debug, PartialEq, Eq)]
11pub struct ReferentialSubsetAllowance {
12    /// Stable subset identity retained in diagnostics and derived event provenance.
13    pub id: String,
14    /// Pitch classes admitted by the subset.
15    pub pitch_classes: BTreeSet<PitchClass>,
16}
17
18impl ReferentialSubsetAllowance {
19    /// Creates one named referential subset allowance.
20    pub fn new(
21        id: impl Into<String>,
22        pitch_classes: impl IntoIterator<Item = PitchClass>,
23    ) -> Result<Self, String> {
24        let id = id.into();
25        if id.trim().is_empty() {
26            return Err("referential subset id cannot be empty".to_owned());
27        }
28        let pitch_classes = pitch_classes.into_iter().collect::<BTreeSet<_>>();
29        if pitch_classes.is_empty() {
30            return Err("referential subset pitch classes cannot be empty".to_owned());
31        }
32        Ok(Self { id, pitch_classes })
33    }
34}
35
36/// Serial material categories that may license one added note.
37#[derive(Clone, Debug, PartialEq, Eq)]
38pub enum SerialAllowanceKind {
39    /// Reuse a pitch class already present in the current structural partition.
40    CurrentPartition,
41    /// Reuse a pitch class already stated earlier in the structural reading.
42    StatedPitchClasses,
43    /// Borrow a pitch class that remains in the structural aggregate ahead.
44    AggregateRemainder,
45    /// Borrow a caller-declared referential subset without claiming structure.
46    ReferentialSubset {
47        /// Stable subset identity.
48        id: String,
49    },
50    /// Reuse a landed pitch class already exposed by an attached modal spine report.
51    ModalProjection,
52    /// Reuse non-structural derived material already present in the plan.
53    DerivedReservoir,
54    /// Reuse material already declared foreign by the plan.
55    ExplicitForeignMaterial,
56}
57
58impl SerialAllowanceKind {
59    pub(crate) fn label(&self) -> String {
60        match self {
61            Self::CurrentPartition => "current-partition".to_owned(),
62            Self::StatedPitchClasses => "stated-pitch-classes".to_owned(),
63            Self::AggregateRemainder => "aggregate-remainder".to_owned(),
64            Self::ReferentialSubset { id } => format!("referential-subset/{id}"),
65            Self::ModalProjection => "modal-projection".to_owned(),
66            Self::DerivedReservoir => "derived-reservoir".to_owned(),
67            Self::ExplicitForeignMaterial => "explicit-foreign-material".to_owned(),
68        }
69    }
70}
71
72/// One concrete allowance match for one added note.
73#[derive(Clone, Debug, PartialEq, Eq)]
74pub struct SerialAllowanceMatch {
75    /// Category that licensed the note.
76    pub kind: SerialAllowanceKind,
77    /// Structural ordinals or cited sources associated with the match.
78    pub ordinals: Vec<OrdinalRef>,
79}
80
81/// Admission policy layered over generic completion candidates.
82#[derive(Clone, Debug, PartialEq, Eq)]
83pub struct SerialCompletionAllowances {
84    /// Admit notes that reuse the current structural partition.
85    pub current_partition: bool,
86    /// Admit notes that reuse already stated structural pitch classes.
87    pub stated_pitch_classes: bool,
88    /// Admit notes that borrow a future structural remainder pitch class.
89    pub aggregate_remainder: bool,
90    /// Admit notes covered by caller-declared referential subsets.
91    pub referential_subsets: Vec<ReferentialSubsetAllowance>,
92    /// Admit landed pitch classes already exposed by a modal spine report.
93    pub modal_projection: bool,
94    /// Admit pitch classes already present in non-structural derived material.
95    pub derived_reservoir: bool,
96    /// Admit pitch classes already present in explicit foreign material.
97    pub explicitly_foreign_material: bool,
98}
99
100impl Default for SerialCompletionAllowances {
101    fn default() -> Self {
102        Self {
103            current_partition: true,
104            stated_pitch_classes: true,
105            aggregate_remainder: false,
106            referential_subsets: Vec::new(),
107            modal_projection: false,
108            derived_reservoir: false,
109            explicitly_foreign_material: false,
110        }
111    }
112}