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