Skip to main content

music21/stream/
consecutive.rs

1//! The notes that follow one another in a stream, and the intervals between
2//! them: music21's `Stream.findConsecutiveNotes` and `melodicIntervals`.
3
4use super::{Stream, StreamElement};
5use crate::defaults::FloatType;
6use crate::error::Result;
7use crate::interval::Interval;
8use crate::makenotation::op_frac;
9use crate::pitch::Pitch;
10
11/// Which notes [`Stream::find_consecutive_notes`] passes over, and where it
12/// marks a break: music21's keyword arguments to `findConsecutiveNotes`.
13/// Every option is off by default.
14#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
15#[non_exhaustive]
16pub struct ConsecutiveOptions {
17    /// Whether a rest is passed over rather than breaking the line:
18    /// `skipRests`.
19    pub skip_rests: bool,
20    /// Whether a chord breaks the line rather than joining it:
21    /// `skipChords`.
22    pub skip_chords: bool,
23    /// Whether a note sounding the pitch of the note before, or a chord
24    /// sounding the pitches of the chord before, is passed over:
25    /// `skipUnisons`. Pitches are compared as sounded, so `F#` and `G-` are
26    /// one. What is passed over still sounds until it ends, so no break
27    /// follows it.
28    pub skip_unisons: bool,
29    /// Whether a note an octave or more from the note before, in the same
30    /// pitch class, is passed over too: `skipOctaves`, which implies
31    /// `skip_unisons`.
32    pub skip_octaves: bool,
33    /// Whether time with nothing in it is passed over rather than breaking
34    /// the line: `skipGaps`.
35    pub skip_gaps: bool,
36    /// Whether a note starting before the one before it ends is kept:
37    /// `getOverlaps`.
38    pub get_overlaps: bool,
39    /// Whether the list leaves out its breaks: `noNone`.
40    pub no_none: bool,
41}
42
43/// The interval from one note of a line to the next, as
44/// [`Stream::melodic_intervals`] finds it.
45#[derive(Clone, Debug, PartialEq)]
46pub struct MelodicInterval {
47    interval: Interval,
48    start: usize,
49    end: usize,
50    offset: FloatType,
51    quarter_length: FloatType,
52}
53
54impl MelodicInterval {
55    /// The interval, from the first note to the second; to a chord's first
56    /// note where either is a chord.
57    pub fn interval(&self) -> &Interval {
58        &self.interval
59    }
60
61    /// The position in [`Stream::leaves`] of the note the interval leaves.
62    pub fn start(&self) -> usize {
63        self.start
64    }
65
66    /// The position in [`Stream::leaves`] of the note the interval reaches.
67    pub fn end(&self) -> usize {
68        self.end
69    }
70
71    /// Where the interval begins, counted from the stream's start: where
72    /// the note it leaves ends.
73    pub fn offset(&self) -> FloatType {
74        self.offset
75    }
76
77    /// How long the interval lasts: from where the first note ends to where
78    /// the second starts, nothing for notes that touch.
79    pub fn quarter_length(&self) -> FloatType {
80        self.quarter_length
81    }
82}
83
84/// One stream in the walk, as music21's `recurse(streamsOnly=True,
85/// includeSelf=True)` hands it over.
86struct Container {
87    /// Where it stands in the stream holding it.
88    offset: FloatType,
89    /// How far its own contents reach.
90    highest_time: FloatType,
91    /// The elements it holds itself that are notes, rests and the like, each
92    /// with its offset in it and its position among the leaves.
93    notes: Vec<(FloatType, usize)>,
94}
95
96fn containers(stream: &Stream, offset: FloatType, position: &mut usize, out: &mut Vec<Container>) {
97    let index = out.len();
98    out.push(Container {
99        offset,
100        highest_time: stream.end_offset(),
101        notes: Vec::new(),
102    });
103    for event in &stream.events {
104        match &event.element {
105            StreamElement::Stream(inner) => containers(inner, event.offset, position, out),
106            element => {
107                if is_general_note(element) {
108                    out[index].notes.push((event.offset, *position));
109                }
110                *position += 1;
111            }
112        }
113    }
114}
115
116/// music21's `GeneralNote`: anything written that sounds or rests.
117fn is_general_note(element: &StreamElement) -> bool {
118    matches!(
119        element,
120        StreamElement::Note(_)
121            | StreamElement::Chord(_)
122            | StreamElement::Rest(_)
123            | StreamElement::Unpitched(_)
124            | StreamElement::PercussionChord(_)
125            | StreamElement::ChordSymbol(_)
126    )
127}
128
129/// The pitches a chord sounds, for a chord or a chord symbol.
130fn chord_pitches(element: &StreamElement) -> Result<Option<Vec<Pitch>>> {
131    Ok(match element {
132        StreamElement::Chord(chord) => Some(chord.pitches()),
133        StreamElement::ChordSymbol(symbol) => Some(symbol.pitches()?),
134        _ => None,
135    })
136}
137
138impl Stream {
139    /// The notes and chords that follow one another in this stream, as
140    /// positions in [`Stream::leaves`], with `None` wherever the line breaks:
141    /// music21's `findConsecutiveNotes`.
142    ///
143    /// Each stream is read on its own, its contents in order, and the line
144    /// breaks at a rest, at time with nothing in it, at a chord when chords
145    /// are skipped, and where one stream starts before the one read before
146    /// it ended -- the second voice of a measure, the second part of a
147    /// score. A note starting before the one before it ends is left out.
148    /// `options` changes each of those. A chord of one note is passed over,
149    /// as are unpitched strokes; a chord symbol counts as a chord. The list
150    /// never ends on a break.
151    ///
152    /// # Errors
153    ///
154    /// A chord symbol whose notes cannot be worked out.
155    pub fn find_consecutive_notes(
156        &self,
157        options: &ConsecutiveOptions,
158    ) -> Result<Vec<Option<usize>>> {
159        let leaves = self.leaves();
160        let mut walk = Vec::new();
161        containers(self, 0.0, &mut 0, &mut walk);
162
163        let skip_unisons = options.skip_unisons || options.skip_octaves;
164        let mut found: Vec<Option<usize>> = Vec::new();
165        let mut last_container_end: FloatType = 0.0;
166        let mut last_was_none = false;
167        let mut last_pitches: Vec<Pitch> = Vec::new();
168
169        for container in &walk {
170            let holds_notes = !container.notes.is_empty();
171            if container.offset < last_container_end && holds_notes && !options.no_none {
172                found.push(None);
173                last_was_none = true;
174                last_pitches.clear();
175            }
176            let mut last_end: FloatType = 0.0;
177            if holds_notes {
178                last_container_end = container.highest_time;
179            }
180            for &(offset, position) in &container.notes {
181                let element = leaves[position].1;
182                if !last_was_none && !options.skip_gaps && offset > last_end && !options.no_none {
183                    found.push(None);
184                    last_was_none = true;
185                }
186                let length = element.quarter_length();
187                if let StreamElement::Note(note) = element {
188                    let pitch = note.pitch();
189                    // A unison is judged against a single note only. One
190                    // passed over still sounds until it ends.
191                    if skip_unisons
192                        && last_pitches.len() == 1
193                        && pitch.pitch_class() == last_pitches[0].pitch_class()
194                        && (options.skip_octaves || pitch.ps() == last_pitches[0].ps())
195                    {
196                        last_end = last_end.max(op_frac(offset + length));
197                        continue;
198                    }
199                    if !options.get_overlaps && offset < last_end {
200                        continue;
201                    }
202                    found.push(Some(position));
203                    if offset < last_end {
204                        continue;
205                    }
206                    last_end = op_frac(offset + length);
207                    last_was_none = false;
208                    last_pitches = vec![pitch.clone()];
209                } else if let Some(pitches) = chord_pitches(element)? {
210                    if pitches.len() <= 1 {
211                        continue;
212                    }
213                    if options.skip_chords {
214                        if !last_was_none && !options.no_none {
215                            found.push(None);
216                            last_was_none = true;
217                            last_pitches.clear();
218                        }
219                    } else if skip_unisons && same_sounds(&pitches, &last_pitches) {
220                        // A repeated chord is passed over, but still sounds
221                        // until it ends.
222                        last_end = last_end.max(op_frac(offset + length));
223                    } else if options.get_overlaps || offset >= last_end {
224                        found.push(Some(position));
225                        if offset < last_end {
226                            continue;
227                        }
228                        last_end = op_frac(offset + length);
229                        last_pitches = pitches;
230                        last_was_none = false;
231                    }
232                } else if let StreamElement::Rest(_) = element {
233                    if options.skip_rests {
234                        last_end = op_frac(offset + length);
235                    } else if !last_was_none && !options.no_none {
236                        found.push(None);
237                        last_was_none = true;
238                        last_pitches.clear();
239                    }
240                }
241            }
242        }
243        if last_was_none {
244            found.pop();
245        }
246        Ok(found)
247    }
248
249    /// The interval between each two notes [`Stream::find_consecutive_notes`]
250    /// finds next to each other, with no break between them: music21's
251    /// `melodicIntervals`. An interval to or from a chord is taken from
252    /// the chord's first note.
253    ///
254    /// ```
255    /// use music21::stream::ConsecutiveOptions;
256    /// use music21::tinynotation::from_tiny_notation;
257    ///
258    /// let line = from_tiny_notation("3/4 c4 d' r b b'")?;
259    /// let names: Vec<String> = line
260    ///     .melodic_intervals(&ConsecutiveOptions::default())?
261    ///     .iter()
262    ///     .map(|step| step.interval().short_name())
263    ///     .collect();
264    /// assert_eq!(names, ["M9", "P8"]);
265    /// # Ok::<(), music21::Error>(())
266    /// ```
267    ///
268    /// # Errors
269    ///
270    /// As [`Stream::find_consecutive_notes`], or two notes no interval can
271    /// be spelled between.
272    pub fn melodic_intervals(&self, options: &ConsecutiveOptions) -> Result<Vec<MelodicInterval>> {
273        let found = self.find_consecutive_notes(options)?;
274        let leaves = self.leaves();
275        let mut intervals = Vec::new();
276        for pair in found.windows(2) {
277            let (Some(start), Some(end)) = (pair[0], pair[1]) else {
278                continue;
279            };
280            let (Some(from), Some(to)) =
281                (first_pitch(leaves[start].1)?, first_pitch(leaves[end].1)?)
282            else {
283                continue;
284            };
285            let offset = op_frac(leaves[start].0 + leaves[start].1.quarter_length());
286            intervals.push(MelodicInterval {
287                interval: Interval::between_pitches(&from, &to)?,
288                start,
289                end,
290                offset,
291                quarter_length: op_frac(leaves[end].0 - offset),
292            });
293        }
294        Ok(intervals)
295    }
296
297    /// For each note this stream holds itself, by its index here, the
298    /// interval from it to the first note of `other` sounding where it
299    /// starts -- starting there, or sounding on to it or ending there --
300    /// that an interval can be taken to: what music21's
301    /// `attachIntervalsBetweenStreams` attaches to each note as its
302    /// `harmonicInterval`. `None` where no note of `other` sounds.
303    ///
304    /// ```
305    /// use music21::tinynotation::from_tiny_notation;
306    ///
307    /// let upper = from_tiny_notation("4/4 e'2 d'2")?.flatten();
308    /// let lower = from_tiny_notation("4/4 c1")?.flatten();
309    /// let names: Vec<_> = upper
310    ///     .harmonic_intervals(&lower)
311    ///     .into_iter()
312    ///     .map(|(_, interval)| interval.map(|interval| interval.name()))
313    ///     .collect();
314    /// let expected = ["Major Tenth", "Major Ninth"].map(|name| Some(name.to_string()));
315    /// assert_eq!(names, expected);
316    /// # Ok::<(), music21::Error>(())
317    /// ```
318    pub fn harmonic_intervals(&self, other: &Stream) -> Vec<(usize, Option<Interval>)> {
319        self.events
320            .iter()
321            .enumerate()
322            .filter_map(|(index, event)| match event.element() {
323                StreamElement::Note(note) => Some((index, event.offset(), note)),
324                _ => None,
325            })
326            .map(|(index, offset, note)| {
327                let sounding = super::span::OffsetSpan {
328                    must_begin_in_span: false,
329                    ..super::span::OffsetSpan::at(offset)
330                };
331                let interval = other
332                    .events
333                    .iter()
334                    .filter(|event| {
335                        sounding.takes_at(event.offset(), event.element().quarter_length())
336                    })
337                    .filter_map(|event| match event.element() {
338                        StreamElement::Note(other) => Interval::between_notes(note, other).ok(),
339                        _ => None,
340                    })
341                    .next();
342                (index, interval)
343            })
344            .collect()
345    }
346}
347
348/// Whether two chords sound the same pitches, in any order.
349fn same_sounds(pitches: &[Pitch], other: &[Pitch]) -> bool {
350    let sorted = |pitches: &[Pitch]| {
351        let mut sounds: Vec<FloatType> = pitches.iter().map(Pitch::ps).collect();
352        sounds.sort_by(FloatType::total_cmp);
353        sounds
354    };
355    pitches.len() == other.len() && sorted(pitches) == sorted(other)
356}
357
358/// A note's pitch, or a chord's first note's.
359fn first_pitch(element: &StreamElement) -> Result<Option<Pitch>> {
360    Ok(match element {
361        StreamElement::Note(note) => Some(note.pitch().clone()),
362        element => chord_pitches(element)?.and_then(|pitches| pitches.into_iter().next()),
363    })
364}
365
366#[cfg(test)]
367mod tests {
368    use super::*;
369    use crate::tinynotation::from_tiny_notation;
370
371    #[test]
372    fn a_skipped_unison_does_not_break_the_line() {
373        let line = from_tiny_notation("4/4 c4 c4 d4 e4").unwrap();
374        let options = ConsecutiveOptions {
375            skip_unisons: true,
376            ..ConsecutiveOptions::default()
377        };
378        let found = line.find_consecutive_notes(&options).unwrap();
379        assert_eq!(found.len(), 3);
380        assert!(found.iter().all(Option::is_some));
381    }
382}