Skip to main content

music21/chord/
mod.rs

1/// Guitar tuning and fingering helpers.
2pub 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/// A collection of notes analyzed as one vertical sonority.
43///
44/// `Chord` accepts several note-like inputs, including whitespace-separated
45/// pitch names, slices of pitches or notes, MIDI pitch numbers, vectors, and
46/// `None` for an empty chord.
47#[must_use]
48pub struct Chord {
49    notes: Vec<Note>,
50    duration: Option<Duration>,
51    /// The tick a MIDI file starts it on: music21's
52    /// `editorial.midiTickStart`.
53    #[cfg_attr(feature = "serde", serde(default))]
54    midi_tick_start: crate::display::Drawn<Option<u64>>,
55    /// How far quantizing moved its offset and its length: music21's
56    /// `editorial.offsetQuantizationError` and
57    /// `quarterLengthQuantizationError`.
58    #[cfg_attr(feature = "serde", serde(default))]
59    quantization_errors: crate::display::Drawn<(Option<FloatType>, Option<FloatType>)>,
60    /// A volume for the chord as a whole, used when its notes carry none.
61    #[cfg_attr(feature = "serde", serde(default))]
62    volume: Option<Volume>,
63    /// A colour for the chord as a whole, used when its notes carry none.
64    #[cfg_attr(feature = "serde", serde(default))]
65    color: Option<String>,
66    /// The notation the chord carries in its own right, apart from its
67    /// notes': music21 keeps these on `NotRest`, which a chord is, and a
68    /// chord's are read independently of the notes inside it.
69    #[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    /// The beams joining the chord's flags to its neighbours'.
78    #[cfg_attr(feature = "serde", serde(default))]
79    beams: Beams,
80    /// The instrument the chord is played on, over whichever is in force;
81    /// boxed, since an instrument is large and most chords carry none.
82    #[cfg_attr(feature = "serde", serde(default))]
83    stored_instrument: Option<Box<crate::instrument::Instrument>>,
84    /// A root the caller decided on, which wins over the one the pitches
85    /// imply: music21's overridden root, for chords spelled oddly or with
86    /// added notes.
87    #[cfg_attr(feature = "serde", serde(default))]
88    root_override: Option<Pitch>,
89    /// A bass the caller decided on, which wins over the lowest pitch.
90    #[cfg_attr(feature = "serde", serde(default))]
91    bass_override: Option<Pitch>,
92    /// An inversion the caller decided on, which wins over the one the
93    /// bass and root imply: music21's `setInversion(n, transpose=False)`.
94    #[cfg_attr(feature = "serde", serde(default))]
95    inversion_override: Option<u8>,
96    /// The fermatas, ornaments and arpeggios written on the chord as a
97    /// whole.
98    #[cfg_attr(feature = "serde", serde(default))]
99    expressions: Vec<crate::expressions::Expression>,
100    /// How the chord as a whole is played, fingerings one to a note.
101    #[cfg_attr(feature = "serde", serde(default))]
102    articulations: Vec<crate::articulations::Articulation>,
103    /// The roman numeral the chord stands for, where it was read as one:
104    /// music21's `RomanNumeral` is a chord, and a RomanText analysis is a
105    /// part of them. Boxed, as most chords carry none.
106    #[cfg_attr(feature = "serde", serde(default))]
107    numeral: Option<Box<crate::roman::RomanNumeral>>,
108    /// The name the chord is known by, where a reader found one.
109    #[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    /// The tick a MIDI file starts the chord on: music21's
222    /// `editorial.midiTickStart`.
223    pub fn midi_tick_start(&self) -> Option<u64> {
224        self.midi_tick_start.0
225    }
226
227    /// Says which tick a MIDI file starts the chord on.
228    pub fn set_midi_tick_start(&mut self, tick: Option<u64>) {
229        self.midi_tick_start.0 = tick;
230    }
231
232    /// How far quantizing moved the chord's offset and its length, where
233    /// it moved them.
234    pub fn quantization_errors(&self) -> (Option<FloatType>, Option<FloatType>) {
235        self.quantization_errors.0
236    }
237
238    /// Says how far quantizing moved the chord.
239    pub fn set_quantization_errors(&mut self, errors: (Option<FloatType>, Option<FloatType>)) {
240        self.quantization_errors.0 = errors;
241    }
242
243    /// Builds a chord from any supported note collection.
244    ///
245    /// Empty inputs are valid: pass `""`, an empty vector or slice, or
246    /// `Option::<&str>::None` to construct an empty chord.
247    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    /// Builds an empty chord.
258    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    /// The name the chord is known by, where a reader found one: music21's
283    /// `id`, from MEI's `xml:id`.
284    pub fn id(&self) -> Option<&str> {
285        self.id.as_deref()
286    }
287
288    /// Names the chord, or takes its name away.
289    pub fn set_id(&mut self, id: Option<String>) {
290        self.id = id;
291    }
292
293    /// The roman numeral the chord stands for, where it is one: a chord a
294    /// RomanText analysis is read into says which numeral it sounds.
295    pub fn numeral(&self) -> Option<&crate::roman::RomanNumeral> {
296        self.numeral.as_deref()
297    }
298
299    /// Says which roman numeral the chord stands for, or that it stands for
300    /// none. The notes are left as they are.
301    pub fn set_numeral(&mut self, numeral: Option<crate::roman::RomanNumeral>) {
302        self.numeral = numeral.map(Box::new);
303    }
304
305    /// The chord, standing for a roman numeral.
306    pub fn with_numeral(mut self, numeral: crate::roman::RomanNumeral) -> Self {
307        self.set_numeral(Some(numeral));
308        self
309    }
310
311    /// Returns a suggested standard-tuning guitar fingering.
312    ///
313    /// The fingering is a compact voicing on six-string guitar in
314    /// E2-A2-D3-G3-B3-E4 tuning. It prefers shapes that cover all chord pitches,
315    /// place the
316    /// root in the bass when possible, avoid internal muted strings, and stay
317    /// within a small fret span.
318    pub fn guitar_fingering(&self) -> Option<GuitarFingering> {
319        guitar::suggested_guitar_fingering(self)
320    }
321
322    /// Returns a suggested guitar fingering for the supplied tuning.
323    ///
324    /// The tuning strings must be ordered from low to high. Fingering generation
325    /// uses exact pitch spaces, so both the chord pitches and open-string
326    /// octaves affect the result.
327    pub fn guitar_fingering_with_tuning(&self, tuning: &GuitarTuning) -> Option<GuitarFingering> {
328        guitar::suggested_guitar_fingering_with_tuning(self, tuning)
329    }
330
331    /// Returns the distinct pitch classes in ascending order.
332    pub fn pitch_classes(&self) -> Vec<u8> {
333        self.ordered_pitch_classes()
334    }
335
336    /// Maps this chord's pitch classes to a reduced integer polyrhythm ratio.
337    ///
338    /// Pitch classes are measured from the inferred root when possible, or
339    /// from the lowest pitch class otherwise. Each semitone offset is mapped
340    /// to a compact just-intonation ratio and reduced to whole-number
341    /// components.
342    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    /// Returns [`Self::polyrhythm_components`] formatted as `a:b:c`.
376    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    /// Adds pitches or notes to the end of the chord, as music21's
385    /// `Chord.add` does. The chord is not re-sorted: the new notes sit after
386    /// the ones already there.
387    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    /// Removes the first note whose pitch equals this one, as music21's
396    /// `Chord.remove` does, and errors when the chord has no such pitch.
397    pub fn remove(&mut self, pitch: &Pitch) -> Result<()> {
398        let found = self.notes.iter().position(|note| &note.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    /// Removes the first note whose written pitch name matches, as music21's
409    /// `Chord.remove` does with a string.
410    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    /// Returns cloned pitches for every note in the chord, in input order.
425    pub fn pitches(&self) -> Vec<Pitch> {
426        self.notes.iter().map(|note| note.pitch.clone()).collect()
427    }
428
429    /// Borrows the pitches in input order.
430    ///
431    /// [`Chord::pitches`] is music21's accessor and clones each pitch; an
432    /// `Accidental` owns two `String`s, so that is two allocations a pitch a
433    /// caller that only reads them does not need.
434    pub fn iter_pitches(&self) -> impl Iterator<Item = &Pitch> + '_ {
435        self.notes.iter().map(|note| &note.pitch)
436    }
437
438    /// Returns the notes in input order.
439    pub fn notes(&self) -> &[Note] {
440        &self.notes
441    }
442
443    /// The notes in input order, for editing in place. This is how a note's
444    /// notation is changed through the chord: `chord.notes_mut()[1]
445    /// .set_notehead(Notehead::Diamond)`.
446    pub fn notes_mut(&mut self) -> &mut [Note] {
447        &mut self.notes
448    }
449
450    /// How many notes the chord holds: what `len(chord)` answers upstream.
451    pub fn len(&self) -> usize {
452        self.notes.len()
453    }
454
455    /// Whether the chord holds no notes at all.
456    pub fn is_empty(&self) -> bool {
457        self.notes.is_empty()
458    }
459
460    /// The first note whose pitch equals this one: music21's per-note
461    /// accessors take a pitch this way.
462    pub fn note_for_pitch(&self, pitch: &Pitch) -> Option<&Note> {
463        self.notes.iter().find(|note| &note.pitch == pitch)
464    }
465
466    /// The first note whose pitch equals this one, for editing in place.
467    pub fn note_for_pitch_mut(&mut self, pitch: &Pitch) -> Option<&mut Note> {
468        self.notes.iter_mut().find(|note| &note.pitch == pitch)
469    }
470
471    /// Returns the chord duration when one has been assigned.
472    pub fn duration(&self) -> Option<&Duration> {
473        self.duration.as_ref()
474    }
475
476    /// Assigns a duration to the chord.
477    pub fn set_duration(&mut self, duration: Duration) {
478        self.duration = Some(duration);
479    }
480
481    /// Returns a copy of this chord with the supplied duration.
482    pub fn with_duration(mut self, duration: Duration) -> Self {
483        self.set_duration(duration);
484        self
485    }
486
487    /// Returns the inversion number the way music21 finds it: root position
488    /// is `0`, and the number climbs with the chord step the bass sits on,
489    /// so a bass on the seventh is `3`. `None` only for an empty chord.
490    ///
491    /// An inversion fixed with [`Self::set_inversion_override`] is the
492    /// answer, whatever the notes say.
493    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    /// The inversion the notes imply, ignoring any override.
504    pub fn found_inversion(&self) -> Option<u8> {
505        self.inversion_with_root(self.root()?)
506    }
507
508    /// The inversion a caller fixed with [`Self::set_inversion_override`],
509    /// and nothing when none was.
510    pub fn overridden_inversion(&self) -> Option<u8> {
511        self.inversion_override
512    }
513
514    /// Fixes the inversion the chord reports without moving a note, or
515    /// clears the override with `None`: music21's
516    /// `setInversion(n, transpose=False)`, for a chord badly spelt or with a
517    /// note added that is to be read in a given inversion all the same.
518    ///
519    /// ```
520    /// use music21::Chord;
521    ///
522    /// let mut chord = Chord::new("C4 E4 G4")?;
523    /// chord.set_inversion_override(Some(1));
524    /// assert_eq!(chord.inversion(), Some(1));
525    /// assert_eq!(chord.found_inversion(), Some(0));
526    /// assert_eq!(chord.inversion_name()?, Some(6));
527    /// # Ok::<(), music21::Error>(())
528    /// ```
529    pub fn set_inversion_override(&mut self, inversion: Option<u8>) {
530        self.inversion_override = inversion;
531    }
532
533    /// The inversion the way [`Self::inversion`] counts it, but measured from a
534    /// root the caller has decided on rather than the one the chord infers.
535    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    /// The figured-bass number music21's `inversionName` answers: `53`, `6`
541    /// and `64` for a triad, `7`, `65`, `43` and `42` for a seventh. `None`
542    /// when the chord has no inversion, and an error when it is neither a
543    /// triad nor a seventh, as music21 raises there. For the words, see
544    /// [`Self::inversion_text`].
545    pub fn inversion_name(&self) -> Result<Option<IntegerType>> {
546        // The root is found once and every question below is asked of it.
547        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    /// The figured-bass number for this chord standing in `inversion`,
560    /// whatever inversion it is in: what [`Self::inversion_name`] answers
561    /// once the inversion is known. An inversion of `-1` is music21's for
562    /// none, and answers `None`; one past what a triad or a seventh has is
563    /// an error, as music21 raises for an inversion fixed by hand.
564    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        // A seventh chord has a seventh, so having one is the whole test.
586        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    /// Returns a copy with simplified enharmonic spellings.
602    ///
603    /// This mirrors music21's explicit enharmonic simplification workflow:
604    /// construction stays side-effect free, and callers can request simpler
605    /// spellings with an optional key-signature context.
606    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    /// Simplifies this chord's pitch spellings in place.
613    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    /// Returns the root, found the way music21's `Chord.root` finds it.
633    pub fn root(&self) -> Option<&Pitch> {
634        self.root_override
635            .as_ref()
636            .or_else(|| self.find_root_pitch())
637    }
638
639    /// The root the pitches imply, ignoring any override.
640    pub fn found_root(&self) -> Option<&Pitch> {
641        self.find_root_pitch()
642    }
643
644    /// Fixes the root the chord reports, or clears the override with `None`:
645    /// music21's `root(newroot)`. The pitch need not be in the chord, which
646    /// is the point of it for oddly spelled or added-note chords.
647    pub fn set_root(&mut self, root: Option<Pitch>) {
648        self.root_override = root;
649    }
650
651    /// Returns the lowest pitch, or the bass a caller fixed with
652    /// [`Self::set_bass`].
653    pub fn bass(&self) -> Option<&Pitch> {
654        self.bass_override.as_ref().or_else(|| self.bass_pitch())
655    }
656
657    /// The lowest pitch, ignoring any override.
658    pub fn found_bass(&self) -> Option<&Pitch> {
659        self.bass_pitch()
660    }
661
662    /// The bass a caller fixed with [`Self::set_bass`], and nothing when
663    /// none was.
664    pub fn overridden_bass(&self) -> Option<&Pitch> {
665        self.bass_override.as_ref()
666    }
667
668    /// The root a caller fixed with [`Self::set_root`], and nothing when
669    /// none was.
670    pub fn overridden_root(&self) -> Option<&Pitch> {
671        self.root_override.as_ref()
672    }
673
674    /// Fixes the bass the chord reports, or clears the override with `None`:
675    /// music21's `bass(newbass)`. A pitch the chord does not already carry is
676    /// added below the others, as music21 adds it.
677    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    /// Rearranges the chord so it stands in the given inversion, raising the
693    /// bass by octaves until it does: music21's `inversion(newInversion)`.
694    /// An inversion or bass fixed by hand is forgotten, as music21 forgets
695    /// them. An inversion the chord cannot reach is an error.
696    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    /// Returns a copy with every pitch brought within an octave above the
736    /// bass, duplicates removed and the notes sorted, as music21's
737    /// `closedPosition` does. `force_octave` moves the bass to that octave
738    /// first, carrying the rest of the chord with it.
739    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(&note.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    /// The chord with duplicate pitches removed, and the pitches that went:
781    /// music21's `removeRedundantPitches`, which hands back what it dropped.
782    pub fn remove_redundant_pitches_reporting(&self) -> (Self, Vec<Pitch>) {
783        self.reduced_reporting(spelling_and_octave)
784    }
785
786    /// The chord with pitches of the same name removed, and the ones that
787    /// went: music21's `removeRedundantPitchNames`.
788    pub fn remove_redundant_pitch_names_reporting(&self) -> (Self, Vec<Pitch>) {
789        self.reduced_reporting(Pitch::name)
790    }
791
792    /// The chord with pitches of the same pitch class removed, and the ones
793    /// that went: music21's `removeRedundantPitchClasses`.
794    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(&note.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    /// Returns a copy keeping the first of every pitch that appears more than
816    /// once with the same name and octave. `Bb1` and `B` in octave -1 are
817    /// different pitches (music21 prints both `B-1`), and so are `C` with no
818    /// octave of its own and `C4`.
819    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    /// Returns a copy keeping the first of every pitch name, regardless of
826    /// octave.
827    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    /// Returns a copy keeping the first of every pitch class, so `C#` and
834    /// `D-` count as one.
835    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    /// Returns a copy sorted by staff position and then pitch space, so
842    /// `F##` sorts below `G-`.
843    pub fn sort_ascending(&self) -> Self {
844        let mut chord = self.clone();
845        chord.sort_ascending_in_place();
846        chord
847    }
848
849    /// How many distinct pitch names the chord has, without writing any of
850    /// them: two pitches share a name when they share a letter and an
851    /// accidental, whatever octave each is in.
852    ///
853    /// music21 counts these to decide whether a chord is a triad, a seventh
854    /// or a ninth, so a chord is asked constantly -- and a name is a
855    /// `String`, so building the set cost an allocation a pitch and a tree
856    /// besides. The walk is quadratic in the notes, which for a chord is a
857    /// handful of comparisons of two characters and a modifier.
858    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, &note.pitch))
866            })
867            .count()
868    }
869
870    /// Which note carries the bass [`Self::found_bass`] finds: the one
871    /// written lowest, as music21's `bass()` reads it, so `E#4` is under
872    /// `Fb4` though it sounds above.
873    fn bass_index(&self) -> Option<usize> {
874        let bass = self.bass_pitch()?;
875        self.notes
876            .iter()
877            .position(|note| std::ptr::eq(&note.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(&note.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    /// The indices of the notes, sorted by staff position and then pitch
902    /// space, equal notes keeping their order.
903    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    /// Returns a copy with every note transposed by the interval.
919    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(&note.pitch)?;
923        }
924        // A root or bass a caller fixed moves with the chord, as music21
925        // moves it: a chord symbol carries its root as an override, and one
926        // transposed up a semitone is a chord on the note above.
927        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        // The numeral stays the same figure in the key moved with it.
934        if let Some(numeral) = &chord.numeral {
935            chord.numeral = Some(Box::new(numeral.transpose(interval)?));
936        }
937        Ok(chord)
938    }
939
940    /// Returns each pitch's degree in `scale` with the accidental that
941    /// separates it from the scale's spelling, as music21's `scaleDegrees`
942    /// does: `C E- G` in C major is `(1, None), (3, flat), (5, None)`. A pitch
943    /// whose letter the scale lacks reports `(None, None)`.
944    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| &note.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
988/// Whether two pitches are written with the same letter and accidental,
989/// which is what having the same name is.
990fn same_pitch_name(left: &Pitch, right: &Pitch) -> bool {
991    left.step() == right.step() && left.accidental().modifier() == right.accidental().modifier()
992}
993
994/// Tries to convert a supported chord input into notes.
995///
996/// Implementations are provided for strings, slices, vectors, other chords,
997/// integer pitch inputs, and `Option<T>`. `None` converts to an empty note list.
998/// String and integer inputs can fail while constructing pitches or simplifying
999/// enharmonics, so this trait stays explicitly fallible.
1000pub trait IntoNotes {
1001    /// Whether this input should be treated as integer-derived pitches.
1002    const FROM_INTEGER_PITCHES: bool = false;
1003
1004    /// Iterator-like collection returned by the conversion.
1005    type Notes: IntoIterator<Item = Note>;
1006
1007    /// Converts the input into notes.
1008    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
1047/// What `removeRedundantPitches` compares: the spelling and the octave as
1048/// music21 keeps them, not the printed `nameWithOctave`, which in music21
1049/// reads `B-1` for both B-flat 1 and B in octave -1, and the same for `C`
1050/// with and without an octave.
1051fn 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    /// Returns music21's `inversionText`: `Root Position`, `First Inversion`
1163    /// and so on, or `Unknown Position` for an empty chord.
1164    pub fn inversion_text(&self) -> String {
1165        Self::inversion_text_of(self.inversion().map_or(-1, IntegerType::from))
1166    }
1167
1168    /// music21's `inversionText` for a chord standing in `inversion`: `-1`,
1169    /// music21's for none, is `Unknown Position`, and so is an inversion
1170    /// fixed by hand past any ordinal.
1171    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    /// Returns the chord in closed position with every repeated step raised
1185    /// an octave, so an eight-note cluster spreads into a scale: music21's
1186    /// `semiClosedPosition`.
1187    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    /// Returns a copy sorted by pitch space alone, so enharmonic pairs keep
1216    /// their input order: music21's `sortChromaticAscending`.
1217    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    /// Returns a copy sorted by staff position and then pitch space, so
1229    /// `B#3` sorts below `C4`: music21's `sortDiatonicAscending`, which is
1230    /// also what [`Self::sort_ascending`] does.
1231    pub fn sort_diatonic_ascending(&self) -> Self {
1232        self.sort_ascending()
1233    }
1234
1235    /// Where each note of [`Self::sort_diatonic_ascending`] comes from: the
1236    /// index in this chord of the note it puts first, then second, and so
1237    /// on, equal notes keeping their order.
1238    ///
1239    /// ```
1240    /// use music21::Chord;
1241    ///
1242    /// assert_eq!(Chord::new("G4 C4 E-4 D#4")?.diatonic_ascending_order(), [1, 3, 2, 0]);
1243    /// # Ok::<(), music21::Error>(())
1244    /// ```
1245    pub fn diatonic_ascending_order(&self) -> Vec<usize> {
1246        self.ascending_order()
1247    }
1248
1249    /// Whether the notes already stand in the order
1250    /// [`Self::sort_diatonic_ascending`] puts them in, lowest to highest on
1251    /// the staff: music21's `isSortedDiatonicAscending`.
1252    ///
1253    /// Staff position decides first, so `B#3 C4` is sorted and `C4 B#3` is
1254    /// not though both sound the same two notes, and `C4 D#4 E-4` is sorted
1255    /// where `C4 E-4 D#4` is not. A chord of one note, or of none, is
1256    /// sorted.
1257    ///
1258    /// ```
1259    /// use music21::Chord;
1260    ///
1261    /// assert!(Chord::new("C4 E4 G4")?.is_sorted_diatonic_ascending());
1262    /// assert!(!Chord::new("E4 C4 G4")?.is_sorted_diatonic_ascending());
1263    /// # Ok::<(), music21::Error>(())
1264    /// ```
1265    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    /// Returns a copy sorted by frequency: music21's `sortFrequencyAscending`.
1274    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    /// Returns the pitch names in input order, without octaves.
1286    pub fn pitch_names(&self) -> Vec<String> {
1287        self.notes.iter().map(|note| note.pitch.name()).collect()
1288    }
1289
1290    /// The pitch class of every note, in the order the chord holds them and
1291    /// with repeats kept: music21's `pitchClasses`. For the sorted, distinct
1292    /// list see [`Self::pitch_classes`].
1293    pub fn note_pitch_classes(&self) -> Vec<u8> {
1294        self.notes
1295            .iter()
1296            .map(|note| root::pitch_class(&note.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    /// Every answer here is music21's own for the same chord.
1721    #[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            // Staff position decides before pitch space, so two notes that
1727            // sound alike are sorted one way round and not the other.
1728            ("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            // Sorting it says so, whatever it said before.
1742            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        // A chord with no duration of its own reads as a quarter, which is
1753        // the duration music21 gives every chord by default.
1754        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        // A bass the chord does not carry is added below the others.
1829        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        // A copy keeps it, and so does a chord moved to another key.
1869        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        // A bass a sixth above the root is music21's sixth inversion.
1902        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        // music21 prints both `B-1`; with `b` for flat they no longer collide.
1941        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(&notes[..]).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        // The setter applies whether or not the chord holds notes.
2007        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            // The bass is the note written lowest, not the one sounding
2401            // lowest: E#4 sits under F-4 on the staff.
2402            ("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        // A voicing sounds chord *tones*, in whatever octave falls under the
2944        // hand; it is not required to reproduce the written octaves.
2945        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    /// Renders a fingering as the `x 3 2 0 1 0` notation guitarists read.
2986    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        // The shapes any player would name for these chords. Before voicings
3004        // were matched by pitch class these all came back as `x x x n n n`.
3005        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        // Omitting a chord tone costs a voicing far more than omitting a
3020        // written octave, so a seventh chord keeps its seventh.
3021        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        // A chord that is neither a triad nor carries a seventh has no
3175        // figured-bass number, which music21 reports by raising.
3176        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}