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