Skip to main content

sim_lib_music_serial/
hypothesis.rs

1//! Ranked serial-row extraction hypotheses and stable evidence records.
2
3use sim_lib_music_core::{ObjectId, Time};
4use sim_lib_pitch_serial::{RowClassAlias, ToneRow};
5
6/// Exact half-open span used by extracted serial evidence.
7#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
8pub struct SerialTimeSpan {
9    /// Inclusive start.
10    pub start: Time,
11    /// Exclusive end.
12    pub end: Time,
13}
14
15impl SerialTimeSpan {
16    /// Builds an ordered half-open span.
17    pub fn new(start: Time, end: Time) -> Self {
18        debug_assert!(start <= end);
19        Self { start, end }
20    }
21
22    /// Returns the exact duration.
23    pub fn duration(&self) -> Time {
24        self.end - self.start
25    }
26}
27
28/// Stable sort key used when ranking extraction hypotheses.
29#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
30pub struct SerialStableRank {
31    /// Count of missing pitch classes from a complete twelve-tone aggregate.
32    pub omissions: usize,
33    /// Count of repeated pitch classes observed before aggregate completion.
34    pub duplicates_before_completion: usize,
35    /// Count of repeated pitch classes observed after aggregate completion.
36    pub order_errors: usize,
37    /// Exact span from the first contributing attack to the last contributing release.
38    pub occupied_span: Time,
39    /// Stable tie-breaker derived from the chosen attack ordering.
40    pub stable_key: String,
41}
42
43/// Ordering policy selected for one same-onset attack block.
44#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
45pub enum SerialReadingOrder {
46    /// Preserve the canonical exact-window note order.
47    WindowOrder,
48    /// Sort equal-onset attacks by pitch, low to high.
49    PitchAscending,
50    /// Sort equal-onset attacks by pitch, high to low.
51    PitchDescending,
52    /// Sort equal-onset attacks by voice id, low to high.
53    VoiceAscending,
54    /// Sort equal-onset attacks by voice id, high to low.
55    VoiceDescending,
56}
57
58impl SerialReadingOrder {
59    /// Stable machine-readable name.
60    pub const fn as_str(self) -> &'static str {
61        match self {
62            Self::WindowOrder => "window-order",
63            Self::PitchAscending => "pitch-ascending",
64            Self::PitchDescending => "pitch-descending",
65            Self::VoiceAscending => "voice-ascending",
66            Self::VoiceDescending => "voice-descending",
67        }
68    }
69}
70
71/// One note attack cited by a ranked hypothesis.
72#[derive(Clone, Debug, PartialEq, Eq)]
73pub struct SerialObservation {
74    /// Voice identity.
75    pub voice_id: ObjectId,
76    /// Source note identity.
77    pub note_id: ObjectId,
78    /// Source event identity.
79    pub event_id: ObjectId,
80    /// Zero-based pitch-class ordinal in the hypothesis row.
81    pub ordinal: usize,
82    /// Exact source attack span inherited from the sounding-window partition.
83    pub span: SerialTimeSpan,
84}
85
86/// One exact same-onset block chosen during extraction.
87#[derive(Clone, Debug, PartialEq, Eq)]
88pub struct SerialObservationBlock {
89    /// Exact span for the block's source sounding window.
90    pub span: SerialTimeSpan,
91    /// Ordering policy chosen for this block.
92    pub order: SerialReadingOrder,
93    /// Notes contributing new or repeated row evidence in chosen order.
94    pub observations: Vec<SerialObservation>,
95}
96
97/// Alias evidence attached to a ranked row hypothesis.
98#[derive(Clone, Debug, PartialEq, Eq)]
99pub struct SerialAliasEvidence {
100    /// The alias operation preserved by the observed row class.
101    pub alias: RowClassAlias,
102    /// Stable printable form such as `P0` or `RI11`.
103    pub label: String,
104}
105
106/// One ranked hypothesis extracted from exact source attacks.
107#[derive(Clone, Debug, PartialEq, Eq)]
108pub struct RankedSerialHypothesis {
109    /// Stable ranking key; lower values are stronger.
110    pub stable_rank: SerialStableRank,
111    /// Observed row order inferred from first pitch-class occurrence.
112    pub row: ToneRow,
113    /// Exact same-onset blocks cited by the hypothesis.
114    pub blocks: Vec<SerialObservationBlock>,
115    /// Count of repeated pitch classes seen before aggregate completion.
116    pub duplicates_before_completion: usize,
117    /// Count of repeated pitch classes seen after aggregate completion.
118    pub order_errors: usize,
119    /// Missing pitch classes relative to an exact twelve-tone aggregate.
120    pub omissions: usize,
121    /// Exact span covered by the hypothesis.
122    pub span: SerialTimeSpan,
123    /// P/I/R/RI alias evidence for the observed row class.
124    pub aliases: Vec<SerialAliasEvidence>,
125}