Skip to main content

music21_rs/
tree.rs

1//! A stream's elements as spans of time, and the moments where what sounds
2//! changes: music21's `tree` package, its timespan trees and
3//! verticalities.
4//!
5//! [`as_timespans`] reads every element of a stream, nested ones included,
6//! as an [`ElementTimespan`] from where it starts to where it ends, with the
7//! streams it stands in; a [`TimespanTree`] answers which start, stop or
8//! sound at an offset; and a [`Verticality`] is one such offset, with the
9//! pitches sounding there.
10//!
11//! ```
12//! use music21_rs::tinynotation::from_tiny_notation;
13//! use music21_rs::tree::{Flatten, as_timespans};
14//!
15//! let line = from_tiny_notation("4/4 c2 e4 g")?;
16//! let tree = as_timespans(&line, Flatten::Flat, None);
17//! let third = tree.verticality_at(3.0);
18//! assert_eq!(third.start_timespans().len(), 1);
19//! assert_eq!(tree.maximum_overlap(), Some(3));
20//! # Ok::<(), music21_rs::Error>(())
21//! ```
22
23use 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/// How [`as_timespans`] reads the streams a stream holds.
34#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
35pub enum Flatten {
36    /// Their elements only: music21's `flatten=True`.
37    #[default]
38    Flat,
39    /// Their elements and the streams themselves: music21's
40    /// `flatten='semiFlat'`.
41    SemiFlat,
42}
43
44/// One element of a stream, from where it starts to where it ends:
45/// music21's `ElementTimespan`, and its `PitchedTimespan` where the element
46/// is a note-like one or a stream.
47#[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    /// The element.
61    pub fn element(&self) -> &'a StreamElement {
62        self.element
63    }
64
65    /// Where it starts in the stream read.
66    pub fn offset(&self) -> FloatType {
67        self.offset
68    }
69
70    /// Where it ends: music21's `endTime`.
71    pub fn end_time(&self) -> FloatType {
72        self.end_time
73    }
74
75    /// How long it lasts.
76    pub fn quarter_length(&self) -> FloatType {
77        self.end_time - self.offset
78    }
79
80    /// Where the stream holding it starts: music21's `parentOffset`.
81    pub fn parent_offset(&self) -> FloatType {
82        self.parent_offset
83    }
84
85    /// Where the stream holding it ends: music21's `parentEndTime`.
86    pub fn parent_end_time(&self) -> FloatType {
87        self.parent_end_time
88    }
89
90    /// The streams it stands in, the one holding it first and the stream
91    /// read last: music21's `parentage`.
92    pub fn parentage(&self) -> &[&'a Stream] {
93        &self.parentage
94    }
95
96    /// The part it is in, if any: music21's `part`.
97    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    /// The number of the measure it is in, if it is in one: music21's
105    /// `measureNumber`.
106    pub fn measure_number(&self) -> Option<IntegerType> {
107        self.measure
108    }
109
110    /// Whether it is music21's `PitchedTimespan`: the element a note, chord,
111    /// chord symbol, unpitched stroke, percussion chord or stream.
112    pub fn is_pitched(&self) -> bool {
113        self.pitched
114    }
115
116    /// The pitches the element sounds, none for one that is not pitched:
117    /// music21's `pitches`.
118    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    /// The same element over another span: music21's `new`.
131    fn spanning(&self, offset: FloatType, end_time: FloatType) -> Self {
132        Self {
133            offset,
134            end_time,
135            ..self.clone()
136        }
137    }
138}
139
140/// Whether music21 makes a `PitchedTimespan` of an element: a `NotRest` or
141/// a stream.
142fn 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/// The timespans of a stream in time, each answer in music21's order: by
155/// where they start, then where they end, then as the stream holds them:
156/// music21's `TimespanTree`.
157#[derive(Clone, Debug)]
158pub struct TimespanTree<'a> {
159    source: &'a Stream,
160    timespans: Vec<ElementTimespan<'a>>,
161}
162
163/// Every element of a stream, nested ones included, as a timespan:
164/// music21's `asTimespans`. `filter` keeps only the elements it accepts, as
165/// music21's `classList` does; the streams are walked into either way.
166pub 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        // A measure's own number is its measure number, as music21 reads it.
208        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
247/// music21's `Pitch.__le__`: lower in pitch space, or the same pitch
248/// spelled the same.
249fn at_most(a: &Pitch, b: &Pitch) -> bool {
250    a.ps() < b.ps() || a == b
251}
252
253impl<'a> TimespanTree<'a> {
254    /// Every timespan, in order.
255    pub fn timespans(&self) -> &[ElementTimespan<'a>] {
256        &self.timespans
257    }
258
259    /// How many timespans there are.
260    pub fn len(&self) -> usize {
261        self.timespans.len()
262    }
263
264    /// Whether there are none.
265    pub fn is_empty(&self) -> bool {
266        self.timespans.is_empty()
267    }
268
269    /// Where the earliest timespan starts: music21's `lowestPosition`.
270    pub fn lowest_position(&self) -> Option<FloatType> {
271        self.timespans.first().map(|span| span.offset)
272    }
273
274    /// Where the latest starts: music21's `highestPosition`.
275    pub fn highest_position(&self) -> Option<FloatType> {
276        self.timespans.last().map(|span| span.offset)
277    }
278
279    /// Where the last to end ends: music21's `endTime`.
280    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    /// Each offset a timespan starts at, once, in order: music21's
288    /// `allOffsets`.
289    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    /// Each offset a timespan starts or ends at, once, in order: music21's
296    /// `allTimePoints`.
297    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    /// The first offset after this one a timespan starts at: music21's
309    /// `getPositionAfter`.
310    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    /// The last offset before this one a timespan starts at: music21's
318    /// `getPositionBefore`.
319    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    /// The timespans starting at an offset: music21's `elementsStartingAt`.
328    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    /// The timespans ending at an offset: music21's `elementsStoppingAt`.
336    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    /// The timespans sounding across an offset, started before it and
344    /// ending after: music21's `elementsOverlappingOffset`.
345    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    /// The offsets a timespan starts at, or with `include_stop_points` also
353    /// ends at, where another sounds across: music21's
354    /// `overlapTimePoints`.
355    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    /// What starts, stops and sounds across an offset: music21's
368    /// `getVerticalityAt`.
369    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    /// The verticality at an offset where something starts there, or the
380    /// one before: music21's `getVerticalityAtOrBefore`.
381    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    /// The verticality at each offset a timespan starts at, in order:
391    /// music21's `iterateVerticalities`.
392    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    /// The most timespans starting at or sounding across any one offset a
400    /// timespan starts at, nothing for an empty tree: music21's
401    /// `maximumOverlap`.
402    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    /// The stream the tree was read from: music21's `source`.
412    pub fn source(&self) -> &'a Stream {
413        self.source
414    }
415
416    /// Cuts every timespan sounding across one of these offsets in two
417    /// there: music21's `splitAt`. The pieces come after what already
418    /// starts and ends where each does.
419    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    /// Runs of `n` verticalities, each starting one after the last, in
439    /// order or `reverse`d: music21's `iterateVerticalitiesNwise`. Where
440    /// there are fewer than `n`, or with `pad_end` at the end, a run is
441    /// filled out with an empty verticality at the tree's end.
442    ///
443    /// # Errors
444    ///
445    /// A run of no verticalities.
446    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    /// Each run of verticalities from one whose chord is consonant to the
488    /// next that is, with at least one between: music21's
489    /// `iterateConsonanceBoundedVerticalities`.
490    ///
491    /// # Errors
492    ///
493    /// A verticality whose pitches make no chord.
494    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    /// The next timespan after this one, starting at a later offset, in the
518    /// same part: music21's `findNextPitchedTimespanInSameStreamByClass`.
519    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    /// The timespan before this one, starting at an earlier offset, in the
539    /// same part: music21's `findPreviousPitchedTimespanInSameStreamByClass`.
540    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
560/// Whether two parts are the same stream, or both are none.
561fn 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/// What starts, stops and sounds across one offset of a [`TimespanTree`]:
570/// music21's `Verticality`.
571#[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    /// The offset.
582    pub fn offset(&self) -> FloatType {
583        self.offset
584    }
585
586    /// The timespans starting here: music21's `startTimespans`.
587    pub fn start_timespans(&self) -> &[&'t ElementTimespan<'a>] {
588        &self.start_timespans
589    }
590
591    /// The timespans sounding across: music21's `overlapTimespans`.
592    pub fn overlap_timespans(&self) -> &[&'t ElementTimespan<'a>] {
593        &self.overlap_timespans
594    }
595
596    /// The timespans ending here: music21's `stopTimespans`.
597    pub fn stop_timespans(&self) -> &[&'t ElementTimespan<'a>] {
598        &self.stop_timespans
599    }
600
601    /// Those starting, then those sounding across: music21's
602    /// `startAndOverlapTimespans`.
603    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    /// Each pitch sounding, each name and octave once, lowest first:
612    /// music21's `pitchSet`, ordered as its `toChord` orders it.
613    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    /// Each pitch class sounding, once, in order: music21's
630    /// `pitchClassSet`, by its classes.
631    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    /// A chord of the pitches sounding, lowest first: music21's `toChord`.
643    ///
644    /// # Errors
645    ///
646    /// None in practice; a chord of no pitches is an empty chord.
647    pub fn to_chord(&self) -> Result<Chord> {
648        Chord::new(self.pitch_set().as_slice())
649    }
650
651    /// The timespan sounding the lowest pitch, the last of those sounding it
652    /// alike: music21's `bassTimespan`.
653    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    /// The measure of the first timespan starting here: music21's
672    /// `measureNumber`.
673    pub fn measure_number(&self) -> Option<IntegerType> {
674        self.start_timespans.first().and_then(|span| span.measure)
675    }
676
677    /// Each pitched timespan starting here paired with the one before it in
678    /// its part, and, with `include_oblique`, each pitched one sounding
679    /// across paired with itself: music21's `getPairedMotion`. Without
680    /// `include_rests`, a pair is kept only where the earlier one stops
681    /// here; without `include_oblique`, a pair of the same pitches is not
682    /// kept.
683    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    /// Each two pairs of [`Self::paired_motion`], the earlier pair first:
723    /// music21's `getAllVoiceLeadingQuartets(returnObjects=False)`. Without
724    /// `include_no_motion`, two pairs neither of which moves are left out;
725    /// with `part_pairs`, only those whose two parts are one of the pairs of
726    /// the source's parts given, by their places among them.
727    ///
728    /// # Errors
729    ///
730    /// A part pair naming a part the source has not got.
731    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    /// The same quartets as voice-leading quartets, where all four are
776    /// notes: music21's `getAllVoiceLeadingQuartets`.
777    ///
778    /// # Errors
779    ///
780    /// As [`Self::voice_leading_timespans`], or two pitches no interval can
781    /// be spelled between.
782    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    /// A chord of every pitch sounding here, lasting `length` or, without
816    /// one, until the next start -- a quarter where nothing follows -- or a
817    /// rest where nothing sounds: music21's `makeElement`. With
818    /// `add_ties`, a note begun before or going on after is tied; with
819    /// `remove_redundant_pitches`, a pitch two timespans sound is one note.
820    ///
821    /// # Errors
822    ///
823    /// A length no duration can have, or a chord symbol whose pitches
824    /// cannot be worked out.
825    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    /// The next offset a timespan starts at: music21's `nextStartOffset`.
855    pub fn next_start_offset(&self) -> Option<FloatType> {
856        self.tree.position_after(self.offset)
857    }
858
859    /// The verticality there: music21's `nextVerticality`.
860    pub fn next(&self) -> Option<Verticality<'t, 'a>> {
861        self.next_start_offset()
862            .map(|offset| self.tree.verticality_at(offset))
863    }
864
865    /// The verticality at the last offset before this one a timespan starts
866    /// at: music21's `previousVerticality`.
867    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    /// How long until the next start, or the end of the tree after the
874    /// last: music21's `timeToNextEvent`.
875    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
881/// Each part's timespans across a run of verticalities -- those sounding
882/// across or starting at the first, then those starting at the rest --
883/// part by part in the order first met: music21's
884/// `VerticalitySequence.unwrap`.
885pub 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/// One part's timespans one after another: music21's `Horizontality`.
917#[derive(Clone, Debug)]
918pub struct Horizontality<'t, 'a> {
919    timespans: Vec<&'t ElementTimespan<'a>>,
920}
921
922impl<'t, 'a> Horizontality<'t, 'a> {
923    /// The timespans, in order.
924    pub fn timespans(&self) -> &[&'t ElementTimespan<'a>] {
925        &self.timespans
926    }
927
928    /// The first pitch of each of the first three, where there are three
929    /// and every timespan has a pitch, as music21 asks.
930    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    /// Whether the first pitches of the first three rise all the way or
944    /// fall all the way: music21's `hasPassingTone`.
945    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    /// Whether the first and third of the first three are the same pitch and
952    /// the second is less than three semitones from them: music21's
953    /// `hasNeighborTone`.
954    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    /// Whether every timespan sounds the same pitches, written alike:
960    /// music21's `hasNoMotion`. music21 gathers the pitches in a set, which
961    /// tells apart pitches equal but for how their accidentals are shown or
962    /// whether their spelling was worked out, so this does too.
963    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
975/// What tells two pitches apart in music21's sets of them: what its
976/// `Pitch.__hash__` reads -- the accidental and how it is shown, the
977/// fundamental, whether the spelling was worked out, the microtone, octave
978/// and step.
979fn set_key(pitch: &Pitch) -> String {
980    pitch_set_key(pitch)
981}
982
983/// What music21 hashes a pitch by, as text: a set of pitches holds two
984/// pitches once only where these agree.
985pub(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}