1use sim_lib_pitch_core::PitchClass;
4use sim_lib_pitch_serial::{
5 DerivationKind, RowError, RowForm, RowOperation, RowSegment, SegmentInvariant, ToneRow,
6 analyze_derivation_partition, analyze_invariance,
7};
8
9use crate::techniques::derived_cells::build_derived_cell_plan;
10use crate::{
11 RowInstanceId, SerialDeployError, SerialEventId, SerialPlan, StructuralLicense, VoiceId,
12};
13
14#[derive(Clone, Debug, PartialEq, Eq)]
16pub struct DerivedCellOccurrence {
17 pub event_id: SerialEventId,
19 pub voice: VoiceId,
21 pub occurrence_index: usize,
23 pub source_ordinals: Vec<u8>,
25 pub generator_ordinals: Vec<u8>,
27 pub generator_classes: Vec<PitchClass>,
29 pub operation: RowOperation,
31}
32
33#[derive(Clone, Debug, PartialEq, Eq)]
35pub struct DerivedCellDeployment {
36 pub plan: SerialPlan,
38 pub kind: DerivationKind,
40 pub generator_size: usize,
42 pub occurrences: Vec<DerivedCellOccurrence>,
44}
45
46pub fn deploy_derived_cells(
48 row_id: RowInstanceId,
49 row_form: RowForm,
50 generator_size: usize,
51 voices: Vec<VoiceId>,
52 event_prefix: impl AsRef<str>,
53 rationale: impl AsRef<str>,
54 license: StructuralLicense,
55) -> Result<DerivedCellDeployment, SerialDeployError> {
56 let derivation = analyze_derivation_partition(row_form.row(), generator_size)
57 .map_err(|error| SerialDeployError::Plan(error.to_string()))?
58 .ok_or_else(|| {
59 SerialDeployError::Plan(format!(
60 "row {} is not derivational at generator size {generator_size}",
61 row_id.as_str()
62 ))
63 })?;
64 let deployed = build_derived_cell_plan(
65 row_id,
66 row_form,
67 derivation.clone(),
68 voices,
69 event_prefix.as_ref(),
70 rationale.as_ref(),
71 license,
72 )?;
73 Ok(DerivedCellDeployment {
74 plan: deployed.plan,
75 kind: derivation.kind,
76 generator_size: derivation.generator_size,
77 occurrences: deployed
78 .occurrences
79 .into_iter()
80 .map(|occurrence| DerivedCellOccurrence {
81 event_id: occurrence.event_id,
82 voice: occurrence.voice,
83 occurrence_index: occurrence.occurrence_index,
84 source_ordinals: occurrence.source_ordinals,
85 generator_ordinals: occurrence.generator_ordinals,
86 generator_classes: occurrence.generator_classes,
87 operation: occurrence.operation,
88 })
89 .collect(),
90 })
91}
92
93#[derive(Copy, Clone, Debug, PartialEq, Eq)]
95pub enum SymmetryRequirement {
96 None,
98 PitchPalindrome,
100 RetrogradeSource,
102}
103
104#[derive(Copy, Clone, Debug, PartialEq, Eq)]
106pub struct InvariantRequirement {
107 pub preserve_source_ordinals: bool,
109 pub preserve_pitch_identity: bool,
111 pub require_transposition: bool,
113 pub require_inversion: bool,
115 pub preserve_interval_order: bool,
117 pub preserve_set_class: bool,
119 pub symmetry: SymmetryRequirement,
121}
122
123impl InvariantRequirement {
124 pub const fn any() -> Self {
126 Self {
127 preserve_source_ordinals: false,
128 preserve_pitch_identity: false,
129 require_transposition: false,
130 require_inversion: false,
131 preserve_interval_order: false,
132 preserve_set_class: false,
133 symmetry: SymmetryRequirement::None,
134 }
135 }
136}
137
138#[derive(Clone, Debug, PartialEq, Eq)]
140pub struct SymmetryCertificate {
141 pub pitch_palindrome: bool,
143 pub retrograde_source: bool,
145}
146
147#[derive(Clone, Debug, PartialEq, Eq)]
149pub struct InvariantCertificate {
150 pub operation: RowOperation,
152 pub candidate_ordinals: Vec<u8>,
154 pub invariant: SegmentInvariant,
156 pub symmetry: SymmetryCertificate,
158}
159
160impl InvariantCertificate {
161 pub fn satisfies(&self, requirement: &InvariantRequirement) -> bool {
163 (!requirement.preserve_source_ordinals || self.invariant.ordinal_identity)
164 && (!requirement.preserve_pitch_identity || self.invariant.pitch_identity)
165 && (!requirement.require_transposition || self.invariant.transposition.is_some())
166 && (!requirement.require_inversion || self.invariant.inversion.is_some())
167 && (!requirement.preserve_interval_order || self.invariant.interval_order_identity)
168 && (!requirement.preserve_set_class || self.invariant.set_class_identity)
169 && match requirement.symmetry {
170 SymmetryRequirement::None => true,
171 SymmetryRequirement::PitchPalindrome => self.symmetry.pitch_palindrome,
172 SymmetryRequirement::RetrogradeSource => self.symmetry.retrograde_source,
173 }
174 }
175}
176
177#[derive(Clone, Debug, PartialEq, Eq)]
179pub struct InvariantFormCandidate {
180 pub form: RowForm,
182 pub certificate: InvariantCertificate,
184}
185
186#[derive(Clone, Debug, PartialEq, Eq)]
188pub struct UnsatisfiedInvariantRequest {
189 pub requirement: InvariantRequirement,
191 pub forms_checked: usize,
193 pub segments_checked: usize,
195}
196
197#[derive(Clone, Debug, PartialEq, Eq)]
199pub struct InvariantFormCandidates {
200 candidates: Vec<InvariantFormCandidate>,
201 unsatisfied: Option<UnsatisfiedInvariantRequest>,
202}
203
204impl InvariantFormCandidates {
205 pub fn iter(&self) -> impl Iterator<Item = &InvariantFormCandidate> {
207 self.candidates.iter()
208 }
209
210 pub fn as_slice(&self) -> &[InvariantFormCandidate] {
212 &self.candidates
213 }
214
215 pub fn unsatisfied(&self) -> Option<&UnsatisfiedInvariantRequest> {
217 self.unsatisfied.as_ref()
218 }
219}
220
221pub fn forms_with_invariant(
223 row: &ToneRow,
224 segment: &RowSegment,
225 requirement: InvariantRequirement,
226) -> Result<InvariantFormCandidates, RowError> {
227 let segment_len = segment.classes().len();
228 let mut candidates = Vec::new();
229 let mut forms_checked = 0usize;
230 let mut segments_checked = 0usize;
231
232 for family in [
233 sim_lib_pitch_serial::RowFamily::P,
234 sim_lib_pitch_serial::RowFamily::I,
235 sim_lib_pitch_serial::RowFamily::R,
236 sim_lib_pitch_serial::RowFamily::RI,
237 ] {
238 for addend in 0..12 {
239 forms_checked += 1;
240 let form = row.apply(RowOperation::new(family, addend));
241 for start in 0..=(form.classes().len() - segment_len) {
242 segments_checked += 1;
243 let candidate_segment = form.row().segment(start, segment_len)?;
244 let certificate = build_certificate(segment, &candidate_segment, form.operation());
245 if certificate.satisfies(&requirement) {
246 candidates.push(InvariantFormCandidate {
247 form: form.clone(),
248 certificate,
249 });
250 }
251 }
252 }
253 }
254
255 let unsatisfied = candidates
256 .is_empty()
257 .then_some(UnsatisfiedInvariantRequest {
258 requirement,
259 forms_checked,
260 segments_checked,
261 });
262 Ok(InvariantFormCandidates {
263 candidates,
264 unsatisfied,
265 })
266}
267
268fn build_certificate(
269 source: &RowSegment,
270 candidate: &RowSegment,
271 operation: RowOperation,
272) -> InvariantCertificate {
273 InvariantCertificate {
274 operation,
275 candidate_ordinals: candidate.ordinals().to_vec(),
276 invariant: analyze_invariance(source, candidate),
277 symmetry: SymmetryCertificate {
278 pitch_palindrome: is_pitch_palindrome(candidate.classes()),
279 retrograde_source: candidate.classes().iter().copied().eq(source
280 .classes()
281 .iter()
282 .rev()
283 .copied()),
284 },
285 }
286}
287
288fn is_pitch_palindrome(classes: &[PitchClass]) -> bool {
289 classes.iter().eq(classes.iter().rev())
290}