Skip to main content

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