1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
//! Strict serial-plan realization output and failures.
use std::collections::BTreeMap;
use sim_lib_music_core::{Note, Pitch, Time};
use sim_lib_pitch_serial::RowForm;
use thiserror::Error;
use crate::{
InvariantLedger, OrdinalRef, RealizerId, SerialEventId, SerialPlan, SerialSpineReport,
StructuralLicense, VoiceId,
};
/// Complete serial provenance for one realized sounding note.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RealizedSerialOrigin {
/// Stable realizer identity that produced this note.
pub realizer_id: RealizerId,
/// Structural readings that license the realized note.
pub licenses: Vec<StructuralLicense>,
/// Every structural ordinal cited by the planned event.
pub ordinals: Vec<OrdinalRef>,
/// The specific ordinal realized by this note.
pub source_ordinal: OrdinalRef,
/// Row forms keyed by the row instances referenced by `ordinals`.
pub row_forms: BTreeMap<crate::RowInstanceId, RowForm>,
}
/// One realized sounding note with stable plan/event provenance.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RealizedSerialNote {
/// Stable planned event identity.
pub event_id: SerialEventId,
/// Stable voice identity chosen for the note.
pub voice: VoiceId,
/// Stable ordinal occurrence within the event's rendered note list.
pub note_index: usize,
/// Exact absolute onset in whole-note units.
pub onset: Time,
/// Sounding note payload.
pub note: Note,
/// Serial provenance retained for this note.
pub origin: RealizedSerialOrigin,
}
/// One realized event span, which may sound notes or occupy time as a rest.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RealizedSerialEvent {
/// Stable planned event identity.
pub event_id: SerialEventId,
/// Exact onset assigned during realization.
pub onset: Time,
/// Exact occupied duration, whether or not the event sounds notes.
pub duration: Time,
/// Whether this event occupies silence rather than sounding notes.
pub is_rest: bool,
/// Whether this event tied into the following same-voice event.
pub ties_into_next: bool,
}
/// Realized serial notes plus exact event spans, retaining the source plan unchanged.
#[derive(Clone, Debug, PartialEq)]
pub struct SerialRealization {
plan: SerialPlan,
events: Vec<RealizedSerialEvent>,
notes: Vec<RealizedSerialNote>,
ledger: InvariantLedger<RealizerId>,
spine_report: Option<SerialSpineReport>,
}
impl SerialRealization {
/// Builds one exact realization from the preserved plan, event spans, and notes.
pub fn new(
plan: SerialPlan,
events: Vec<RealizedSerialEvent>,
notes: Vec<RealizedSerialNote>,
ledger: InvariantLedger<RealizerId>,
) -> Self {
Self::new_with_spine(plan, events, notes, ledger, None)
}
/// Builds one exact realization and attaches an optional adaptation report.
pub fn new_with_spine(
plan: SerialPlan,
mut events: Vec<RealizedSerialEvent>,
mut notes: Vec<RealizedSerialNote>,
ledger: InvariantLedger<RealizerId>,
spine_report: Option<SerialSpineReport>,
) -> Self {
events.sort_by(|left, right| {
left.onset
.cmp(&right.onset)
.then_with(|| left.event_id.cmp(&right.event_id))
});
notes.sort_by(|left, right| {
left.onset
.cmp(&right.onset)
.then_with(|| left.voice.cmp(&right.voice))
.then_with(|| left.note.pitch.cmp(&right.note.pitch))
.then_with(|| left.event_id.cmp(&right.event_id))
.then_with(|| left.note_index.cmp(&right.note_index))
});
Self {
plan,
events,
notes,
ledger,
spine_report,
}
}
/// Returns the equality-identical structural source plan.
pub fn plan(&self) -> &SerialPlan {
&self.plan
}
/// Returns the exact realized event spans in canonical time order.
pub fn events(&self) -> &[RealizedSerialEvent] {
&self.events
}
/// Returns the realized sounding notes in canonical order.
pub fn notes(&self) -> &[RealizedSerialNote] {
&self.notes
}
/// Returns the sounding pitches in canonical note order.
pub fn sounding_pitches(&self) -> Vec<Pitch> {
self.notes.iter().map(|note| note.note.pitch).collect()
}
/// Returns the realizer invariant ledger recorded for this realization.
pub fn ledger(&self) -> &InvariantLedger<RealizerId> {
&self.ledger
}
/// Returns the optional serial-spine report attached by an adaptive realizer.
pub fn spine_report(&self) -> Option<&SerialSpineReport> {
self.spine_report.as_ref()
}
}
/// Failure while realizing or rendering a strict serial plan.
#[derive(Clone, Debug, PartialEq, Eq, Error)]
pub enum StrictRealizationError {
/// A registry lookup named a missing realizer id.
#[error("serial realizer {0} is not registered")]
UnknownRealizer(RealizerId),
/// One plan event lacked an explicit realization spec.
#[error("plan event {0} is missing a strict realization spec")]
MissingSpec(SerialEventId),
/// One event spec named an impossible register or octave displacement result.
#[error("event {event_id} realizes MIDI pitch {midi}, outside 0..=127")]
MidiOutOfRange {
/// Affected event.
event_id: SerialEventId,
/// Rejected MIDI note number.
midi: i16,
},
/// The caller supplied a non-positive duration.
#[error("event {0} must have a strictly positive duration")]
NonPositiveDuration(SerialEventId),
/// The octave-displacement vector does not match the event's cardinality.
#[error("event {event_id} has {ordinals} ordinals but {displacements} octave displacements")]
OctaveDisplacementMismatch {
/// Affected event.
event_id: SerialEventId,
/// Event ordinal count.
ordinals: usize,
/// Supplied displacement count.
displacements: usize,
},
/// A tie requested a following same-voice event that did not exist.
#[error("event {0} ties into the next event, but no later same-voice event exists")]
MissingTieTarget(SerialEventId),
/// A tie target did not realize the same pitch multiplicity.
#[error("event {source_event} cannot tie into {target_event}: {reason}")]
InvalidTieTarget {
/// Source event requesting the tie.
source_event: SerialEventId,
/// Target event.
target_event: SerialEventId,
/// Human-readable mismatch reason.
reason: &'static str,
},
/// Rendering through canonical music-core score conversion failed.
#[error("music-core rendering failed: {0}")]
MusicCore(String),
/// A modal or adapted realizer requires a scale context.
#[error("serial realizer {0} requires a modal scale in the realization context")]
MissingModalScale(RealizerId),
/// One adaptation pitch-map operation failed.
#[error("pitch-map adaptation failed: {0}")]
PitchMap(String),
}