1use crate::alpha::analysis::aligner::{Change, ChangeOp, StreamAligner};
12use crate::alpha::analysis::hasher::HashReference;
13use crate::alpha::analysis::ornament_recognizer::{TrillRecognizer, TurnRecognizer};
14use crate::braille::equality::equal;
15use crate::defaults::FloatType;
16use crate::error::{Error, Result};
17use crate::expressions::{Expression, Ornament};
18use crate::interval::notes_to_chromatic;
19use crate::note::Note;
20use crate::pitch::Accidental;
21use crate::stream::{Stream, StreamElement, StreamKind};
22
23enum Named<'a> {
26 Element(&'a StreamElement),
27 InChord(&'a Note),
28}
29
30impl Named<'_> {
31 fn note(&self) -> Option<&Note> {
32 match self {
33 Self::Element(StreamElement::Note(note)) => Some(note),
34 Self::InChord(note) => Some(*note),
35 Self::Element(_) => None,
36 }
37 }
38
39 fn quarter_length(&self) -> FloatType {
40 match self {
41 Self::Element(element) => element.quarter_length(),
42 Self::InChord(note) => StreamElement::Note((*note).clone()).quarter_length(),
43 }
44 }
45
46 fn element(&self) -> StreamElement {
47 match self {
48 Self::Element(element) => (*element).clone(),
49 Self::InChord(note) => StreamElement::Note((*note).clone()),
50 }
51 }
52}
53
54fn element_at(stream: &Stream, place: usize) -> Option<&StreamElement> {
57 stream.recurse().get(place).map(|(_, element)| *element)
58}
59
60fn element_at_mut(stream: &mut Stream, place: usize) -> Option<&mut StreamElement> {
62 fn walk<'a>(
63 stream: &'a mut Stream,
64 place: usize,
65 seen: &mut usize,
66 ) -> Option<&'a mut StreamElement> {
67 for event in stream.events_mut() {
68 if *seen == place {
69 return Some(event.element_mut());
70 }
71 *seen += 1;
72 if let StreamElement::Stream(inner) = event.element_mut()
73 && let Some(found) = walk(inner, place, seen)
74 {
75 return Some(found);
76 }
77 }
78 None
79 }
80 walk(stream, place, &mut 0)
81}
82
83fn container_of(stream: &Stream, place: usize) -> Option<(&Stream, usize)> {
86 fn walk<'a>(stream: &'a Stream, place: usize, seen: &mut usize) -> Option<(&'a Stream, usize)> {
87 for (index, event) in stream.events().iter().enumerate() {
88 if *seen == place {
89 return Some((stream, index));
90 }
91 *seen += 1;
92 if let StreamElement::Stream(inner) = event.element()
93 && let Some(found) = walk(inner, place, seen)
94 {
95 return Some(found);
96 }
97 }
98 None
99 }
100 walk(stream, place, &mut 0)
101}
102
103fn named(stream: &Stream, reference: HashReference) -> Result<Named<'_>> {
104 let element = element_at(stream, reference.element).ok_or_else(|| {
105 Error::Analysis(format!(
106 "no element at place {} of the stream",
107 reference.element
108 ))
109 })?;
110 match (reference.component, element) {
111 (None, element) => Ok(Named::Element(element)),
112 (Some(component), StreamElement::Chord(chord)) => chord
113 .notes()
114 .get(component)
115 .map(Named::InChord)
116 .ok_or_else(|| Error::Analysis(format!("the chord has no note {component}"))),
117 (Some(_), _) => Err(Error::Analysis(
118 "a reference to a note of something that is not a chord".to_string(),
119 )),
120 }
121}
122
123fn note_mut(stream: &mut Stream, reference: HashReference) -> Option<&mut Note> {
125 match (
126 reference.component,
127 element_at_mut(stream, reference.element)?,
128 ) {
129 (None, StreamElement::Note(note)) => Some(note),
130 (Some(component), StreamElement::Chord(chord)) => chord.notes_mut().get_mut(component),
131 _ => None,
132 }
133}
134
135fn references(change: &Change) -> Result<(HashReference, HashReference)> {
138 match (change.target, change.source) {
139 (Some(midi), Some(omr)) => Ok((midi, omr)),
140 _ => Err(Error::Analysis(
141 "'NoteHash' object has no attribute 'reference'".to_string(),
142 )),
143 }
144}
145
146fn colour(stream: &mut Stream, reference: HashReference, colour: &str) {
149 let colour = Some(colour.to_string());
150 if reference.component.is_some() {
151 if let Some(note) = note_mut(stream, reference) {
152 note.set_color(colour);
153 }
154 return;
155 }
156 match element_at_mut(stream, reference.element) {
157 Some(StreamElement::Note(note)) => note.set_color(colour),
158 Some(StreamElement::Chord(chord)) => chord.set_color(colour),
159 _ => {}
160 }
161}
162
163pub fn fix_enharmonics(changes: &[Change], midi: &Stream, omr: &mut Stream) -> Result<()> {
184 for change in changes {
185 let (midi_reference, omr_reference) = references(change)?;
186 colour(omr, omr_reference, "black");
187 let Some(played) = named(midi, midi_reference)?.note().cloned() else {
188 continue;
189 };
190 let Some(read) = named(omr, omr_reference)?.note().cloned() else {
191 continue;
192 };
193 if change.op == ChangeOp::NoChange {
194 continue;
195 }
196 if notes_to_chromatic(played.pitch(), read.pitch())?.interval_class() > 5 {
197 continue;
198 }
199 let Some(fixed) = note_mut(omr, omr_reference) else {
200 continue;
201 };
202 let (played, read) = (played.pitch(), read.pitch());
203 let accidental = read.written_accidental().map(Accidental::name);
204 let has_sharp_or_flat = accidental.is_some_and(|name| name != "natural");
205 let same_step = played.step() == read.step();
206 let mut pitch = read.clone();
207 if accidental == Some("natural") {
208 if played.ps() == read.ps() {
209 pitch.set_written_accidental(None);
210 } else if read.ps() > played.ps() {
211 if read.ps() - 1.0 == played.ps() {
212 pitch.set_written_accidental(Some(Accidental::new("flat")?));
213 }
214 } else if read.ps() < played.ps() && read.ps() + 1.0 == played.ps() {
215 pitch.set_written_accidental(Some(Accidental::new("sharp")?));
216 }
217 } else if has_sharp_or_flat && same_step {
218 pitch.set_written_accidental(played.written_accidental().cloned());
219 } else if has_sharp_or_flat {
220 if read.ps() > played.ps() {
221 if accidental == Some("sharp") && read.ps() - 2.0 == played.ps() {
222 pitch.set_written_accidental(Some(Accidental::new("flat")?));
223 }
224 } else if read.ps() < played.ps()
225 && accidental == Some("flat")
226 && read.ps() + 2.0 == played.ps()
227 {
228 pitch.set_written_accidental(Some(Accidental::new("sharp")?));
229 }
230 } else if read != played
231 && played
232 .written_accidental()
233 .is_some_and(|accidental| accidental.name() != "natural")
234 && same_step
235 {
236 pitch = played.clone();
237 }
238 fixed.set_pitch(pitch);
239 }
240 Ok(())
241}
242
243pub fn delete_measures(changes: &[Change], midi: &Stream, omr: &mut Stream) -> Result<()> {
256 let mut removed: Vec<usize> = Vec::new();
257 let mut result = Ok(());
258 for change in changes {
259 let (midi_reference, omr_reference) = references(change)?;
260 if named(midi, midi_reference)?.note().is_none()
261 || named(omr, omr_reference)?.note().is_none()
262 || change.op == ChangeOp::NoChange
263 {
264 continue;
265 }
266 match measure_holding(omr, omr_reference.element) {
267 Some(Some(top)) => {
268 if !removed.contains(&top) {
269 removed.push(top);
270 }
271 }
272 Some(None) => {}
273 None => {
274 result = Err(Error::Analysis(
275 "None is not a Music21Object; got <class 'NoneType'>".to_string(),
276 ));
277 break;
278 }
279 }
280 }
281 removed.sort_unstable();
282 for top in removed.into_iter().rev() {
283 omr.remove_event(top);
284 }
285 result
286}
287
288fn measure_holding(stream: &Stream, place: usize) -> Option<Option<usize>> {
292 fn path(stream: &Stream, place: usize, seen: &mut usize, out: &mut Vec<usize>) -> bool {
295 for (index, event) in stream.events().iter().enumerate() {
296 out.push(index);
297 if *seen == place {
298 return true;
299 }
300 *seen += 1;
301 if let StreamElement::Stream(inner) = event.element()
302 && path(inner, place, seen, out)
303 {
304 return true;
305 }
306 out.pop();
307 }
308 false
309 }
310 let mut places = Vec::new();
311 if !path(stream, place, &mut 0, &mut places) {
312 return None;
313 }
314 let mut holders = Vec::new();
316 let mut current = stream;
317 for (depth, &index) in places[..places.len() - 1].iter().enumerate() {
318 let StreamElement::Stream(inner) = current.events()[index].element() else {
319 return None;
320 };
321 holders.push((depth, inner.kind()));
322 current = inner;
323 }
324 let (depth, _) = holders
325 .into_iter()
326 .rev()
327 .find(|(_, kind)| *kind == StreamKind::Measure)?;
328 Some((depth == 0).then_some(places[0]))
329}
330
331pub fn notes_within_duration(
338 container: &Stream,
339 index: usize,
340 total: FloatType,
341) -> Vec<StreamElement> {
342 let events = container.events();
343 let Some(first) = events.get(index) else {
344 return Vec::new();
345 };
346 let first = first.element();
347 if first.quarter_length() > total {
348 return Vec::new();
349 }
350 let mut left = total - first.quarter_length();
351 let mut out = vec![first.clone()];
352 for event in &events[index + 1..] {
353 let element = event.element();
354 if !is_general_note(element) {
355 continue;
356 }
357 if left < element.quarter_length() {
358 break;
359 }
360 left -= element.quarter_length();
361 out.push(element.clone());
362 }
363 out
364}
365
366fn is_general_note(element: &StreamElement) -> bool {
368 matches!(
369 element,
370 StreamElement::Note(_)
371 | StreamElement::Chord(_)
372 | StreamElement::Rest(_)
373 | StreamElement::Unpitched(_)
374 | StreamElement::PercussionChord(_)
375 | StreamElement::ChordSymbol(_)
376 )
377}
378
379fn busy_notes(
382 stream: &Stream,
383 reference: HashReference,
384 total: FloatType,
385) -> Result<Vec<StreamElement>> {
386 if reference.component.is_some() {
387 let note = named(stream, reference)?;
389 return Ok(if note.quarter_length() > total {
390 Vec::new()
391 } else {
392 vec![note.element()]
393 });
394 }
395 let (container, index) = container_of(stream, reference.element).ok_or_else(|| {
396 Error::Analysis(format!(
397 "no element at place {} of the stream",
398 reference.element
399 ))
400 })?;
401 Ok(notes_within_duration(container, index, total))
402}
403
404#[derive(Clone, Debug, PartialEq)]
406pub enum Recognizer {
407 Trill(TrillRecognizer),
409 Turn(TurnRecognizer),
411}
412
413impl Recognizer {
414 fn recognize(
415 &self,
416 busy: &[StreamElement],
417 simple: &[StreamElement],
418 ) -> Result<Option<Ornament>> {
419 match self {
420 Self::Trill(recognizer) => recognizer.recognize(busy, simple),
421 Self::Turn(recognizer) => recognizer.recognize(busy, simple),
422 }
423 }
424}
425
426#[derive(Clone, Debug, PartialEq)]
429pub struct OrnamentFixer {
430 pub recognizers: Vec<Recognizer>,
432 pub mark_change_color: String,
435}
436
437impl OrnamentFixer {
438 pub fn trills() -> Self {
441 Self {
442 recognizers: vec![
443 Recognizer::Trill(TrillRecognizer::default()),
444 Recognizer::Trill(TrillRecognizer {
445 check_nachschlag: true,
446 ..TrillRecognizer::default()
447 }),
448 ],
449 mark_change_color: "blue".to_string(),
450 }
451 }
452
453 pub fn turns() -> Self {
455 Self {
456 recognizers: vec![Recognizer::Turn(TurnRecognizer::default())],
457 mark_change_color: "blue".to_string(),
458 }
459 }
460
461 pub fn find_ornament(
468 &self,
469 busy: &[StreamElement],
470 simple: &[StreamElement],
471 ) -> Result<Option<Ornament>> {
472 for recognizer in &self.recognizers {
473 if let Some(found) = recognizer.recognize(busy, simple)? {
474 return Ok(Some(found));
475 }
476 }
477 Ok(None)
478 }
479
480 pub fn fix(
494 &self,
495 changes: &[Change],
496 midi: &Stream,
497 omr: &mut Stream,
498 mark: bool,
499 ) -> Result<()> {
500 let mut labelled: Vec<StreamElement> = Vec::new();
501 let mut used: Vec<StreamElement> = Vec::new();
502 for change in changes {
503 if matches!(change.op, ChangeOp::NoChange | ChangeOp::Deletion) {
504 continue;
505 }
506 let (midi_reference, omr_reference) = references(change)?;
507 let read = named(omr, omr_reference)?;
508 let read_element = read.element();
509 if labelled.iter().any(|done| equal(done, &read_element)) {
510 continue;
511 }
512 let busy = busy_notes(midi, midi_reference, read.quarter_length())?;
513 if busy
514 .iter()
515 .any(|note| used.iter().any(|done| equal(done, note)))
516 {
517 continue;
518 }
519 let Some(ornament) = self.find_ornament(&busy, std::slice::from_ref(&read_element))?
520 else {
521 continue;
522 };
523 used.extend(busy);
524 if let Some(note) = note_mut(omr, omr_reference) {
525 let already = note
526 .expressions()
527 .iter()
528 .any(|expression| matches!(expression, Expression::Ornament(_)));
529 if !already {
530 note.expressions_mut()
531 .push(Expression::Ornament(Box::new(ornament)));
532 if mark {
533 note.set_color(Some(self.mark_change_color.clone()));
534 }
535 }
536 labelled.push(StreamElement::Note(note.clone()));
537 }
538 }
539 Ok(())
540 }
541
542 pub fn fixed(&self, midi: &Stream, omr: &Stream) -> Result<Stream> {
550 let mut aligner = StreamAligner::new();
551 aligner.align(midi, omr)?;
552 let mut fixed = omr.clone();
553 self.fix(&aligner.changes, midi, &mut fixed, false)?;
554 Ok(fixed)
555 }
556}
557
558#[cfg(test)]
559mod tests {
560 use super::*;
561 use crate::duration::Duration;
562 use crate::expressions::OrnamentKind;
563
564 fn note(name: &str, length: FloatType) -> Note {
565 Note::from_name(name)
566 .unwrap()
567 .with_duration(Duration::new(length).unwrap())
568 }
569
570 fn measure(notes: &[(&str, FloatType)]) -> Stream {
571 let mut measure = Stream::with_kind(StreamKind::Measure);
572 for (name, length) in notes {
573 measure.push(note(name, *length));
574 }
575 measure
576 }
577
578 fn reference(element: usize) -> Option<HashReference> {
579 Some(HashReference {
580 element,
581 component: None,
582 })
583 }
584
585 #[test]
588 fn enharmonics_are_respelled_as_music21_respells_them() {
589 for (played, read, fixed) in [
590 ("B-4", "A#4", "A#4"),
591 ("A#4", "A#4", "A#4"),
592 ("A4", "An4", "A4"),
593 ("G#4", "An4", "A-4"),
594 ("G-4", "Fn4", "F#4"),
595 ("G#4", "Gn4", "G#4"),
596 ("G#4", "G-4", "G#4"),
597 ("G#4", "A#4", "A-4"),
598 ("A-4", "G-4", "G#4"),
599 ] {
600 let midi = measure(&[(played, 1.0)]);
601 let mut omr = measure(&[(read, 1.0)]);
602 let op = if played == read {
603 ChangeOp::NoChange
604 } else {
605 ChangeOp::Substitution
606 };
607 let changes = [Change {
608 target: reference(0),
609 source: reference(0),
610 op,
611 }];
612 fix_enharmonics(&changes, &midi, &mut omr).unwrap();
613 let StreamElement::Note(result) = omr.events()[0].element() else {
614 unreachable!("the measure holds a note")
615 };
616 let named = result.pitch().name_with_octave();
617 let expected = Note::from_name(fixed).unwrap().pitch().name_with_octave();
618 assert_eq!(named, expected, "{played} played, {read} read");
619 assert_eq!(result.color(), Some("black"));
620 if read == "An4" && played == "A4" {
621 assert!(result.pitch().written_accidental().is_none());
622 }
623 }
624 }
625
626 #[test]
628 fn notes_within_a_length_are_the_ones_that_fit() {
629 let m2 = measure(&[("C", 1.0), ("D", 0.5), ("E", 0.5)]);
630 let names = |total: FloatType| -> Vec<String> {
631 notes_within_duration(&m2, 0, total)
632 .iter()
633 .map(|element| match element {
634 StreamElement::Note(note) => note.pitch().name(),
635 _ => "other".to_string(),
636 })
637 .collect()
638 };
639 assert_eq!(names(1.0), ["C"]);
640 assert_eq!(names(2.0), ["C", "D", "E"]);
641 assert_eq!(names(4.0), ["C", "D", "E"]);
642 assert_eq!(names(1.5), ["C", "D"]);
643 assert_eq!(names(1.75), ["C", "D"]);
644 assert!(names(0.5).is_empty());
645 }
646
647 fn ornaments(stream: &Stream) -> Vec<Vec<(OrnamentKind, FloatType, bool)>> {
648 stream
649 .events()
650 .iter()
651 .map(|event| match event.element() {
652 StreamElement::Note(note) => note
653 .expressions()
654 .iter()
655 .filter_map(|expression| match expression {
656 Expression::Ornament(ornament) => Some((
657 ornament.kind(),
658 ornament.quarter_length(),
659 ornament.nachschlag(),
660 )),
661 _ => None,
662 })
663 .collect(),
664 _ => Vec::new(),
665 })
666 .collect()
667 }
668
669 fn fixed(
670 fixer: &OrnamentFixer,
671 midi: &Stream,
672 omr: &Stream,
673 ) -> Vec<Vec<(OrnamentKind, FloatType, bool)>> {
674 let copied = fixer.fixed(midi, omr).unwrap();
676 let mut aligner = StreamAligner::new();
677 aligner.align(midi, omr).unwrap();
678 let mut in_place = omr.clone();
679 fixer
680 .fix(&aligner.changes, midi, &mut in_place, false)
681 .unwrap();
682 assert_eq!(ornaments(&copied), ornaments(&in_place));
683 ornaments(&in_place)
684 }
685
686 #[test]
688 fn trills_are_written_as_music21_writes_them() {
689 use OrnamentKind::Trill;
690 let trills = OrnamentFixer::trills();
691 let mut played = measure(&[("G", 0.25), ("A", 0.25), ("G", 0.25), ("A", 0.25)]);
692 for name in ["C", "B3", "C", "B3", "C", "B3", "C", "B3"] {
693 played.push(note(name, 0.0625));
694 }
695 let read = measure(&[("G", 1.0), ("B3", 1.0)]);
696 assert_eq!(
697 fixed(&trills, &played, &read),
698 [vec![(Trill, 0.25, false)], vec![(Trill, 0.125, false)]]
701 );
702
703 let wrong = measure(&[("C", 0.25), ("A", 0.25), ("C", 0.25), ("A", 0.25)]);
704 assert_eq!(fixed(&trills, &wrong, &measure(&[("C", 1.0)])), [vec![]]);
705 let other = measure(&[("C", 0.25), ("D", 0.25), ("C", 0.25), ("D", 0.25)]);
706 assert_eq!(fixed(&trills, &other, &measure(&[("A", 1.0)])), [vec![]]);
707
708 let nachschlag = measure(&[
709 ("E", 0.125),
710 ("F", 0.125),
711 ("E", 0.125),
712 ("F", 0.125),
713 ("E", 0.125),
714 ("F", 0.125),
715 ("E", 0.125),
716 ("D", 0.125),
717 ]);
718 assert_eq!(
719 fixed(&trills, &nachschlag, &measure(&[("E", 1.0)])),
720 [vec![(Trill, 0.125, true)]]
721 );
722
723 let mut trilled = measure(&[("F", 1.0)]);
724 let mut written = Ornament::of_kind(Trill);
725 written.set_quarter_length(0.125);
726 if let StreamElement::Note(note) = trilled.events_mut()[0].element_mut() {
727 note.expressions_mut()
728 .push(Expression::Ornament(Box::new(written)));
729 }
730 let played = measure(&[("F", 0.125), ("G", 0.125), ("F", 0.125), ("G", 0.125)]);
731 assert_eq!(
732 fixed(&trills, &played, &trilled),
733 [vec![(Trill, 0.125, false)]]
734 );
735 }
736
737 #[test]
739 fn turns_are_written_as_music21_writes_them() {
740 use OrnamentKind::{InvertedTurn, Turn};
741 let turns = OrnamentFixer::turns();
742 let played = measure(&[("G", 1.0), ("F", 1.0), ("E", 1.0), ("F", 1.0)]);
743 assert_eq!(
744 fixed(&turns, &played, &measure(&[("F", 4.0)])),
745 [vec![(Turn, 1.0, false)]]
746 );
747
748 let read = measure(&[("B-", 1.0), ("G", 1.0), ("B-", 1.0)]);
749 let played = measure(&[
750 ("A", 0.25),
751 ("B-", 0.25),
752 ("C5", 0.25),
753 ("B-", 0.25),
754 ("G", 1.0),
755 ("G#", 0.25),
756 ("A#", 0.25),
757 ("B", 0.25),
758 ("A#", 0.25),
759 ]);
760 assert_eq!(
761 fixed(&turns, &played, &read),
762 [
763 vec![(InvertedTurn, 0.25, false)],
764 vec![],
765 vec![(InvertedTurn, 0.25, false)]
766 ]
767 );
768
769 let played = measure(&[("B", 1.0), ("A", 1.0), ("G", 1.0), ("F", 1.0)]);
770 assert_eq!(fixed(&turns, &played, &measure(&[("A", 4.0)])), [vec![]]);
771 }
772}