sim_lib_music_transform/exact/
composition.rs1use std::collections::{BTreeMap, BTreeSet};
2
3use sim_lib_music_core::{ObjectId, Staff, StaffNote, StaffVoice, Time};
4
5use super::{MusicTransform, MusicTransformChange, RhythmMask, finish};
6use crate::TransformError;
7
8pub fn sequence_staff(parts: &[Staff]) -> Result<MusicTransform<Staff>, TransformError> {
13 let mut voices = BTreeMap::<ObjectId, StaffVoice>::new();
14 let mut seen = BTreeSet::new();
15 let mut offset = Time::from_integer(0);
16 let mut changes = Vec::new();
17 for staff in parts {
18 for voice in &staff.voices {
19 let output = voices
20 .entry(voice.id.clone())
21 .or_insert_with(|| StaffVoice {
22 id: voice.id.clone(),
23 name: voice.name.clone(),
24 duration: Time::from_integer(0),
25 notes: Vec::new(),
26 });
27 if output.name != voice.name {
28 return Err(TransformError::InvalidTransformOutput {
29 transform: "sequence-staff",
30 reason: "matching voice ids have different names",
31 });
32 }
33 for note in &voice.notes {
34 require_unique_note(note, &mut seen, "sequence-staff")?;
35 let mut shifted = note.clone();
36 shifted.onset += offset;
37 if shifted.onset != note.onset {
38 changes.push(MusicTransformChange::Onset {
39 event_id: note.event_id.clone(),
40 before: note.onset,
41 after: shifted.onset,
42 });
43 }
44 output.notes.push(shifted);
45 }
46 }
47 offset += staff.duration();
48 }
49 for voice in voices.values_mut() {
50 voice.duration = offset;
51 }
52 finish(voices.into_values().collect(), changes)
53}
54
55pub fn parallel_staff(parts: &[Staff]) -> Result<MusicTransform<Staff>, TransformError> {
57 let mut voices = BTreeMap::<ObjectId, StaffVoice>::new();
58 let mut seen = BTreeSet::new();
59 for staff in parts {
60 for voice in &staff.voices {
61 let output = voices
62 .entry(voice.id.clone())
63 .or_insert_with(|| StaffVoice {
64 id: voice.id.clone(),
65 name: voice.name.clone(),
66 duration: Time::from_integer(0),
67 notes: Vec::new(),
68 });
69 if output.name != voice.name {
70 return Err(TransformError::InvalidTransformOutput {
71 transform: "parallel-staff",
72 reason: "matching voice ids have different names",
73 });
74 }
75 output.duration = output.duration.max(voice.duration);
76 for note in &voice.notes {
77 require_unique_note(note, &mut seen, "parallel-staff")?;
78 output.notes.push(note.clone());
79 }
80 }
81 }
82 finish(voices.into_values().collect(), Vec::new())
83}
84
85pub fn slice_staff(
87 staff: &Staff,
88 start: Time,
89 end: Time,
90) -> Result<MusicTransform<Staff>, TransformError> {
91 if start < Time::from_integer(0) || end < start {
92 return Err(TransformError::InvalidTransformOutput {
93 transform: "slice-staff",
94 reason: "slice must satisfy 0 <= start <= end",
95 });
96 }
97 let duration = end - start;
98 let mut voices = Vec::new();
99 let mut changes = Vec::new();
100 for voice in &staff.voices {
101 let mut notes = Vec::new();
102 for note in &voice.notes {
103 let clipped_start = note.onset.max(start);
104 let clipped_end = note.end().min(end);
105 if clipped_start >= clipped_end {
106 changes.push(MusicTransformChange::Removed {
107 note_id: note.note_id.clone(),
108 event_id: note.event_id.clone(),
109 reason: "outside slice",
110 });
111 continue;
112 }
113 let mut clipped = note.clone();
114 clipped.onset = clipped_start - start;
115 clipped.note.duration = clipped_end - clipped_start;
116 if clipped.onset != note.onset {
117 changes.push(MusicTransformChange::Onset {
118 event_id: note.event_id.clone(),
119 before: note.onset,
120 after: clipped.onset,
121 });
122 }
123 if clipped.note.duration != note.note.duration {
124 changes.push(MusicTransformChange::Duration {
125 event_id: note.event_id.clone(),
126 before: note.note.duration,
127 after: clipped.note.duration,
128 });
129 }
130 notes.push(clipped);
131 }
132 voices.push(StaffVoice {
133 id: voice.id.clone(),
134 name: voice.name.clone(),
135 duration,
136 notes,
137 });
138 }
139 finish(voices, changes)
140}
141
142pub fn apply_rhythm_mask(
144 staff: &Staff,
145 mask: &RhythmMask,
146) -> Result<MusicTransform<Staff>, TransformError> {
147 let mut voices = staff.voices.clone();
148 let mut changes = Vec::new();
149 for voice in &mut voices {
150 voice.notes.retain(|note| {
151 if mask.keeps(note.onset) {
152 true
153 } else {
154 changes.push(MusicTransformChange::Removed {
155 note_id: note.note_id.clone(),
156 event_id: note.event_id.clone(),
157 reason: "rhythm mask",
158 });
159 false
160 }
161 });
162 }
163 finish(voices, changes)
164}
165
166fn require_unique_note(
167 note: &StaffNote,
168 seen: &mut BTreeSet<ObjectId>,
169 transform: &'static str,
170) -> Result<(), TransformError> {
171 if !seen.insert(note.note_id.clone()) || !seen.insert(note.event_id.clone()) {
172 return Err(TransformError::InvalidTransformOutput {
173 transform,
174 reason: "duplicate note or event identity",
175 });
176 }
177 Ok(())
178}