Skip to main content

music21_rs/stream/
ties.rs

1//! Tied notes folded into one: music21's `stripTies`.
2
3use super::{Stream, StreamElement, StreamKind};
4use crate::defaults::FloatType;
5use crate::error::{Error, Result};
6use crate::notation::{Tie, TieType};
7use crate::pitch::Pitch;
8
9impl Stream {
10    /// Folds every run of tied notes into its first note, lengthened by the
11    /// rest of the run, which are taken out: music21's `stripTies`.
12    ///
13    /// A stream holding parts has each of them done, and one holding voices
14    /// each voice. Otherwise every note, chord and rest is taken in one line
15    /// in the order the stream flattens to, measures and voices alike, and a
16    /// run of them is joined by its ties. With `match_by_pitch` a note joins
17    /// the run before it only by sounding the pitch the last of it does --
18    /// a chord, every pitch on the same letter and enharmonic -- whatever its
19    /// tie says; without, a chord joins one whose notes are all tied on and
20    /// as many. The note kept is given the run's length as a plain value and
21    /// loses its tie; a spanner joined to a note taken out is joined to the
22    /// note kept instead. Notes of no length, grace notes among them, are
23    /// left as they are.
24    ///
25    /// ```
26    /// use music21_rs::notation::{Tie, TieType};
27    /// use music21_rs::{Duration, Note, Stream, StreamElement};
28    ///
29    /// let mut stream = Stream::new();
30    /// let mut first = Note::from_name("C4")?.with_duration(Duration::half());
31    /// first.set_tie(Some(Tie::new(TieType::Start)));
32    /// let mut second = Note::from_name("C4")?;
33    /// second.set_tie(Some(Tie::new(TieType::Stop)));
34    /// stream.push(first);
35    /// stream.push(second);
36    /// stream.strip_ties(true)?;
37    ///
38    /// let leaves = stream.leaves();
39    /// assert_eq!(leaves.len(), 1);
40    /// assert_eq!(leaves[0].1.quarter_length(), 3.0);
41    /// # Ok::<(), music21_rs::Error>(())
42    /// ```
43    ///
44    /// # Errors
45    ///
46    /// A run whose notes after the first last no time at all, as music21
47    /// raises for one.
48    pub fn strip_ties(&mut self, match_by_pitch: bool) -> Result<()> {
49        if self.has_part_like_streams() {
50            for event in &mut self.events {
51                if let StreamElement::Stream(inner) = &mut event.element {
52                    inner.strip_ties(match_by_pitch)?;
53                }
54            }
55            return Ok(());
56        }
57        let has_voices = self.events.iter().any(|event| {
58            matches!(&event.element, StreamElement::Stream(inner) if inner.kind == StreamKind::Voice)
59        });
60        if has_voices {
61            for event in &mut self.events {
62                if let StreamElement::Stream(inner) = &mut event.element
63                    && inner.kind == StreamKind::Voice
64                {
65                    inner.strip_ties(match_by_pitch)?;
66                }
67            }
68            return Ok(());
69        }
70
71        let leaves = self.leaves();
72        // The leaves in the order music21's `flatten` gives them.
73        let mut order: Vec<usize> = (0..leaves.len()).collect();
74        order.sort_by(|left, right| {
75            let (left_offset, left_element) = leaves[*left];
76            let (right_offset, right_element) = leaves[*right];
77            let grace = |element: &StreamElement| {
78                element
79                    .duration()
80                    .is_some_and(crate::duration::Duration::is_grace)
81            };
82            left_offset
83                .total_cmp(&right_offset)
84                .then(
85                    left_element
86                        .class_sort_order()
87                        .cmp(&right_element.class_sort_order()),
88                )
89                .then(grace(right_element).cmp(&grace(left_element)))
90        });
91        let line: Vec<(usize, Tied)> = order
92            .into_iter()
93            .filter_map(|position| {
94                let element = leaves[position].1;
95                Tied::of(element)
96                    .filter(|_| element.quarter_length() > 0.0)
97                    .map(|tied| (position, tied))
98            })
99            .collect();
100
101        let mut merged: Vec<(usize, FloatType)> = Vec::new();
102        let mut taken: Vec<(usize, usize)> = Vec::new();
103        let mut connected: Vec<usize> = Vec::new();
104        for index in 0..line.len() {
105            let tied = &line[index].1;
106            let last = index.checked_sub(1);
107            let ends = |connected: &[usize]| {
108                ends_run(
109                    tied,
110                    last.map(|last| &line[last].1),
111                    last,
112                    connected,
113                    match_by_pitch,
114                )
115            };
116            let mut end_match = None;
117            match tied.tie {
118                Some(TieType::Start) => {
119                    if last.is_none_or(|last| !connected.contains(&last)) {
120                        connected = vec![index];
121                    } else {
122                        connected.push(index);
123                    }
124                    end_match = Some(false);
125                }
126                Some(TieType::Continue) => {
127                    if connected.is_empty() {
128                        connected.push(index);
129                    } else if match_by_pitch {
130                        if ends(&connected) {
131                            connected.push(index);
132                        } else {
133                            connected = vec![index];
134                        }
135                    } else if tied.all_continue {
136                        let before = last.map(|last| &line[last].1);
137                        if before.is_some_and(|before| {
138                            before.sounds && before.pitches.len() != tied.pitches.len()
139                        }) {
140                            connected = vec![index];
141                        } else {
142                            connected.push(index);
143                        }
144                    } else {
145                        connected.clear();
146                    }
147                    end_match = Some(false);
148                }
149                _ => {}
150            }
151            let end_match = end_match.unwrap_or_else(|| ends(&connected));
152            if !end_match {
153                continue;
154            }
155            connected.push(index);
156            if connected.len() < 2 {
157                connected.clear();
158                continue;
159            }
160            let first = connected[0];
161            let mut sum = 0.0;
162            for &later in &connected[1..] {
163                sum += line[later].1.quarter_length;
164                taken.push((line[later].0, line[first].0));
165            }
166            if sum == 0.0 {
167                return Err(Error::Stream(
168                    "aggregated ties have a zero duration sum".to_string(),
169                ));
170            }
171            merged.push((
172                line[first].0,
173                crate::makenotation::op_frac(line[first].1.quarter_length + sum),
174            ));
175            connected.clear();
176        }
177        if merged.is_empty() {
178            return Ok(());
179        }
180
181        // A spanner holding a note taken out holds the note kept.
182        let mut moved: Vec<Option<usize>> = (0..leaves.len()).map(Some).collect();
183        for &(gone, kept) in &taken {
184            moved[gone] = Some(kept);
185        }
186        self.move_spanners_everywhere(&moved);
187        let gone: Vec<usize> = taken.iter().map(|(gone, _)| *gone).collect();
188        let mut failure = None;
189        self.retain_leaves(&mut |position, element| {
190            if gone.contains(&position) {
191                return false;
192            }
193            if let Some((_, length)) = merged.iter().find(|(kept, _)| *kept == position)
194                && let Err(error) = lengthen(element, *length)
195            {
196                failure = Some(error);
197            }
198            true
199        });
200        failure.map_or(Ok(()), Err)
201    }
202
203    /// Moves the places of every spanner here and in every nested stream,
204    /// each by the leaves of the stream holding it.
205    fn move_spanners_everywhere(&mut self, moved: &[Option<usize>]) {
206        let mut first = 0;
207        self.move_spanners_from(&mut first, moved);
208    }
209
210    fn move_spanners_from(&mut self, first: &mut usize, moved: &[Option<usize>]) {
211        let start = *first;
212        let count = self.leaves().len();
213        let mine: Vec<Option<usize>> = (0..count)
214            .map(|place| {
215                moved
216                    .get(start + place)
217                    .copied()
218                    .flatten()
219                    .and_then(|to| to.checked_sub(start))
220                    .filter(|to| *to < count)
221            })
222            .collect();
223        for spanner in &mut self.labels.spanners {
224            spanner.move_places(&mine);
225            spanner.drop_repeated_places();
226        }
227        for event in &mut self.events {
228            match &mut event.element {
229                StreamElement::Stream(inner) => inner.move_spanners_from(first, moved),
230                _ => *first += 1,
231            }
232        }
233    }
234}
235
236/// What `stripTies` asks of a note, chord or rest.
237struct Tied {
238    tie: Option<TieType>,
239    /// Whether it sounds: music21's `NotRest`.
240    sounds: bool,
241    /// A note's one pitch, for a note alone.
242    pitch: Option<Pitch>,
243    /// The pitches of a chord, or of anything else that sounds.
244    pitches: Vec<Pitch>,
245    is_chord: bool,
246    /// Whether it is tied on, and for a chord every note of it.
247    all_continue: bool,
248    /// Whether every note of a chord ends a tie.
249    all_stop: bool,
250    quarter_length: FloatType,
251}
252
253impl Tied {
254    fn of(element: &StreamElement) -> Option<Self> {
255        let tie_type = |tie: Option<&Tie>| tie.map(Tie::tie_type);
256        let quarter_length = element.quarter_length();
257        Some(match element {
258            StreamElement::Note(note) => Self {
259                tie: tie_type(note.tie()),
260                sounds: true,
261                pitch: Some(note.pitch().clone()),
262                pitches: vec![note.pitch().clone()],
263                is_chord: false,
264                all_continue: false,
265                all_stop: false,
266                quarter_length,
267            },
268            StreamElement::Chord(chord) => {
269                let ties: Vec<Option<TieType>> = chord
270                    .notes()
271                    .iter()
272                    .map(|note| tie_type(note.tie()))
273                    .collect();
274                Self {
275                    tie: tie_type(chord.tie()),
276                    sounds: true,
277                    pitch: None,
278                    pitches: chord.pitches(),
279                    is_chord: true,
280                    all_continue: ties.iter().all(|tie| *tie == Some(TieType::Continue)),
281                    all_stop: ties.iter().all(|tie| *tie == Some(TieType::Stop)),
282                    quarter_length,
283                }
284            }
285            StreamElement::ChordSymbol(symbol) => Self {
286                tie: None,
287                sounds: true,
288                pitch: None,
289                pitches: symbol.pitches().unwrap_or_default(),
290                is_chord: true,
291                all_continue: false,
292                all_stop: false,
293                quarter_length,
294            },
295            StreamElement::Rest(rest) => Self {
296                tie: tie_type(rest.tie()),
297                sounds: false,
298                pitch: None,
299                pitches: Vec::new(),
300                is_chord: false,
301                all_continue: false,
302                all_stop: false,
303                quarter_length,
304            },
305            // A stroke has no pitch music21 compares; it has its written
306            // note's tie.
307            StreamElement::Unpitched(stroke) => Self {
308                tie: tie_type(stroke.written().tie()),
309                sounds: true,
310                pitch: None,
311                pitches: Vec::new(),
312                is_chord: false,
313                all_continue: false,
314                all_stop: false,
315                quarter_length,
316            },
317            StreamElement::PercussionChord(chord) => Self {
318                tie: tie_type(chord.written().tie()),
319                sounds: true,
320                pitch: None,
321                pitches: chord.pitches(),
322                is_chord: false,
323                all_continue: false,
324                all_stop: false,
325                quarter_length,
326            },
327            _ => return None,
328        })
329        .map(|mut tied| {
330            // music21's `allTiesAreContinue`: the tie continues, and for a
331            // chord every note's does.
332            tied.all_continue =
333                tied.tie == Some(TieType::Continue) && (!tied.is_chord || tied.all_continue);
334            tied
335        })
336    }
337}
338
339/// music21's `updateEndMatch`: whether `tied` ends the run the notes in
340/// `connected` make, `last` being the note before it.
341fn ends_run(
342    tied: &Tied,
343    before: Option<&Tied>,
344    last: Option<usize>,
345    connected: &[usize],
346    match_by_pitch: bool,
347) -> bool {
348    if !tied.is_chord && tied.tie == Some(TieType::Stop) {
349        return true;
350    }
351    if !match_by_pitch {
352        return tied.is_chord
353            && before.is_some_and(|before| {
354                before.is_chord && tied.all_stop && before.pitches.len() == tied.pitches.len()
355            });
356    }
357    let Some(before) = before else {
358        return false;
359    };
360    let joined = last.is_some_and(|last| connected.contains(&last));
361    if !joined {
362        return false;
363    }
364    if let (Some(previous), Some(pitch)) = (&before.pitch, &tied.pitch) {
365        return previous == pitch;
366    }
367    // Anything but a note, against whatever came before it: every pitch on
368    // the same letter and enharmonic, a rest having none.
369    if tied.pitch.is_none() {
370        return before.pitches.len() == tied.pitches.len()
371            && before
372                .pitches
373                .iter()
374                .zip(&tied.pitches)
375                .all(|(previous, pitch)| {
376                    previous.step() == pitch.step() && previous.is_enharmonic(pitch)
377                });
378    }
379    false
380}
381
382/// Gives the note kept by a run its run's length, as a plain value, and
383/// takes its tie off.
384fn lengthen(element: &mut StreamElement, length: FloatType) -> Result<()> {
385    let duration = crate::duration::Duration::new(length)?;
386    match element {
387        StreamElement::Note(note) => {
388            note.set_duration(duration);
389            note.set_tie(None);
390        }
391        StreamElement::Chord(chord) => {
392            chord.set_duration(duration);
393            chord.set_tie(None);
394        }
395        StreamElement::ChordSymbol(symbol) => symbol.set_duration(duration),
396        StreamElement::Rest(rest) => {
397            rest.set_duration(duration);
398            rest.set_tie(None);
399        }
400        StreamElement::Unpitched(stroke) => {
401            stroke.written_mut().set_duration(duration);
402            stroke.written_mut().set_tie(None);
403        }
404        StreamElement::PercussionChord(chord) => {
405            chord.written_mut().set_duration(duration);
406            chord.written_mut().set_tie(None);
407        }
408        _ => {}
409    }
410    Ok(())
411}