music21_rs/roman/
realize.rs1use super::*;
6
7impl RomanNumeral {
8 pub(super) fn reading(&self) -> Result<Reading> {
10 if let Some(scale) = &self.scale {
11 return Ok(Reading::Scale(scale.clone()));
12 }
13 let key = self.effective_key()?;
14 if matches!(self.kind, RomanKind::AugmentedSixth(_)) && key.mode() != "minor" {
17 return Ok(Reading::Key(Key::from_tonic_mode(
18 &key.tonic_pitch().name(),
19 Some("minor"),
20 )?));
21 }
22 Ok(Reading::Key(key))
23 }
24
25 pub fn to_chord(&self) -> Result<Chord> {
35 let reading = self.reading()?;
36 let numbers = self.figures.numbers();
37 let implies_root = FIGURES_IMPLYING_ROOT.contains(&numbers.as_slice());
38 let bass_degree = self.bass_scale_degree(&numbers, implies_root)?;
39
40 let mut pitches = vec![reading.pitch_at(bass_degree)?];
41 for figure in self.figures.figures().iter().rev() {
42 let Some(number) = figure.number() else {
43 continue;
44 };
45 let degree = bass_degree + number - 1;
46 let mut pitch = figure.modifier().modify(&reading.pitch_at(degree)?)?;
47 let below = pitches.last().map_or(0.0, Pitch::ps);
48 if pitch.ps() < below {
49 pitch.set_octave(Some(pitch.octave().unwrap_or(4) + 1));
50 }
51 pitches.push(pitch);
52 }
53
54 if self.accidental != 0 {
59 let untouched = upper_extension_indices(&pitches)?;
60 for (index, pitch) in pitches.iter_mut().enumerate() {
61 if untouched.contains(&index) {
62 continue;
63 }
64 let alter = pitch.accidental().alter() + FloatType::from(self.accidental);
65 if alter == 0.0 {
68 pitch.set_written_accidental(None);
69 } else {
70 pitch.set_accidental(Accidental::new(alter)?);
71 }
72 }
73 }
74
75 let root = if implies_root {
77 None
78 } else {
79 Some(pitches[0].clone())
80 };
81
82 self.match_accidentals_to_quality(&mut pitches, root.as_ref())?;
83 self.correct_bracketed_pitches(&mut pitches, root.as_ref())?;
84
85 let root = root.map(|_| pitches[0].clone());
89
90 let altered = !self.figures.omitted.is_empty() || !self.figures.added.is_empty();
93 let recorded = match &root {
94 Some(root) => Some(root.clone()),
95 None if altered => Chord::new(pitches.as_slice())?.root().cloned(),
96 None => None,
97 };
98
99 self.omit_steps(&mut pitches, recorded.as_ref())?;
100 self.add_steps(&mut pitches, &reading)?;
101
102 let mut chord = Chord::new(pitches.as_slice())?;
103 chord.set_root(recorded);
104 Ok(chord)
105 }
106
107 pub(super) fn bass_scale_degree(
109 &self,
110 numbers: &[u8],
111 implies_root: bool,
112 ) -> Result<IntegerType> {
113 if !implies_root {
114 return Ok(IntegerType::from(self.degree));
115 }
116 bass_scale_degree_from_notation_in(self.degree, numbers, self.reading()?.cardinality())
117 .map(IntegerType::from)
118 }
119
120 pub(super) fn match_accidentals_to_quality(
124 &self,
125 pitches: &mut [Pitch],
126 root: Option<&Pitch>,
127 ) -> Result<()> {
128 let written: Vec<u8> = [3u8, 5, 7]
129 .into_iter()
130 .filter(|step| self.figures.alters(*step))
131 .collect();
132 match_pitches_to_quality(pitches, root, self.implied_quality, &written)
133 }
134
135 pub(super) fn correct_bracketed_pitches(
138 &self,
139 pitches: &mut [Pitch],
140 root: Option<&Pitch>,
141 ) -> Result<()> {
142 for (alter, step) in &self.figures.bracketed {
143 let Some(index) = chord_step_index(pitches, root, *step)? else {
144 continue;
145 };
146 let moved = pitches[index].accidental().alter() + FloatType::from(*alter);
147 pitches[index].set_accidental(Accidental::new(moved)?);
148 }
149 Ok(())
150 }
151
152 pub(super) fn omit_steps(&self, pitches: &mut Vec<Pitch>, root: Option<&Pitch>) -> Result<()> {
154 if self.figures.omitted.is_empty() {
155 return Ok(());
156 }
157 let mut dropped = Vec::new();
158 for step in &self.figures.omitted {
159 if let Some(index) = chord_step_index(pitches, root, *step)? {
160 dropped.push(pitches[index].name());
161 }
162 }
163 pitches.retain(|pitch| !dropped.contains(&pitch.name()));
164 Ok(())
165 }
166
167 pub(super) fn add_steps(&self, pitches: &mut Vec<Pitch>, reading: &Reading) -> Result<()> {
170 if self.figures.added.is_empty() {
171 return Ok(());
172 }
173 let bass = pitches.first().map_or(0.0, Pitch::ps);
174 for (alter, step) in &self.figures.added {
175 let degree = IntegerType::from(self.degree) + IntegerType::from(*step) - 1;
176 let mut added = reading.pitch_at(degree)?;
177 let moved = added.accidental().alter() + FloatType::from(*alter);
178 added.set_accidental(Accidental::new(moved)?);
179 while added.ps() < bass {
180 added.set_octave(Some(added.octave().unwrap_or(4) + 1));
181 }
182 if added.ps() == bass
185 && pitches
186 .first()
187 .is_some_and(|low| added.diatonic_note_number() < low.diatonic_note_number())
188 {
189 added.set_octave(Some(added.octave().unwrap_or(4) + 1));
190 }
191 if !pitches
192 .iter()
193 .any(|pitch| pitch.name_with_octave() == added.name_with_octave())
194 {
195 pitches.push(added);
196 }
197 }
198 pitches.sort_by(|left, right| {
201 left.ps()
202 .partial_cmp(&right.ps())
203 .unwrap_or(std::cmp::Ordering::Equal)
204 .then(
205 left.diatonic_note_number()
206 .cmp(&right.diatonic_note_number()),
207 )
208 });
209 Ok(())
210 }
211}
212
213pub fn match_pitches_to_quality(
221 pitches: &mut [Pitch],
222 root: Option<&Pitch>,
223 quality: ImpliedQuality,
224 written: &[u8],
225) -> Result<()> {
226 let correct = quality.correct_semitones();
227 for (step, want) in [3u8, 5, 7].into_iter().zip(correct.iter().copied()) {
228 if written.contains(&step) {
229 continue;
230 }
231 let Some(index) = chord_step_index(pitches, root, step)? else {
232 continue;
233 };
234 let have = step_semitones(pitches, root, index)?;
235 if have == IntegerType::from(want) {
236 continue;
237 }
238 correct_faulty_pitch(&mut pitches[index], IntegerType::from(want) - have)?;
239 }
240
241 if correct.len() == 2
244 && quality == ImpliedQuality::Minor
245 && !written.contains(&7)
246 && let Some(index) = chord_step_index(pitches, root, 7)?
247 && step_semitones(pitches, root, index)? == 11
248 {
249 correct_faulty_pitch(&mut pitches[index], -1)?;
250 }
251 Ok(())
252}
253
254pub(super) enum Reading {
259 Key(Key),
260 Scale(crate::scale::Scale),
261}
262
263impl Reading {
264 fn cardinality(&self) -> u8 {
266 match self {
267 Self::Key(_) => 7,
268 Self::Scale(scale) => scale.degree_count() as u8,
269 }
270 }
271
272 fn pitch_at(&self, degree: IntegerType) -> Result<Pitch> {
275 match self {
276 Self::Key(key) => degree_pitch(key, degree),
277 Self::Scale(scale) => {
278 let count = IntegerType::from(self.cardinality());
279 let wrapped = (degree - 1).rem_euclid(count) + 1;
280 scale.pitch_at_degree(wrapped)
281 }
282 }
283 }
284}
285
286pub(super) fn degree_pitch(key: &Key, degree: IntegerType) -> Result<Pitch> {
290 let wrapped = (degree - 1).rem_euclid(7) + 1;
291 key.pitch_from_degree(wrapped as usize)
292}
293
294pub(super) fn natural_at_diatonic_number(number: IntegerType) -> Result<Pitch> {
297 let (letter, octave) = crate::interval::convert_diatonic_number_to_step(number);
298 Pitch::builder().step(letter).octave(octave).build()
299}
300
301pub(super) fn upper_extension_indices(pitches: &[Pitch]) -> Result<Vec<usize>> {
306 let chord = Chord::new(pitches)?;
307 let Some(root) = chord.root().cloned() else {
308 return Ok(Vec::new());
309 };
310 let mut indices = Vec::new();
311 for step in [7u8, 2, 4, 6] {
312 if let Some(index) = chord_step_index(pitches, Some(&root), step)? {
313 let found = pitches[index].clone();
316 indices.extend((0..pitches.len()).filter(|other| pitches[*other] == found));
317 }
318 }
319 indices.sort_unstable();
320 indices.dedup();
321 Ok(indices)
322}
323
324pub(super) fn chord_step_index(
327 pitches: &[Pitch],
328 root: Option<&Pitch>,
329 step: u8,
330) -> Result<Option<usize>> {
331 let inferred;
332 let root = match root {
333 Some(root) => root,
334 None => {
335 let chord = Chord::new(pitches)?;
336 let Some(found) = chord.root().cloned() else {
337 return Ok(None);
338 };
339 inferred = found;
340 &inferred
341 }
342 };
343 let wanted = IntegerType::from(step);
344 Ok(pitches.iter().position(|pitch| {
345 (pitch.diatonic_note_number() - root.diatonic_note_number()).rem_euclid(7) + 1 == wanted
346 }))
347}
348
349pub(super) fn step_semitones(
351 pitches: &[Pitch],
352 root: Option<&Pitch>,
353 index: usize,
354) -> Result<IntegerType> {
355 let inferred;
356 let root = match root {
357 Some(root) => root,
358 None => {
359 let chord = Chord::new(pitches)?;
360 let Some(found) = chord.root().cloned() else {
361 return Ok(0);
362 };
363 inferred = found;
364 &inferred
365 }
366 };
367 let distance = (pitches[index].ps() - root.ps()).round() as IntegerType;
368 Ok(distance.rem_euclid(12))
369}
370
371pub(super) fn correct_faulty_pitch(pitch: &mut Pitch, correction: IntegerType) -> Result<()> {
374 let folded = fold_correction(correction) + pitch.accidental().alter() as IntegerType;
375 let alter = fold_correction(folded);
376 pitch.set_accidental(Accidental::new(FloatType::from(alter))?);
377 Ok(())
378}
379
380pub(super) fn fold_correction(semitones: IntegerType) -> IntegerType {
382 if semitones >= 6 {
383 semitones - 12
384 } else if semitones <= -6 {
385 semitones + 12
386 } else {
387 semitones
388 }
389}