1use crate::{
24 chord::Chord,
25 defaults::{FloatType, IntegerType},
26 error::{Error, Result},
27 makenotation::op_frac,
28 pitch::Pitch,
29 stream::{Stream, StreamElement, StreamKind},
30 voiceleading::VoiceLeadingQuartet,
31};
32
33#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
35pub enum Flatten {
36 #[default]
38 Flat,
39 SemiFlat,
42}
43
44#[derive(Clone, Debug)]
48pub struct ElementTimespan<'a> {
49 element: &'a StreamElement,
50 offset: FloatType,
51 end_time: FloatType,
52 parent_offset: FloatType,
53 parent_end_time: FloatType,
54 parentage: Vec<&'a Stream>,
55 measure: Option<IntegerType>,
56 pitched: bool,
57}
58
59impl<'a> ElementTimespan<'a> {
60 pub fn element(&self) -> &'a StreamElement {
62 self.element
63 }
64
65 pub fn offset(&self) -> FloatType {
67 self.offset
68 }
69
70 pub fn end_time(&self) -> FloatType {
72 self.end_time
73 }
74
75 pub fn quarter_length(&self) -> FloatType {
77 self.end_time - self.offset
78 }
79
80 pub fn parent_offset(&self) -> FloatType {
82 self.parent_offset
83 }
84
85 pub fn parent_end_time(&self) -> FloatType {
87 self.parent_end_time
88 }
89
90 pub fn parentage(&self) -> &[&'a Stream] {
93 &self.parentage
94 }
95
96 pub fn part(&self) -> Option<&'a Stream> {
98 self.parentage
99 .iter()
100 .copied()
101 .find(|stream| matches!(stream.kind(), StreamKind::Part | StreamKind::PartStaff))
102 }
103
104 pub fn measure_number(&self) -> Option<IntegerType> {
107 self.measure
108 }
109
110 pub fn is_pitched(&self) -> bool {
113 self.pitched
114 }
115
116 pub fn pitches(&self) -> Vec<Pitch> {
119 if !self.pitched {
120 return Vec::new();
121 }
122 match self.element {
123 StreamElement::ChordSymbol(symbol) => symbol.pitches().unwrap_or_default(),
124 element => element.pitches(),
125 }
126 }
127}
128
129impl ElementTimespan<'_> {
130 fn spanning(&self, offset: FloatType, end_time: FloatType) -> Self {
132 Self {
133 offset,
134 end_time,
135 ..self.clone()
136 }
137 }
138}
139
140fn is_pitched(element: &StreamElement) -> bool {
143 matches!(
144 element,
145 StreamElement::Note(_)
146 | StreamElement::Chord(_)
147 | StreamElement::ChordSymbol(_)
148 | StreamElement::Unpitched(_)
149 | StreamElement::PercussionChord(_)
150 | StreamElement::Stream(_)
151 )
152}
153
154#[derive(Clone, Debug)]
158pub struct TimespanTree<'a> {
159 source: &'a Stream,
160 timespans: Vec<ElementTimespan<'a>>,
161}
162
163pub fn as_timespans<'a>(
167 stream: &'a Stream,
168 flatten: Flatten,
169 filter: Option<fn(&StreamElement) -> bool>,
170) -> TimespanTree<'a> {
171 let mut timespans = Vec::new();
172 let measure = (stream.kind() == StreamKind::Measure).then(|| stream.number());
173 walk(
174 stream,
175 0.0,
176 &mut vec![stream],
177 measure,
178 flatten,
179 filter,
180 &mut timespans,
181 );
182 timespans.sort_by(|a: &ElementTimespan<'_>, b: &ElementTimespan<'_>| {
183 a.offset
184 .total_cmp(&b.offset)
185 .then_with(|| a.end_time.total_cmp(&b.end_time))
186 });
187 TimespanTree {
188 source: stream,
189 timespans,
190 }
191}
192
193fn walk<'a>(
194 stream: &'a Stream,
195 initial: FloatType,
196 parentage: &mut Vec<&'a Stream>,
197 measure: Option<IntegerType>,
198 flatten: Flatten,
199 filter: Option<fn(&StreamElement) -> bool>,
200 out: &mut Vec<ElementTimespan<'a>>,
201) {
202 let parent_end_time = op_frac(initial + stream.end_offset());
203 let reversed: Vec<&'a Stream> = parentage.iter().rev().copied().collect();
204 for event in stream.events() {
205 let offset = op_frac(event.offset() + initial);
206 let element = event.element();
207 let mut own_measure = measure;
209 if let StreamElement::Stream(inner) = element {
210 parentage.push(inner);
211 let inner_measure = if inner.kind() == StreamKind::Measure {
212 Some(inner.number())
213 } else {
214 measure
215 };
216 walk(
217 inner,
218 offset,
219 parentage,
220 inner_measure,
221 flatten,
222 filter,
223 out,
224 );
225 parentage.pop();
226 own_measure = inner_measure;
227 if flatten == Flatten::Flat {
228 continue;
229 }
230 }
231 if filter.is_some_and(|accepts| !accepts(element)) {
232 continue;
233 }
234 out.push(ElementTimespan {
235 element,
236 offset,
237 end_time: op_frac(offset + element.quarter_length()),
238 parent_offset: initial,
239 parent_end_time,
240 parentage: reversed.clone(),
241 measure: own_measure,
242 pitched: is_pitched(element),
243 });
244 }
245}
246
247fn at_most(a: &Pitch, b: &Pitch) -> bool {
250 a.ps() < b.ps() || a == b
251}
252
253impl<'a> TimespanTree<'a> {
254 pub fn timespans(&self) -> &[ElementTimespan<'a>] {
256 &self.timespans
257 }
258
259 pub fn len(&self) -> usize {
261 self.timespans.len()
262 }
263
264 pub fn is_empty(&self) -> bool {
266 self.timespans.is_empty()
267 }
268
269 pub fn lowest_position(&self) -> Option<FloatType> {
271 self.timespans.first().map(|span| span.offset)
272 }
273
274 pub fn highest_position(&self) -> Option<FloatType> {
276 self.timespans.last().map(|span| span.offset)
277 }
278
279 pub fn end_time(&self) -> Option<FloatType> {
281 self.timespans
282 .iter()
283 .map(|span| span.end_time)
284 .reduce(FloatType::max)
285 }
286
287 pub fn all_offsets(&self) -> Vec<FloatType> {
290 let mut offsets: Vec<FloatType> = self.timespans.iter().map(|span| span.offset).collect();
291 offsets.dedup();
292 offsets
293 }
294
295 pub fn all_time_points(&self) -> Vec<FloatType> {
298 let mut points: Vec<FloatType> = self
299 .timespans
300 .iter()
301 .flat_map(|span| [span.offset, span.end_time])
302 .collect();
303 points.sort_by(FloatType::total_cmp);
304 points.dedup();
305 points
306 }
307
308 pub fn position_after(&self, offset: FloatType) -> Option<FloatType> {
311 self.timespans
312 .iter()
313 .map(|span| span.offset)
314 .find(|start| *start > offset)
315 }
316
317 pub fn position_before(&self, offset: FloatType) -> Option<FloatType> {
320 self.timespans
321 .iter()
322 .rev()
323 .map(|span| span.offset)
324 .find(|start| *start < offset)
325 }
326
327 pub fn starting_at(&self, offset: FloatType) -> Vec<&ElementTimespan<'a>> {
329 self.timespans
330 .iter()
331 .filter(|span| span.offset == offset)
332 .collect()
333 }
334
335 pub fn stopping_at(&self, offset: FloatType) -> Vec<&ElementTimespan<'a>> {
337 self.timespans
338 .iter()
339 .filter(|span| span.end_time == offset)
340 .collect()
341 }
342
343 pub fn overlapping(&self, offset: FloatType) -> Vec<&ElementTimespan<'a>> {
346 self.timespans
347 .iter()
348 .filter(|span| span.offset < offset && offset < span.end_time)
349 .collect()
350 }
351
352 pub fn overlap_time_points(&self, include_stop_points: bool) -> Vec<FloatType> {
356 let points = if include_stop_points {
357 self.all_time_points()
358 } else {
359 self.all_offsets()
360 };
361 points
362 .into_iter()
363 .filter(|point| !self.overlapping(*point).is_empty())
364 .collect()
365 }
366
367 pub fn verticality_at(&self, offset: FloatType) -> Verticality<'_, 'a> {
370 Verticality {
371 tree: self,
372 offset,
373 start_timespans: self.starting_at(offset),
374 overlap_timespans: self.overlapping(offset),
375 stop_timespans: self.stopping_at(offset),
376 }
377 }
378
379 pub fn verticality_at_or_before(&self, offset: FloatType) -> Option<Verticality<'_, 'a>> {
382 let verticality = self.verticality_at(offset);
383 if verticality.start_timespans.is_empty() {
384 verticality.previous()
385 } else {
386 Some(verticality)
387 }
388 }
389
390 pub fn verticalities(&self) -> Vec<Verticality<'_, 'a>> {
393 self.all_offsets()
394 .into_iter()
395 .map(|offset| self.verticality_at(offset))
396 .collect()
397 }
398
399 pub fn maximum_overlap(&self) -> Option<usize> {
403 self.verticalities()
404 .iter()
405 .map(|verticality| {
406 verticality.start_timespans.len() + verticality.overlap_timespans.len()
407 })
408 .max()
409 }
410
411 pub fn source(&self) -> &'a Stream {
413 self.source
414 }
415
416 pub fn remove_timespan(&mut self, index: usize) -> Option<ElementTimespan<'a>> {
420 (index < self.timespans.len()).then(|| self.timespans.remove(index))
421 }
422
423 pub fn remove_timespan_list(&mut self, indices: &[usize]) {
426 let mut index = 0;
427 self.timespans.retain(|_| {
428 let kept = !indices.contains(&index);
429 index += 1;
430 kept
431 });
432 }
433
434 pub fn all_parts(&self) -> Vec<&'a Stream> {
439 let mut parts: Vec<&'a Stream> = Vec::new();
440 for part in self.timespans.iter().filter_map(ElementTimespan::part) {
441 if !parts.iter().any(|known| std::ptr::eq(*known, part)) {
442 parts.push(part);
443 }
444 }
445 let order = self.source.parts();
446 parts.sort_by_key(|part| {
447 let place = order
448 .iter()
449 .position(|known| std::ptr::eq(*known, *part))
450 .unwrap_or(order.len());
451 (
452 place,
453 part.id().is_none(),
454 part.id().unwrap_or_default().to_string(),
455 )
456 });
457 parts
458 }
459
460 pub fn to_partwise_timespan_trees(&self) -> Vec<TimespanTree<'a>> {
464 self.all_parts()
465 .into_iter()
466 .map(|part| TimespanTree {
467 source: part,
468 timespans: self
469 .timespans
470 .iter()
471 .filter(|span| span.part().is_some_and(|own| std::ptr::eq(own, part)))
472 .cloned()
473 .collect(),
474 })
475 .collect()
476 }
477
478 pub fn split_at(&mut self, offsets: &[FloatType]) {
482 for &offset in offsets {
483 let (across, mut kept): (Vec<ElementTimespan<'a>>, Vec<ElementTimespan<'a>>) =
484 std::mem::take(&mut self.timespans)
485 .into_iter()
486 .partition(|span| span.offset < offset && offset < span.end_time);
487 for span in across {
488 kept.push(span.spanning(span.offset, offset));
489 kept.push(span.spanning(offset, span.end_time));
490 }
491 kept.sort_by(|a, b| {
492 a.offset
493 .total_cmp(&b.offset)
494 .then_with(|| a.end_time.total_cmp(&b.end_time))
495 });
496 self.timespans = kept;
497 }
498 }
499
500 pub fn verticalities_nwise(
509 &self,
510 n: usize,
511 reverse: bool,
512 pad_end: bool,
513 ) -> Result<Vec<Vec<Verticality<'_, 'a>>>> {
514 if n == 0 {
515 return Err(Error::Tree(format!(
516 "The number of verticalities in the group must be at least one. Got {n}"
517 )));
518 }
519 let sentinel = Verticality {
520 tree: self,
521 offset: self.end_time().unwrap_or(FloatType::INFINITY),
522 start_timespans: Vec::new(),
523 overlap_timespans: Vec::new(),
524 stop_timespans: Vec::new(),
525 };
526 let mut sequence = self.verticalities();
527 if reverse {
528 sequence.reverse();
529 }
530 if pad_end {
531 sequence.extend(std::iter::repeat_n(sentinel.clone(), n - 1));
532 }
533 let mut windows: Vec<Vec<Verticality<'_, 'a>>> = if sequence.len() < n {
534 let mut window = sequence;
535 let missing = n - window.len();
536 window.extend(std::iter::repeat_n(sentinel, missing));
537 vec![window]
538 } else {
539 sequence.windows(n).map(<[_]>::to_vec).collect()
540 };
541 if reverse {
542 for window in &mut windows {
543 window.reverse();
544 }
545 }
546 Ok(windows)
547 }
548
549 pub fn consonance_bounded_verticalities(&self) -> Result<Vec<Vec<Verticality<'_, 'a>>>> {
557 let mut runs = Vec::new();
558 let mut buffer: Vec<Verticality<'_, 'a>> = Vec::new();
559 for verticality in self.verticalities() {
560 let consonant = verticality.to_chord()?.is_consonant();
561 if buffer.is_empty() {
562 if consonant {
563 buffer.push(verticality);
564 }
565 continue;
566 }
567 buffer.push(verticality);
568 if consonant {
569 if buffer.len() > 2 {
570 runs.push(buffer.clone());
571 }
572 let last = buffer.pop().expect("the verticality just pushed");
573 buffer = vec![last];
574 }
575 }
576 Ok(runs)
577 }
578
579 pub fn next_in_same_part(
582 &self,
583 timespan: &ElementTimespan<'a>,
584 ) -> Option<&ElementTimespan<'a>> {
585 let part = timespan.part();
586 let mut offset = timespan.offset;
587 while let Some(next) = self.position_after(offset) {
588 if let Some(found) = self
589 .starting_at(next)
590 .into_iter()
591 .find(|span| same_stream(span.part(), part))
592 {
593 return Some(found);
594 }
595 offset = next;
596 }
597 None
598 }
599
600 pub fn previous_in_same_part(
603 &self,
604 timespan: &ElementTimespan<'a>,
605 ) -> Option<&ElementTimespan<'a>> {
606 let part = timespan.part();
607 let mut offset = timespan.offset;
608 while let Some(previous) = self.position_before(offset) {
609 if let Some(found) = self
610 .starting_at(previous)
611 .into_iter()
612 .find(|span| same_stream(span.part(), part))
613 {
614 return Some(found);
615 }
616 offset = previous;
617 }
618 None
619 }
620}
621
622fn same_stream(a: Option<&Stream>, b: Option<&Stream>) -> bool {
624 match (a, b) {
625 (Some(a), Some(b)) => std::ptr::eq(a, b),
626 (None, None) => true,
627 _ => false,
628 }
629}
630
631#[derive(Clone, Debug)]
634pub struct Verticality<'t, 'a> {
635 tree: &'t TimespanTree<'a>,
636 offset: FloatType,
637 start_timespans: Vec<&'t ElementTimespan<'a>>,
638 overlap_timespans: Vec<&'t ElementTimespan<'a>>,
639 stop_timespans: Vec<&'t ElementTimespan<'a>>,
640}
641
642impl<'t, 'a> Verticality<'t, 'a> {
643 pub fn offset(&self) -> FloatType {
645 self.offset
646 }
647
648 pub fn start_timespans(&self) -> &[&'t ElementTimespan<'a>] {
650 &self.start_timespans
651 }
652
653 pub fn overlap_timespans(&self) -> &[&'t ElementTimespan<'a>] {
655 &self.overlap_timespans
656 }
657
658 pub fn stop_timespans(&self) -> &[&'t ElementTimespan<'a>] {
660 &self.stop_timespans
661 }
662
663 pub fn start_and_overlap_timespans(&self) -> Vec<&'t ElementTimespan<'a>> {
666 self.start_timespans
667 .iter()
668 .chain(&self.overlap_timespans)
669 .copied()
670 .collect()
671 }
672
673 pub fn pitch_set(&self) -> Vec<Pitch> {
676 let mut seen: Vec<String> = Vec::new();
677 let mut pitches: Vec<Pitch> = Vec::new();
678 for span in self.start_and_overlap_timespans() {
679 for pitch in span.pitches() {
680 let name = pitch.name_with_octave();
681 if !seen.contains(&name) {
682 seen.push(name);
683 pitches.push(pitch);
684 }
685 }
686 }
687 pitches.sort_by(|a, b| a.ps().total_cmp(&b.ps()));
688 pitches
689 }
690
691 pub fn pitch_class_set(&self) -> Vec<u8> {
694 let mut classes: Vec<u8> = self
695 .pitch_set()
696 .iter()
697 .map(|pitch| (pitch.ps().round_ties_even() as i64).rem_euclid(12) as u8)
698 .collect();
699 classes.sort_unstable();
700 classes.dedup();
701 classes
702 }
703
704 pub fn to_chord(&self) -> Result<Chord> {
710 Chord::new(self.pitch_set().as_slice())
711 }
712
713 pub fn bass_timespan(&self) -> Option<&'t ElementTimespan<'a>> {
716 let mut lowest: Option<(Pitch, &'t ElementTimespan<'a>)> = None;
717 for span in self.start_and_overlap_timespans() {
718 let mut pitches = span.pitches();
719 if pitches.is_empty() {
720 continue;
721 }
722 pitches.sort_by(|a, b| a.ps().total_cmp(&b.ps()));
723 let pitch = pitches.swap_remove(0);
724 match &lowest {
725 None => lowest = Some((pitch, span)),
726 Some((overall, _)) if at_most(&pitch, overall) => lowest = Some((pitch, span)),
727 Some(_) => {}
728 }
729 }
730 lowest.map(|(_, span)| span)
731 }
732
733 pub fn measure_number(&self) -> Option<IntegerType> {
736 self.start_timespans.first().and_then(|span| span.measure)
737 }
738
739 pub fn paired_motion(
746 &self,
747 include_rests: bool,
748 include_oblique: bool,
749 ) -> Vec<(&'t ElementTimespan<'a>, &'t ElementTimespan<'a>)> {
750 let mut pairs = Vec::new();
751 for &starting in &self.start_timespans {
752 if !starting.pitched {
753 continue;
754 }
755 let Some(previous) = self.tree.previous_in_same_part(starting) else {
756 continue;
757 };
758 if !previous.pitched {
759 continue;
760 }
761 if !include_rests
762 && !self
763 .stop_timespans
764 .iter()
765 .any(|stopping| std::ptr::eq(*stopping, previous))
766 {
767 continue;
768 }
769 if !include_oblique && starting.pitches() == previous.pitches() {
770 continue;
771 }
772 pairs.push((previous, starting));
773 }
774 if include_oblique {
775 for &overlap in &self.overlap_timespans {
776 if overlap.pitched {
777 pairs.push((overlap, overlap));
778 }
779 }
780 }
781 pairs
782 }
783
784 pub fn voice_leading_timespans(
794 &self,
795 include_rests: bool,
796 include_oblique: bool,
797 include_no_motion: bool,
798 part_pairs: Option<&[(usize, usize)]>,
799 ) -> Result<Vec<[&'t ElementTimespan<'a>; 4]>> {
800 let pairs = self.paired_motion(include_rests, include_oblique);
801 let parts = self.tree.source.parts();
802 let mut quartets = Vec::new();
803 for (index, upper) in pairs.iter().enumerate() {
804 for lower in &pairs[index + 1..] {
805 if !include_no_motion
806 && upper.0.pitches() == upper.1.pitches()
807 && lower.0.pitches() == lower.1.pitches()
808 {
809 continue;
810 }
811 if let Some(part_pairs) = part_pairs {
812 let (top, bottom) = (upper.0.part(), lower.0.part());
813 let is_either = |index: usize| -> Result<bool> {
814 let part = parts
815 .get(index)
816 .copied()
817 .ok_or_else(|| Error::Tree(format!("the score has no part {index}")))?;
818 Ok(same_stream(Some(part), top) || same_stream(Some(part), bottom))
819 };
820 let mut appropriate = false;
821 for (first, second) in part_pairs {
822 if is_either(*first)? && is_either(*second)? {
823 appropriate = true;
824 break;
825 }
826 }
827 if !appropriate {
828 continue;
829 }
830 }
831 quartets.push([upper.0, upper.1, lower.0, lower.1]);
832 }
833 }
834 Ok(quartets)
835 }
836
837 pub fn voice_leading_quartets(
845 &self,
846 include_rests: bool,
847 include_oblique: bool,
848 include_no_motion: bool,
849 part_pairs: Option<&[(usize, usize)]>,
850 ) -> Result<Vec<VoiceLeadingQuartet>> {
851 let mut quartets = Vec::new();
852 for spans in self.voice_leading_timespans(
853 include_rests,
854 include_oblique,
855 include_no_motion,
856 part_pairs,
857 )? {
858 let pitches: Vec<Pitch> = spans
859 .iter()
860 .filter_map(|span| match span.element {
861 StreamElement::Note(note) => Some(note.pitch().clone()),
862 _ => None,
863 })
864 .collect();
865 if let [v1n1, v1n2, v2n1, v2n2] = pitches.as_slice() {
866 quartets.push(VoiceLeadingQuartet::new(
867 v1n1.clone(),
868 v1n2.clone(),
869 v2n1.clone(),
870 v2n2.clone(),
871 )?);
872 }
873 }
874 Ok(quartets)
875 }
876
877 pub fn make_element(
888 &self,
889 length: Option<FloatType>,
890 add_ties: bool,
891 remove_redundant_pitches: bool,
892 ) -> Result<StreamElement> {
893 let length = match length {
894 Some(length) => op_frac(length),
895 None => self.time_to_next_event().unwrap_or(1.0),
896 };
897 let spans: Vec<crate::stream::chordify::Span<'a>> = self
898 .start_and_overlap_timespans()
899 .into_iter()
900 .filter(|span| span.pitched)
901 .map(|span| crate::stream::chordify::Span {
902 start: span.offset,
903 end: span.end_time,
904 element: span.element,
905 part: 0,
906 })
907 .collect();
908 let options = crate::stream::ChordifyOptions {
909 add_ties,
910 remove_redundant_pitches,
911 to_sounding_pitch: false,
912 };
913 crate::stream::chordify::make_element(&spans, self.offset, length, &options)
914 }
915
916 pub fn next_start_offset(&self) -> Option<FloatType> {
918 self.tree.position_after(self.offset)
919 }
920
921 pub fn next(&self) -> Option<Verticality<'t, 'a>> {
923 self.next_start_offset()
924 .map(|offset| self.tree.verticality_at(offset))
925 }
926
927 pub fn previous(&self) -> Option<Verticality<'t, 'a>> {
930 self.tree
931 .position_before(self.offset)
932 .map(|offset| self.tree.verticality_at(offset))
933 }
934
935 pub fn time_to_next_event(&self) -> Option<FloatType> {
938 let next = self.next_start_offset().or_else(|| self.tree.end_time())?;
939 Some(op_frac(next - self.offset))
940 }
941}
942
943pub fn unwrap<'t, 'a>(
948 verticalities: &[Verticality<'t, 'a>],
949) -> Vec<(Option<&'a Stream>, Horizontality<'t, 'a>)> {
950 let Some((first, rest)) = verticalities.split_first() else {
951 return Vec::new();
952 };
953 let mut parts: Vec<(Option<&'a Stream>, Vec<&'t ElementTimespan<'a>>)> = Vec::new();
954 let spans = first
955 .overlap_timespans
956 .iter()
957 .chain(&first.start_timespans)
958 .chain(
959 rest.iter()
960 .flat_map(|verticality| &verticality.start_timespans),
961 );
962 for &span in spans {
963 let part = span.part();
964 match parts
965 .iter_mut()
966 .find(|(known, _)| same_stream(*known, part))
967 {
968 Some((_, spans)) => spans.push(span),
969 None => parts.push((part, vec![span])),
970 }
971 }
972 parts
973 .into_iter()
974 .map(|(part, timespans)| (part, Horizontality { timespans }))
975 .collect()
976}
977
978#[derive(Clone, Debug)]
980pub struct Horizontality<'t, 'a> {
981 timespans: Vec<&'t ElementTimespan<'a>>,
982}
983
984impl<'t, 'a> Horizontality<'t, 'a> {
985 pub fn timespans(&self) -> &[&'t ElementTimespan<'a>] {
987 &self.timespans
988 }
989
990 fn first_three(&self) -> Option<[Pitch; 3]> {
993 if self.timespans.len() < 3 || self.timespans.iter().any(|span| span.pitches().is_empty()) {
994 return None;
995 }
996 let firsts: Vec<Pitch> = self
997 .timespans
998 .iter()
999 .take(3)
1000 .map(|span| span.pitches().into_iter().next())
1001 .collect::<Option<_>>()?;
1002 Some([firsts[0].clone(), firsts[1].clone(), firsts[2].clone()])
1003 }
1004
1005 pub fn has_passing_tone(&self) -> bool {
1008 self.first_three().is_some_and(|[a, b, c]| {
1009 (a.ps() < b.ps() && b.ps() < c.ps()) || (a.ps() > b.ps() && b.ps() > c.ps())
1010 })
1011 }
1012
1013 pub fn has_neighbor_tone(&self) -> bool {
1017 self.first_three()
1018 .is_some_and(|[a, b, c]| a == c && (b.ps() - a.ps()).abs() < 3.0)
1019 }
1020
1021 pub fn has_no_motion(&self) -> bool {
1026 let mut seen: Vec<Vec<String>> = Vec::new();
1027 for span in &self.timespans {
1028 let keys: Vec<String> = span.pitches().iter().map(set_key).collect();
1029 if !seen.contains(&keys) {
1030 seen.push(keys);
1031 }
1032 }
1033 seen.len() == 1
1034 }
1035}
1036
1037fn set_key(pitch: &Pitch) -> String {
1042 pitch_set_key(pitch)
1043}
1044
1045pub(crate) fn pitch_set_key(pitch: &Pitch) -> String {
1048 let accidental = pitch.written_accidental().map(|accidental| {
1049 format!(
1050 "{}|{:?}|{}|{}|{}|{}|{}|{}",
1051 accidental.alter(),
1052 accidental.display_status(),
1053 accidental.display_type(),
1054 accidental.modifier(),
1055 accidental.name(),
1056 accidental.display_location(),
1057 accidental.display_size(),
1058 accidental.display_style()
1059 )
1060 });
1061 format!(
1062 "{accidental:?}|{:?}|{}|{:?}|{:?}|{}",
1063 pitch.fundamental().map(pitch_set_key),
1064 pitch.spelling_is_inferred(),
1065 pitch.microtone().map(|microtone| microtone.cents()),
1066 pitch.octave(),
1067 pitch.name_with_octave()
1068 )
1069}
1070
1071#[cfg(test)]
1072mod tests {
1073 use super::*;
1074 use crate::tinynotation::from_tiny_notation;
1075
1076 #[test]
1077 fn a_line_is_read_as_timespans_and_verticalities() -> Result<()> {
1078 let line = from_tiny_notation("4/4 c2 e4 g")?;
1079 let tree = as_timespans(&line, Flatten::Flat, None);
1080 let notes: Vec<(FloatType, FloatType)> = tree
1081 .timespans()
1082 .iter()
1083 .filter(|span| matches!(span.element(), StreamElement::Note(_)))
1084 .map(|span| (span.offset(), span.end_time()))
1085 .collect();
1086 assert_eq!(notes, [(0.0, 2.0), (2.0, 3.0), (3.0, 4.0)]);
1087 let at_one = tree.verticality_at(1.0);
1088 assert!(at_one.start_timespans().is_empty());
1089 assert_eq!(at_one.overlap_timespans().len(), 1);
1090 assert_eq!(at_one.pitch_class_set(), [0]);
1091 assert_eq!(
1092 tree.verticality_at(2.0).next().map(|next| next.offset()),
1093 Some(3.0)
1094 );
1095 assert_eq!(tree.verticality_at(3.0).time_to_next_event(), Some(1.0));
1096 Ok(())
1097 }
1098
1099 fn two_parts() -> Result<Stream> {
1101 let mut score = Stream::with_kind(StreamKind::Score);
1102 for (id, line) in [("upper", "4/4 c'2 e'2"), ("lower", "4/4 c1")] {
1103 let mut part = from_tiny_notation(line)?;
1104 part.set_kind(StreamKind::Part);
1105 part.set_id(Some(id.to_string()));
1106 score.insert(0.0, part);
1107 }
1108 Ok(score)
1109 }
1110
1111 #[test]
1112 fn the_parts_come_in_the_score_order() -> Result<()> {
1113 let score = two_parts()?;
1114 let notes = as_timespans(
1115 &score,
1116 Flatten::Flat,
1117 Some(|element| matches!(element, StreamElement::Note(_))),
1118 );
1119 let ids: Vec<Option<&str>> = notes.all_parts().iter().map(|part| part.id()).collect();
1120 assert_eq!(ids, [Some("upper"), Some("lower")]);
1121
1122 let partwise = notes.to_partwise_timespan_trees();
1123 let counts: Vec<usize> = partwise.iter().map(TimespanTree::len).collect();
1124 assert_eq!(counts, [2, 1]);
1125 assert_eq!(partwise[1].source().id(), Some("lower"));
1126 assert_eq!(partwise[1].timespans()[0].end_time(), 4.0);
1127 Ok(())
1128 }
1129
1130 #[test]
1131 fn timespans_are_taken_out_by_place() -> Result<()> {
1132 let line = from_tiny_notation("4/4 c4 d e f")?;
1133 let mut tree = as_timespans(
1134 &line,
1135 Flatten::Flat,
1136 Some(|element| matches!(element, StreamElement::Note(_))),
1137 );
1138 let taken = tree.remove_timespan(1).map(|span| span.offset());
1139 assert_eq!(taken, Some(1.0));
1140 assert!(tree.remove_timespan(7).is_none());
1141 tree.remove_timespan_list(&[0, 2]);
1142 let left: Vec<FloatType> = tree
1143 .timespans()
1144 .iter()
1145 .map(ElementTimespan::offset)
1146 .collect();
1147 assert_eq!(left, [2.0]);
1148 Ok(())
1149 }
1150}