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 split_at(&mut self, offsets: &[FloatType]) {
420 for &offset in offsets {
421 let (across, mut kept): (Vec<ElementTimespan<'a>>, Vec<ElementTimespan<'a>>) =
422 std::mem::take(&mut self.timespans)
423 .into_iter()
424 .partition(|span| span.offset < offset && offset < span.end_time);
425 for span in across {
426 kept.push(span.spanning(span.offset, offset));
427 kept.push(span.spanning(offset, span.end_time));
428 }
429 kept.sort_by(|a, b| {
430 a.offset
431 .total_cmp(&b.offset)
432 .then_with(|| a.end_time.total_cmp(&b.end_time))
433 });
434 self.timespans = kept;
435 }
436 }
437
438 pub fn verticalities_nwise(
447 &self,
448 n: usize,
449 reverse: bool,
450 pad_end: bool,
451 ) -> Result<Vec<Vec<Verticality<'_, 'a>>>> {
452 if n == 0 {
453 return Err(Error::Tree(format!(
454 "The number of verticalities in the group must be at least one. Got {n}"
455 )));
456 }
457 let sentinel = Verticality {
458 tree: self,
459 offset: self.end_time().unwrap_or(FloatType::INFINITY),
460 start_timespans: Vec::new(),
461 overlap_timespans: Vec::new(),
462 stop_timespans: Vec::new(),
463 };
464 let mut sequence = self.verticalities();
465 if reverse {
466 sequence.reverse();
467 }
468 if pad_end {
469 sequence.extend(std::iter::repeat_n(sentinel.clone(), n - 1));
470 }
471 let mut windows: Vec<Vec<Verticality<'_, 'a>>> = if sequence.len() < n {
472 let mut window = sequence;
473 let missing = n - window.len();
474 window.extend(std::iter::repeat_n(sentinel, missing));
475 vec![window]
476 } else {
477 sequence.windows(n).map(<[_]>::to_vec).collect()
478 };
479 if reverse {
480 for window in &mut windows {
481 window.reverse();
482 }
483 }
484 Ok(windows)
485 }
486
487 pub fn consonance_bounded_verticalities(&self) -> Result<Vec<Vec<Verticality<'_, 'a>>>> {
495 let mut runs = Vec::new();
496 let mut buffer: Vec<Verticality<'_, 'a>> = Vec::new();
497 for verticality in self.verticalities() {
498 let consonant = verticality.to_chord()?.is_consonant();
499 if buffer.is_empty() {
500 if consonant {
501 buffer.push(verticality);
502 }
503 continue;
504 }
505 buffer.push(verticality);
506 if consonant {
507 if buffer.len() > 2 {
508 runs.push(buffer.clone());
509 }
510 let last = buffer.pop().expect("the verticality just pushed");
511 buffer = vec![last];
512 }
513 }
514 Ok(runs)
515 }
516
517 pub fn next_in_same_part(
520 &self,
521 timespan: &ElementTimespan<'a>,
522 ) -> Option<&ElementTimespan<'a>> {
523 let part = timespan.part();
524 let mut offset = timespan.offset;
525 while let Some(next) = self.position_after(offset) {
526 if let Some(found) = self
527 .starting_at(next)
528 .into_iter()
529 .find(|span| same_stream(span.part(), part))
530 {
531 return Some(found);
532 }
533 offset = next;
534 }
535 None
536 }
537
538 pub fn previous_in_same_part(
541 &self,
542 timespan: &ElementTimespan<'a>,
543 ) -> Option<&ElementTimespan<'a>> {
544 let part = timespan.part();
545 let mut offset = timespan.offset;
546 while let Some(previous) = self.position_before(offset) {
547 if let Some(found) = self
548 .starting_at(previous)
549 .into_iter()
550 .find(|span| same_stream(span.part(), part))
551 {
552 return Some(found);
553 }
554 offset = previous;
555 }
556 None
557 }
558}
559
560fn same_stream(a: Option<&Stream>, b: Option<&Stream>) -> bool {
562 match (a, b) {
563 (Some(a), Some(b)) => std::ptr::eq(a, b),
564 (None, None) => true,
565 _ => false,
566 }
567}
568
569#[derive(Clone, Debug)]
572pub struct Verticality<'t, 'a> {
573 tree: &'t TimespanTree<'a>,
574 offset: FloatType,
575 start_timespans: Vec<&'t ElementTimespan<'a>>,
576 overlap_timespans: Vec<&'t ElementTimespan<'a>>,
577 stop_timespans: Vec<&'t ElementTimespan<'a>>,
578}
579
580impl<'t, 'a> Verticality<'t, 'a> {
581 pub fn offset(&self) -> FloatType {
583 self.offset
584 }
585
586 pub fn start_timespans(&self) -> &[&'t ElementTimespan<'a>] {
588 &self.start_timespans
589 }
590
591 pub fn overlap_timespans(&self) -> &[&'t ElementTimespan<'a>] {
593 &self.overlap_timespans
594 }
595
596 pub fn stop_timespans(&self) -> &[&'t ElementTimespan<'a>] {
598 &self.stop_timespans
599 }
600
601 pub fn start_and_overlap_timespans(&self) -> Vec<&'t ElementTimespan<'a>> {
604 self.start_timespans
605 .iter()
606 .chain(&self.overlap_timespans)
607 .copied()
608 .collect()
609 }
610
611 pub fn pitch_set(&self) -> Vec<Pitch> {
614 let mut seen: Vec<String> = Vec::new();
615 let mut pitches: Vec<Pitch> = Vec::new();
616 for span in self.start_and_overlap_timespans() {
617 for pitch in span.pitches() {
618 let name = pitch.name_with_octave();
619 if !seen.contains(&name) {
620 seen.push(name);
621 pitches.push(pitch);
622 }
623 }
624 }
625 pitches.sort_by(|a, b| a.ps().total_cmp(&b.ps()));
626 pitches
627 }
628
629 pub fn pitch_class_set(&self) -> Vec<u8> {
632 let mut classes: Vec<u8> = self
633 .pitch_set()
634 .iter()
635 .map(|pitch| (pitch.ps().round_ties_even() as i64).rem_euclid(12) as u8)
636 .collect();
637 classes.sort_unstable();
638 classes.dedup();
639 classes
640 }
641
642 pub fn to_chord(&self) -> Result<Chord> {
648 Chord::new(self.pitch_set().as_slice())
649 }
650
651 pub fn bass_timespan(&self) -> Option<&'t ElementTimespan<'a>> {
654 let mut lowest: Option<(Pitch, &'t ElementTimespan<'a>)> = None;
655 for span in self.start_and_overlap_timespans() {
656 let mut pitches = span.pitches();
657 if pitches.is_empty() {
658 continue;
659 }
660 pitches.sort_by(|a, b| a.ps().total_cmp(&b.ps()));
661 let pitch = pitches.swap_remove(0);
662 match &lowest {
663 None => lowest = Some((pitch, span)),
664 Some((overall, _)) if at_most(&pitch, overall) => lowest = Some((pitch, span)),
665 Some(_) => {}
666 }
667 }
668 lowest.map(|(_, span)| span)
669 }
670
671 pub fn measure_number(&self) -> Option<IntegerType> {
674 self.start_timespans.first().and_then(|span| span.measure)
675 }
676
677 pub fn paired_motion(
684 &self,
685 include_rests: bool,
686 include_oblique: bool,
687 ) -> Vec<(&'t ElementTimespan<'a>, &'t ElementTimespan<'a>)> {
688 let mut pairs = Vec::new();
689 for &starting in &self.start_timespans {
690 if !starting.pitched {
691 continue;
692 }
693 let Some(previous) = self.tree.previous_in_same_part(starting) else {
694 continue;
695 };
696 if !previous.pitched {
697 continue;
698 }
699 if !include_rests
700 && !self
701 .stop_timespans
702 .iter()
703 .any(|stopping| std::ptr::eq(*stopping, previous))
704 {
705 continue;
706 }
707 if !include_oblique && starting.pitches() == previous.pitches() {
708 continue;
709 }
710 pairs.push((previous, starting));
711 }
712 if include_oblique {
713 for &overlap in &self.overlap_timespans {
714 if overlap.pitched {
715 pairs.push((overlap, overlap));
716 }
717 }
718 }
719 pairs
720 }
721
722 pub fn voice_leading_timespans(
732 &self,
733 include_rests: bool,
734 include_oblique: bool,
735 include_no_motion: bool,
736 part_pairs: Option<&[(usize, usize)]>,
737 ) -> Result<Vec<[&'t ElementTimespan<'a>; 4]>> {
738 let pairs = self.paired_motion(include_rests, include_oblique);
739 let parts = self.tree.source.parts();
740 let mut quartets = Vec::new();
741 for (index, upper) in pairs.iter().enumerate() {
742 for lower in &pairs[index + 1..] {
743 if !include_no_motion
744 && upper.0.pitches() == upper.1.pitches()
745 && lower.0.pitches() == lower.1.pitches()
746 {
747 continue;
748 }
749 if let Some(part_pairs) = part_pairs {
750 let (top, bottom) = (upper.0.part(), lower.0.part());
751 let is_either = |index: usize| -> Result<bool> {
752 let part = parts
753 .get(index)
754 .copied()
755 .ok_or_else(|| Error::Tree("list index out of range".to_string()))?;
756 Ok(same_stream(Some(part), top) || same_stream(Some(part), bottom))
757 };
758 let mut appropriate = false;
759 for (first, second) in part_pairs {
760 if is_either(*first)? && is_either(*second)? {
761 appropriate = true;
762 break;
763 }
764 }
765 if !appropriate {
766 continue;
767 }
768 }
769 quartets.push([upper.0, upper.1, lower.0, lower.1]);
770 }
771 }
772 Ok(quartets)
773 }
774
775 pub fn voice_leading_quartets(
783 &self,
784 include_rests: bool,
785 include_oblique: bool,
786 include_no_motion: bool,
787 part_pairs: Option<&[(usize, usize)]>,
788 ) -> Result<Vec<VoiceLeadingQuartet>> {
789 let mut quartets = Vec::new();
790 for spans in self.voice_leading_timespans(
791 include_rests,
792 include_oblique,
793 include_no_motion,
794 part_pairs,
795 )? {
796 let pitches: Vec<Pitch> = spans
797 .iter()
798 .filter_map(|span| match span.element {
799 StreamElement::Note(note) => Some(note.pitch().clone()),
800 _ => None,
801 })
802 .collect();
803 if let [v1n1, v1n2, v2n1, v2n2] = pitches.as_slice() {
804 quartets.push(VoiceLeadingQuartet::new(
805 v1n1.clone(),
806 v1n2.clone(),
807 v2n1.clone(),
808 v2n2.clone(),
809 )?);
810 }
811 }
812 Ok(quartets)
813 }
814
815 pub fn make_element(
826 &self,
827 length: Option<FloatType>,
828 add_ties: bool,
829 remove_redundant_pitches: bool,
830 ) -> Result<StreamElement> {
831 let length = match length {
832 Some(length) => op_frac(length),
833 None => self.time_to_next_event().unwrap_or(1.0),
834 };
835 let spans: Vec<crate::stream::chordify::Span<'a>> = self
836 .start_and_overlap_timespans()
837 .into_iter()
838 .filter(|span| span.pitched)
839 .map(|span| crate::stream::chordify::Span {
840 start: span.offset,
841 end: span.end_time,
842 element: span.element,
843 part: 0,
844 })
845 .collect();
846 let options = crate::stream::ChordifyOptions {
847 add_ties,
848 remove_redundant_pitches,
849 to_sounding_pitch: false,
850 };
851 crate::stream::chordify::make_element(&spans, self.offset, length, &options)
852 }
853
854 pub fn next_start_offset(&self) -> Option<FloatType> {
856 self.tree.position_after(self.offset)
857 }
858
859 pub fn next(&self) -> Option<Verticality<'t, 'a>> {
861 self.next_start_offset()
862 .map(|offset| self.tree.verticality_at(offset))
863 }
864
865 pub fn previous(&self) -> Option<Verticality<'t, 'a>> {
868 self.tree
869 .position_before(self.offset)
870 .map(|offset| self.tree.verticality_at(offset))
871 }
872
873 pub fn time_to_next_event(&self) -> Option<FloatType> {
876 let next = self.next_start_offset().or_else(|| self.tree.end_time())?;
877 Some(op_frac(next - self.offset))
878 }
879}
880
881pub fn unwrap<'t, 'a>(
886 verticalities: &[Verticality<'t, 'a>],
887) -> Vec<(Option<&'a Stream>, Horizontality<'t, 'a>)> {
888 let Some((first, rest)) = verticalities.split_first() else {
889 return Vec::new();
890 };
891 let mut parts: Vec<(Option<&'a Stream>, Vec<&'t ElementTimespan<'a>>)> = Vec::new();
892 let spans = first
893 .overlap_timespans
894 .iter()
895 .chain(&first.start_timespans)
896 .chain(
897 rest.iter()
898 .flat_map(|verticality| &verticality.start_timespans),
899 );
900 for &span in spans {
901 let part = span.part();
902 match parts
903 .iter_mut()
904 .find(|(known, _)| same_stream(*known, part))
905 {
906 Some((_, spans)) => spans.push(span),
907 None => parts.push((part, vec![span])),
908 }
909 }
910 parts
911 .into_iter()
912 .map(|(part, timespans)| (part, Horizontality { timespans }))
913 .collect()
914}
915
916#[derive(Clone, Debug)]
918pub struct Horizontality<'t, 'a> {
919 timespans: Vec<&'t ElementTimespan<'a>>,
920}
921
922impl<'t, 'a> Horizontality<'t, 'a> {
923 pub fn timespans(&self) -> &[&'t ElementTimespan<'a>] {
925 &self.timespans
926 }
927
928 fn first_three(&self) -> Option<[Pitch; 3]> {
931 if self.timespans.len() < 3 || self.timespans.iter().any(|span| span.pitches().is_empty()) {
932 return None;
933 }
934 let firsts: Vec<Pitch> = self
935 .timespans
936 .iter()
937 .take(3)
938 .map(|span| span.pitches().into_iter().next())
939 .collect::<Option<_>>()?;
940 Some([firsts[0].clone(), firsts[1].clone(), firsts[2].clone()])
941 }
942
943 pub fn has_passing_tone(&self) -> bool {
946 self.first_three().is_some_and(|[a, b, c]| {
947 (a.ps() < b.ps() && b.ps() < c.ps()) || (a.ps() > b.ps() && b.ps() > c.ps())
948 })
949 }
950
951 pub fn has_neighbor_tone(&self) -> bool {
955 self.first_three()
956 .is_some_and(|[a, b, c]| a == c && (b.ps() - a.ps()).abs() < 3.0)
957 }
958
959 pub fn has_no_motion(&self) -> bool {
964 let mut seen: Vec<Vec<String>> = Vec::new();
965 for span in &self.timespans {
966 let keys: Vec<String> = span.pitches().iter().map(set_key).collect();
967 if !seen.contains(&keys) {
968 seen.push(keys);
969 }
970 }
971 seen.len() == 1
972 }
973}
974
975fn set_key(pitch: &Pitch) -> String {
980 pitch_set_key(pitch)
981}
982
983pub(crate) fn pitch_set_key(pitch: &Pitch) -> String {
986 let accidental = pitch.written_accidental().map(|accidental| {
987 format!(
988 "{}|{:?}|{}|{}|{}|{}|{}|{}",
989 accidental.alter(),
990 accidental.display_status(),
991 accidental.display_type(),
992 accidental.modifier(),
993 accidental.name(),
994 accidental.display_location(),
995 accidental.display_size(),
996 accidental.display_style()
997 )
998 });
999 format!(
1000 "{accidental:?}|{:?}|{}|{:?}|{:?}|{}",
1001 pitch.fundamental().map(pitch_set_key),
1002 pitch.spelling_is_inferred(),
1003 pitch.microtone().map(|microtone| microtone.cents()),
1004 pitch.octave(),
1005 pitch.name_with_octave()
1006 )
1007}
1008
1009#[cfg(test)]
1010mod tests {
1011 use super::*;
1012 use crate::tinynotation::from_tiny_notation;
1013
1014 #[test]
1015 fn a_line_is_read_as_timespans_and_verticalities() -> Result<()> {
1016 let line = from_tiny_notation("4/4 c2 e4 g")?;
1017 let tree = as_timespans(&line, Flatten::Flat, None);
1018 let notes: Vec<(FloatType, FloatType)> = tree
1019 .timespans()
1020 .iter()
1021 .filter(|span| matches!(span.element(), StreamElement::Note(_)))
1022 .map(|span| (span.offset(), span.end_time()))
1023 .collect();
1024 assert_eq!(notes, [(0.0, 2.0), (2.0, 3.0), (3.0, 4.0)]);
1025 let at_one = tree.verticality_at(1.0);
1026 assert!(at_one.start_timespans().is_empty());
1027 assert_eq!(at_one.overlap_timespans().len(), 1);
1028 assert_eq!(at_one.pitch_class_set(), [0]);
1029 assert_eq!(
1030 tree.verticality_at(2.0).next().map(|next| next.offset()),
1031 Some(3.0)
1032 );
1033 assert_eq!(tree.verticality_at(3.0).time_to_next_event(), Some(1.0));
1034 Ok(())
1035 }
1036}