Skip to main content

music21_rs/stream/
sounding.rs

1//! A stream at sounding pitch and at written pitch: music21's
2//! `atSoundingPitch`, `toSoundingPitch` and `toWrittenPitch`.
3
4use super::{Stream, StreamElement, StreamKind};
5use crate::defaults::FloatType;
6use crate::error::Result;
7use crate::interval::Interval;
8use crate::spanner::SpannerKind;
9
10// Offsets from a flattened walk are sums of floats; two that music21 holds as
11// one fraction may differ by a rounding error.
12const NEAR: FloatType = 1e-9;
13
14impl Stream {
15    /// Whether this stream's notes are written at the pitch they sound
16    /// (`Some(true)`), at the pitch a transposing instrument reads
17    /// (`Some(false)`), or nothing has said (`None`): music21's
18    /// `atSoundingPitch`, whose `'unknown'` is `None`.
19    ///
20    /// Setting it moves no notes; [`Stream::to_written_pitch`] and
21    /// [`Stream::to_sounding_pitch`] do that. A stream that says nothing is
22    /// read as the nearest stream holding it that does.
23    pub fn at_sounding_pitch(&self) -> Option<bool> {
24        self.labels.at_sounding_pitch
25    }
26
27    /// Says whether this stream's notes are at sounding pitch.
28    pub fn set_at_sounding_pitch(&mut self, at_sounding_pitch: Option<bool>) {
29        self.labels.at_sounding_pitch = at_sounding_pitch;
30    }
31
32    /// This stream with its notes at the pitch each part's instruments read
33    /// them at: music21's `toWrittenPitch`.
34    ///
35    /// A part at sounding pitch is transposed by the reverse of each of its
36    /// instruments' transpositions -- the notes, chords, chord symbols and
37    /// key signatures from where the instrument stands to where the next one
38    /// does -- and then says it is at written pitch. A part already at
39    /// written pitch, or one that says nothing and stands in nothing that
40    /// does, is left as it is. A score or an opus is taken part by part, and
41    /// then says it is at written pitch too.
42    ///
43    /// ```
44    /// use music21_rs::{Instrument, Note, Stream, StreamElement, StreamKind};
45    ///
46    /// let mut part = Stream::with_kind(StreamKind::Part);
47    /// part.push(StreamElement::Instrument(Box::new(Instrument::of_kind(
48    ///     "Clarinet",
49    /// )?)));
50    /// part.push(StreamElement::Note(Note::from_name("Bb4")?));
51    /// part.set_at_sounding_pitch(Some(true));
52    ///
53    /// let written = part.to_written_pitch()?;
54    /// let StreamElement::Note(note) = written.leaves()[1].1 else { unreachable!() };
55    /// assert_eq!(note.pitch().name_with_octave(), "C5");
56    /// assert_eq!(written.at_sounding_pitch(), Some(false));
57    /// # Ok::<(), music21_rs::Error>(())
58    /// ```
59    pub fn to_written_pitch(&self) -> Result<Stream> {
60        self.to_written_pitch_by(false)
61    }
62
63    /// [`Stream::to_written_pitch`], given music21's `ottavasToSounding`:
64    /// whether the notes under an octave line are then put where they
65    /// sound, as music21's MusicXML writer has them, rather than where the
66    /// line has them written.
67    pub fn to_written_pitch_by(&self, ottavas_to_sounding: bool) -> Result<Stream> {
68        let mut written = self.clone();
69        written.make_written_pitch(ottavas_to_sounding)?;
70        Ok(written)
71    }
72
73    /// This stream with its notes at the pitch they sound: music21's
74    /// `toSoundingPitch`, [`Stream::to_written_pitch`] the other way round.
75    /// A part at written pitch is transposed by each of its instruments'
76    /// transpositions, and then says it is at sounding pitch; the notes
77    /// under an octave line are put where they sound, and the line says so.
78    pub fn to_sounding_pitch(&self) -> Result<Stream> {
79        let mut sounding = self.clone();
80        sounding.make_sounding(None)?;
81        sounding.move_under_ottavas(true)?;
82        Ok(sounding)
83    }
84
85    /// [`Stream::to_written_pitch_by`] in place.
86    pub(crate) fn make_written_pitch(&mut self, ottavas_to_sounding: bool) -> Result<()> {
87        self.make_written(None)?;
88        self.move_under_ottavas(ottavas_to_sounding)
89    }
90
91    /// music21's `performTransposition` (`to_sounding`) or
92    /// `undoTransposition` on every octave line of this stream and of every
93    /// stream inside it: a line whose notes are not yet where `to_sounding`
94    /// asks moves its notes, chords and chord symbols by its interval, or
95    /// back by it, and says where they now are.
96    fn move_under_ottavas(&mut self, to_sounding: bool) -> Result<()> {
97        let mut moves: Vec<(Vec<usize>, Interval)> = Vec::new();
98        for spanner in &mut self.labels.spanners {
99            if spanner.kind() != SpannerKind::Ottava {
100                continue;
101            }
102            let Some(mut shift) = spanner.shift() else {
103                continue;
104            };
105            // A transposing line has its notes where they are read.
106            if shift.transposing() != to_sounding {
107                continue;
108            }
109            shift.set_transposing(!to_sounding);
110            spanner.set_shift(Some(shift));
111            let interval = if to_sounding {
112                shift.interval()
113            } else {
114                shift.interval().reversed()?
115            };
116            let places = spanner.spanned().iter().flatten().copied().collect();
117            moves.push((places, interval));
118        }
119        if !moves.is_empty() {
120            let mut position = 0;
121            let mut failure = None;
122            self.for_each_mut(&mut |_, element| {
123                for (places, interval) in &moves {
124                    if failure.is_none()
125                        && places.contains(&position)
126                        && let Err(error) = transpose_pitches(element, interval)
127                    {
128                        failure = Some(error);
129                    }
130                }
131                position += 1;
132            });
133            if let Some(error) = failure {
134                return Err(error);
135            }
136        }
137        for event in &mut self.events {
138            if let StreamElement::Stream(inner) = &mut event.element {
139                inner.move_under_ottavas(to_sounding)?;
140            }
141        }
142        Ok(())
143    }
144
145    /// Whether this stream or one inside it holds an octave line whose notes
146    /// are written where they are read, which writing them moves.
147    #[cfg(feature = "musicxml")]
148    pub(crate) fn holds_transposing_ottava(&self) -> bool {
149        self.spanners().iter().any(|spanner| {
150            spanner.kind() == SpannerKind::Ottava
151                && spanner.shift().is_some_and(|shift| shift.transposing())
152        }) || self.events.iter().any(|event| match &event.element {
153            StreamElement::Stream(inner) => inner.holds_transposing_ottava(),
154            _ => false,
155        })
156    }
157
158    /// `toWrittenPitch(inPlace=True)`, `held_in` being what the nearest
159    /// stream holding this one says.
160    fn make_written(&mut self, held_in: Option<bool>) -> Result<()> {
161        if self.has_part_like_streams() || self.kind == StreamKind::Opus {
162            let here = self.at_sounding_pitch().or(held_in);
163            for event in &mut self.events {
164                if let StreamElement::Stream(inner) = &mut event.element {
165                    inner.make_written(here)?;
166                }
167            }
168            self.set_at_sounding_pitch(Some(false));
169        } else if self.treat_at_sounding_pitch(held_in)? == Some(true) {
170            self.transpose_by_instrument(true)?;
171            self.say_everywhere(false);
172        }
173        Ok(())
174    }
175
176    /// `toSoundingPitch(inPlace=True)`.
177    fn make_sounding(&mut self, held_in: Option<bool>) -> Result<()> {
178        if self.has_part_like_streams() || self.kind == StreamKind::Opus {
179            let here = self.at_sounding_pitch().or(held_in);
180            for event in &mut self.events {
181                if let StreamElement::Stream(inner) = &mut event.element {
182                    inner.make_sounding(here)?;
183                }
184            }
185            self.set_at_sounding_pitch(Some(true));
186        } else if self.treat_at_sounding_pitch(held_in)? == Some(false) {
187            self.transpose_by_instrument(false)?;
188            self.say_everywhere(true);
189        }
190        Ok(())
191    }
192
193    /// music21's `_treatAtSoundingPitch`: what this stream says, or what the
194    /// nearest stream holding it says; and with that settled, a stream
195    /// inside it saying otherwise is brought round to agree.
196    fn treat_at_sounding_pitch(&mut self, held_in: Option<bool>) -> Result<Option<bool>> {
197        let Some(at) = self.at_sounding_pitch().or(held_in) else {
198            return Ok(None);
199        };
200        self.bring_round(at)?;
201        Ok(Some(at))
202    }
203
204    /// Every stream inside this one that says the opposite of `at` is
205    /// transposed to agree, however deep.
206    fn bring_round(&mut self, at: bool) -> Result<()> {
207        for event in &mut self.events {
208            if let StreamElement::Stream(inner) = &mut event.element {
209                match inner.at_sounding_pitch() {
210                    Some(said) if said != at && at => inner.make_sounding(None)?,
211                    Some(said) if said != at => inner.make_written(None)?,
212                    _ => {}
213                }
214                inner.bring_round(at)?;
215            }
216        }
217        Ok(())
218    }
219
220    /// Says `at` of this stream and of every stream inside it.
221    fn say_everywhere(&mut self, at: bool) {
222        self.set_at_sounding_pitch(Some(at));
223        for event in &mut self.events {
224            if let StreamElement::Stream(inner) = &mut event.element {
225                inner.say_everywhere(at);
226            }
227        }
228    }
229
230    /// music21's `_transposeByInstrument`: each instrument's transposition,
231    /// reversed for `reverse`, applied to the notes, chords, chord symbols
232    /// and key signatures that begin from where it stands up to where the
233    /// next instrument does, the last up to the end of the stream.
234    fn transpose_by_instrument(&mut self, reverse: bool) -> Result<()> {
235        let leaves = self.leaves();
236        let end_of_stream = leaves
237            .iter()
238            .map(|(offset, element)| offset + element.quarter_length())
239            .fold(0.0, FloatType::max);
240        let mut instruments: Vec<(FloatType, Option<Interval>)> = leaves
241            .iter()
242            .filter_map(|(offset, element)| match element {
243                StreamElement::Instrument(instrument) => {
244                    Some((*offset, instrument.transposition().cloned()))
245                }
246                _ => None,
247            })
248            .collect();
249        // Stable, so instruments at one offset keep the order they stand in.
250        instruments.sort_by(|left, right| left.0.total_cmp(&right.0));
251        let mut spans: Vec<(FloatType, FloatType, Interval)> = Vec::new();
252        for (index, (start, transposition)) in instruments.iter().enumerate() {
253            let Some(transposition) = transposition else {
254                continue;
255            };
256            let end = instruments
257                .get(index + 1)
258                .map_or(end_of_stream, |next| next.0);
259            let interval = if reverse {
260                transposition.reversed()?
261            } else {
262                transposition.clone()
263            };
264            spans.push((*start, end, interval));
265        }
266        if spans.is_empty() {
267            return Ok(());
268        }
269        let mut failure = None;
270        self.for_each_mut(&mut |offset, element| {
271            if failure.is_some() {
272                return;
273            }
274            let Some((_, _, interval)) = spans
275                .iter()
276                .find(|(start, end, _)| offset > start - NEAR && offset < end - NEAR)
277            else {
278                return;
279            };
280            if let Err(error) = transpose_element(element, interval) {
281                failure = Some(error);
282            }
283        });
284        failure.map_or(Ok(()), Err)
285    }
286}
287
288/// An element moved by an instrument's transposition: what music21's
289/// `classFilterList` names, a note, a chord (a chord symbol among them) and a
290/// key signature (a key among them).
291fn transpose_element(element: &mut StreamElement, interval: &Interval) -> Result<()> {
292    match element {
293        StreamElement::Note(note) => *note = note.transpose(interval)?,
294        StreamElement::Chord(chord) => *chord = chord.transpose(interval)?,
295        StreamElement::ChordSymbol(symbol) => *symbol = symbol.transpose(interval)?,
296        StreamElement::Key(key) => *key = key.transpose(interval)?,
297        StreamElement::KeySignature(signature) => *signature = signature.transpose(interval)?,
298        _ => {}
299    }
300    Ok(())
301}
302
303/// The pitches of an element moved by an octave line: what has `pitches` in
304/// music21, a note, a chord and a chord symbol.
305fn transpose_pitches(element: &mut StreamElement, interval: &Interval) -> Result<()> {
306    match element {
307        StreamElement::Note(note) => *note = note.transpose(interval)?,
308        StreamElement::Chord(chord) => *chord = chord.transpose(interval)?,
309        StreamElement::ChordSymbol(symbol) => *symbol = symbol.transpose(interval)?,
310        _ => {}
311    }
312    Ok(())
313}
314
315#[cfg(test)]
316mod tests {
317    use crate::instrument::Instrument;
318    use crate::note::Note;
319    use crate::spanner::{OctaveShift, Spanner};
320    use crate::stream::{Stream, StreamElement, StreamKind};
321
322    fn names(stream: &Stream) -> Vec<String> {
323        stream
324            .leaves()
325            .iter()
326            .filter_map(|(_, element)| match element {
327                StreamElement::Note(note) => Some(note.pitch().name_with_octave()),
328                _ => None,
329            })
330            .collect()
331    }
332
333    fn part_with(kind: &str, notes: &[&str]) -> Stream {
334        let mut part = Stream::with_kind(StreamKind::Part);
335        part.push(StreamElement::Instrument(Box::new(
336            Instrument::of_kind(kind).unwrap(),
337        )));
338        for name in notes {
339            part.push(StreamElement::Note(Note::from_name(name).unwrap()));
340        }
341        part
342    }
343
344    #[test]
345    fn a_part_at_sounding_pitch_is_written_up_for_a_clarinet_and_back() {
346        let mut part = part_with("Clarinet", &["Bb4", "D5"]);
347        part.set_at_sounding_pitch(Some(true));
348        let written = part.to_written_pitch().unwrap();
349        assert_eq!(names(&written), ["C5", "E5"]);
350        assert_eq!(written.at_sounding_pitch(), Some(false));
351        let sounding = written.to_sounding_pitch().unwrap();
352        assert_eq!(names(&sounding), ["Bb4", "D5"]);
353        assert_eq!(sounding.at_sounding_pitch(), Some(true));
354    }
355
356    #[test]
357    fn a_part_that_says_nothing_is_left_as_it_is() {
358        let part = part_with("Clarinet", &["Bb4"]);
359        assert_eq!(names(&part.to_written_pitch().unwrap()), ["Bb4"]);
360        assert_eq!(part.to_written_pitch().unwrap().at_sounding_pitch(), None);
361    }
362
363    #[test]
364    fn a_part_takes_what_the_score_holding_it_says() {
365        let mut score = Stream::with_kind(StreamKind::Score);
366        score.push(StreamElement::Stream(Box::new(part_with("Horn", &["C4"]))));
367        score.set_at_sounding_pitch(Some(true));
368        let written = score.to_written_pitch().unwrap();
369        // A horn in F sounds a fifth below what it reads.
370        assert_eq!(names(&written), ["G4"]);
371        assert_eq!(written.at_sounding_pitch(), Some(false));
372    }
373
374    #[test]
375    fn each_instrument_moves_the_notes_from_where_it_stands() {
376        let mut part = part_with("Clarinet", &["Bb4"]);
377        part.push(StreamElement::Instrument(Box::new(
378            Instrument::of_kind("Flute").unwrap(),
379        )));
380        part.push(StreamElement::Note(Note::from_name("Bb4").unwrap()));
381        part.set_at_sounding_pitch(Some(true));
382        assert_eq!(names(&part.to_written_pitch().unwrap()), ["C5", "Bb4"]);
383    }
384
385    fn under_an_ottava(name: &str, transposing: bool) -> Stream {
386        let mut part = Stream::with_kind(StreamKind::Part);
387        for note in ["C5", "D5", "E5"] {
388            part.push(StreamElement::Note(Note::from_name(note).unwrap()));
389        }
390        let shift = OctaveShift::from_name(name, transposing).unwrap();
391        part.add_spanner(Spanner::ottava(shift, vec![0, 1]));
392        part
393    }
394
395    fn transposing(stream: &Stream) -> bool {
396        stream.spanners()[0].shift().unwrap().transposing()
397    }
398
399    #[test]
400    fn the_notes_under_an_ottava_sound_an_octave_from_where_they_are_written() {
401        let part = under_an_ottava("8va", true);
402        let sounding = part.to_sounding_pitch().unwrap();
403        assert_eq!(names(&sounding), ["C6", "D6", "E5"]);
404        assert!(!transposing(&sounding));
405        // Sounding twice moves nothing twice.
406        assert_eq!(
407            names(&sounding.to_sounding_pitch().unwrap()),
408            ["C6", "D6", "E5"]
409        );
410        let written = sounding.to_written_pitch().unwrap();
411        assert_eq!(names(&written), ["C5", "D5", "E5"]);
412        assert!(transposing(&written));
413        // music21's writer has them where they sound.
414        assert_eq!(
415            names(&written.to_written_pitch_by(true).unwrap()),
416            ["C6", "D6", "E5"]
417        );
418    }
419
420    #[test]
421    fn an_ottava_already_at_sounding_pitch_is_written_where_its_line_says() {
422        let part = under_an_ottava("15mb", false);
423        assert_eq!(
424            names(&part.to_sounding_pitch().unwrap()),
425            ["C5", "D5", "E5"]
426        );
427        let written = part.to_written_pitch().unwrap();
428        assert_eq!(names(&written), ["C7", "D7", "E5"]);
429        assert!(transposing(&written));
430    }
431}