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