Skip to main content

music21_rs/chord/
notation.rs

1//! The notation a chord carries through its notes: volume, colour,
2//! notehead, stem, beams, tie and lyrics, and the intervals a chord can
3//! be annotated with.
4
5use super::*;
6
7impl Chord {
8    /// Whether *every* note carries a volume of its own: music21's
9    /// `hasComponentVolumes`, which counts the notes that have one and
10    /// compares the count against the whole chord. A chord where only some
11    /// notes have been given a volume reads as having none.
12    pub fn has_component_volumes(&self) -> bool {
13        self.notes.iter().all(Note::has_volume_information)
14    }
15
16    /// How loud the chord is. With volumes on its notes this is their
17    /// average velocity, as music21 reads it; otherwise it is the chord's
18    /// own volume.
19    pub fn volume(&self) -> Volume {
20        // music21 asks in this order: a volume of the chord's own wins, then
21        // the notes' average, and a chord with neither — an empty one
22        // included — reads as a default volume.
23        if let Some(volume) = &self.volume {
24            return volume.clone();
25        }
26        if !self.has_component_volumes() {
27            return Volume::default();
28        }
29        let velocities: Vec<IntegerType> = self
30            .notes
31            .iter()
32            .filter_map(|note| note.volume().velocity())
33            .collect();
34        if velocities.is_empty() {
35            return Volume::default();
36        }
37        let total: IntegerType = velocities.iter().sum();
38        let mean = FloatType::from(total) / velocities.len() as FloatType;
39        Volume::from_velocity(mean.round_ties_even() as IntegerType)
40    }
41
42    /// This chord as a grace chord, written as it is and sounding for no
43    /// time: music21's `getGrace`, as [`Note::grace_note`] makes a note one.
44    pub fn grace_note(&self, appoggiatura: bool) -> Self {
45        let written = self.duration().cloned().unwrap_or_default();
46        let mut grace = self.clone();
47        grace.set_duration(written.grace_duration_as(appoggiatura));
48        grace
49    }
50
51    /// The instrument this chord is played on, over whichever is in force
52    /// where it stands: music21's `storedInstrument`.
53    pub fn stored_instrument(&self) -> Option<&crate::instrument::Instrument> {
54        self.stored_instrument.as_deref()
55    }
56
57    /// Sets the instrument this chord is played on, or clears it.
58    pub fn set_stored_instrument(&mut self, instrument: Option<crate::instrument::Instrument>) {
59        self.stored_instrument = instrument.map(Box::new);
60    }
61
62    /// The instrument that plays this chord, as far as the chord itself can
63    /// say: music21's `getInstrument`, whose search of the streams around it
64    /// is a question for whoever holds the stream.
65    pub fn instrument(&self) -> Option<&crate::instrument::Instrument> {
66        self.stored_instrument()
67    }
68
69    /// Whether a volume was set on the chord itself: music21's
70    /// `hasVolumeInformation`, which does not look at the notes.
71    pub fn has_volume_information(&self) -> bool {
72        self.volume.is_some()
73    }
74
75    /// Sets the chord's own volume, which drops any the notes carried.
76    pub fn set_volume(&mut self, volume: Option<Volume>) {
77        for note in &mut self.notes {
78            note.set_volume(None);
79        }
80        self.volume = volume;
81    }
82
83    /// Gives each note a volume from the list, cycling through it when the
84    /// chord has more notes than the list has volumes: music21's
85    /// `setVolumes`. The chord's own volume is dropped.
86    pub fn set_volumes(&mut self, volumes: &[Volume]) -> Result<()> {
87        if volumes.is_empty() {
88            return Err(Error::Chord(
89                "setVolumes needs at least one volume".to_string(),
90            ));
91        }
92        self.volume = None;
93        for (index, note) in self.notes.iter_mut().enumerate() {
94            note.set_volume(Some(volumes[index % volumes.len()].clone()));
95        }
96        Ok(())
97    }
98
99    /// The colour the chord is written in, when the chord itself carries one
100    /// rather than its notes.
101    pub fn color(&self) -> Option<&str> {
102        self.color.as_deref()
103    }
104
105    /// Sets the colour the chord as a whole is written in.
106    pub fn set_color(&mut self, color: Option<String>) {
107        self.color = color;
108    }
109
110    /// The shape the chord as a whole is drawn with: music21's `notehead`,
111    /// which a chord has in its own right and not only through its notes.
112    pub fn notehead(&self) -> Notehead {
113        self.notehead
114    }
115
116    /// Sets that shape.
117    pub fn set_notehead(&mut self, notehead: Notehead) {
118        self.notehead = notehead;
119    }
120
121    /// Whether the chord's own note heads are filled, when it says.
122    pub fn notehead_fill(&self) -> Option<bool> {
123        self.notehead_fill
124    }
125
126    /// Says whether they are filled.
127    pub fn set_notehead_fill(&mut self, fill: Option<bool>) {
128        self.notehead_fill = fill;
129    }
130
131    /// Whether the chord's own note heads are bracketed.
132    pub fn notehead_parenthesis(&self) -> bool {
133        self.notehead_parenthesis
134    }
135
136    /// Says whether they are bracketed.
137    pub fn set_notehead_parenthesis(&mut self, parenthesis: bool) {
138        self.notehead_parenthesis = parenthesis;
139    }
140
141    /// Which way the chord's own stem points.
142    pub fn stem_direction(&self) -> StemDirection {
143        self.stem_direction
144    }
145
146    /// Sets which way it points.
147    pub fn set_stem_direction(&mut self, direction: StemDirection) {
148        self.stem_direction = direction;
149    }
150
151    /// The beams joining the chord's flags to its neighbours'.
152    pub fn beams(&self) -> &Beams {
153        &self.beams
154    }
155
156    /// Replaces those beams.
157    pub fn set_beams(&mut self, beams: Beams) {
158        self.beams = beams;
159    }
160
161    /// The colour a pitch is written in: the note's own colour when it has
162    /// one, and the chord's otherwise, as music21's `getColor` reads it.
163    pub fn color_of_pitch(&self, pitch: &Pitch) -> Option<&str> {
164        self.note_for_pitch(pitch)
165            .and_then(Note::color)
166            .or(self.color())
167    }
168
169    /// The tie of the first note that carries one: music21's chord-level
170    /// `tie`.
171    pub fn tie(&self) -> Option<&Tie> {
172        self.notes.iter().find_map(Note::tie)
173    }
174
175    /// Ties every note in the chord, or unties them all with `None`.
176    pub fn set_tie(&mut self, tie: Option<Tie>) {
177        for note in &mut self.notes {
178            note.set_tie(tie.clone());
179        }
180    }
181
182    /// The fermatas, ornaments and arpeggios written on the chord as a
183    /// whole: music21's `expressions`.
184    pub fn expressions(&self) -> &[crate::expressions::Expression] {
185        &self.expressions
186    }
187
188    /// The same, to be changed.
189    pub fn expressions_mut(&mut self) -> &mut Vec<crate::expressions::Expression> {
190        &mut self.expressions
191    }
192
193    /// How the chord is played: music21's `articulations`, where a
194    /// fingering is one note's and anything else the whole chord's.
195    pub fn articulations(&self) -> &[crate::articulations::Articulation] {
196        &self.articulations
197    }
198
199    /// The same, to be changed.
200    pub fn articulations_mut(&mut self) -> &mut Vec<crate::articulations::Articulation> {
201        &mut self.articulations
202    }
203
204    /// The syllables sung on the chord, kept on its first note as music21
205    /// keeps them on the chord itself.
206    pub fn lyrics(&self) -> &[Lyric] {
207        self.notes.first().map_or(&[], Note::lyrics)
208    }
209
210    /// Adds a syllable as the next verse: music21's `addLyric`.
211    pub fn add_lyric(
212        &mut self,
213        text: &str,
214        number: Option<IntegerType>,
215        apply_raw: bool,
216    ) -> Result<()> {
217        match self.notes.first_mut() {
218            Some(note) => note.add_lyric(text, number, apply_raw),
219            None => Err(Error::Chord(
220                "an empty chord has nothing to sing".to_string(),
221            )),
222        }
223    }
224
225    /// Names the interval from the chord's lowest pitch up to each of the
226    /// others, highest first: music21's `annotateIntervals`.
227    ///
228    /// With `strip_specifiers` the names are bare numbers (`8`, `5`, `3`)
229    /// and sorted downward; without it they are full interval names (`P8`,
230    /// `P5`, `M3`). Repeated pitches are dropped first, and `sort_pitches`
231    /// measures from the lowest pitch rather than the written first one.
232    pub fn annotate_intervals(
233        &self,
234        strip_specifiers: bool,
235        sort_pitches: bool,
236    ) -> Result<Vec<String>> {
237        let mut reduced = self.remove_redundant_pitches();
238        if sort_pitches {
239            reduced = reduced.sort_ascending();
240        }
241        let pitches = reduced.pitches();
242        let Some(lowest) = pitches.first() else {
243            return Ok(Vec::new());
244        };
245        let mut names = Vec::with_capacity(pitches.len().saturating_sub(1));
246        for pitch in pitches.iter().skip(1).rev() {
247            let interval = Interval::between_pitches(lowest, pitch)?;
248            names.push(if strip_specifiers {
249                interval.generic().semi_simple_undirected().to_string()
250            } else {
251                interval.semi_simple_name()
252            });
253        }
254        if strip_specifiers && sort_pitches {
255            names.sort_by(|left, right| right.cmp(left));
256        }
257        Ok(names)
258    }
259
260    /// Writes the interval names of [`Self::annotate_intervals`] onto the
261    /// chord as lyrics, one verse each.
262    pub fn annotated_with_intervals(
263        &self,
264        strip_specifiers: bool,
265        sort_pitches: bool,
266    ) -> Result<Self> {
267        let names = self.annotate_intervals(strip_specifiers, sort_pitches)?;
268        let mut annotated = self.clone();
269        for name in names {
270            annotated.add_lyric(&name, None, false)?;
271        }
272        Ok(annotated)
273    }
274
275    /// Reads a string harmonic: given a chord whose second note is written
276    /// with a diamond head, the sounding pitch is the harmonic of the first
277    /// note that their distance picks out, and the chord comes back with it
278    /// added on top. A chord not written as a harmonic answers `None`.
279    ///
280    /// This is music21's `Pitch.getStringHarmonic`, which reads the notehead
281    /// off the chord rather than off the pitch it is called on.
282    pub fn string_harmonic(&self) -> Result<Option<Self>> {
283        let [stopped, touched] = match self.notes.get(..2) {
284            Some([first, second]) => [first, second],
285            _ => return Ok(None),
286        };
287        if touched.notehead() != Notehead::Diamond {
288            return Ok(None);
289        }
290        let distance = crate::interval::notes_to_chromatic(&stopped.pitch, &touched.pitch)?;
291        let harmonic = match distance.interval_class() {
292            0 => 2,
293            7 => 3,
294            5 => 4,
295            4 => 5,
296            3 => 6,
297            6 => 7,
298            _ => 1,
299        };
300        let sounding = if harmonic == 1 {
301            stopped.pitch.clone()
302        } else {
303            stopped.pitch.harmonic(harmonic)?
304        };
305        let mut sounding_note = Note::from_pitch(sounding);
306        sounding_note.set_notehead_parenthesis(true);
307        sounding_note.set_notehead_fill(Some(true));
308        sounding_note.set_stem_direction(crate::notation::StemDirection::NoStem);
309        let mut touched_note = Note::from_pitch(touched.pitch.clone());
310        touched_note.set_notehead(touched.notehead());
311        let notes = vec![
312            Note::from_pitch(stopped.pitch.clone()),
313            touched_note,
314            sounding_note,
315        ];
316        Ok(Some(Self::new(notes.as_slice())?))
317    }
318}
319
320#[cfg(test)]
321mod tests {
322    use super::*;
323    use crate::notation::TieType;
324
325    #[test]
326    fn annotate_intervals_matches_music21() {
327        let triad = Chord::new("C4 E4 G4").unwrap();
328        assert_eq!(triad.annotate_intervals(true, true).unwrap(), ["5", "3"]);
329        assert_eq!(triad.annotate_intervals(false, true).unwrap(), ["P5", "M3"]);
330        let with_octave = Chord::new("C4 E4 G4 C5").unwrap();
331        assert_eq!(
332            with_octave.annotate_intervals(true, true).unwrap(),
333            ["8", "5", "3"]
334        );
335        assert_eq!(
336            with_octave.annotate_intervals(false, true).unwrap(),
337            ["P8", "P5", "M3"]
338        );
339        // A pitch repeated at the same octave drops out before the
340        // intervals are read; one an octave up is a tenth and stays, which
341        // reads as another third.
342        let doubled = Chord::new("C4 E4 G4 E4").unwrap();
343        assert_eq!(doubled.annotate_intervals(true, true).unwrap(), ["5", "3"]);
344        let spread = Chord::new("C4 E4 G4 E5").unwrap();
345        assert_eq!(
346            spread.annotate_intervals(true, true).unwrap(),
347            ["5", "3", "3"]
348        );
349        assert!(
350            Chord::empty()
351                .annotate_intervals(true, true)
352                .unwrap()
353                .is_empty()
354        );
355    }
356
357    #[test]
358    fn annotated_intervals_become_lyrics() {
359        let annotated = Chord::new("C4 E4 G4")
360            .unwrap()
361            .annotated_with_intervals(true, true)
362            .unwrap();
363        let texts: Vec<String> = annotated.lyrics().iter().map(Lyric::text).collect();
364        assert_eq!(texts, ["5", "3"]);
365        assert_eq!(annotated.lyrics()[1].number(), 2);
366    }
367
368    #[test]
369    fn component_volumes_average_into_the_chord() {
370        let mut chord = Chord::new("C4 E4 G4").unwrap();
371        assert!(!chord.has_component_volumes());
372        assert_eq!(chord.volume().velocity(), None);
373
374        chord
375            .set_volumes(&[
376                Volume::from_velocity(60),
377                Volume::from_velocity(20),
378                Volume::from_velocity(120),
379            ])
380            .unwrap();
381        assert!(chord.has_component_volumes());
382        let velocities: Vec<Option<IntegerType>> = chord
383            .notes()
384            .iter()
385            .map(|note| note.volume().velocity())
386            .collect();
387        assert_eq!(velocities, [Some(60), Some(20), Some(120)]);
388        assert_eq!(chord.volume().velocity(), Some(67));
389
390        // A volume set on the chord replaces the ones on its notes.
391        chord.set_volume(Some(Volume::from_velocity(90)));
392        assert!(!chord.has_component_volumes());
393        assert_eq!(chord.volume().velocity(), Some(90));
394        assert!(chord.set_volumes(&[]).is_err());
395    }
396
397    #[test]
398    fn a_shorter_volume_list_cycles() {
399        let mut chord = Chord::new("C4 E4 G4 B-4").unwrap();
400        chord
401            .set_volumes(&[Volume::from_velocity(40), Volume::from_velocity(80)])
402            .unwrap();
403        let velocities: Vec<Option<IntegerType>> = chord
404            .notes()
405            .iter()
406            .map(|note| note.volume().velocity())
407            .collect();
408        assert_eq!(velocities, [Some(40), Some(80), Some(40), Some(80)]);
409    }
410
411    #[test]
412    fn ties_and_colours_reach_the_notes() {
413        let mut chord = Chord::new("C4 E4 G4").unwrap();
414        assert!(chord.tie().is_none());
415        chord.set_tie(Some(Tie::new(TieType::Start)));
416        assert_eq!(chord.tie().map(Tie::tie_type), Some(TieType::Start));
417        assert!(chord.notes().iter().all(|note| note.tie().is_some()));
418        chord.set_tie(None);
419        assert!(chord.tie().is_none());
420
421        let e4 = Pitch::from_name("E4").unwrap();
422        chord.set_color(Some("blue".to_string()));
423        assert_eq!(chord.color_of_pitch(&e4), Some("blue"));
424        chord
425            .note_for_pitch_mut(&e4)
426            .unwrap()
427            .set_color(Some("red".to_string()));
428        assert_eq!(chord.color_of_pitch(&e4), Some("red"));
429        let c4 = Pitch::from_name("C4").unwrap();
430        assert_eq!(chord.color_of_pitch(&c4), Some("blue"));
431    }
432
433    #[test]
434    fn a_diamond_notehead_reads_as_a_string_harmonic() {
435        let mut chord = Chord::new("D3 G3").unwrap();
436        assert!(chord.string_harmonic().unwrap().is_none());
437        chord.notes_mut()[1].set_notehead(Notehead::Diamond);
438        let sounded = chord.string_harmonic().unwrap().unwrap();
439        assert_eq!(
440            sounded
441                .pitches()
442                .iter()
443                .map(Pitch::name_with_octave)
444                .collect::<Vec<_>>(),
445            ["D3", "G3", "D5"]
446        );
447        let sounding = &sounded.notes()[2];
448        assert!(sounding.notehead_parenthesis());
449        assert_eq!(sounding.stem_direction(), StemDirection::NoStem);
450        assert_eq!(sounded.notes()[1].notehead(), Notehead::Diamond);
451    }
452}