1use super::*;
6
7impl Chord {
8 pub fn has_component_volumes(&self) -> bool {
13 self.notes.iter().all(Note::has_volume_information)
14 }
15
16 pub fn volume(&self) -> Volume {
20 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 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 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 pub fn color(&self) -> Option<&str> {
69 self.color.as_deref()
70 }
71
72 pub fn set_color(&mut self, color: Option<String>) {
74 self.color = color;
75 }
76
77 pub fn notehead(&self) -> Notehead {
80 self.notehead
81 }
82
83 pub fn set_notehead(&mut self, notehead: Notehead) {
85 self.notehead = notehead;
86 }
87
88 pub fn notehead_fill(&self) -> Option<bool> {
90 self.notehead_fill
91 }
92
93 pub fn set_notehead_fill(&mut self, fill: Option<bool>) {
95 self.notehead_fill = fill;
96 }
97
98 pub fn notehead_parenthesis(&self) -> bool {
100 self.notehead_parenthesis
101 }
102
103 pub fn set_notehead_parenthesis(&mut self, parenthesis: bool) {
105 self.notehead_parenthesis = parenthesis;
106 }
107
108 pub fn stem_direction(&self) -> StemDirection {
110 self.stem_direction
111 }
112
113 pub fn set_stem_direction(&mut self, direction: StemDirection) {
115 self.stem_direction = direction;
116 }
117
118 pub fn beams(&self) -> &Beams {
120 &self.beams
121 }
122
123 pub fn set_beams(&mut self, beams: Beams) {
125 self.beams = beams;
126 }
127
128 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 pub fn tie(&self) -> Option<&Tie> {
139 self.notes.iter().find_map(Note::tie)
140 }
141
142 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 pub fn lyrics(&self) -> &[Lyric] {
152 self.notes.first().map_or(&[], Note::lyrics)
153 }
154
155 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 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 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 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 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 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}