1pub mod guitar;
3pub(crate) mod root;
4pub mod tables;
5
6use crate::common::numbertools::ORDINALS;
7use crate::defaults::{FloatType, IntegerType, UnsignedIntegerType};
8use crate::duration::Duration;
9use crate::error::Error;
10use crate::error::Result;
11use crate::interval::{Interval, PitchOrNote};
12use crate::key::Key;
13use crate::key::keysignature::KeySignature;
14use crate::notation::{Beams, Lyric, Notehead, StemDirection, Tie};
15use crate::note::{IntoNote, Note};
16use crate::pitch::{Pitch, PitchClass, PitchClassSpecifier};
17use crate::volume::Volume;
18
19pub use guitar::{GuitarFingering, GuitarStringFingering, GuitarTuning, GuitarTuningString};
20
21use num::integer::{gcd, lcm};
22use std::fmt::{Display, Formatter};
23use std::ops::Index;
24use std::str::FromStr;
25use std::sync::LazyLock;
26
27mod names;
28mod notation;
29mod quality;
30mod resolution;
31mod setclass;
32
33pub use names::KnownChordType;
34pub use quality::TriadQuality;
35pub use resolution::ChordResolutionSuggestion;
36pub use setclass::{ChordTableAddress, format_vector_string};
37
38use quality::*;
39use resolution::*;
40
41#[derive(Debug, Clone)]
42#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
43#[must_use]
49pub struct Chord {
50 notes: Vec<Note>,
51 duration: Option<Duration>,
52 #[cfg_attr(feature = "serde", serde(default))]
54 volume: Option<Volume>,
55 #[cfg_attr(feature = "serde", serde(default))]
57 color: Option<String>,
58 #[cfg_attr(feature = "serde", serde(default))]
62 notehead: Notehead,
63 #[cfg_attr(feature = "serde", serde(default))]
64 notehead_fill: Option<bool>,
65 #[cfg_attr(feature = "serde", serde(default))]
66 notehead_parenthesis: bool,
67 #[cfg_attr(feature = "serde", serde(default))]
68 stem_direction: StemDirection,
69 #[cfg_attr(feature = "serde", serde(default))]
71 beams: Beams,
72 #[cfg_attr(feature = "serde", serde(skip))]
73 from_integer_pitches: bool,
74 #[cfg_attr(feature = "serde", serde(default))]
78 root_override: Option<Pitch>,
79 #[cfg_attr(feature = "serde", serde(default))]
81 bass_override: Option<Pitch>,
82}
83
84use crate::interval::constants::PERFECT_FIFTH_UP as PERFECT_FIFTH;
85
86impl Index<usize> for Chord {
87 type Output = Note;
88
89 fn index(&self, index: usize) -> &Self::Output {
90 &self.notes[index]
91 }
92}
93
94impl IntoIterator for Chord {
95 type Item = Note;
96 type IntoIter = std::vec::IntoIter<Note>;
97
98 fn into_iter(self) -> Self::IntoIter {
99 self.notes.into_iter()
100 }
101}
102
103impl<'a> IntoIterator for &'a Chord {
104 type Item = &'a Note;
105 type IntoIter = std::slice::Iter<'a, Note>;
106
107 fn into_iter(self) -> Self::IntoIter {
108 self.notes.iter()
109 }
110}
111
112impl<'a> IntoIterator for &'a mut Chord {
113 type Item = &'a mut Note;
114 type IntoIter = std::slice::IterMut<'a, Note>;
115
116 fn into_iter(self) -> Self::IntoIter {
117 self.notes.iter_mut()
118 }
119}
120
121impl FromStr for Chord {
122 type Err = Error;
123
124 fn from_str(value: &str) -> Result<Self> {
125 Self::new(value)
126 }
127}
128
129impl TryFrom<&str> for Chord {
130 type Error = Error;
131
132 fn try_from(value: &str) -> Result<Self> {
133 Self::new(value)
134 }
135}
136
137impl TryFrom<String> for Chord {
138 type Error = Error;
139
140 fn try_from(value: String) -> Result<Self> {
141 Self::new(value)
142 }
143}
144
145impl TryFrom<&[Pitch]> for Chord {
146 type Error = Error;
147
148 fn try_from(value: &[Pitch]) -> Result<Self> {
149 Self::new(value)
150 }
151}
152
153impl TryFrom<&[Note]> for Chord {
154 type Error = Error;
155
156 fn try_from(value: &[Note]) -> Result<Self> {
157 Self::new(value)
158 }
159}
160
161impl TryFrom<&[IntegerType]> for Chord {
162 type Error = Error;
163
164 fn try_from(value: &[IntegerType]) -> Result<Self> {
165 Self::new(value)
166 }
167}
168
169impl TryFrom<&[&str]> for Chord {
170 type Error = Error;
171
172 fn try_from(value: &[&str]) -> Result<Self> {
173 Self::new(value)
174 }
175}
176
177impl TryFrom<&[String]> for Chord {
178 type Error = Error;
179
180 fn try_from(value: &[String]) -> Result<Self> {
181 Self::new(value)
182 }
183}
184
185impl Display for Chord {
186 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
187 write!(f, "{}", self.pitched_common_name())
188 }
189}
190
191impl Chord {
192 pub fn new<T>(notes: T) -> Result<Self>
197 where
198 T: IntoNotes,
199 {
200 Ok(Self {
201 notes: notes.try_into_notes()?.into_iter().collect(),
202 duration: None,
203 from_integer_pitches: T::FROM_INTEGER_PITCHES,
204 volume: None,
205 color: None,
206 notehead: Notehead::default(),
207 notehead_fill: None,
208 notehead_parenthesis: false,
209 stem_direction: StemDirection::default(),
210 beams: Beams::default(),
211 root_override: None,
212 bass_override: None,
213 })
214 }
215
216 pub fn empty() -> Self {
218 Self {
219 notes: Vec::new(),
220 duration: None,
221 from_integer_pitches: false,
222 volume: None,
223 color: None,
224 notehead: Notehead::default(),
225 notehead_fill: None,
226 notehead_parenthesis: false,
227 stem_direction: StemDirection::default(),
228 beams: Beams::default(),
229 root_override: None,
230 bass_override: None,
231 }
232 }
233
234 pub fn guitar_fingering(&self) -> Option<GuitarFingering> {
242 guitar::suggested_guitar_fingering(self)
243 }
244
245 pub fn guitar_fingering_with_tuning(&self, tuning: &GuitarTuning) -> Option<GuitarFingering> {
251 guitar::suggested_guitar_fingering_with_tuning(self, tuning)
252 }
253
254 pub fn pitch_classes(&self) -> Vec<u8> {
256 self.ordered_pitch_classes()
257 }
258
259 pub fn polyrhythm_components(&self) -> Vec<UnsignedIntegerType> {
266 let pitch_classes = self.ordered_pitch_classes();
267 if pitch_classes.is_empty() {
268 return vec![1];
269 }
270
271 let root_pc = self
272 .find_root_pitch()
273 .map(root::pitch_class)
274 .filter(|root_pc| pitch_classes.contains(root_pc))
275 .unwrap_or(pitch_classes[0]);
276 let mut offsets = pitch_classes
277 .iter()
278 .map(|pc| (*pc + 12 - root_pc) % 12)
279 .collect::<Vec<_>>();
280 offsets.sort_unstable();
281
282 let ratios = offsets
283 .into_iter()
284 .map(Self::just_ratio_for_semitone)
285 .collect::<Vec<_>>();
286 let common_denominator = ratios
287 .iter()
288 .fold(1, |acc, (_, denominator)| lcm(acc, *denominator));
289 let integers = ratios
290 .iter()
291 .map(|(numerator, denominator)| numerator * (common_denominator / denominator))
292 .collect::<Vec<_>>();
293 let divisor = integers.iter().copied().reduce(gcd).unwrap_or(1).max(1);
294
295 integers.into_iter().map(|value| value / divisor).collect()
296 }
297
298 pub fn polyrhythm_ratio_string(&self) -> String {
300 self.polyrhythm_components()
301 .into_iter()
302 .map(|component| component.to_string())
303 .collect::<Vec<_>>()
304 .join(":")
305 }
306
307 pub fn add<T>(&mut self, notes: T) -> Result<()>
311 where
312 T: IntoNotes,
313 {
314 self.notes.extend(notes.try_into_notes()?);
315 Ok(())
316 }
317
318 pub fn remove(&mut self, pitch: &Pitch) -> Result<()> {
321 let found = self.notes.iter().position(|note| ¬e.pitch == pitch);
322 match found {
323 Some(index) => {
324 let _ = self.notes.remove(index);
325 Ok(())
326 }
327 None => Err(Error::Chord("Chord.remove(x), x not in chord".to_string())),
328 }
329 }
330
331 pub fn remove_named(&mut self, name_with_octave: &str) -> Result<()> {
334 let found = self
335 .notes
336 .iter()
337 .position(|note| note.pitch.name_with_octave() == name_with_octave);
338 match found {
339 Some(index) => {
340 let _ = self.notes.remove(index);
341 Ok(())
342 }
343 None => Err(Error::Chord("Chord.remove(x), x not in chord".to_string())),
344 }
345 }
346
347 pub fn pitches(&self) -> Vec<Pitch> {
349 self.notes.iter().map(|note| note.pitch.clone()).collect()
350 }
351
352 pub fn iter_pitches(&self) -> impl Iterator<Item = &Pitch> + '_ {
358 self.notes.iter().map(|note| ¬e.pitch)
359 }
360
361 pub fn notes(&self) -> &[Note] {
363 &self.notes
364 }
365
366 pub fn notes_mut(&mut self) -> &mut [Note] {
370 &mut self.notes
371 }
372
373 pub fn len(&self) -> usize {
375 self.notes.len()
376 }
377
378 pub fn is_empty(&self) -> bool {
380 self.notes.is_empty()
381 }
382
383 pub fn note_for_pitch(&self, pitch: &Pitch) -> Option<&Note> {
386 self.notes.iter().find(|note| ¬e.pitch == pitch)
387 }
388
389 pub fn note_for_pitch_mut(&mut self, pitch: &Pitch) -> Option<&mut Note> {
391 self.notes.iter_mut().find(|note| ¬e.pitch == pitch)
392 }
393
394 pub fn duration(&self) -> Option<&Duration> {
396 self.duration.as_ref()
397 }
398
399 pub fn set_duration(&mut self, duration: Duration) {
401 self.duration = Some(duration);
402 }
403
404 pub fn with_duration(mut self, duration: Duration) -> Self {
406 self.set_duration(duration);
407 self
408 }
409
410 pub fn inversion(&self) -> Option<u8> {
414 let bass_to_root = diatonic_steps_above(self.bass()?, self.root()?);
415 Some([0, 3, 6, 2, 5, 1, 4][usize::from(bass_to_root - 1)])
416 }
417
418 pub(crate) fn inversion_with_root(&self, root: &Pitch) -> Option<u8> {
421 let bass_to_root = diatonic_steps_above(self.bass()?, root);
422 Some([0, 3, 6, 2, 5, 1, 4][usize::from(bass_to_root - 1)])
423 }
424
425 pub fn inversion_name(&self) -> Result<Option<IntegerType>> {
434 let Some(inversion) = self.inversion() else {
435 return Ok(None);
436 };
437 let inversion = usize::from(inversion);
438 if self.is_seventh() || self.seventh().is_some() {
439 return [7, 65, 43, 42]
440 .get(inversion)
441 .copied()
442 .map(Some)
443 .ok_or_else(|| {
444 Error::Chord(format!("Not a normal inversion for a seventh: {inversion}"))
445 });
446 }
447 if self.is_triad() {
448 return [53, 6, 64]
449 .get(inversion)
450 .copied()
451 .map(Some)
452 .ok_or_else(|| {
453 Error::Chord(format!("Not a normal inversion for a triad: {inversion}"))
454 });
455 }
456 Err(Error::Chord(
457 "Not a triad or Seventh, cannot determine inversion.".to_string(),
458 ))
459 }
460
461 pub fn simplify_enharmonics(&self, key_context: Option<KeySignature>) -> Result<Self> {
467 let mut chord = self.clone();
468 chord.simplify_enharmonics_in_place(key_context)?;
469 Ok(chord)
470 }
471
472 pub fn simplify_enharmonics_in_place(
474 &mut self,
475 key_context: Option<KeySignature>,
476 ) -> Result<()> {
477 match crate::pitch::simplify_multiple_enharmonics(&self.pitches(), None, key_context) {
478 Ok(pitches) => {
479 for (i, pitch) in pitches.iter().enumerate() {
480 if let Some(note) = self.notes.get_mut(i) {
481 note.pitch = pitch.clone();
482 }
483 }
484 Ok(())
485 }
486 Err(err) => Err(Error::Chord(format!(
487 "simplifying multiple enharmonics failed because of {err}"
488 ))),
489 }
490 }
491
492 pub fn root(&self) -> Option<&Pitch> {
494 self.root_override
495 .as_ref()
496 .or_else(|| self.find_root_pitch())
497 }
498
499 pub fn found_root(&self) -> Option<&Pitch> {
501 self.find_root_pitch()
502 }
503
504 pub fn set_root(&mut self, root: Option<Pitch>) {
508 self.root_override = root;
509 }
510
511 pub fn bass(&self) -> Option<&Pitch> {
514 self.bass_override.as_ref().or_else(|| self.bass_pitch())
515 }
516
517 pub fn found_bass(&self) -> Option<&Pitch> {
519 self.bass_pitch()
520 }
521
522 pub fn overridden_bass(&self) -> Option<&Pitch> {
525 self.bass_override.as_ref()
526 }
527
528 pub fn overridden_root(&self) -> Option<&Pitch> {
531 self.root_override.as_ref()
532 }
533
534 pub fn set_bass(&mut self, bass: Option<Pitch>) {
538 let Some(bass) = bass else {
539 self.bass_override = None;
540 return;
541 };
542 let known = self
543 .notes
544 .iter()
545 .any(|note| note.pitch.name_with_octave() == bass.name_with_octave());
546 if !known {
547 self.notes.insert(0, Note::from_pitch(bass.clone()));
548 }
549 self.bass_override = Some(bass);
550 }
551
552 pub fn set_inversion(&mut self, inversion: u8) -> Result<()> {
556 self.bass_override = None;
557 let mut runs = self.notes.len() + 2;
558 while self.inversion() != Some(inversion) {
559 if runs == 0 {
560 return Err(Error::Chord(
561 "Could not invert chord: inversion may not exist".to_string(),
562 ));
563 }
564 runs -= 1;
565 let highest_ps = self
566 .notes
567 .iter()
568 .map(|note| note.pitch.ps())
569 .fold(FloatType::NEG_INFINITY, FloatType::max);
570 let Some(bass_name) = self.bass().map(Pitch::name_with_octave) else {
571 return Err(Error::Chord(
572 "Could not invert chord: inversion may not exist".to_string(),
573 ));
574 };
575 let Some(index) = self
576 .notes
577 .iter()
578 .position(|note| note.pitch.name_with_octave() == bass_name)
579 else {
580 return Err(Error::Chord(
581 "Could not invert chord: inversion may not exist".to_string(),
582 ));
583 };
584 while self.notes[index].pitch.ps() < highest_ps {
585 let octave = self.notes[index].pitch.implicit_octave();
586 self.notes[index].pitch.octave_setter(Some(octave + 1));
587 }
588 }
589 *self = self.sort_ascending();
590 Ok(())
591 }
592
593 pub fn closed_position(
598 &self,
599 force_octave: Option<IntegerType>,
600 leave_redundant_pitches: bool,
601 ) -> Self {
602 let mut chord = self.clone();
603 let Some(bass_index) = chord.bass_index() else {
604 return chord;
605 };
606 let implicit_octave = crate::defaults::PITCH_OCTAVE as IntegerType;
607 if let Some(force_octave) = force_octave {
608 let bass_octave = chord.notes[bass_index]
609 .pitch
610 .octave()
611 .unwrap_or(implicit_octave);
612 let shift = force_octave - bass_octave;
613 for note in &mut chord.notes {
614 let octave = note.pitch.octave().unwrap_or(implicit_octave);
615 note.pitch.octave_setter(Some(octave + shift));
616 }
617 }
618 let bass_ps = chord.notes[bass_index].pitch.ps();
619 let bass_number = root::diatonic_note_number(&chord.notes[bass_index].pitch);
620 for note in &mut chord.notes {
621 let mut octave = note.pitch.octave().unwrap_or(implicit_octave);
622 note.pitch.octave_setter(Some(octave));
623 while note.pitch.ps() >= bass_ps + 12.0 {
624 octave -= 1;
625 note.pitch.octave_setter(Some(octave));
626 }
627 if root::diatonic_note_number(¬e.pitch) < bass_number {
628 note.pitch.octave_setter(Some(octave + 1));
629 }
630 }
631 if !leave_redundant_pitches {
632 chord.retain_first_by(spelling_and_octave);
633 }
634 chord.sort_ascending_in_place();
635 chord
636 }
637
638 pub fn remove_redundant_pitches_reporting(&self) -> (Self, Vec<Pitch>) {
641 self.reduced_reporting(spelling_and_octave)
642 }
643
644 pub fn remove_redundant_pitch_names_reporting(&self) -> (Self, Vec<Pitch>) {
647 self.reduced_reporting(Pitch::name)
648 }
649
650 pub fn remove_redundant_pitch_classes_reporting(&self) -> (Self, Vec<Pitch>) {
653 self.reduced_reporting(root::pitch_class)
654 }
655
656 fn reduced_reporting<K: PartialEq>(&self, key: impl Fn(&Pitch) -> K) -> (Self, Vec<Pitch>) {
657 let mut kept = self.clone();
658 let mut seen: Vec<K> = Vec::with_capacity(self.notes.len());
659 let mut removed = Vec::new();
660 kept.notes.retain(|note| {
661 let candidate = key(¬e.pitch);
662 if seen.contains(&candidate) {
663 removed.push(note.pitch.clone());
664 false
665 } else {
666 seen.push(candidate);
667 true
668 }
669 });
670 (kept, removed)
671 }
672
673 pub fn remove_redundant_pitches(&self) -> Self {
678 let mut chord = self.clone();
679 chord.retain_first_by(spelling_and_octave);
680 chord
681 }
682
683 pub fn remove_redundant_pitch_names(&self) -> Self {
686 let mut chord = self.clone();
687 chord.retain_first_by(Pitch::name);
688 chord
689 }
690
691 pub fn remove_redundant_pitch_classes(&self) -> Self {
694 let mut chord = self.clone();
695 chord.retain_first_by(root::pitch_class);
696 chord
697 }
698
699 pub fn sort_ascending(&self) -> Self {
702 let mut chord = self.clone();
703 chord.sort_ascending_in_place();
704 chord
705 }
706
707 pub(crate) fn unique_pitch_names(&self) -> std::collections::BTreeSet<String> {
708 self.pitch_refs().map(Pitch::name).collect()
709 }
710
711 fn bass_index(&self) -> Option<usize> {
712 self.notes
713 .iter()
714 .enumerate()
715 .min_by(|(_, left), (_, right)| {
716 left.pitch
717 .ps()
718 .partial_cmp(&right.pitch.ps())
719 .unwrap_or(std::cmp::Ordering::Equal)
720 })
721 .map(|(index, _)| index)
722 }
723
724 fn retain_first_by<K: PartialEq>(&mut self, key: impl Fn(&Pitch) -> K) {
725 let mut seen: Vec<K> = Vec::with_capacity(self.notes.len());
726 self.notes.retain(|note| {
727 let candidate = key(¬e.pitch);
728 if seen.contains(&candidate) {
729 false
730 } else {
731 seen.push(candidate);
732 true
733 }
734 });
735 }
736
737 fn sort_ascending_in_place(&mut self) {
738 self.notes.sort_by(|left, right| {
739 root::diatonic_note_number(&left.pitch)
740 .cmp(&root::diatonic_note_number(&right.pitch))
741 .then_with(|| {
742 left.pitch
743 .ps()
744 .partial_cmp(&right.pitch.ps())
745 .unwrap_or(std::cmp::Ordering::Equal)
746 })
747 });
748 }
749
750 pub fn transpose(&self, interval: &Interval) -> Result<Self> {
752 let mut chord = self.clone();
753 for note in &mut chord.notes {
754 note.pitch = interval.transpose_pitch(¬e.pitch)?;
755 }
756 if let Some(root) = &chord.root_override {
760 chord.root_override = Some(interval.transpose_pitch(root)?);
761 }
762 if let Some(bass) = &chord.bass_override {
763 chord.bass_override = Some(interval.transpose_pitch(bass)?);
764 }
765 Ok(chord)
766 }
767
768 pub fn scale_degrees(
773 &self,
774 scale: &crate::scale::Scale,
775 ) -> Result<Vec<(Option<usize>, Option<crate::pitch::Accidental>)>> {
776 self.pitch_refs()
777 .map(|pitch| match scale.degree_and_accidental_of(pitch) {
778 Ok((degree, accidental)) => Ok((Some(degree), accidental)),
779 Err(Error::Scale(_)) => Ok((None, None)),
780 Err(error) => Err(error),
781 })
782 .collect()
783 }
784
785 fn bass_pitch(&self) -> Option<&Pitch> {
786 root::bass_pitch(self.pitch_refs())
787 }
788
789 fn find_root_pitch(&self) -> Option<&Pitch> {
790 root::find_root_pitch(self.pitch_refs())
791 }
792
793 fn pitch_refs(&self) -> impl Iterator<Item = &Pitch> {
794 self.notes.iter().map(|note| ¬e.pitch)
795 }
796
797 fn just_ratio_for_semitone(offset: u8) -> (UnsignedIntegerType, UnsignedIntegerType) {
798 const RATIOS: [(UnsignedIntegerType, UnsignedIntegerType); 12] = [
799 (1, 1),
800 (16, 15),
801 (9, 8),
802 (6, 5),
803 (5, 4),
804 (4, 3),
805 (7, 5),
806 (3, 2),
807 (25, 16),
808 (5, 3),
809 (7, 4),
810 (15, 8),
811 ];
812 RATIOS[offset as usize % 12]
813 }
814}
815
816pub trait IntoNotes {
823 const FROM_INTEGER_PITCHES: bool = false;
825
826 type Notes: IntoIterator<Item = Note>;
828
829 fn try_into_notes(self) -> Result<Self::Notes>;
831}
832
833impl<T> IntoNotes for Option<T>
834where
835 T: IntoNotes,
836{
837 const FROM_INTEGER_PITCHES: bool = T::FROM_INTEGER_PITCHES;
838
839 type Notes = Vec<Note>;
840
841 fn try_into_notes(self) -> Result<Self::Notes> {
842 match self {
843 Some(notes) => Ok(notes.try_into_notes()?.into_iter().collect()),
844 None => Ok(Vec::new()),
845 }
846 }
847}
848
849impl<T> IntoNotes for Vec<T>
850where
851 T: IntoNote,
852{
853 const FROM_INTEGER_PITCHES: bool = T::FROM_INTEGER_PITCH;
854
855 type Notes = Vec<Note>;
856
857 fn try_into_notes(self) -> Result<Self::Notes> {
858 let mut notes = self
859 .into_iter()
860 .map(IntoNote::try_into_note)
861 .collect::<Result<Vec<_>>>()?;
862 if Self::FROM_INTEGER_PITCHES {
863 simplify_integer_notes(&mut notes)?;
864 }
865 Ok(notes)
866 }
867}
868
869fn spelling_and_octave(pitch: &Pitch) -> (String, crate::defaults::Octave) {
873 (pitch.name(), pitch.octave())
874}
875
876fn simplify_integer_notes(notes: &mut [Note]) -> Result<()> {
877 if notes.is_empty() {
878 return Ok(());
879 }
880
881 let pitches = notes
882 .iter()
883 .map(|note| note.pitch.clone())
884 .collect::<Vec<_>>();
885 for (note, pitch) in notes
886 .iter_mut()
887 .zip(crate::pitch::simplify_multiple_enharmonics(
888 &pitches, None, None,
889 )?)
890 {
891 note.pitch = pitch;
892 }
893
894 Ok(())
895}
896
897impl IntoNotes for &[Pitch] {
898 type Notes = Vec<Note>;
899
900 fn try_into_notes(self) -> Result<Self::Notes> {
901 Ok(self.iter().cloned().map(Note::from_pitch).collect())
902 }
903}
904
905impl IntoNotes for &[Note] {
906 type Notes = Vec<Note>;
907
908 fn try_into_notes(self) -> Result<Self::Notes> {
909 Ok(self.to_vec())
910 }
911}
912
913impl IntoNotes for &[Chord] {
914 type Notes = Vec<Note>;
915
916 fn try_into_notes(self) -> Result<Self::Notes> {
917 Ok(self.iter().flat_map(|chord| chord.notes.clone()).collect())
918 }
919}
920
921impl IntoNotes for &[String] {
922 type Notes = Vec<Note>;
923
924 fn try_into_notes(self) -> Result<Self::Notes> {
925 self.iter()
926 .map(|name| Note::from_name(name.as_str()))
927 .collect::<Result<Vec<_>>>()
928 }
929}
930
931impl IntoNotes for String {
932 type Notes = Vec<Note>;
933
934 fn try_into_notes(self) -> Result<Self::Notes> {
935 if self.trim().is_empty() {
936 Ok(Vec::new())
937 } else if self.contains(char::is_whitespace) {
938 self.split_whitespace()
939 .collect::<Vec<&str>>()
940 .as_slice()
941 .try_into_notes()
942 } else {
943 Ok(vec![Note::from_name(self)?])
944 }
945 }
946}
947
948impl IntoNotes for &[&str] {
949 type Notes = Vec<Note>;
950
951 fn try_into_notes(self) -> Result<Self::Notes> {
952 let mut vec = vec![];
953 for str in self {
954 vec.append(&mut str.try_into_notes()?);
955 }
956 Ok(vec)
957 }
958}
959
960impl IntoNotes for &str {
961 type Notes = Vec<Note>;
962
963 fn try_into_notes(self) -> Result<Self::Notes> {
964 if self.trim().is_empty() {
965 Ok(Vec::new())
966 } else if self.contains(char::is_whitespace) {
967 self.split_whitespace()
968 .collect::<Vec<&str>>()
969 .try_into_notes()
970 } else {
971 Ok(vec![Note::from_name(self)?])
972 }
973 }
974}
975
976impl IntoNotes for &[IntegerType] {
977 const FROM_INTEGER_PITCHES: bool = true;
978
979 type Notes = Vec<Note>;
980
981 fn try_into_notes(self) -> Result<Self::Notes> {
982 let mut notes = self
983 .iter()
984 .map(|number| Note::from_number(*number as FloatType))
985 .collect::<Result<Vec<_>>>()?;
986 simplify_integer_notes(&mut notes)?;
987 Ok(notes)
988 }
989}
990
991impl Chord {
992 pub fn inversion_text(&self) -> String {
995 match self.inversion() {
996 Some(0) => "Root Position".to_string(),
997 Some(inversion) => format!("{} Inversion", ORDINALS[usize::from(inversion)]),
998 None => "Unknown Position".to_string(),
999 }
1000 }
1001
1002 pub fn semi_closed_position(
1006 &self,
1007 force_octave: Option<IntegerType>,
1008 leave_redundant_pitches: bool,
1009 ) -> Self {
1010 let mut chord = self.closed_position(force_octave, leave_redundant_pitches);
1011 let implicit_octave = crate::defaults::PITCH_OCTAVE as IntegerType;
1012 let mut remaining: Vec<usize> = (0..chord.notes.len()).collect();
1013 while !remaining.is_empty() {
1014 let mut used_steps = Vec::new();
1015 let mut still_clashing = Vec::new();
1016 for index in remaining {
1017 let pitch = &mut chord.notes[index].pitch;
1018 let step = root::diatonic_note_number(pitch).rem_euclid(7);
1019 if used_steps.contains(&step) {
1020 let octave = pitch.octave().unwrap_or(implicit_octave) + 1;
1021 pitch.octave_setter(Some(octave));
1022 still_clashing.push(index);
1023 } else {
1024 used_steps.push(step);
1025 }
1026 }
1027 remaining = still_clashing;
1028 }
1029 chord.sort_ascending_in_place();
1030 chord
1031 }
1032
1033 pub fn sort_chromatic_ascending(&self) -> Self {
1036 let mut chord = self.clone();
1037 chord.notes.sort_by(|left, right| {
1038 left.pitch
1039 .ps()
1040 .partial_cmp(&right.pitch.ps())
1041 .unwrap_or(std::cmp::Ordering::Equal)
1042 });
1043 chord
1044 }
1045
1046 pub fn sort_diatonic_ascending(&self) -> Self {
1050 self.sort_ascending()
1051 }
1052
1053 pub fn sort_frequency_ascending(&self) -> Self {
1055 let mut chord = self.clone();
1056 chord.notes.sort_by(|left, right| {
1057 left.pitch
1058 .frequency_hz()
1059 .partial_cmp(&right.pitch.frequency_hz())
1060 .unwrap_or(std::cmp::Ordering::Equal)
1061 });
1062 chord
1063 }
1064
1065 pub fn pitch_names(&self) -> Vec<String> {
1067 self.notes.iter().map(|note| note.pitch.name()).collect()
1068 }
1069
1070 pub fn note_pitch_classes(&self) -> Vec<u8> {
1074 self.notes
1075 .iter()
1076 .map(|note| root::pitch_class(¬e.pitch))
1077 .collect()
1078 }
1079}
1080
1081#[cfg(test)]
1082mod tests {
1083
1084 #[test]
1085 fn a_chord_indexes_and_iterates_over_its_notes() {
1086 let chord = Chord::new("C4 E4 G4").unwrap();
1087
1088 assert_eq!(chord.len(), 3);
1089 assert!(!chord.is_empty());
1090 assert_eq!(chord[1].pitch.name_with_octave(), "E4");
1091
1092 let borrowed: Vec<String> = (&chord)
1093 .into_iter()
1094 .map(|note| note.pitch.name_with_octave())
1095 .collect();
1096 assert_eq!(borrowed, vec!["C4", "E4", "G4"]);
1097 assert_eq!(chord.into_iter().count(), 3);
1098 }
1099
1100 #[test]
1101 fn forte_and_interval_vector_constructors_match_music21() {
1102 let names = |chord: &Chord| chord.pitch_names();
1103 assert_eq!(
1104 names(&Chord::from_forte_class("3-11").unwrap()),
1105 ["C", "E-", "G"]
1106 );
1107 assert_eq!(
1108 names(&Chord::from_forte_class("3-11B").unwrap()),
1109 ["C", "E", "G"]
1110 );
1111 assert_eq!(
1112 names(&Chord::from_forte_class("3-11a").unwrap()),
1113 ["C", "E-", "G"]
1114 );
1115 assert_eq!(
1116 names(&Chord::from_forte_address(4, 27, Some(-1)).unwrap()),
1117 ["C", "E-", "G-", "A-"]
1118 );
1119 assert_eq!(
1120 Chord::from_forte_class("3-11").unwrap().prime_form_string(),
1121 "<037>"
1122 );
1123 assert!(Chord::from_forte_class("311").is_err());
1124 assert!(Chord::from_forte_class("3-99").is_err());
1125 assert_eq!(
1126 Chord::from_forte_class("4-z15")
1127 .unwrap()
1128 .forte_class()
1129 .as_deref(),
1130 Some("4-15A")
1131 );
1132 assert_eq!(
1133 Chord::from_forte_class("4-Z15").unwrap().pitch_names(),
1134 Chord::from_forte_class("4-15").unwrap().pitch_names()
1135 );
1136
1137 assert_eq!(
1138 names(&Chord::from_interval_vector(&[0, 0, 1, 1, 1, 0], false).unwrap()),
1139 ["C", "E-", "G"]
1140 );
1141 assert_eq!(
1142 names(&Chord::from_interval_vector(&[1, 1, 1, 1, 1, 1], false).unwrap()),
1143 ["C", "C#", "E", "F#"]
1144 );
1145 assert_eq!(
1146 names(&Chord::from_interval_vector(&[1, 1, 1, 1, 1, 1], true).unwrap()),
1147 ["C", "D-", "E-", "G"]
1148 );
1149 assert!(Chord::from_interval_vector(&[9, 9, 9, 9, 9, 9], false).is_none());
1150 }
1151
1152 #[test]
1153 fn geometric_normal_form_matches_music21() {
1154 let cases: [(&str, &[u8]); 9] = [
1155 ("C4 E4 G4", &[0, 3, 8]),
1156 ("E4 G4 C5", &[0, 3, 8]),
1157 ("C4 D-4 E4 G-4", &[0, 1, 4, 6]),
1158 ("C4 E-4 G-4 A4", &[0, 3, 6, 9]),
1159 ("C4", &[0]),
1160 ("C4 C5", &[0]),
1161 ("B3 C4 E4", &[0, 1, 5]),
1162 ("F#4 A4 C5 E-5", &[0, 3, 6, 9]),
1163 ("C4 D4 E4 F4 G4 A4 B4", &[0, 1, 3, 5, 6, 8, 10]),
1164 ];
1165 for (notes, expected) in cases {
1166 assert_eq!(
1167 Chord::new(notes).unwrap().geometric_normal_form(),
1168 expected,
1169 "{notes}"
1170 );
1171 }
1172 assert!(Chord::empty().geometric_normal_form().is_empty());
1173 }
1174
1175 #[test]
1176 #[allow(clippy::type_complexity)]
1177 fn set_class_strings_and_flags_match_music21() {
1178 let cases: [(&str, &str, &str, u8, &str, usize, bool, bool, &str, bool); 14] = [
1179 (
1180 "C4 E4 G4",
1181 "<001110>",
1182 "<047>",
1183 11,
1184 "3-11B",
1185 3,
1186 true,
1187 false,
1188 "Root Position",
1189 false,
1190 ),
1191 (
1192 "C4 E-4 G-4 B--4",
1193 "<004002>",
1194 "<0369>",
1195 28,
1196 "4-28",
1197 4,
1198 false,
1199 false,
1200 "Root Position",
1201 true,
1202 ),
1203 (
1204 "C4 E4 G4 B-4",
1205 "<012111>",
1206 "<47A0>",
1207 27,
1208 "4-27B",
1209 4,
1210 true,
1211 false,
1212 "Root Position",
1213 false,
1214 ),
1215 (
1216 "C3 G3 E4 C5",
1217 "<001110>",
1218 "<047>",
1219 11,
1220 "3-11B",
1221 4,
1222 true,
1223 false,
1224 "Root Position",
1225 false,
1226 ),
1227 (
1228 "E-4 G4 C5",
1229 "<001110>",
1230 "<037>",
1231 11,
1232 "3-11A",
1233 3,
1234 false,
1235 false,
1236 "First Inversion",
1237 false,
1238 ),
1239 (
1240 "C4 D-4 E4 G-4",
1241 "<111111>",
1242 "<0146>",
1243 15,
1244 "4-15A",
1245 4,
1246 false,
1247 true,
1248 "Root Position",
1249 false,
1250 ),
1251 (
1252 "C4 D-4 E-4 G4",
1253 "<111111>",
1254 "<0137>",
1255 29,
1256 "4-29A",
1257 4,
1258 false,
1259 true,
1260 "Root Position",
1261 false,
1262 ),
1263 (
1264 "C4 C#4 D4 E4 F#4 G4 A4 B4",
1265 "<465472>",
1266 "<B0124679>",
1267 23,
1268 "8-23",
1269 8,
1270 false,
1271 false,
1272 "Root Position",
1273 false,
1274 ),
1275 (
1276 "G3 C4 E4",
1277 "<001110>",
1278 "<047>",
1279 11,
1280 "3-11B",
1281 3,
1282 true,
1283 false,
1284 "Second Inversion",
1285 false,
1286 ),
1287 (
1288 "B#3 C4 E4 G-4 F#4",
1289 "<010101>",
1290 "<046>",
1291 8,
1292 "3-8B",
1293 5,
1294 true,
1295 false,
1296 "Third Inversion",
1297 false,
1298 ),
1299 (
1300 "C4 E-4 G-4 A4",
1301 "<004002>",
1302 "<0369>",
1303 28,
1304 "4-28",
1305 4,
1306 false,
1307 false,
1308 "First Inversion",
1309 true,
1310 ),
1311 (
1312 "C4",
1313 "<000000>",
1314 "<0>",
1315 1,
1316 "1-1",
1317 1,
1318 false,
1319 false,
1320 "Root Position",
1321 false,
1322 ),
1323 (
1324 "C4 F4 G4",
1325 "<010020>",
1326 "<570>",
1327 9,
1328 "3-9",
1329 3,
1330 false,
1331 false,
1332 "Second Inversion",
1333 false,
1334 ),
1335 (
1336 "D4 F4 A-4 C-5",
1337 "<004002>",
1338 "<258B>",
1339 28,
1340 "4-28",
1341 4,
1342 false,
1343 false,
1344 "Root Position",
1345 true,
1346 ),
1347 ];
1348 for (
1349 notes,
1350 vector,
1351 normal_order,
1352 forte_number,
1353 forte_tn,
1354 cardinality,
1355 prime_inversion,
1356 z_relation,
1357 inversion_text,
1358 false_diminished,
1359 ) in cases
1360 {
1361 let chord = Chord::new(notes).unwrap();
1362 assert_eq!(chord.interval_vector_string(), vector, "{notes}");
1363 assert_eq!(chord.normal_order_string(), normal_order, "{notes}");
1364 assert_eq!(chord.forte_class_number(), Some(forte_number), "{notes}");
1365 assert_eq!(chord.forte_class_tn().as_deref(), Some(forte_tn), "{notes}");
1366 assert_eq!(chord.multiset_cardinality(), cardinality, "{notes}");
1367 assert_eq!(chord.is_prime_form_inversion(), prime_inversion, "{notes}");
1368 assert_eq!(chord.has_z_relation(), z_relation, "{notes}");
1369 assert_eq!(chord.inversion_text(), inversion_text, "{notes}");
1370 assert_eq!(
1371 chord.is_false_diminished_seventh(),
1372 false_diminished,
1373 "{notes}"
1374 );
1375 }
1376
1377 let empty = Chord::empty();
1378 assert_eq!(empty.interval_vector_string(), "<000000>");
1379 assert_eq!(empty.normal_order_string(), "<>");
1380 assert_eq!(empty.forte_class_number(), None);
1381 assert_eq!(empty.multiset_cardinality(), 0);
1382 assert!(!empty.has_z_relation());
1383 assert_eq!(empty.inversion_text(), "Unknown Position");
1384 }
1385
1386 #[test]
1387 fn z_relations_pair_up_like_music21() {
1388 let z15 = Chord::new("C4 D-4 E4 G-4").unwrap();
1389 let z29 = Chord::new("C4 D-4 E-4 G4").unwrap();
1390 let triad = Chord::new("C E G").unwrap();
1391 assert!(z15.are_z_relations(&z29));
1392 assert!(z29.are_z_relations(&z15));
1393 assert!(!z15.are_z_relations(&triad));
1394 assert!(!triad.are_z_relations(&z15));
1395 }
1396
1397 #[test]
1398 fn interval_from_chord_step_matches_music21() {
1399 let cases: [(&str, Option<&str>, Option<&str>); 9] = [
1400 ("C4 E4 G4", Some("M3"), Some("P5")),
1401 ("C4 E-4 G-4 B--4", Some("m3"), Some("d5")),
1402 ("C3 G3 E4 C5", Some("M10"), Some("P5")),
1403 ("E-4 G4 C5", Some("M6"), Some("P4")),
1404 ("C4 D-4 E4 G-4", Some("M3"), Some("d5")),
1405 ("C4 E-4 G-4 A4", Some("M6"), Some("A4")),
1406 ("A2 C#4 E4 G4", Some("M10"), Some("P12")),
1407 ("C4", None, None),
1408 ("C4 F4 G4", None, Some("P4")),
1409 ];
1410 for (notes, third, fifth) in cases {
1411 let chord = Chord::new(notes).unwrap();
1412 let name = |interval: Option<Interval>| interval.map(|interval| interval.short_name());
1413 assert_eq!(
1414 name(chord.interval_from_chord_step(3)).as_deref(),
1415 third,
1416 "{notes}"
1417 );
1418 assert_eq!(
1419 name(chord.interval_from_chord_step(5)).as_deref(),
1420 fifth,
1421 "{notes}"
1422 );
1423 }
1424 assert!(Chord::empty().interval_from_chord_step(3).is_none());
1425 }
1426
1427 fn octave_names(chord: &Chord) -> Vec<String> {
1428 chord
1429 .pitches()
1430 .iter()
1431 .map(Pitch::name_with_octave)
1432 .collect()
1433 }
1434
1435 #[test]
1436 fn semi_closed_position_matches_music21() {
1437 let cases: [(&str, &[&str], &[&str]); 7] = [
1438 ("C4 E4 G4", &["C4", "E4", "G4"], &["C3", "E3", "G3"]),
1439 ("C3 G3 E4 C5", &["C3", "E3", "G3"], &["C3", "E3", "G3"]),
1440 ("E-4 G4 C5", &["E-4", "G4", "C5"], &["E-3", "G3", "C4"]),
1441 (
1442 "C4 C#4 D4 E4 F#4 G4 A4 B4",
1443 &["C4", "D4", "E4", "F#4", "G4", "A4", "B4", "C#5"],
1444 &["C3", "D3", "E3", "F#3", "G3", "A3", "B3", "C#4"],
1445 ),
1446 ("C4 E4 G4 C5 E5", &["C4", "E4", "G4"], &["C3", "E3", "G3"]),
1447 (
1448 "B#3 C4 E4 G-4 F#4",
1449 &["B#3", "C4", "E4", "F#4", "G-4"],
1450 &["B#3", "C4", "E4", "F#4", "G-4"],
1451 ),
1452 (
1453 "E4 G#4 B4 D5 F5",
1454 &["E4", "F4", "G#4", "B4", "D5"],
1455 &["E3", "F3", "G#3", "B3", "D4"],
1456 ),
1457 ];
1458 for (notes, expected, expected_forced) in cases {
1459 let chord = Chord::new(notes).unwrap();
1460 assert_eq!(
1461 octave_names(&chord.semi_closed_position(None, false)),
1462 expected,
1463 "{notes}"
1464 );
1465 assert_eq!(
1466 octave_names(&chord.semi_closed_position(Some(3), false)),
1467 expected_forced,
1468 "{notes} forced to octave 3"
1469 );
1470 }
1471 }
1472
1473 #[test]
1474 fn sort_variants_match_music21() {
1475 let chord = Chord::new("B#3 C4 E4 G-4 F#4").unwrap();
1476 assert_eq!(
1477 octave_names(&chord.sort_chromatic_ascending()),
1478 ["B#3", "C4", "E4", "G-4", "F#4"]
1479 );
1480 assert_eq!(
1481 octave_names(&chord.sort_diatonic_ascending()),
1482 ["B#3", "C4", "E4", "F#4", "G-4"]
1483 );
1484 assert_eq!(
1485 octave_names(&chord.sort_frequency_ascending()),
1486 ["B#3", "C4", "E4", "G-4", "F#4"]
1487 );
1488 let spread = Chord::new("C5 G3 E4 C3").unwrap();
1489 assert_eq!(
1490 octave_names(&spread.sort_chromatic_ascending()),
1491 ["C3", "G3", "E4", "C5"]
1492 );
1493 assert_eq!(
1494 octave_names(&spread.sort_frequency_ascending()),
1495 ["C3", "G3", "E4", "C5"]
1496 );
1497 assert_eq!(chord.pitch_names(), ["B#", "C", "E", "G-", "F#"]);
1498 }
1499
1500 #[test]
1501 fn full_name_matches_music21() {
1502 let chord = Chord::new("C4 E-4 G-4 B--4").unwrap();
1505 assert_eq!(
1506 chord.full_name(),
1507 "Chord {C in octave 4 | E-flat in octave 4 | G-flat in octave 4 | B-double-flat in octave 4} Quarter"
1508 );
1509 let quarter = chord.clone().with_duration(Duration::quarter());
1510 assert_eq!(
1511 quarter.full_name(),
1512 "Chord {C in octave 4 | E-flat in octave 4 | G-flat in octave 4 | B-double-flat in octave 4} Quarter"
1513 );
1514 let dotted = Chord::new("C4 E4 G4")
1515 .unwrap()
1516 .with_duration(Duration::new(1.5).unwrap());
1517 assert_eq!(
1518 dotted.full_name(),
1519 "Chord {C in octave 4 | E in octave 4 | G in octave 4} Dotted Quarter"
1520 );
1521 }
1522 use crate::{Duration, GuitarTuning, Interval, Key, Pitch, chord::Chord, chord::TriadQuality};
1523
1524 #[test]
1525 fn a_chord_carries_notation_for_all_its_notes() {
1526 use crate::notation::{Beams, Notehead, StemDirection};
1527
1528 let mut chord = Chord::new("C4 E4 G4").unwrap();
1529 assert_eq!(chord.notehead(), Notehead::Normal);
1530 chord.set_notehead(Notehead::Diamond);
1531 assert_eq!(chord.notehead(), Notehead::Diamond);
1532 assert_eq!(chord.notehead_fill(), None);
1533 chord.set_notehead_fill(Some(false));
1534 assert_eq!(chord.notehead_fill(), Some(false));
1535 assert!(!chord.notehead_parenthesis());
1536 chord.set_notehead_parenthesis(true);
1537 assert!(chord.notehead_parenthesis());
1538 chord.set_stem_direction(StemDirection::Down);
1539 assert_eq!(chord.stem_direction(), StemDirection::Down);
1540 assert!(chord.beams().is_empty());
1541 let mut beams = Beams::default();
1542 beams
1543 .fill(crate::duration::DurationType::Eighth, None)
1544 .unwrap();
1545 chord.set_beams(beams.clone());
1546 assert_eq!(chord.beams(), &beams);
1547 }
1548
1549 #[test]
1550 fn notes_are_added_and_removed_as_music21_adds_and_removes_them() {
1551 let mut chord = Chord::new("C4 E4 G4").unwrap();
1552 chord.add("B-4").unwrap();
1553 assert_eq!(chord.pitch_names(), ["C", "E", "G", "B-"]);
1554 chord.remove(&Pitch::from_name("E4").unwrap()).unwrap();
1555 assert!(chord.remove(&Pitch::from_name("E4").unwrap()).is_err());
1556 chord.remove_named("G4").unwrap();
1557 assert!(chord.remove_named("G4").is_err());
1558 assert_eq!(chord.pitch_names(), ["C", "B-"]);
1559 assert_eq!(chord.note_pitch_classes(), [0, 10]);
1560 assert_eq!(
1561 Chord::new("E4 C4 G4 C5").unwrap().note_pitch_classes(),
1562 [4, 0, 7, 0]
1563 );
1564 }
1565
1566 #[test]
1567 fn an_overridden_bass_or_root_is_kept_apart_from_the_inferred_one() {
1568 let mut chord = Chord::new("C4 E4 G4").unwrap();
1569 let e = Pitch::from_name("E4").unwrap();
1570 assert_eq!(chord.overridden_bass(), None);
1571 assert_eq!(chord.found_bass().map(Pitch::name), Some("C".to_string()));
1572 chord.set_bass(Some(e.clone()));
1573 assert_eq!(chord.overridden_bass(), Some(&e));
1574 assert_eq!(chord.bass().map(Pitch::name), Some("E".to_string()));
1575 assert_eq!(chord.found_bass().map(Pitch::name), Some("C".to_string()));
1576 chord.set_bass(None);
1577 assert_eq!(chord.overridden_bass(), None);
1578 chord.set_bass(Some(Pitch::from_name("A3").unwrap()));
1580 assert_eq!(chord.pitch_names(), ["A", "C", "E", "G"]);
1581
1582 let mut chord = Chord::new("C4 E4 G4").unwrap();
1583 assert_eq!(chord.overridden_root(), None);
1584 chord.set_root(Some(e.clone()));
1585 assert_eq!(chord.overridden_root(), Some(&e));
1586 assert_eq!(chord.root().map(Pitch::name), Some("E".to_string()));
1587 assert_eq!(chord.found_root().map(Pitch::name), Some("C".to_string()));
1588 }
1589
1590 #[test]
1591 fn set_inversion_raises_the_bass_until_the_chord_stands_in_it() {
1592 let mut chord = Chord::new("C4 E4 G4").unwrap();
1593 chord.set_inversion(1).unwrap();
1594 assert_eq!(chord.inversion(), Some(1));
1595 assert_eq!(
1596 chord
1597 .pitches()
1598 .iter()
1599 .map(Pitch::name_with_octave)
1600 .collect::<Vec<_>>(),
1601 ["E4", "G4", "C5"]
1602 );
1603 chord.set_inversion(0).unwrap();
1604 assert_eq!(chord.bass().map(Pitch::name), Some("C".to_string()));
1605 assert!(chord.set_inversion(5).is_err());
1606 }
1607
1608 #[test]
1609 fn chord_steps_can_be_measured_from_a_root_the_caller_names() {
1610 let chord = Chord::new("E4 G4 C5").unwrap();
1611 let c = Pitch::from_name("C4").unwrap();
1612 let g = Pitch::from_name("G4").unwrap();
1613 assert_eq!(chord.inversion_from_root(&c), Some(1));
1614 assert_eq!(chord.inversion_from_root(&g), Some(6));
1616 assert_eq!(
1617 chord.chord_step_with_root(3, &c).map(Pitch::name),
1618 Some("E".to_string())
1619 );
1620 assert_eq!(chord.chord_step_with_root(3, &g), None);
1621 assert_eq!(chord.semitones_from_chord_step_with_root(5, &c), Some(7));
1622 assert_eq!(chord.semitones_from_chord_step_with_root(3, &g), None);
1623 }
1624
1625 #[test]
1626 fn the_reductions_report_what_they_dropped() {
1627 let names = |pitches: Vec<Pitch>| -> Vec<String> {
1628 pitches.iter().map(Pitch::name_with_octave).collect()
1629 };
1630 let (kept, dropped) = Chord::new("C4 E4 G4 C4")
1631 .unwrap()
1632 .remove_redundant_pitches_reporting();
1633 assert_eq!(kept.pitch_names(), ["C", "E", "G"]);
1634 assert_eq!(names(dropped), ["C4"]);
1635 let (kept, dropped) = Chord::new("C4 E4 G4 C5")
1636 .unwrap()
1637 .remove_redundant_pitch_names_reporting();
1638 assert_eq!(kept.pitch_names(), ["C", "E", "G"]);
1639 assert_eq!(names(dropped), ["C5"]);
1640 let (kept, dropped) = Chord::new("C4 E4 G4 B#4")
1641 .unwrap()
1642 .remove_redundant_pitch_classes_reporting();
1643 assert_eq!(kept.pitch_names(), ["C", "E", "G"]);
1644 assert_eq!(names(dropped), ["B#4"]);
1645 }
1646
1647 #[test]
1648 fn pitches_that_only_print_alike_are_not_redundant() {
1649 let mut low_b: Pitch = "B".parse().unwrap();
1650 low_b.octave_setter(Some(-1));
1651 let mut b_flat: Pitch = "B-".parse().unwrap();
1652 b_flat.octave_setter(Some(1));
1653 assert_eq!(low_b.name_with_octave(), b_flat.name_with_octave());
1654 let chord = Chord::new(vec![low_b, b_flat]).unwrap();
1655 let (kept, dropped) = chord.remove_redundant_pitches_reporting();
1656 assert_eq!(kept.notes.len(), 2);
1657 assert!(dropped.is_empty());
1658 assert_eq!(chord.remove_redundant_pitches().notes.len(), 2);
1659
1660 let chord = Chord::new("C C4").unwrap();
1661 assert_eq!(chord.remove_redundant_pitches().notes.len(), 2);
1662 assert_eq!(
1663 Chord::new("C4 C4")
1664 .unwrap()
1665 .remove_redundant_pitches()
1666 .notes
1667 .len(),
1668 1
1669 );
1670 }
1671
1672 #[test]
1673 fn the_chord_table_address_is_a_record_and_an_empty_chord_has_one() {
1674 let address = Chord::new("C E G").unwrap().chord_tables_address_entry();
1675 assert_eq!(address.cardinality, 3);
1676 assert_eq!(address.forte_class, 11);
1677 assert_eq!(address.inversion, -1);
1678 assert_eq!(address.pitch_class_original, 0);
1679 let empty = Chord::new("").unwrap().chord_tables_address_entry();
1680 assert_eq!(
1681 (
1682 empty.cardinality,
1683 empty.forte_class,
1684 empty.inversion,
1685 empty.pitch_class_original
1686 ),
1687 (0, 0, 0, 0)
1688 );
1689 assert_eq!(super::format_vector_string(&[0, 0, 1, 1, 1, 0]), "<001110>");
1690 }
1691
1692 #[test]
1693 fn a_chord_is_built_from_any_of_the_inputs_try_from_accepts() {
1694 use crate::note::Note;
1695
1696 let names = |chord: Chord| chord.pitch_names();
1697 assert_eq!(names(Chord::try_from("C E G").unwrap()), ["C", "E", "G"]);
1698 assert_eq!(
1699 names(Chord::try_from("C E G".to_string()).unwrap()),
1700 ["C", "E", "G"]
1701 );
1702 let pitches = [
1703 Pitch::from_name("C4").unwrap(),
1704 Pitch::from_name("E4").unwrap(),
1705 ];
1706 assert_eq!(names(Chord::try_from(&pitches[..]).unwrap()), ["C", "E"]);
1707 let notes = [Note::from_pitch(pitches[0].clone())];
1708 assert_eq!(names(Chord::try_from(¬es[..]).unwrap()), ["C"]);
1709 assert_eq!(names(Chord::try_from(&[60, 64][..]).unwrap()), ["C", "E"]);
1710 assert_eq!(names(Chord::try_from(&["C", "G"][..]).unwrap()), ["C", "G"]);
1711 let owned = ["D".to_string(), "A".to_string()];
1712 assert_eq!(names(Chord::try_from(&owned[..]).unwrap()), ["D", "A"]);
1713 }
1714
1715 #[test]
1716 fn set_duration_applies_to_non_empty_chords() {
1717 for input in ["", "C", "C E G", "C E G B-"] {
1719 let mut chord = Chord::new(input).unwrap();
1720 chord.set_duration(Duration::whole());
1721 assert_eq!(
1722 chord.duration().map(Duration::quarter_length),
1723 Some(4.0),
1724 "set_duration on {input:?}"
1725 );
1726 }
1727 }
1728
1729 struct PredicateCase {
1730 notes: &'static str,
1731 quality: TriadQuality,
1732 flags: [bool; 14],
1733 third: Option<&'static str>,
1734 fifth: Option<&'static str>,
1735 seventh: Option<&'static str>,
1736 enharmonic: bool,
1737 repeated_third: bool,
1738 third_semitones: Option<u8>,
1739 }
1740
1741 #[test]
1742 fn triad_and_seventh_predicates_match_music21() {
1743 use TriadQuality::*;
1744 let t = true;
1745 let f = false;
1746 let cases = [
1747 PredicateCase {
1748 notes: "C E G",
1749 quality: Major,
1750 flags: [t, t, f, f, f, f, f, f, f, t, f, f, t, f],
1751 third: Some("E"),
1752 fifth: Some("G"),
1753 seventh: None,
1754 enharmonic: f,
1755 repeated_third: f,
1756 third_semitones: Some(4),
1757 },
1758 PredicateCase {
1759 notes: "C E- G",
1760 quality: Minor,
1761 flags: [t, f, t, f, f, f, f, f, f, t, f, f, t, f],
1762 third: Some("E-"),
1763 fifth: Some("G"),
1764 seventh: None,
1765 enharmonic: f,
1766 repeated_third: f,
1767 third_semitones: Some(3),
1768 },
1769 PredicateCase {
1770 notes: "C E- G-",
1771 quality: Diminished,
1772 flags: [t, f, f, t, f, f, f, f, f, f, f, f, t, f],
1773 third: Some("E-"),
1774 fifth: Some("G-"),
1775 seventh: None,
1776 enharmonic: f,
1777 repeated_third: f,
1778 third_semitones: Some(3),
1779 },
1780 PredicateCase {
1781 notes: "C E G#",
1782 quality: Augmented,
1783 flags: [t, f, f, f, t, f, f, f, f, f, f, f, t, f],
1784 third: Some("E"),
1785 fifth: Some("G#"),
1786 seventh: None,
1787 enharmonic: f,
1788 repeated_third: f,
1789 third_semitones: Some(4),
1790 },
1791 PredicateCase {
1792 notes: "C4 E4 G4 B-4",
1793 quality: Major,
1794 flags: [f, f, f, f, f, t, t, f, f, f, f, f, t, t],
1795 third: Some("E4"),
1796 fifth: Some("G4"),
1797 seventh: Some("B-4"),
1798 enharmonic: f,
1799 repeated_third: f,
1800 third_semitones: Some(4),
1801 },
1802 PredicateCase {
1803 notes: "C E- G- B--",
1804 quality: Diminished,
1805 flags: [f, f, f, f, f, t, f, f, t, f, f, f, t, t],
1806 third: Some("E-"),
1807 fifth: Some("G-"),
1808 seventh: Some("B--"),
1809 enharmonic: f,
1810 repeated_third: f,
1811 third_semitones: Some(3),
1812 },
1813 PredicateCase {
1814 notes: "C E- G- B-",
1815 quality: Diminished,
1816 flags: [f, f, f, f, f, t, f, t, f, f, f, f, t, t],
1817 third: Some("E-"),
1818 fifth: Some("G-"),
1819 seventh: Some("B-"),
1820 enharmonic: f,
1821 repeated_third: f,
1822 third_semitones: Some(3),
1823 },
1824 PredicateCase {
1825 notes: "C E G B",
1826 quality: Major,
1827 flags: [f, f, f, f, f, t, f, f, f, f, f, f, t, t],
1828 third: Some("E"),
1829 fifth: Some("G"),
1830 seventh: Some("B"),
1831 enharmonic: f,
1832 repeated_third: f,
1833 third_semitones: Some(4),
1834 },
1835 PredicateCase {
1836 notes: "E G C",
1837 quality: Major,
1838 flags: [t, t, f, f, f, f, f, f, f, t, f, f, t, f],
1839 third: Some("E"),
1840 fifth: Some("G"),
1841 seventh: None,
1842 enharmonic: f,
1843 repeated_third: f,
1844 third_semitones: Some(4),
1845 },
1846 PredicateCase {
1847 notes: "G C E",
1848 quality: Major,
1849 flags: [t, t, f, f, f, f, f, f, f, t, f, f, t, f],
1850 third: Some("E"),
1851 fifth: Some("G"),
1852 seventh: None,
1853 enharmonic: f,
1854 repeated_third: f,
1855 third_semitones: Some(4),
1856 },
1857 PredicateCase {
1858 notes: "C E",
1859 quality: Major,
1860 flags: [f, f, f, f, f, f, f, f, f, t, t, f, f, f],
1861 third: Some("E"),
1862 fifth: None,
1863 seventh: None,
1864 enharmonic: f,
1865 repeated_third: f,
1866 third_semitones: Some(4),
1867 },
1868 PredicateCase {
1869 notes: "C E-",
1870 quality: Minor,
1871 flags: [f, f, f, f, f, f, f, f, f, t, f, t, f, f],
1872 third: Some("E-"),
1873 fifth: None,
1874 seventh: None,
1875 enharmonic: f,
1876 repeated_third: f,
1877 third_semitones: Some(3),
1878 },
1879 PredicateCase {
1880 notes: "C G",
1881 quality: Other,
1882 flags: [f, f, f, f, f, f, f, f, f, t, f, f, f, f],
1883 third: None,
1884 fifth: Some("G"),
1885 seventh: None,
1886 enharmonic: f,
1887 repeated_third: f,
1888 third_semitones: None,
1889 },
1890 PredicateCase {
1891 notes: "C F",
1892 quality: Other,
1893 flags: [f, f, f, f, f, f, f, f, f, f, f, f, f, f],
1894 third: None,
1895 fifth: Some("C"),
1896 seventh: None,
1897 enharmonic: f,
1898 repeated_third: f,
1899 third_semitones: None,
1900 },
1901 PredicateCase {
1902 notes: "C4 F4",
1903 quality: Other,
1904 flags: [f, f, f, f, f, f, f, f, f, f, f, f, f, f],
1905 third: None,
1906 fifth: Some("C4"),
1907 seventh: None,
1908 enharmonic: f,
1909 repeated_third: f,
1910 third_semitones: None,
1911 },
1912 PredicateCase {
1913 notes: "C E G C5",
1914 quality: Major,
1915 flags: [t, t, f, f, f, f, f, f, f, t, f, f, t, f],
1916 third: Some("E"),
1917 fifth: Some("G"),
1918 seventh: None,
1919 enharmonic: f,
1920 repeated_third: f,
1921 third_semitones: Some(4),
1922 },
1923 PredicateCase {
1924 notes: "C E E- G",
1925 quality: Other,
1926 flags: [f, f, f, f, f, f, f, f, f, f, f, f, t, f],
1927 third: Some("E"),
1928 fifth: Some("G"),
1929 seventh: None,
1930 enharmonic: f,
1931 repeated_third: t,
1932 third_semitones: Some(4),
1933 },
1934 PredicateCase {
1935 notes: "B# E G",
1936 quality: Other,
1937 flags: [t, f, f, f, f, f, f, f, f, f, f, f, t, f],
1938 third: Some("G"),
1939 fifth: Some("B#"),
1940 seventh: None,
1941 enharmonic: f,
1942 repeated_third: f,
1943 third_semitones: Some(3),
1944 },
1945 PredicateCase {
1946 notes: "C F# G",
1947 quality: Other,
1948 flags: [f, f, f, f, f, f, f, f, f, f, f, f, f, f],
1949 third: None,
1950 fifth: Some("C"),
1951 seventh: None,
1952 enharmonic: f,
1953 repeated_third: f,
1954 third_semitones: None,
1955 },
1956 PredicateCase {
1957 notes: "C E G B- D",
1958 quality: Major,
1959 flags: [f, f, f, f, f, f, f, f, f, f, f, f, t, t],
1960 third: Some("E"),
1961 fifth: Some("G"),
1962 seventh: Some("B-"),
1963 enharmonic: f,
1964 repeated_third: f,
1965 third_semitones: Some(4),
1966 },
1967 PredicateCase {
1968 notes: "C",
1969 quality: Other,
1970 flags: [f, f, f, f, f, f, f, f, f, t, f, f, f, f],
1971 third: None,
1972 fifth: None,
1973 seventh: None,
1974 enharmonic: f,
1975 repeated_third: f,
1976 third_semitones: None,
1977 },
1978 PredicateCase {
1979 notes: "E-4 G4 B-4",
1980 quality: Major,
1981 flags: [t, t, f, f, f, f, f, f, f, t, f, f, t, f],
1982 third: Some("G4"),
1983 fifth: Some("B-4"),
1984 seventh: None,
1985 enharmonic: f,
1986 repeated_third: f,
1987 third_semitones: Some(4),
1988 },
1989 PredicateCase {
1990 notes: "C#4 E4 G4",
1991 quality: Diminished,
1992 flags: [t, f, f, t, f, f, f, f, f, f, f, f, t, f],
1993 third: Some("E4"),
1994 fifth: Some("G4"),
1995 seventh: None,
1996 enharmonic: f,
1997 repeated_third: f,
1998 third_semitones: Some(3),
1999 },
2000 PredicateCase {
2001 notes: "C4 E4 G4 E5",
2002 quality: Major,
2003 flags: [t, t, f, f, f, f, f, f, f, t, f, f, t, f],
2004 third: Some("E4"),
2005 fifth: Some("G4"),
2006 seventh: None,
2007 enharmonic: f,
2008 repeated_third: f,
2009 third_semitones: Some(4),
2010 },
2011 PredicateCase {
2012 notes: "C#4 D-4 E4",
2013 quality: Minor,
2014 flags: [f, f, f, f, f, f, f, f, f, f, f, t, f, f],
2015 third: Some("E4"),
2016 fifth: None,
2017 seventh: None,
2018 enharmonic: t,
2019 repeated_third: f,
2020 third_semitones: Some(3),
2021 },
2022 ];
2023 for case in cases {
2024 let chord = Chord::new(case.notes).unwrap();
2025 let notes = case.notes;
2026 let name = |pitch: Option<&Pitch>| pitch.map(Pitch::name_with_octave);
2027 assert_eq!(chord.quality(), case.quality, "{notes} quality");
2028 let actual = [
2029 chord.is_triad(),
2030 chord.is_major_triad(),
2031 chord.is_minor_triad(),
2032 chord.is_diminished_triad(),
2033 chord.is_augmented_triad(),
2034 chord.is_seventh(),
2035 chord.is_dominant_seventh(),
2036 chord.is_half_diminished_seventh(),
2037 chord.is_diminished_seventh(),
2038 chord.is_consonant(),
2039 chord.is_incomplete_major_triad(),
2040 chord.is_incomplete_minor_triad(),
2041 chord.contains_triad(),
2042 chord.contains_seventh(),
2043 ];
2044 assert_eq!(actual, case.flags, "{notes} predicates");
2045 assert_eq!(name(chord.third()).as_deref(), case.third, "{notes} third");
2046 assert_eq!(name(chord.fifth()).as_deref(), case.fifth, "{notes} fifth");
2047 assert_eq!(
2048 name(chord.seventh()).as_deref(),
2049 case.seventh,
2050 "{notes} seventh"
2051 );
2052 assert_eq!(
2053 chord.has_any_enharmonic_spelled_pitches(),
2054 case.enharmonic,
2055 "{notes} enharmonic"
2056 );
2057 assert_eq!(
2058 chord.has_repeated_chord_step(3),
2059 case.repeated_third,
2060 "{notes} repeated third"
2061 );
2062 assert_eq!(
2063 chord.semitones_from_chord_step(3),
2064 case.third_semitones,
2065 "{notes} third semitones"
2066 );
2067 }
2068
2069 let empty = Chord::empty();
2070 assert_eq!(empty.quality(), TriadQuality::Other);
2071 assert!(!empty.is_triad());
2072 assert!(!empty.is_consonant());
2073 assert!(empty.third().is_none());
2074 assert!(!empty.contains_triad());
2075 assert_eq!(TriadQuality::Diminished.to_string(), "diminished");
2076 }
2077
2078 #[test]
2079 fn consonance_of_dyads_follows_closed_position() {
2080 assert!(Chord::new("C4 C5 E5").unwrap().is_consonant());
2081 assert!(!Chord::new("C4 F4 C5").unwrap().is_consonant());
2082 assert!(Chord::new("F4 C5").unwrap().is_consonant());
2083 assert!(!Chord::new("C4 G3").unwrap().is_consonant());
2084 }
2085
2086 #[test]
2087 fn closed_position_matches_music21() {
2088 let names = |chord: Chord| {
2089 chord
2090 .pitches()
2091 .iter()
2092 .map(Pitch::name_with_octave)
2093 .collect::<Vec<_>>()
2094 };
2095 let cases = [
2096 ("C#4 G5 E6", None, vec!["C#4", "E4", "G4"]),
2097 ("C#4 G5 E6", Some(2), vec!["C#2", "E2", "G2"]),
2098 ("C#4 G5 E6", Some(6), vec!["C#6", "E6", "G6"]),
2099 ("C#4 F4 C5 F5", None, vec!["C#4", "F4", "C5"]),
2100 ("A B", None, vec!["A4", "B4"]),
2101 ("C4 B#7", None, vec!["C4", "B#4"]),
2102 ("E4 C5 G5", None, vec!["E4", "G4", "C5"]),
2103 (
2104 "C3 C#3 E-3 E3 E#3 G3",
2105 None,
2106 vec!["C3", "C#3", "E-3", "E3", "E#3", "G3"],
2107 ),
2108 ("G4 C4 E4", Some(5), vec!["C5", "E5", "G5"]),
2109 ("C4 E4 G4 C5 E5", None, vec!["C4", "E4", "G4"]),
2110 ("C#4 D-4 E4", None, vec!["C#4", "D-4", "E4"]),
2111 ];
2112 for (notes, force_octave, expected) in cases {
2113 let chord = Chord::new(notes).unwrap();
2114 assert_eq!(
2115 names(chord.closed_position(force_octave, false)),
2116 expected,
2117 "{notes}"
2118 );
2119 }
2120 assert!(
2121 Chord::empty()
2122 .closed_position(None, false)
2123 .notes()
2124 .is_empty()
2125 );
2126 assert_eq!(
2127 names(
2128 Chord::new("C4 E4 C4 E5")
2129 .unwrap()
2130 .remove_redundant_pitches()
2131 ),
2132 vec!["C4", "E4", "E5"]
2133 );
2134 assert_eq!(
2135 names(
2136 Chord::new("C4 E4 C5 E5")
2137 .unwrap()
2138 .remove_redundant_pitch_names()
2139 ),
2140 vec!["C4", "E4"]
2141 );
2142 assert_eq!(
2143 names(
2144 Chord::new("C#4 D-4 E4")
2145 .unwrap()
2146 .remove_redundant_pitch_classes()
2147 ),
2148 vec!["C#4", "E4"]
2149 );
2150 assert_eq!(
2151 names(Chord::new("G-4 F##4 E4").unwrap().sort_ascending()),
2152 vec!["E4", "F##4", "G-4"]
2153 );
2154 }
2155
2156 #[test]
2157 fn inversions_match_music21() {
2158 let cases = [
2159 ("C E G", 0, "C", "C"),
2160 ("E G C", 0, "C", "C"),
2161 ("G C E", 0, "C", "C"),
2162 ("C4 E4 G4 B-4", 0, "C4", "C4"),
2163 ("E4 G4 B-4 C5", 1, "C5", "E4"),
2164 ("B-3 C4 E4 G4", 3, "C4", "B-3"),
2165 ("G3 C4 E4 B-4", 2, "C4", "G3"),
2166 ("A-4 C5 F#5", 1, "F#5", "A-4"),
2167 ("C5 F#5 A-5", 2, "F#5", "C5"),
2168 ("F#4 A-4 C5", 0, "F#4", "F#4"),
2169 ("C F G", 2, "F", "C"),
2170 ("C4 G4 E5", 0, "C4", "C4"),
2171 ("C", 0, "C", "C"),
2172 ("G C", 0, "C", "C"),
2173 ("E C", 0, "C", "C"),
2174 ("F A C E", 2, "F", "C"),
2175 ("E4 C5 G5", 1, "C5", "E4"),
2176 ("C E G A", 1, "A", "C"),
2177 ("C F# G", 2, "F#", "C"),
2178 ("D4 F#4 A4 C5 E5", 0, "D4", "D4"),
2179 ("A-3 C4 E-4 F#4", 1, "F#4", "A-3"),
2180 ("C4 A-4 E-5 F#5", 2, "F#5", "C4"),
2181 ("E3 G3 B-3 D-4", 0, "E3", "E3"),
2182 ];
2183 for (notes, inversion, root, bass) in cases {
2184 let chord = Chord::new(notes).unwrap();
2185 assert_eq!(chord.inversion(), Some(inversion), "{notes} inversion");
2186 assert_eq!(
2187 chord.root().map(Pitch::name_with_octave).as_deref(),
2188 Some(root),
2189 "{notes} root"
2190 );
2191 assert_eq!(
2192 chord.bass().map(Pitch::name_with_octave).as_deref(),
2193 Some(bass),
2194 "{notes} bass"
2195 );
2196 }
2197 assert_eq!(Chord::empty().inversion(), None);
2198 assert_eq!(
2199 Chord::new("B#3 C4 E4")
2200 .unwrap()
2201 .bass()
2202 .unwrap()
2203 .name_with_octave(),
2204 "B#3"
2205 );
2206 }
2207
2208 #[test]
2209 fn augmented_sixths_and_set_class_helpers_match_music21() {
2210 struct Case {
2211 notes: &'static str,
2212 flags: [bool; 11],
2213 prime_form: &'static str,
2214 forte_tni: &'static str,
2215 cardinality: usize,
2216 ordered: &'static str,
2217 }
2218 let t = true;
2219 let f = false;
2220 let cases = [
2221 Case {
2222 notes: "A-4 C5 F#5",
2223 flags: [t, t, f, f, f, t, t, f, f, f, f],
2224 prime_form: "<026>",
2225 forte_tni: "3-8",
2226 cardinality: 3,
2227 ordered: "<068>",
2228 },
2229 Case {
2230 notes: "A-4 C5 D5 F#5",
2231 flags: [t, f, t, f, f, t, f, f, t, f, f],
2232 prime_form: "<0268>",
2233 forte_tni: "4-25",
2234 cardinality: 4,
2235 ordered: "<0268>",
2236 },
2237 Case {
2238 notes: "A-4 C5 E-5 F#5",
2239 flags: [t, f, f, t, f, t, f, f, f, f, f],
2240 prime_form: "<0258>",
2241 forte_tni: "4-27",
2242 cardinality: 4,
2243 ordered: "<0368>",
2244 },
2245 Case {
2246 notes: "A-4 C5 D#5 F#5",
2247 flags: [t, f, f, f, t, t, f, f, f, f, f],
2248 prime_form: "<0258>",
2249 forte_tni: "4-27",
2250 cardinality: 4,
2251 ordered: "<0368>",
2252 },
2253 Case {
2254 notes: "C4 E4 G4",
2255 flags: [f, f, f, f, f, f, f, f, f, t, t],
2256 prime_form: "<037>",
2257 forte_tni: "3-11",
2258 cardinality: 3,
2259 ordered: "<047>",
2260 },
2261 Case {
2262 notes: "F#4 A-4 C5",
2263 flags: [f, f, f, f, f, t, t, f, f, f, f],
2264 prime_form: "<026>",
2265 forte_tni: "3-8",
2266 cardinality: 3,
2267 ordered: "<068>",
2268 },
2269 Case {
2270 notes: "C5 F#5 A-5",
2271 flags: [f, f, f, f, f, t, t, f, f, f, f],
2272 prime_form: "<026>",
2273 forte_tni: "3-8",
2274 cardinality: 3,
2275 ordered: "<068>",
2276 },
2277 Case {
2278 notes: "A-4 F#5 C6",
2279 flags: [t, t, f, f, f, t, t, f, f, f, f],
2280 prime_form: "<026>",
2281 forte_tni: "3-8",
2282 cardinality: 3,
2283 ordered: "<068>",
2284 },
2285 Case {
2286 notes: "A-4 C5 E-5 F#5 A-5",
2287 flags: [t, f, f, t, f, t, f, f, f, f, f],
2288 prime_form: "<0258>",
2289 forte_tni: "4-27",
2290 cardinality: 4,
2291 ordered: "<0368>",
2292 },
2293 Case {
2294 notes: "C E G B- D",
2295 flags: [f, f, f, f, f, f, f, t, f, f, f],
2296 prime_form: "<02469>",
2297 forte_tni: "5-34",
2298 cardinality: 5,
2299 ordered: "<0247A>",
2300 },
2301 Case {
2302 notes: "C E G B D F",
2303 flags: [f, f, f, f, f, f, f, f, f, f, f],
2304 prime_form: "<013568>",
2305 forte_tni: "6-25",
2306 cardinality: 6,
2307 ordered: "<02457B>",
2308 },
2309 Case {
2310 notes: "C E G#",
2311 flags: [f, f, f, f, f, f, f, f, t, f, f],
2312 prime_form: "<048>",
2313 forte_tni: "3-12",
2314 cardinality: 3,
2315 ordered: "<048>",
2316 },
2317 Case {
2318 notes: "C E- G- B--",
2319 flags: [f, f, f, f, f, f, f, f, t, f, f],
2320 prime_form: "<0369>",
2321 forte_tni: "4-28",
2322 cardinality: 4,
2323 ordered: "<0369>",
2324 },
2325 Case {
2326 notes: "C D E F# G# A#",
2327 flags: [f, f, f, f, f, f, f, f, t, f, f],
2328 prime_form: "<02468A>",
2329 forte_tni: "6-35",
2330 cardinality: 6,
2331 ordered: "<02468A>",
2332 },
2333 Case {
2334 notes: "C C# D D# E F F# G G# A A# B",
2335 flags: [f, f, f, f, f, f, f, f, t, f, f],
2336 prime_form: "<0123456789AB>",
2337 forte_tni: "12-1",
2338 cardinality: 12,
2339 ordered: "<0123456789AB>",
2340 },
2341 Case {
2342 notes: "C",
2343 flags: [f, f, f, f, f, f, f, f, f, f, f],
2344 prime_form: "<0>",
2345 forte_tni: "1-1",
2346 cardinality: 1,
2347 ordered: "<0>",
2348 },
2349 Case {
2350 notes: "B- D F A-",
2351 flags: [f, f, f, f, f, f, f, f, f, t, f],
2352 prime_form: "<0258>",
2353 forte_tni: "4-27",
2354 cardinality: 4,
2355 ordered: "<258A>",
2356 },
2357 Case {
2358 notes: "C F#",
2359 flags: [f, f, f, f, f, f, f, f, t, f, f],
2360 prime_form: "<06>",
2361 forte_tni: "2-6",
2362 cardinality: 2,
2363 ordered: "<06>",
2364 },
2365 ];
2366 for case in cases {
2367 let chord = Chord::new(case.notes).unwrap();
2368 let notes = case.notes;
2369 let actual = [
2370 chord.is_augmented_sixth(false),
2371 chord.is_italian_augmented_sixth(false, false),
2372 chord.is_french_augmented_sixth(false),
2373 chord.is_german_augmented_sixth(false),
2374 chord.is_swiss_augmented_sixth(false),
2375 chord.is_augmented_sixth(true),
2376 chord.is_italian_augmented_sixth(true, false),
2377 chord.is_ninth(),
2378 chord.is_transpositionally_symmetrical(false),
2379 chord.can_be_dominant_v(),
2380 chord.can_be_tonic(),
2381 ];
2382 assert_eq!(actual, case.flags, "{notes}");
2383 assert_eq!(
2384 chord.prime_form_string(),
2385 case.prime_form,
2386 "{notes} prime form"
2387 );
2388 assert_eq!(
2389 chord.forte_class_tni().as_deref(),
2390 Some(case.forte_tni),
2391 "{notes} forte tni"
2392 );
2393 assert_eq!(chord.pitch_class_cardinality(), case.cardinality, "{notes}");
2394 assert_eq!(
2395 chord.ordered_pitch_classes_string(),
2396 case.ordered,
2397 "{notes}"
2398 );
2399 }
2400
2401 assert!(
2402 Chord::new("C")
2403 .unwrap()
2404 .is_transpositionally_symmetrical(true)
2405 );
2406 assert!(
2407 !Chord::new("C D-")
2408 .unwrap()
2409 .is_transpositionally_symmetrical(false)
2410 );
2411 assert!(
2412 Chord::new("A-4 C5 D5 F#5")
2413 .unwrap()
2414 .is_transpositionally_symmetrical(false)
2415 );
2416 assert!(
2417 !Chord::new("A-4 C5 D5 F#5")
2418 .unwrap()
2419 .is_transpositionally_symmetrical(true)
2420 );
2421 assert!(
2422 Chord::new("C C# D D# E F F# G G# A A# B")
2423 .unwrap()
2424 .has_any_repeated_diatonic_note()
2425 );
2426 assert!(
2427 !Chord::new("C E G")
2428 .unwrap()
2429 .has_any_repeated_diatonic_note()
2430 );
2431
2432 let empty = Chord::empty();
2433 assert!(empty.is_transpositionally_symmetrical(false));
2434 assert!(empty.prime_form().is_empty());
2435 assert_eq!(empty.prime_form_string(), "<>");
2436 assert_eq!(empty.forte_class_tni(), None);
2437 assert_eq!(empty.pitch_class_cardinality(), 0);
2438 assert_eq!(empty.ordered_pitch_classes_string(), "<>");
2439 }
2440
2441 #[test]
2442 fn transpose_moves_every_note() {
2443 let names = |chord: Chord| {
2444 chord
2445 .pitches()
2446 .iter()
2447 .map(Pitch::name_with_octave)
2448 .collect::<Vec<_>>()
2449 };
2450 let chord = Chord::new("C4 E4 G4").unwrap();
2451 let up = |name: &str| {
2452 chord
2453 .transpose(&Interval::from_name(name).unwrap())
2454 .unwrap()
2455 };
2456 assert_eq!(names(up("M2")), vec!["D4", "F#4", "A4"]);
2457 assert_eq!(names(up("-m3")), vec!["A3", "C#4", "E4"]);
2458 assert_eq!(
2459 names(
2460 chord
2461 .transpose(&Interval::from_semitones(3).unwrap())
2462 .unwrap()
2463 ),
2464 vec!["E-4", "G4", "B-4"]
2465 );
2466 let bare = Chord::new("C E G").unwrap();
2467 assert_eq!(
2468 names(bare.transpose(&Interval::from_name("P5").unwrap()).unwrap()),
2469 vec!["G", "B", "D"]
2470 );
2471 }
2472
2473 #[test]
2474 fn scale_degrees_match_music21() {
2475 let cases = [
2476 (
2477 "C",
2478 "C E G",
2479 vec![(Some(1), None), (Some(3), None), (Some(5), None)],
2480 ),
2481 (
2482 "C",
2483 "C E- G",
2484 vec![(Some(1), None), (Some(3), Some("flat")), (Some(5), None)],
2485 ),
2486 (
2487 "F",
2488 "F A C E",
2489 vec![
2490 (Some(1), None),
2491 (Some(3), None),
2492 (Some(5), None),
2493 (Some(7), None),
2494 ],
2495 ),
2496 (
2497 "a",
2498 "G# B D",
2499 vec![(Some(7), Some("sharp")), (Some(2), None), (Some(4), None)],
2500 ),
2501 (
2502 "B-",
2503 "E- G B-",
2504 vec![(Some(4), None), (Some(6), None), (Some(1), None)],
2505 ),
2506 (
2507 "D",
2508 "C# E G B-",
2509 vec![
2510 (Some(7), None),
2511 (Some(2), None),
2512 (Some(4), None),
2513 (Some(6), Some("flat")),
2514 ],
2515 ),
2516 (
2517 "C",
2518 "C E G B- D-",
2519 vec![
2520 (Some(1), None),
2521 (Some(3), None),
2522 (Some(5), None),
2523 (Some(7), Some("flat")),
2524 (Some(2), Some("flat")),
2525 ],
2526 ),
2527 ];
2528 for (key, notes, expected) in cases {
2529 let scale = Key::from_tonic(key).unwrap().as_scale().unwrap();
2530 let degrees = Chord::new(notes)
2531 .unwrap()
2532 .scale_degrees(&scale)
2533 .unwrap()
2534 .into_iter()
2535 .map(|(degree, accidental)| (degree, accidental.map(|a| a.name().to_string())))
2536 .collect::<Vec<_>>();
2537 let expected = expected
2538 .into_iter()
2539 .map(|(degree, accidental): (Option<usize>, Option<&str>)| {
2540 (degree, accidental.map(str::to_string))
2541 })
2542 .collect::<Vec<_>>();
2543 assert_eq!(degrees, expected, "{key} {notes}");
2544 }
2545 }
2546
2547 #[test]
2548 fn pitched_common_names_match_the_music21_reference() {
2549 let cases = [
2550 ("C E G", "C-major triad"),
2551 ("C E- G", "C-minor triad"),
2552 ("C E G B-", "C-dominant seventh chord"),
2553 ("C E G B", "C-major seventh chord"),
2554 ("C E- G B-", "C-minor seventh chord"),
2555 ("C E- G- B-", "C-half-diminished seventh chord"),
2556 ("C E- G- B--", "C-diminished seventh chord"),
2557 ("C E G B- D", "C-dominant-ninth"),
2558 ("C E G B D", "C-major-ninth chord"),
2559 ("C E- G B- D", "C-minor-ninth chord"),
2560 ("G2 B2 D3 F3", "G-dominant seventh chord"),
2561 ("B2 D3 F3 A3", "B-half-diminished seventh chord"),
2562 ];
2563 for (notes, expected) in cases {
2564 let chord = Chord::new(notes).unwrap();
2565 assert_eq!(chord.pitched_common_name(), expected, "{notes}");
2566 }
2567
2568 let integers: &[crate::IntegerType] = &[1, 2, 3, 4, 5, 10];
2569 let chord = Chord::new(integers).unwrap();
2570 assert_eq!(chord.pitched_common_name(), "forte class 6-36B above C#");
2571 }
2572
2573 #[test]
2574 fn c_e_g_pitchedcommonname() {
2575 let chord = Chord::new("C E G");
2576
2577 assert!(chord.is_ok());
2578
2579 assert_eq!(chord.unwrap().pitched_common_name(), "C-major triad");
2580 }
2581
2582 #[test]
2583 fn new_accepts_empty_inputs() {
2584 assert_eq!(Chord::new("").unwrap().pitched_common_name(), "empty chord");
2585 assert_eq!(
2586 Chord::new(Vec::<Pitch>::new())
2587 .unwrap()
2588 .pitched_common_name(),
2589 "empty chord"
2590 );
2591 assert_eq!(
2592 Chord::new(Option::<&str>::None)
2593 .unwrap()
2594 .pitched_common_name(),
2595 "empty chord"
2596 );
2597 }
2598
2599 #[test]
2600 fn pitched_common_names_returns_aliases() {
2601 let chord = Chord::new("C E G#").unwrap();
2602 assert_eq!(
2603 chord.pitched_common_names(),
2604 vec![
2605 "C-augmented triad".to_string(),
2606 "C-equal 3-part octave division".to_string()
2607 ]
2608 );
2609 }
2610
2611 #[test]
2612 fn chord_symbols_return_symbol_names() {
2613 let major_seventh = Chord::new("C E G B").unwrap();
2614 let petrushka = Chord::new("C4 D4 Eb4 F#4 Ab4 A4").unwrap();
2615 let slash_chord = Chord::new("F4 C5 D5 E-5").unwrap();
2616
2617 assert_eq!(major_seventh.chord_symbol().as_deref(), Some("Cmaj7"));
2618 assert_eq!(
2619 petrushka.chord_symbol().as_deref(),
2620 Some("Ddom7dim5/CaddA,E-")
2621 );
2622 assert_eq!(slash_chord.chord_symbol().as_deref(), None);
2623 }
2624
2625 #[test]
2626 fn chord_symbols_with_root_accept_pitch_names() {
2627 let chord = Chord::new("G3 C4 E4").unwrap();
2628
2629 assert_eq!(
2630 chord.chord_symbol_with_root("C").unwrap().as_deref(),
2631 Some("C/G")
2632 );
2633 assert_eq!(
2634 chord.chord_symbol_with_root(0).unwrap().as_deref(),
2635 Some("C/G")
2636 );
2637 }
2638
2639 #[test]
2640 fn guitar_fingering_covers_common_chord_tones() {
2641 let chord = Chord::new("C E G").unwrap();
2642 let fingering = chord.guitar_fingering().unwrap();
2643
2644 assert_eq!(fingering.strings.len(), 6);
2645 assert_eq!(fingering.covered_pitch_classes, vec![0, 4, 7]);
2648 assert!(fingering.omitted_pitch_classes.is_empty());
2649 assert!(
2650 fingering.covered_pitch_spaces.len() >= 3,
2651 "expected a full voicing, got {:?}",
2652 fingering.covered_pitch_spaces
2653 );
2654 assert!(
2655 fingering
2656 .strings
2657 .iter()
2658 .filter(|string| string.fret.is_some_and(|fret| fret > 0))
2659 .all(|string| string
2660 .finger
2661 .is_some_and(|finger| (1..=4).contains(&finger)))
2662 );
2663 }
2664
2665 #[test]
2666 fn guitar_fingering_still_returns_large_pitch_sets() {
2667 let chord = Chord::new("C D E F G A B").unwrap();
2668 let fingering = chord.guitar_fingering().unwrap();
2669
2670 assert_eq!(fingering.strings.len(), 6);
2671 assert!(!fingering.covered_pitch_classes.is_empty());
2672 assert!(!fingering.omitted_pitch_classes.is_empty());
2673 }
2674
2675 #[test]
2676 fn guitar_fingering_uses_supplied_tuning_and_octaves() {
2677 let chord = Chord::new("D3 A3 D4").unwrap();
2678 let tuning = GuitarTuning::new(["D2", "A2", "D3", "G3", "A3", "D4"]).unwrap();
2679 let fingering = chord.guitar_fingering_with_tuning(&tuning).unwrap();
2680
2681 assert_eq!(fingering.strings.len(), 6);
2682 assert_eq!(fingering.strings[0].string_name, "D2");
2683 assert_eq!(fingering.covered_pitch_classes, vec![2, 9]);
2684 assert!(fingering.omitted_pitch_classes.is_empty());
2685 }
2686
2687 fn shape(notes: &str) -> String {
2689 Chord::new(notes)
2690 .unwrap()
2691 .guitar_fingering()
2692 .unwrap()
2693 .strings
2694 .iter()
2695 .map(|string| match string.fret {
2696 None => "x".to_string(),
2697 Some(fret) => fret.to_string(),
2698 })
2699 .collect::<Vec<_>>()
2700 .join(" ")
2701 }
2702
2703 #[test]
2704 fn guitar_fingering_finds_the_standard_open_chords() {
2705 assert_eq!(shape("C E G"), "x 3 2 0 1 0");
2708 assert_eq!(shape("A C# E"), "x 0 2 2 2 0");
2709 assert_eq!(shape("E G# B"), "0 2 2 1 0 0");
2710 assert_eq!(shape("D F# A"), "x x 0 2 3 2");
2711 assert_eq!(shape("A C E"), "x 0 2 2 1 0");
2712 assert_eq!(shape("E G B"), "0 2 2 0 0 0");
2713 assert_eq!(shape("D F A"), "x x 0 2 3 1");
2714 assert_eq!(shape("G B D F"), "3 2 0 0 0 1");
2715 assert_eq!(shape("C E G B"), "x 3 2 0 0 0");
2716 assert_eq!(shape("A C E G"), "x 0 2 0 1 0");
2717 }
2718
2719 #[test]
2720 fn guitar_fingering_keeps_every_chord_tone() {
2721 for notes in ["G B D F", "C E G B-", "A C E G", "C E G B", "B D F"] {
2724 let fingering = Chord::new(notes).unwrap().guitar_fingering().unwrap();
2725 assert!(
2726 fingering.omitted_pitch_classes.is_empty(),
2727 "{notes} dropped {:?}",
2728 fingering.omitted_pitch_classes
2729 );
2730 }
2731 }
2732
2733 #[test]
2734 fn guitar_fingering_puts_the_root_in_the_bass_for_open_chords() {
2735 for (notes, root) in [("C E G", 0), ("G B D", 7), ("E G# B", 4), ("A C E", 9)] {
2736 let fingering = Chord::new(notes).unwrap().guitar_fingering().unwrap();
2737 let bass = fingering
2738 .strings
2739 .iter()
2740 .find_map(|string| string.fret.and(string.pitch_class))
2741 .expect("a sounding string");
2742 assert_eq!(bass, root, "{notes} should sound its root lowest");
2743 }
2744 }
2745
2746 #[test]
2747 fn guitar_tuning_rejects_empty_tunings() {
2748 assert!(GuitarTuning::new(Vec::<&str>::new()).is_err());
2749 }
2750
2751 #[test]
2752 fn dyad_names_follow_music21_interval_rules() {
2753 let pcs = [0, 1];
2754 let integer_chord = Chord::new(pcs.as_slice()).unwrap();
2755 assert_eq!(integer_chord.common_name(), "Minor Second");
2756 assert_eq!(integer_chord.pitched_common_name(), "Minor Second above C");
2757
2758 let spelled_chord = Chord::new("C C#").unwrap();
2759 assert_eq!(spelled_chord.common_name(), "Augmented Unison");
2760 assert_eq!(
2761 spelled_chord.pitched_common_name(),
2762 "Augmented Unison above C"
2763 );
2764
2765 let octave = Chord::new("D3 D4").unwrap();
2766 assert_eq!(octave.common_name(), "Perfect Octave");
2767 assert_eq!(octave.pitched_common_name(), "Perfect Octave above D");
2768
2769 let compound = Chord::new("E-3 C5 C6").unwrap();
2770 assert_eq!(compound.common_name(), "Major Sixth with octave doublings");
2771 assert_eq!(
2772 compound.pitched_common_name(),
2773 "Major Sixth with octave doublings above Eb"
2774 );
2775 }
2776
2777 #[test]
2778 fn chord_metadata_methods_have_forte_and_inversion() {
2779 let chord = Chord::new("C E G").unwrap();
2780 assert_eq!(chord.root_pitch_name().as_deref(), Some("C"));
2781 assert_eq!(chord.bass_pitch_name().as_deref(), Some("C"));
2782 assert_eq!(chord.inversion(), Some(0));
2783 assert_eq!(chord.inversion_name().unwrap(), Some(53));
2784 assert_eq!(chord.inversion_text(), "Root Position");
2785 assert_eq!(chord.forte_class().as_deref(), Some("3-11B"));
2786 assert_eq!(chord.interval_class_vector(), Some(vec![0, 0, 1, 1, 1, 0]));
2787 assert!(chord.invariance_vector().is_some());
2788 assert_eq!(chord.z_relation(), None);
2789 assert!(
2790 chord
2791 .common_names()
2792 .iter()
2793 .any(|name| name == "major triad")
2794 );
2795 }
2796
2797 #[test]
2798 fn chord_simplifies_enharmonics_explicitly() {
2799 let chord = Chord::new("D# F## A#").unwrap();
2800 let simplified = chord.simplify_enharmonics(None).unwrap();
2801 assert_eq!(chord.pitches()[0].name(), "D#");
2802 assert_eq!(simplified.pitches().len(), chord.pitches().len());
2803
2804 let mut in_place = chord.clone();
2805 in_place.simplify_enharmonics_in_place(None).unwrap();
2806 assert_eq!(
2807 simplified
2808 .pitches()
2809 .into_iter()
2810 .map(|pitch| pitch.name_with_octave())
2811 .collect::<Vec<_>>(),
2812 in_place
2813 .pitches()
2814 .into_iter()
2815 .map(|pitch| pitch.name_with_octave())
2816 .collect::<Vec<_>>()
2817 );
2818 }
2819
2820 #[test]
2821 fn chord_maps_to_reduced_polyrhythm_components() {
2822 let major = Chord::new("C E G").unwrap();
2823 assert_eq!(major.polyrhythm_components(), vec![4, 5, 6]);
2824 assert_eq!(major.polyrhythm_ratio_string(), "4:5:6");
2825
2826 let empty = Chord::empty();
2827 assert_eq!(empty.polyrhythm_ratio_string(), "1");
2828 }
2829
2830 #[test]
2831 fn new_rejects_invalid_pitch_inputs() {
2832 assert!(Chord::new("C nope G").is_err());
2833 }
2834
2835 #[test]
2836 fn chord_supports_rust_conversion_traits() {
2837 let parsed: Chord = "C E G".parse().unwrap();
2838 assert_eq!(parsed.to_string(), "C-major triad");
2839 assert_eq!(parsed.notes().len(), 3);
2840
2841 let from_str = Chord::try_from("C E G").unwrap();
2842 assert_eq!(from_str.pitched_common_name(), "C-major triad");
2843
2844 let midi = [60, 64, 67];
2845 let from_slice = Chord::try_from(midi.as_slice()).unwrap();
2846 assert_eq!(from_slice.pitched_common_name(), "C-major triad");
2847 }
2848
2849 #[test]
2850 fn known_chord_types_include_music21_table_names() {
2851 let known = Chord::known_chord_types();
2852 assert_eq!(known.len(), 351);
2853 assert!(
2854 known
2855 .iter()
2856 .any(|entry| entry.common_names.iter().any(|name| name == "major triad"))
2857 );
2858 assert!(known.iter().any(|entry| {
2859 entry
2860 .common_names
2861 .iter()
2862 .any(|name| name == "dominant seventh chord")
2863 }));
2864 }
2865
2866 #[test]
2867 fn chord_first_inversion_detected() {
2868 let chord = Chord::new("E3 G3 C4").unwrap();
2869 assert_eq!(chord.inversion(), Some(1));
2870 assert_eq!(chord.inversion_name().unwrap(), Some(6));
2871 assert_eq!(chord.inversion_text(), "First Inversion");
2872 assert_eq!(
2873 Chord::new("C E G B-").unwrap().inversion_name().unwrap(),
2874 Some(7)
2875 );
2876 assert!(Chord::new("C D E").unwrap().inversion_name().is_err());
2879 }
2880
2881 #[test]
2882 fn dominant_seventh_resolves_to_tonic() {
2883 let chord = Chord::new("G3 B3 D4 F4").unwrap();
2884 let resolution = chord.resolution_chord("C", Some("major")).unwrap().unwrap();
2885
2886 assert_eq!(resolution.pitched_common_name(), "C-major triad");
2887 }
2888
2889 #[test]
2890 fn resolution_chords_stay_near_source_register() {
2891 let chord = Chord::new("G2 B2 D3 F3").unwrap();
2892 let resolution = chord.resolution_chord("C", Some("major")).unwrap().unwrap();
2893 let names = resolution
2894 .pitches()
2895 .into_iter()
2896 .map(|pitch| pitch.name_with_octave())
2897 .collect::<Vec<_>>();
2898
2899 assert_eq!(names, vec!["C3", "E3", "G3"]);
2900 }
2901
2902 #[test]
2903 fn resolution_suggestions_infer_contexts() {
2904 let chord = Chord::new("G3 B3 D4 F4").unwrap();
2905 let suggestions = chord.resolution_suggestions().unwrap();
2906
2907 assert!(suggestions.iter().any(|suggestion| {
2908 suggestion.key_context == "dominant resolution to C major"
2909 && suggestion.chord.pitched_common_name() == "C-major triad"
2910 }));
2911 assert!(suggestions.iter().any(|suggestion| {
2912 suggestion.key_context == "dominant resolution to C minor"
2913 && suggestion.chord.pitched_common_name() == "C-minor triad"
2914 }));
2915 }
2916
2917 #[test]
2918 fn resolution_suggestions_stay_near_source_register() {
2919 let chord = Chord::new("G2 B2 D3 F3").unwrap();
2920 let suggestions = chord.resolution_suggestions().unwrap();
2921 let c_major = suggestions
2922 .iter()
2923 .find(|suggestion| suggestion.key_context == "dominant resolution to C major")
2924 .unwrap();
2925 let names = c_major
2926 .chord
2927 .pitches()
2928 .into_iter()
2929 .map(|pitch| pitch.name_with_octave())
2930 .collect::<Vec<_>>();
2931
2932 assert_eq!(names, vec!["C3", "E3", "G3"]);
2933 }
2934
2935 #[test]
2936 fn resolution_suggestions_can_use_explicit_key_context() {
2937 let secondary_dominant = Chord::new("D3 F#3 A3 C4").unwrap();
2938 let c_major = Key::from_tonic_mode("C", Some("major")).unwrap();
2939 let suggestions = secondary_dominant
2940 .resolution_suggestions_in_key(&c_major)
2941 .unwrap();
2942
2943 assert_eq!(suggestions.len(), 1);
2944 assert_eq!(suggestions[0].key_context, "dominant resolution in C major");
2945 assert_eq!(suggestions[0].chord.pitched_common_name(), "G-major triad");
2946 }
2947
2948 #[test]
2949 fn dominant_seventh_resolves_to_minor_tonic() {
2950 let chord = Chord::new("G3 B3 D4 F4").unwrap();
2951 let resolution = chord.resolution_chord("C", Some("minor")).unwrap().unwrap();
2952
2953 assert_eq!(resolution.pitched_common_name(), "C-minor triad");
2954 }
2955
2956 #[test]
2957 fn secondary_dominant_resolves_to_diatonic_target() {
2958 let chord = Chord::new("D3 F#3 A3 C4").unwrap();
2959 let resolution = chord.resolution_chord("C", Some("major")).unwrap().unwrap();
2960
2961 assert_eq!(resolution.pitched_common_name(), "G-major triad");
2962 }
2963
2964 #[test]
2965 fn dominant_extensions_resolve_to_tonic() {
2966 let dominant_ninth = Chord::new("G2 B2 D3 F3 A3").unwrap();
2967 let dominant_eleventh = Chord::new("G2 B2 D3 F3 A3 C4").unwrap();
2968 let dominant_thirteenth = Chord::new("G2 B2 D3 F3 A3 C4 E4").unwrap();
2969
2970 for chord in [dominant_ninth, dominant_eleventh, dominant_thirteenth] {
2971 let resolution = chord.resolution_chord("C", Some("major")).unwrap().unwrap();
2972 assert_eq!(resolution.pitched_common_name(), "C-major triad");
2973 }
2974 }
2975
2976 #[test]
2977 fn leading_tone_sevenths_resolve_by_semitone() {
2978 let fully_diminished = Chord::new("B3 D4 F4 A-4").unwrap();
2979 let half_diminished = Chord::new("B3 D4 F4 A4").unwrap();
2980
2981 assert_eq!(
2982 fully_diminished
2983 .resolution_chord("C", Some("major"))
2984 .unwrap()
2985 .unwrap()
2986 .pitched_common_name(),
2987 "C-major triad"
2988 );
2989 assert_eq!(
2990 half_diminished
2991 .resolution_chord("C", Some("major"))
2992 .unwrap()
2993 .unwrap()
2994 .pitched_common_name(),
2995 "C-major triad"
2996 );
2997 }
2998
2999 #[test]
3000 fn leading_tone_diminished_triad_resolves_by_semitone() {
3001 let chord = Chord::new("B3 D4 F4").unwrap();
3002 let resolution = chord.resolution_chord("C", Some("major")).unwrap().unwrap();
3003
3004 assert_eq!(resolution.pitched_common_name(), "C-major triad");
3005 }
3006
3007 #[test]
3008 fn contextual_augmented_sixth_resolves_to_dominant() {
3009 let german_augmented_sixth = Chord::new("A-3 C4 E-4 F#4").unwrap();
3010 let resolution = german_augmented_sixth
3011 .resolution_chord("C", Some("major"))
3012 .unwrap()
3013 .unwrap();
3014
3015 assert_eq!(resolution.pitched_common_name(), "G-major triad");
3016 }
3017
3018 #[test]
3019 fn unsupported_resolution_returns_none() {
3020 let tonic = Chord::new("C E G").unwrap();
3021 assert!(
3022 tonic
3023 .resolution_chord("C", Some("major"))
3024 .unwrap()
3025 .is_none()
3026 );
3027 }
3028}