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