1use super::{Stream, StreamElement, StreamEvent, StreamKind};
5use crate::articulations::Articulation;
6use crate::chord::Chord;
7use crate::defaults::FloatType;
8use crate::duration::Duration;
9use crate::error::Result;
10use crate::expressions::Expression;
11use crate::makenotation::op_frac;
12use crate::notation::{StemDirection, Tie, TieType};
13use crate::note::Note;
14use crate::percussion::PercussionNote;
15use crate::rest::Rest;
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20#[non_exhaustive]
21pub struct ChordifyOptions {
22 pub add_ties: bool,
25 pub remove_redundant_pitches: bool,
28 pub to_sounding_pitch: bool,
31}
32
33impl Default for ChordifyOptions {
34 fn default() -> Self {
35 Self {
36 add_ties: true,
37 remove_redundant_pitches: true,
38 to_sounding_pitch: true,
39 }
40 }
41}
42
43pub(crate) struct Span<'a> {
45 pub(crate) start: FloatType,
46 pub(crate) end: FloatType,
47 pub(crate) element: &'a StreamElement,
48 pub(crate) part: usize,
50}
51
52impl Stream {
53 pub fn chordify(&self) -> Result<Stream> {
83 self.chordify_with(&ChordifyOptions::default())
84 }
85
86 pub fn chordify_with(&self, options: &ChordifyOptions) -> Result<Stream> {
105 Ok(self.chordify_tracking(options)?.0)
106 }
107
108 pub(crate) fn chordify_parts_sounding(&self) -> Result<(Stream, Vec<usize>)> {
113 let options = ChordifyOptions {
114 remove_redundant_pitches: false,
115 ..ChordifyOptions::default()
116 };
117 let ids: Vec<Option<String>> = self
118 .events
119 .iter()
120 .filter_map(|event| match &event.element {
121 StreamElement::Stream(inner) if inner.kind() != StreamKind::Voice => {
122 Some(inner.id().map(str::to_string))
123 }
124 _ => None,
125 })
126 .collect();
127 let (chordified, heard) = self.chordify_tracking(&options)?;
128 let sounding = heard
129 .into_iter()
130 .map(|parts| {
131 let mut named: Vec<String> = Vec::new();
132 for part in parts {
133 let name = ids
134 .get(part)
135 .cloned()
136 .flatten()
137 .unwrap_or_else(|| format!("#{part}"));
138 if !named.contains(&name) {
139 named.push(name);
140 }
141 }
142 named.len()
143 })
144 .collect();
145 Ok((chordified, sounding))
146 }
147
148 fn chordify_tracking(&self, options: &ChordifyOptions) -> Result<(Stream, Vec<Vec<usize>>)> {
150 let mut heard: Vec<Vec<usize>> = Vec::new();
151 let first_stream = |stream: &Stream| -> Option<Stream> {
152 stream.events.iter().find_map(|event| match &event.element {
153 StreamElement::Stream(inner) => Some((**inner).clone()),
154 _ => None,
155 })
156 };
157 let parted = self.has_part_like_streams();
158 let work = if options.to_sounding_pitch
159 && ((parted
160 && first_stream(self)
161 .is_some_and(|first| first.at_sounding_pitch() == Some(false)))
162 || (!parted && self.at_sounding_pitch() == Some(false)))
163 {
164 self.to_sounding_pitch()?
165 } else {
166 self.clone()
167 };
168 let source = if parted {
169 first_stream(&work).unwrap_or_else(|| work.clone())
170 } else {
171 work.clone()
172 };
173 let mut template = template_of(&source);
174
175 if template.measures().is_empty() {
176 let spans = spans_of(&[&work]);
177 fill(&mut template, &spans, options, &mut heard)?;
178 } else {
179 let parts: Vec<&Stream> = if parted {
181 work.events
182 .iter()
183 .filter_map(|event| match &event.element {
184 StreamElement::Stream(inner) if inner.kind() != StreamKind::Voice => {
185 Some(&**inner)
186 }
187 _ => None,
188 })
189 .collect()
190 } else {
191 vec![&work]
192 };
193 let measures_of: Vec<Vec<&Stream>> = parts.iter().map(|part| part.measures()).collect();
194 let mut index = 0;
195 for event in &mut template.events {
196 let StreamElement::Stream(measure) = &mut event.element else {
197 continue;
198 };
199 if measure.kind() != StreamKind::Measure {
200 continue;
201 }
202 let sources: Vec<&Stream> = measures_of
203 .iter()
204 .filter_map(|measures| measures.get(index).copied())
205 .collect();
206 index += 1;
207 let spans = spans_of(&sources);
208 fill(measure, &spans, options, &mut heard)?;
209 }
210 }
211
212 template.for_each_mut(&mut |_, element| {
214 let notes = match element {
215 StreamElement::Chord(chord) => chord.notes_mut(),
216 StreamElement::Note(note) => std::slice::from_mut(note),
217 _ => return,
218 };
219 for note in notes {
220 let mut pitch = note.pitch().clone();
221 if let Some(accidental) = pitch.written_accidental_mut()
222 && accidental.display_type() != "even-tied"
223 {
224 accidental.set_display_status(None);
225 note.set_pitch(pitch);
226 }
227 }
228 });
229 if parted && let Some(metadata) = work.metadata() {
230 template.set_metadata(Some(metadata.clone()));
231 }
232 Ok((template, heard))
233 }
234}
235
236fn sounds_a_note(element: &StreamElement) -> bool {
239 match element {
240 StreamElement::Note(_) => true,
241 StreamElement::Chord(chord) => !chord.notes().is_empty(),
242 StreamElement::ChordSymbol(symbol) => {
243 symbol.pitches().is_ok_and(|pitches| !pitches.is_empty())
244 }
245 StreamElement::PercussionChord(chord) => chord
246 .members()
247 .iter()
248 .any(|member| matches!(member, PercussionNote::Note(_))),
249 _ => false,
250 }
251}
252
253fn is_general_note(element: &StreamElement) -> bool {
256 matches!(
257 element,
258 StreamElement::Note(_)
259 | StreamElement::Chord(_)
260 | StreamElement::Rest(_)
261 | StreamElement::Unpitched(_)
262 | StreamElement::PercussionChord(_)
263 | StreamElement::ChordSymbol(_)
264 )
265}
266
267pub(crate) fn template_of(stream: &Stream) -> Stream {
271 template_removing(stream, false)
272}
273
274pub(crate) fn template_removing(stream: &Stream, marks: bool) -> Stream {
281 let events = stream
282 .events
283 .iter()
284 .filter_map(|event| match &event.element {
285 StreamElement::Stream(inner) if inner.kind() == StreamKind::Voice => None,
286 StreamElement::Stream(inner) => Some(StreamEvent::new(
287 event.offset,
288 template_removing(inner, marks),
289 )),
290 element if is_general_note(element) => None,
291 element
292 if marks
293 && (element.is_instance("Dynamic") || element.is_instance("Expression")) =>
294 {
295 None
296 }
297 _ => Some(event.clone()),
298 })
299 .collect();
300 let mut template = stream.with_events(events);
304 template.set_number_hidden(false);
305 template.set_measure_numbering(None);
306 template.set_name_hidden(false);
307 template.set_abbreviation_hidden(false);
308 template
309}
310
311fn spans_of<'a>(streams: &[&'a Stream]) -> Vec<Span<'a>> {
314 let mut spans = Vec::new();
315 for (part, stream) in streams.iter().enumerate() {
316 for (offset, element) in stream.leaves() {
317 if is_general_note(element) {
318 let start = op_frac(offset);
319 spans.push(Span {
320 start,
321 end: op_frac(start + element.quarter_length()),
322 element,
323 part,
324 });
325 }
326 }
327 }
328 spans
329}
330
331fn fill(
334 stream: &mut Stream,
335 spans: &[Span<'_>],
336 options: &ChordifyOptions,
337 heard: &mut Vec<Vec<usize>>,
338) -> Result<()> {
339 let mut points: Vec<FloatType> = spans
340 .iter()
341 .flat_map(|span| [span.start, span.end])
342 .collect();
343 points.sort_by(FloatType::total_cmp);
344 points.dedup();
345 if !points.contains(&0.0) {
346 points.insert(0, 0.0);
347 }
348 let mut made = Vec::new();
349 for pair in points.windows(2) {
350 let (offset, end) = (pair[0], pair[1]);
351 if (end - offset).abs() <= 1e-7 {
352 continue;
353 }
354 let element = make_element(spans, offset, op_frac(end - offset), options)?;
355 if matches!(element, StreamElement::Chord(_)) {
356 let mut parts = Vec::new();
357 for span in sounding_at(spans, offset) {
358 if sounds_a_note(span.element) && !parts.contains(&span.part) {
359 parts.push(span.part);
360 }
361 }
362 heard.push(parts);
363 }
364 made.push(StreamEvent::new(op_frac(offset), element));
365 }
366 stream.insert_sorted(made);
367 consolidate_rests(stream)
368}
369
370fn sounding_at<'a, 'b>(spans: &'b [Span<'a>], offset: FloatType) -> Vec<&'b Span<'a>> {
374 let mut starting: Vec<&Span<'a>> = spans.iter().filter(|span| span.start == offset).collect();
375 starting.sort_by(|left, right| left.end.total_cmp(&right.end));
376 let mut overlapping: Vec<&Span<'a>> = spans
377 .iter()
378 .filter(|span| span.start < offset && offset < span.end)
379 .collect();
380 overlapping.sort_by(|left, right| {
381 left.start
382 .total_cmp(&right.start)
383 .then(left.end.total_cmp(&right.end))
384 });
385 starting.extend(overlapping);
386 starting
387}
388
389fn notes_of(element: &StreamElement) -> Result<Vec<Note>> {
392 Ok(match element {
393 StreamElement::Note(note) => vec![note.clone()],
394 StreamElement::Chord(chord) => with_chord_marks(
395 chord.notes().to_vec(),
396 chord.articulations(),
397 chord.expressions(),
398 ),
399 StreamElement::ChordSymbol(symbol) => {
400 let duration = symbol.duration().clone();
401 symbol
402 .pitches()?
403 .into_iter()
404 .map(|pitch| Note::from_pitch(pitch).with_duration(duration.clone()))
405 .collect()
406 }
407 StreamElement::PercussionChord(chord) => {
408 let written = chord.written();
409 let notes = chord
410 .members()
411 .into_iter()
412 .filter_map(|member| match member {
413 PercussionNote::Note(note) => Some(note),
414 PercussionNote::Unpitched(_) => None,
415 })
416 .collect();
417 with_chord_marks(notes, written.articulations(), written.expressions())
418 }
419 _ => Vec::new(),
420 })
421}
422
423fn with_chord_marks(
424 mut notes: Vec<Note>,
425 articulations: &[Articulation],
426 expressions: &[Expression],
427) -> Vec<Note> {
428 if let Some(first) = notes.first_mut() {
429 first
430 .articulations_mut()
431 .extend(articulations.iter().cloned());
432 first.expressions_mut().extend(expressions.iter().cloned());
433 }
434 notes
435}
436
437pub(crate) fn make_element(
440 spans: &[Span<'_>],
441 offset: FloatType,
442 length: FloatType,
443 options: &ChordifyOptions,
444) -> Result<StreamElement> {
445 let duration = Duration::new(length)?;
446 let sounding = sounding_at(spans, offset);
447 let mut notes: Vec<(String, Note)> = Vec::new();
448 let mut bust = 0;
449 let mut any_pitch = false;
450 for span in &sounding {
451 for note in notes_of(span.element)? {
452 any_pitch = true;
453 let key = note.pitch().name_with_octave();
454 let made = || new_note(span, ¬e, &duration, offset, length, options);
455 match notes.iter().position(|(known, _)| *known == key) {
456 None => notes.push((key, made())),
457 Some(_) if !options.remove_redundant_pitches => {
458 notes.push((format!("{key}{bust}"), made()));
459 bust += 1;
460 }
461 Some(_) if !options.add_ties => {}
462 Some(place) => {
463 let old = notes[place].1.tie().map(Tie::tie_type);
464 if old == Some(TieType::Continue) {
465 continue;
466 }
467 let candidate = made();
468 let Some(new) = candidate.tie().map(Tie::tie_type) else {
469 continue;
470 };
471 match old {
472 None => notes[place].1 = candidate,
473 Some(old) if is_start_and_stop(old, new) => {
474 if let Some(mut tie) = notes[place].1.tie().cloned() {
475 tie.set_tie_type(TieType::Continue);
476 notes[place].1.set_tie(Some(tie));
477 }
478 }
479 Some(_) if new == TieType::Continue => notes[place].1 = candidate,
480 Some(_) => {}
481 }
482 }
483 }
484 }
485 }
486 if !any_pitch {
487 return Ok(StreamElement::Rest(Rest::new(duration)));
488 }
489
490 let mut ordered: Vec<Note> = notes.into_iter().map(|(_, note)| note).collect();
491 ordered.sort_by(|left, right| left.pitch().ps().total_cmp(&right.pitch().ps()));
495 ordered.sort_by(|left, right| {
496 left.pitch()
497 .diatonic_note_number()
498 .cmp(&right.pitch().diatonic_note_number())
499 .then(left.pitch().ps().total_cmp(&right.pitch().ps()))
500 });
501 let mut articulations: Vec<Articulation> = Vec::new();
502 let mut expressions: Vec<Expression> = Vec::new();
503 for note in &ordered {
504 let tie = note.tie().map(Tie::tie_type);
505 let kept = |attach: &str| match attach {
506 "first" => tie.is_none_or(|tie| tie == TieType::Start),
507 "last" => tie.is_none_or(|tie| tie == TieType::Stop),
508 _ => true,
509 };
510 for articulation in note.articulations() {
511 if kept(articulation.tie_attach())
512 && !articulations
513 .iter()
514 .any(|seen| seen.kind() == articulation.kind())
515 {
516 articulations.push(articulation.clone());
517 }
518 }
519 for expression in note.expressions() {
520 if kept(expression.tie_attach())
521 && !expressions
522 .iter()
523 .any(|seen| same_kind_of_expression(seen, expression))
524 {
525 expressions.push(expression.clone());
526 }
527 }
528 }
529 let mut chord = Chord::new(ordered)?.with_duration(duration);
530 chord.articulations_mut().extend(articulations);
531 chord.expressions_mut().extend(expressions);
532 Ok(StreamElement::Chord(chord))
533}
534
535fn is_start_and_stop(left: TieType, right: TieType) -> bool {
536 matches!(
537 (left, right),
538 (TieType::Start, TieType::Stop) | (TieType::Stop, TieType::Start)
539 )
540}
541
542fn same_kind_of_expression(left: &Expression, right: &Expression) -> bool {
544 match (left, right) {
545 (Expression::Ornament(left), Expression::Ornament(right)) => left.kind() == right.kind(),
546 (Expression::Fermata(_), Expression::Fermata(_))
547 | (Expression::Arpeggio(_), Expression::Arpeggio(_)) => true,
548 _ => false,
549 }
550}
551
552fn new_note(
556 span: &Span<'_>,
557 note: &Note,
558 duration: &Duration,
559 offset: FloatType,
560 length: FloatType,
561 options: &ChordifyOptions,
562) -> Note {
563 let mut made = note.clone().with_duration(duration.clone());
564 made.lyrics_mut().clear();
565 if made.stem_direction() != StemDirection::NoStem {
566 made.set_stem_direction(StemDirection::Unspecified);
567 }
568 if !options.add_ties {
569 return made;
570 }
571 let before = op_frac(offset - span.start);
572 let after = op_frac(span.end - (offset + length));
573 let added = if before == 0.0 && after <= 0.0 {
574 None
575 } else if before > 0.0 {
576 Some(if after > 0.0 {
577 TieType::Continue
578 } else {
579 TieType::Stop
580 })
581 } else {
582 Some(TieType::Start)
583 };
584 match (made.tie().cloned(), added) {
585 (Some(mut tie), Some(added)) if is_start_and_stop(tie.tie_type(), added) => {
586 tie.set_tie_type(TieType::Continue);
587 made.set_tie(Some(tie));
588 }
589 (Some(mut tie), _) if tie.tie_type() == TieType::Continue => {
590 tie.set_placement(None);
591 made.set_tie(Some(tie));
592 }
593 (Some(mut tie), None) => {
594 tie.set_placement(None);
595 made.set_tie(Some(tie));
596 }
597 (_, Some(added)) => made.set_tie(Some(Tie::new(added))),
598 (None, None) => {}
599 }
600 made
601}
602
603fn consolidate_rests(stream: &mut Stream) -> Result<()> {
607 let sounding: Vec<usize> = (0..stream.events.len())
608 .filter(|place| {
609 matches!(
610 stream.events[*place].element,
611 StreamElement::Note(_) | StreamElement::Chord(_) | StreamElement::Rest(_)
612 )
613 })
614 .collect();
615 let mut runs: Vec<Vec<usize>> = Vec::new();
616 let mut run: Vec<usize> = Vec::new();
617 for place in sounding {
618 if matches!(stream.events[place].element, StreamElement::Rest(_)) {
619 run.push(place);
620 } else if !run.is_empty() {
621 runs.push(std::mem::take(&mut run));
622 }
623 }
624 runs.push(run);
625 let runs: Vec<Vec<usize>> = runs.into_iter().filter(|run| run.len() >= 2).collect();
626 if runs.is_empty() {
627 return Ok(());
628 }
629 let mut joined = Vec::new();
630 for run in &runs {
631 let total: FloatType = run
632 .iter()
633 .map(|place| stream.events[*place].element.quarter_length())
634 .sum();
635 let at = stream.events[run[0]].offset;
636 joined.push(StreamEvent::new(
637 at,
638 StreamElement::Rest(Rest::new(Duration::new(total)?)),
639 ));
640 }
641 let dropped: Vec<usize> = runs.into_iter().flatten().collect();
642 let mut place = 0;
643 stream.events.retain(|_| {
644 let keep = !dropped.contains(&place);
645 place += 1;
646 keep
647 });
648 stream.insert_sorted(joined);
649 Ok(())
650}
651
652#[cfg(test)]
653mod tests {
654 use super::*;
655 use crate::pitch::Pitch;
656
657 fn part(notes: &[(&str, FloatType)]) -> Stream {
658 let mut part = Stream::with_kind(StreamKind::Part);
659 for (name, length) in notes {
660 let element: StreamElement = if *name == "r" {
661 Rest::new(Duration::new(*length).unwrap()).into()
662 } else {
663 Note::from_name(name)
664 .unwrap()
665 .with_duration(Duration::new(*length).unwrap())
666 .into()
667 };
668 part.push(element);
669 }
670 part
671 }
672
673 fn score(parts: Vec<Stream>) -> Stream {
674 let mut score = Stream::with_kind(StreamKind::Score);
675 for part in parts {
676 score.insert(0.0, part);
677 }
678 score
679 }
680
681 #[test]
682 fn a_held_note_is_tied_across_the_chords_it_sounds_through() {
683 let chords = score(vec![
684 part(&[("C5", 2.0)]),
685 part(&[("E4", 1.0), ("G4", 1.0)]),
686 ])
687 .chordify()
688 .unwrap();
689 let ties: Vec<Vec<Option<TieType>>> = chords
690 .events()
691 .iter()
692 .filter_map(|event| match event.element() {
693 StreamElement::Chord(chord) => Some(
694 chord
695 .notes()
696 .iter()
697 .map(|note| note.tie().map(Tie::tie_type))
698 .collect(),
699 ),
700 _ => None,
701 })
702 .collect();
703 assert_eq!(
704 ties,
705 [
706 vec![None, Some(TieType::Start)],
707 vec![None, Some(TieType::Stop)]
708 ]
709 );
710 }
711
712 #[test]
713 fn a_pitch_two_parts_sound_is_one_note_and_rests_together_are_one_rest() {
714 let chords = score(vec![
715 part(&[("C4", 1.0), ("r", 1.0), ("r", 1.0)]),
716 part(&[("C4", 1.0), ("r", 2.0)]),
717 ])
718 .chordify()
719 .unwrap();
720 let events = chords.events();
721 assert_eq!(events.len(), 2);
722 let StreamElement::Chord(chord) = events[0].element() else {
723 panic!("a chord first");
724 };
725 assert_eq!(chord.pitches(), [Pitch::from_name("C4").unwrap()]);
726 assert!(matches!(events[1].element(), StreamElement::Rest(_)));
727 assert_eq!(events[1].element().quarter_length(), 2.0);
728 }
729}