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