1use 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 pitch.set_accidental(Some(Accidental::new(alter)?));
66 }
67 }
68
69 let root = if implies_root {
71 None
72 } else {
73 Some(pitches[0].clone())
74 };
75
76 self.match_accidentals_to_quality(&mut pitches, root.as_ref())?;
77 self.correct_bracketed_pitches(&mut pitches, root.as_ref())?;
78
79 let altered = !self.figures.omitted.is_empty() || !self.figures.added.is_empty();
82 let recorded = match &root {
83 Some(root) => Some(root.clone()),
84 None if altered => Chord::new(pitches.as_slice())?.root().cloned(),
85 None => None,
86 };
87
88 self.omit_steps(&mut pitches, recorded.as_ref())?;
89 self.add_steps(&mut pitches, &reading)?;
90
91 let mut chord = Chord::new(pitches.as_slice())?;
92 chord.set_root(recorded);
93 Ok(chord)
94 }
95
96 pub(super) fn bass_scale_degree(
98 &self,
99 numbers: &[u8],
100 implies_root: bool,
101 ) -> Result<IntegerType> {
102 if !implies_root {
103 return Ok(IntegerType::from(self.degree));
104 }
105 bass_scale_degree_from_notation_in(self.degree, numbers, self.reading()?.cardinality())
106 .map(IntegerType::from)
107 }
108
109 pub(super) fn match_accidentals_to_quality(
113 &self,
114 pitches: &mut [Pitch],
115 root: Option<&Pitch>,
116 ) -> Result<()> {
117 let written: Vec<u8> = [3u8, 5, 7]
118 .into_iter()
119 .filter(|step| self.figures.alters(*step))
120 .collect();
121 match_pitches_to_quality(pitches, root, self.implied_quality, &written)
122 }
123
124 pub(super) fn correct_bracketed_pitches(
127 &self,
128 pitches: &mut [Pitch],
129 root: Option<&Pitch>,
130 ) -> Result<()> {
131 for (alter, step) in &self.figures.bracketed {
132 let Some(index) = chord_step_index(pitches, root, *step)? else {
133 continue;
134 };
135 let moved = pitches[index].accidental().alter() + FloatType::from(*alter);
136 pitches[index].set_accidental(Some(Accidental::new(moved)?));
137 }
138 Ok(())
139 }
140
141 pub(super) fn omit_steps(&self, pitches: &mut Vec<Pitch>, root: Option<&Pitch>) -> Result<()> {
143 if self.figures.omitted.is_empty() {
144 return Ok(());
145 }
146 let mut dropped = Vec::new();
147 for step in &self.figures.omitted {
148 if let Some(index) = chord_step_index(pitches, root, *step)? {
149 dropped.push(pitches[index].name());
150 }
151 }
152 pitches.retain(|pitch| !dropped.contains(&pitch.name()));
153 Ok(())
154 }
155
156 pub(super) fn add_steps(&self, pitches: &mut Vec<Pitch>, reading: &Reading) -> Result<()> {
159 if self.figures.added.is_empty() {
160 return Ok(());
161 }
162 let bass = pitches.first().map_or(0.0, Pitch::ps);
163 for (alter, step) in &self.figures.added {
164 let degree = IntegerType::from(self.degree) + IntegerType::from(*step) - 1;
165 let mut added = reading.pitch_at(degree)?;
166 let moved = added.accidental().alter() + FloatType::from(*alter);
167 added.set_accidental(Some(Accidental::new(moved)?));
168 while added.ps() < bass {
169 added.set_octave(Some(added.octave().unwrap_or(4) + 1));
170 }
171 if added.ps() == bass
174 && pitches
175 .first()
176 .is_some_and(|low| added.diatonic_note_number() < low.diatonic_note_number())
177 {
178 added.set_octave(Some(added.octave().unwrap_or(4) + 1));
179 }
180 if !pitches
181 .iter()
182 .any(|pitch| pitch.name_with_octave() == added.name_with_octave())
183 {
184 pitches.push(added);
185 }
186 }
187 pitches.sort_by(|left, right| {
190 left.ps()
191 .partial_cmp(&right.ps())
192 .unwrap_or(std::cmp::Ordering::Equal)
193 .then(
194 left.diatonic_note_number()
195 .cmp(&right.diatonic_note_number()),
196 )
197 });
198 Ok(())
199 }
200}
201
202pub fn match_pitches_to_quality(
210 pitches: &mut [Pitch],
211 root: Option<&Pitch>,
212 quality: ImpliedQuality,
213 written: &[u8],
214) -> Result<()> {
215 let correct = quality.correct_semitones();
216 for (step, want) in [3u8, 5, 7].into_iter().zip(correct.iter().copied()) {
217 if written.contains(&step) {
218 continue;
219 }
220 let Some(index) = chord_step_index(pitches, root, step)? else {
221 continue;
222 };
223 let have = step_semitones(pitches, root, index)?;
224 if have == IntegerType::from(want) {
225 continue;
226 }
227 correct_faulty_pitch(&mut pitches[index], IntegerType::from(want) - have)?;
228 }
229
230 if correct.len() == 2
233 && quality == ImpliedQuality::Minor
234 && !written.contains(&7)
235 && let Some(index) = chord_step_index(pitches, root, 7)?
236 && step_semitones(pitches, root, index)? == 11
237 {
238 correct_faulty_pitch(&mut pitches[index], -1)?;
239 }
240 Ok(())
241}
242
243pub(super) enum Reading {
248 Key(Key),
249 Scale(crate::scale::Scale),
250}
251
252impl Reading {
253 fn cardinality(&self) -> u8 {
255 match self {
256 Self::Key(_) => 7,
257 Self::Scale(scale) => scale.degree_count() as u8,
258 }
259 }
260
261 fn pitch_at(&self, degree: IntegerType) -> Result<Pitch> {
264 match self {
265 Self::Key(key) => degree_pitch(key, degree),
266 Self::Scale(scale) => {
267 let count = IntegerType::from(self.cardinality());
268 let wrapped = (degree - 1).rem_euclid(count) + 1;
269 scale.pitch_at_degree(wrapped)
270 }
271 }
272 }
273}
274
275pub(super) fn degree_pitch(key: &Key, degree: IntegerType) -> Result<Pitch> {
279 let wrapped = (degree - 1).rem_euclid(7) + 1;
280 key.pitch_from_degree(wrapped as usize)
281}
282
283pub(super) fn natural_at_diatonic_number(number: IntegerType) -> Result<Pitch> {
285 const LETTERS: [char; 7] = ['C', 'D', 'E', 'F', 'G', 'A', 'B'];
286 let letter = LETTERS[((number - 1).rem_euclid(7)) as usize];
287 Pitch::builder()
288 .step(letter)
289 .octave((number - 1).div_euclid(7))
290 .build()
291}
292
293pub(super) fn upper_extension_indices(pitches: &[Pitch]) -> Result<Vec<usize>> {
298 let chord = Chord::new(pitches)?;
299 let Some(root) = chord.root().cloned() else {
300 return Ok(Vec::new());
301 };
302 let mut indices = Vec::new();
303 for step in [7u8, 2, 4, 6] {
304 if let Some(index) = chord_step_index(pitches, Some(&root), step)? {
305 indices.push(index);
306 }
307 }
308 Ok(indices)
309}
310
311pub(super) fn chord_step_index(
314 pitches: &[Pitch],
315 root: Option<&Pitch>,
316 step: u8,
317) -> Result<Option<usize>> {
318 let inferred;
319 let root = match root {
320 Some(root) => root,
321 None => {
322 let chord = Chord::new(pitches)?;
323 let Some(found) = chord.root().cloned() else {
324 return Ok(None);
325 };
326 inferred = found;
327 &inferred
328 }
329 };
330 let wanted = IntegerType::from(step);
331 Ok(pitches.iter().position(|pitch| {
332 (pitch.diatonic_note_number() - root.diatonic_note_number()).rem_euclid(7) + 1 == wanted
333 }))
334}
335
336pub(super) fn step_semitones(
338 pitches: &[Pitch],
339 root: Option<&Pitch>,
340 index: usize,
341) -> Result<IntegerType> {
342 let inferred;
343 let root = match root {
344 Some(root) => root,
345 None => {
346 let chord = Chord::new(pitches)?;
347 let Some(found) = chord.root().cloned() else {
348 return Ok(0);
349 };
350 inferred = found;
351 &inferred
352 }
353 };
354 let distance = (pitches[index].ps() - root.ps()).round() as IntegerType;
355 Ok(distance.rem_euclid(12))
356}
357
358pub(super) fn correct_faulty_pitch(pitch: &mut Pitch, correction: IntegerType) -> Result<()> {
361 let folded = fold_correction(correction) + pitch.accidental().alter() as IntegerType;
362 let alter = fold_correction(folded);
363 pitch.set_accidental(Some(Accidental::new(FloatType::from(alter))?));
364 Ok(())
365}
366
367pub(super) fn fold_correction(semitones: IntegerType) -> IntegerType {
369 if semitones >= 6 {
370 semitones - 12
371 } else if semitones <= -6 {
372 semitones + 12
373 } else {
374 semitones
375 }
376}