Skip to main content

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