Skip to main content

klib/core/
chord.rs

1//! A module that contains the [`Chord`] struct and related traits.
2
3use std::{cmp::Ordering, collections::HashSet, fmt::Display};
4
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8use pest::Parser;
9
10use crate::core::{
11    base::{HasDescription, HasName, HasPreciseName, HasStaticName, Parsable, Res},
12    interval::Interval,
13    known_chord::{HasRelativeChord, HasRelativeScale, HasScaleCandidates, IntervalCandidate, IntervalCollectionKind, KnownChord, ScaleCandidate},
14    modifier::{known_modifier_sets, likely_extension_sets, one_off_modifier_sets, Degree, Extension, HasIsDominant, Modifier},
15    named_pitch::HasNamedPitch,
16    note::{CZero, Note, NoteRecreator},
17    octave::{HasOctave, Octave},
18    parser::{note_str_to_note, octave_str_to_octave, ChordParser, Rule},
19    pitch::{HasFrequency, Pitch},
20};
21
22// Traits.
23
24/// A trait that represents a type that has a root note.
25pub trait HasRoot {
26    /// Returns the root note of the implementor (most likely a [`Chord`]).
27    fn root(&self) -> Note;
28}
29
30/// A trait that represents a type that has a slash note.
31pub trait HasSlash {
32    /// Returns the slash note of the implementor (most likely a [`Chord`]).
33    fn slash(&self) -> Note;
34}
35
36/// A trait that represents a type that has modifiers.
37pub trait HasModifiers {
38    /// Returns the modifiers of the implementor (most likely a [`Chord`]).
39    fn modifiers(&self) -> &HashSet<Modifier>;
40}
41
42/// A trait that represents a type that has extensions.
43pub trait HasExtensions {
44    /// Returns the extensions of the implementor (most likely a [`Chord`]).
45    fn extensions(&self) -> &HashSet<Extension>;
46}
47
48/// A trait that represents a type that has an inversion.
49pub trait HasInversion {
50    /// Returns the inversion of the implementor (most likely a [`Chord`]).
51    fn inversion(&self) -> u8;
52}
53
54/// A trait that represents a type that has "crunchiness".
55pub trait HasIsCrunchy {
56    /// Returns the "crunchiness" of the implementor (most likely a [`Chord`]).
57    fn is_crunchy(&self) -> bool;
58}
59
60/// A trait that represents a type that has an octave.
61pub trait HasKnownChord {
62    /// Returns the known chord of the implementor (most likely a [`Chord`]).
63    fn known_chord(&self) -> KnownChord;
64}
65
66/// A trait that represents a type that has a chord.
67pub trait HasScale {
68    /// Returns the scale of the implementor (most likely a [`Chord`]).
69    fn scale(&self) -> Vec<Note>;
70}
71
72/// A trait that represents a type that has a chord.
73pub trait HasChord {
74    /// Returns the chord of the implementor (most likely a [`Chord`]).
75    fn chord(&self) -> Vec<Note>;
76}
77
78/// A trait that represents a type that has a chord.
79///
80/// These methods all take ownership of the existing implementor (usually a [`Chord`]),
81/// and then return a new chord.  This can be circumvented by using an explicit `clone()`.
82/// E.g., `chord.clone().minor()`.
83pub trait Chordable {
84    /// Adds a modifier to the implementor (most likely a [`Chord`]), and returns a new chord.
85    fn with_modifier(self, modifier: Modifier) -> Chord;
86    /// Adds modifiers to the implementor (most likely a [`Chord`]), and returns a new chord.
87    fn with_modifiers(self, modifiers: &[Modifier]) -> Chord;
88    /// Adds an extension to the implementor (most likely a [`Chord`]), and returns a new chord.
89    fn with_extension(self, extension: Extension) -> Chord;
90    /// Adds extensions to the implementor (most likely a [`Chord`]), and returns a new chord.
91    fn with_extensions(self, extensions: &[Extension]) -> Chord;
92    /// Sets the inversion number of the implementor (most likely a [`Chord`]), and returns a new chord.
93    fn with_inversion(self, inversion: u8) -> Chord;
94    /// Sets the slash note of the implementor (most likely a [`Chord`]), and returns a new chord.
95    fn with_slash(self, slash: Note) -> Chord;
96    /// Sets the octave of the implementor (most likely the root note of a chord), and returns a new chord.
97    fn with_octave(self, octave: Octave) -> Chord;
98    /// Sets whether or not the implementor (most likely a [`Chord`]) is crunchy.
99    fn with_crunchy(self, is_crunchy: bool) -> Chord;
100
101    // Modifiers.
102
103    /// Returns a new chord with a minor modifier on the implementor (most likely a [`Chord`]).
104    fn minor(self) -> Chord;
105
106    /// Returns a new chord with a flat 5 modifier on the implementor (most likely a [`Chord`]).
107    fn flat5(self) -> Chord;
108    /// Returns a new chord with a flat 5 modifier on the implementor (most likely a [`Chord`]).
109    fn flat_five(self) -> Chord;
110
111    /// Returns a new chord with a sharp 5 modifier on the implementor (most likely a [`Chord`]).
112    fn augmented(self) -> Chord;
113    /// Returns a new chord with a sharp 5 modifier on the implementor (most likely a [`Chord`]).
114    fn aug(self) -> Chord;
115
116    /// Returns a new chord with a major 7 modifier on the implementor (most likely a [`Chord`]).
117    fn major7(self) -> Chord;
118    /// Returns a new chord with a major 7 modifier on the implementor (most likely a [`Chord`]).
119    fn major_seven(self) -> Chord;
120    /// Returns a new chord with a major 7 modifier on the implementor (most likely a [`Chord`]).
121    fn maj7(self) -> Chord;
122
123    /// Returns a new chord with a dominant 7 modifier on the implementor (most likely a [`Chord`]).
124    fn dominant7(self) -> Chord;
125    /// Returns a new chord with a dominant 7 modifier on the implementor (most likely a [`Chord`]).
126    fn seven(self) -> Chord;
127    /// Returns a new chord with a dominant 9 modifier on the implementor (most likely a [`Chord`]).
128    fn dominant9(self) -> Chord;
129    /// Returns a new chord with a dominant 9 modifier on the implementor (most likely a [`Chord`]).
130    fn nine(self) -> Chord;
131    /// Returns a new chord with a dominant 11 modifier on the implementor (most likely a [`Chord`]).
132    fn dominant11(self) -> Chord;
133    /// Returns a new chord with a dominant 11 modifier on the implementor (most likely a [`Chord`]).
134    fn eleven(self) -> Chord;
135    /// Returns a new chord with a dominant 13 modifier on the implementor (most likely a [`Chord`]).
136    fn dominant13(self) -> Chord;
137    /// Returns a new chord with a dominant 13 modifier on the implementor (most likely a [`Chord`]).
138    fn thirteen(self) -> Chord;
139    /// Returns a new chord with a dominant modifier on the implementor (most likely a [`Chord`]).
140    fn dominant(self, dominant: Degree) -> Chord;
141
142    /// Returns a new chord with a flat 9 modifier on the implementor (most likely a [`Chord`]).
143    fn flat9(self) -> Chord;
144    /// Returns a new chord with a flat 9 modifier on the implementor (most likely a [`Chord`]).
145    fn flat_nine(self) -> Chord;
146
147    /// Returns a new chord with a sharp 9 modifier on the implementor (most likely a [`Chord`]).
148    fn sharp9(self) -> Chord;
149    /// Returns a new chord with a sharp 9 modifier on the implementor (most likely a [`Chord`]).
150    fn sharp_nine(self) -> Chord;
151
152    /// Returns a new chord with a sharp 11 modifier on the implementor (most likely a [`Chord`]).
153    fn sharp11(self) -> Chord;
154    /// Returns a new chord with a sharp 11 modifier on the implementor (most likely a [`Chord`]).
155    fn sharp_eleven(self) -> Chord;
156
157    /// Returns a new chord with a flat 13 modifier on the implementor (most likely a [`Chord`]).
158    fn flat13(self) -> Chord;
159    /// Returns a new chord with a flat 13 modifier on the implementor (most likely a [`Chord`]).
160    fn flat_thirteen(self) -> Chord;
161
162    // Special.
163
164    /// Returns a new chord with a diminished modifier on the implementor (most likely a [`Chord`]).
165    fn diminished(self) -> Chord;
166    /// Returns a new chord with a diminished modifier on the implementor (most likely a [`Chord`]).
167    fn dim(self) -> Chord;
168
169    /// Returns a new chord with a half-diminished (m7♭5) modifier on the implementor (most likely a [`Chord`]).
170    fn half_diminished(self) -> Chord;
171    /// Returns a new chord with a half-diminished (m7♭5) modifier on the implementor (most likely a [`Chord`]).
172    fn half_dim(self) -> Chord;
173
174    // Extensions.
175
176    /// Returns a new chord with a sus2 extension on the implementor (most likely a [`Chord`]).
177    fn sus2(self) -> Chord;
178    /// Returns a new chord with a sus2 extension on the implementor (most likely a [`Chord`]).
179    fn sus_two(self) -> Chord;
180
181    /// Returns a new chord with a sus4 extension on the implementor (most likely a [`Chord`]).
182    fn sus4(self) -> Chord;
183    /// Returns a new chord with a sus4 extension on the implementor (most likely a [`Chord`]).
184    fn sus_four(self) -> Chord;
185    /// Returns a new chord with a sus4 extension on the implementor (most likely a [`Chord`]).
186    fn sustain(self) -> Chord;
187    /// Returns a new chord with a sus4 extension on the implementor (most likely a [`Chord`]).
188    fn sus(self) -> Chord;
189
190    /// Returns a new chord with a flat 11 extension on the implementor (most likely a [`Chord`]).
191    fn flat11(self) -> Chord;
192    /// Returns a new chord with a flat 11 extension on the implementor (most likely a [`Chord`]).
193    fn flat_eleven(self) -> Chord;
194
195    /// Returns a new chord with a sharp 13 extension on the implementor (most likely a [`Chord`]).
196    fn sharp13(self) -> Chord;
197    /// Returns a new chord with a sharp 13 extension on the implementor (most likely a [`Chord`]).
198    fn sharp_thirteen(self) -> Chord;
199
200    /// Returns a new chord with an add2 extension on the implementor (most likely a [`Chord`]).
201    fn add2(self) -> Chord;
202    /// Returns a new chord with an add2 extension on the implementor (most likely a [`Chord`]).
203    fn add_two(self) -> Chord;
204
205    /// Returns a new chord with an add4 extension on the implementor (most likely a [`Chord`]).
206    fn add4(self) -> Chord;
207    /// Returns a new chord with an add4 extension on the implementor (most likely a [`Chord`]).
208    fn add_four(self) -> Chord;
209
210    /// Returns a new chord with an add6 extension on the implementor (most likely a [`Chord`]).
211    fn add6(self) -> Chord;
212    /// Returns a new chord with an add6 extension on the implementor (most likely a [`Chord`]).
213    fn add_six(self) -> Chord;
214
215    /// Returns a new chord with an add9 extension on the implementor (most likely a [`Chord`]).
216    fn add9(self) -> Chord;
217    /// Returns a new chord with an add9 extension on the implementor (most likely a [`Chord`]).
218    fn add_nine(self) -> Chord;
219
220    /// Returns a new chord with an add11 extension on the implementor (most likely a [`Chord`]).
221    fn add11(self) -> Chord;
222    /// Returns a new chord with an add11 extension on the implementor (most likely a [`Chord`]).
223    fn add_eleven(self) -> Chord;
224
225    /// Returns a new chord with an add13 extension on the implementor (most likely a [`Chord`]).
226    fn add13(self) -> Chord;
227    /// Returns a new chord with an add13 extension on the implementor (most likely a [`Chord`]).
228    fn add_thirteen(self) -> Chord;
229}
230
231/// A trait for types that have a dominant degree; i.e., 7, 9, 11, 13.
232pub trait HasDomninantDegree {
233    /// Returns the dominant degree of the implementor (most likely a [`Chord`]).
234    fn dominant_degree(&self) -> Option<Degree>;
235}
236
237// Struct.
238
239/// The primary chord struct.
240#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
241#[derive(PartialEq, Eq, Clone, Debug)]
242pub struct Chord {
243    /// The root note of the chord.
244    root: Note,
245    /// The slash note of the chord.
246    slash: Option<Note>,
247    /// The modifiers of the chord.
248    modifiers: HashSet<Modifier>,
249    /// The extensions of the chord.
250    extensions: HashSet<Extension>,
251    /// The inversion of the chord.
252    inversion: u8,
253    /// Whether or not this chord is "crunchy".
254    ///
255    /// Crunchy chords take extensions down an octave, which gives the chord some "crunch".
256    is_crunchy: bool,
257}
258
259// Impls.
260
261impl Ord for Chord {
262    fn cmp(&self, other: &Self) -> Ordering {
263        let a_inversion = self.inversion;
264        let b_inversion = other.inversion;
265        let cmp_inversion = a_inversion.cmp(&b_inversion);
266
267        let a_slashes = self.slash.is_some() as u8;
268        let b_slashes = other.slash.is_some() as u8;
269        let cmp_slashes = a_slashes.cmp(&b_slashes);
270
271        let a_crunchy = self.is_crunchy as u8;
272        let b_crunchy = other.is_crunchy as u8;
273        let cmp_crunchy = a_crunchy.cmp(&b_crunchy);
274
275        let a_extensions_len = self.extensions.len() as u8;
276        let b_extensions_len = other.extensions.len() as u8;
277        let cmp_extensions = {
278            let result = a_extensions_len.cmp(&b_extensions_len);
279
280            if result.is_eq() {
281                let a_extensions = Vec::from_iter(&self.extensions);
282                let b_extensions = Vec::from_iter(&other.extensions);
283
284                a_extensions.cmp(&b_extensions)
285            } else {
286                result
287            }
288        };
289
290        let a_modifiers_len = self.modifiers.len() as u8;
291        let b_modifiers_len = other.modifiers.len() as u8;
292        let cmp_modifiers = {
293            let result = a_modifiers_len.cmp(&b_modifiers_len);
294
295            if result.is_eq() {
296                let a_modifiers = Vec::from_iter(&self.modifiers);
297                let b_modifiers = Vec::from_iter(&other.modifiers);
298
299                a_modifiers.cmp(&b_modifiers)
300            } else {
301                result
302            }
303        };
304
305        // Give a slight preference to chords without slashes and inversions.
306        let a_inversion_exists = u8::from(a_inversion != 0);
307        let b_inversion_exists = u8::from(b_inversion != 0);
308
309        let a_all_changes_len = a_extensions_len + a_modifiers_len + 2 * a_slashes + 2 * a_inversion_exists;
310        let b_all_changes_len = b_extensions_len + b_modifiers_len + 2 * b_slashes + 2 * b_inversion_exists;
311
312        let cmp_all_changes = a_all_changes_len.cmp(&b_all_changes_len);
313
314        let a_root = self.root;
315        let b_root = other.root;
316        let cmp_root = a_root.cmp(&b_root);
317
318        cmp_all_changes
319            .then(cmp_inversion)
320            .then(cmp_slashes)
321            .then(cmp_extensions)
322            .then(cmp_modifiers)
323            .then(cmp_root)
324            .then(cmp_crunchy)
325    }
326}
327
328impl PartialOrd for Chord {
329    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
330        Some(self.cmp(other))
331    }
332}
333
334impl Chord {
335    /// Returns a new chord with the given root.
336    pub fn new(root: Note) -> Self {
337        Self {
338            root,
339            slash: None,
340            modifiers: HashSet::new(),
341            extensions: HashSet::new(),
342            inversion: 0,
343            is_crunchy: false,
344        }
345    }
346}
347
348impl Chord {
349    /// Attempts to guess the chord from detected pitch classes.
350    /// Tries intelligent permutations across octaves to find all plausible voicings.
351    /// The `try_from_notes` method will handle slash chords and inversions automatically.
352    pub fn try_from_pitches(all_pitches: &[Pitch]) -> Res<Vec<Self>> {
353        use crate::core::named_pitch::NamedPitch;
354        use crate::core::octave::Octave;
355
356        if all_pitches.is_empty() {
357            return Err(anyhow::Error::msg("Must have at least one pitch to guess a chord."));
358        }
359
360        let mut all_note_combinations = Vec::new();
361
362        // Convert pitches to notes at octave 4 as base
363        let mut base_notes: Vec<Note> = all_pitches.iter().map(|&p| Note::new(NamedPitch::from(p), Octave::Four)).collect();
364        base_notes.sort();
365
366        // Generate rotations - each rotation bumps the first note(s) to octave 5
367        for rotation in 0..base_notes.len() {
368            let mut rotated = base_notes.clone();
369
370            // Move first 'rotation' notes up an octave
371            for i in 0..rotation {
372                rotated[i] = rotated[i].with_octave(Octave::Five);
373            }
374
375            // Re-sort after octave adjustments
376            rotated.sort();
377
378            // Add this voicing to try
379            all_note_combinations.push(rotated);
380        }
381
382        // Try all combinations through the existing chord guesser
383        let mut all_candidates = Vec::new();
384        for notes in all_note_combinations {
385            if notes.len() >= 3 {
386                if let Ok(candidates) = Self::try_from_notes(&notes) {
387                    all_candidates.extend(candidates);
388                }
389            }
390        }
391
392        // Sort by complexity (simplest first), then deduplicate by string representation
393        all_candidates.sort();
394        all_candidates.dedup_by_key(|c| c.to_string());
395
396        if all_candidates.is_empty() {
397            return Err(anyhow::Error::msg("Could not determine chord from pitches."));
398        }
399
400        Ok(all_candidates)
401    }
402
403    /// Attempts to guess the chord from the notes.
404    pub fn try_from_notes(notes: &[Note]) -> Res<Vec<Self>> {
405        if notes.len() < 3 {
406            return Err(anyhow::Error::msg("Must have at least three notes to guess a chord."));
407        }
408
409        let mut notes = notes.to_vec();
410        notes.sort();
411
412        let mut result = Vec::new();
413
414        // Iterate through all known chords (and some likely extensions) and find the longest match.
415        for inversion in 0..3 {
416            let proper_root = if inversion == 0 {
417                notes[0]
418            } else {
419                let note = notes[notes.len() - inversion];
420
421                note.with_octave(note.octave() - 1)
422            };
423
424            let proper_root_slash = if inversion == 0 {
425                notes[1]
426            } else {
427                let note = notes[notes.len() - inversion];
428
429                note.with_octave(note.octave() - 1)
430            };
431
432            for mod_set in known_modifier_sets() {
433                for mod_set2 in one_off_modifier_sets() {
434                    for ext_set in likely_extension_sets() {
435                        for is_crunchy in [false, true] {
436                            // Check using the first note as the root.
437                            let candidate_chord_root = Chord::new(proper_root)
438                                .with_modifiers(mod_set)
439                                .with_modifiers(mod_set2)
440                                .with_extensions(ext_set)
441                                .with_inversion(inversion as u8)
442                                .with_crunchy(is_crunchy);
443                            let candidate_chord_root_notes = candidate_chord_root.chord();
444
445                            if notes.len() == candidate_chord_root_notes.len() && notes.iter().zip(&candidate_chord_root.chord()).all(|(a, b)| a.frequency() == b.frequency()) {
446                                result.push(candidate_chord_root);
447                            }
448
449                            // Check using the first note as a slash.
450                            let candidate_chord_slash = Chord::new(proper_root_slash)
451                                .with_slash(notes[0])
452                                .with_modifiers(mod_set)
453                                .with_modifiers(mod_set2)
454                                .with_extensions(ext_set)
455                                .with_inversion(inversion as u8)
456                                .with_crunchy(is_crunchy);
457                            let candidate_chord_slash_notes = candidate_chord_slash.chord();
458
459                            if notes.len() == candidate_chord_slash_notes.len() && notes.iter().zip(&candidate_chord_slash.chord()).all(|(a, b)| a.frequency() == b.frequency()) {
460                                result.push(candidate_chord_slash);
461                            }
462                        }
463                    }
464                }
465            }
466        }
467
468        // Remove extensions and modifiers that are expressed elsewhere in the chord.
469        for c in &mut result {
470            let dominant_degree = c.dominant_degree();
471
472            if let Some(degree) = dominant_degree {
473                match degree {
474                    Degree::Nine => {
475                        c.extensions.remove(&Extension::Add9);
476                    }
477                    Degree::Eleven => {
478                        c.extensions.remove(&Extension::Add9);
479                        c.extensions.remove(&Extension::Add11);
480                    }
481                    Degree::Thirteen => {
482                        c.extensions.remove(&Extension::Add9);
483                        c.extensions.remove(&Extension::Add11);
484                        c.extensions.remove(&Extension::Add13);
485                    }
486                    Degree::Seven => {}
487                }
488            }
489
490            if c.modifiers.contains(&Modifier::Diminished) {
491                c.modifiers.remove(&Modifier::Minor);
492                c.modifiers.remove(&Modifier::Flat5);
493                c.modifiers.remove(&Modifier::Augmented5);
494            }
495        }
496
497        // Order the candidates by "simplicity" (i.e., least slashes, least extensions, least modifiers, and least inversion).
498        result.sort();
499
500        // Remove duplicates (and ignore crunchy; i.e., `C7` and `C7!` should be treated as "the same").
501        result.dedup_by(|a, b| a.modifiers == b.modifiers && a.extensions == b.extensions && a.slash == b.slash && a.inversion == b.inversion);
502
503        Ok(result)
504    }
505}
506
507impl HasName for Chord {
508    fn name(&self) -> String {
509        let known_name = self.known_chord().name();
510        let known_name = known_name.as_str();
511        let mut name = String::new();
512
513        name.push_str(self.root.static_name());
514
515        name.push_str(known_name);
516
517        // Add special modifiers that are true modifiers when not part of their "special case".
518
519        if self.modifiers.contains(&Modifier::Flat5) && !known_name.contains("(♭5)") {
520            name.push_str("(♭5)");
521        }
522
523        if self.modifiers.contains(&Modifier::Augmented5) && !known_name.contains('+') && !known_name.contains("(♯5)") {
524            name.push_str("(♯5)");
525        }
526
527        if self.modifiers.contains(&Modifier::Flat9) && !known_name.contains("(♭9)") {
528            name.push_str("(♭9)");
529        }
530
531        if self.modifiers.contains(&Modifier::Sharp9) && !known_name.contains("(♯9)") {
532            name.push_str("(♯9)");
533        }
534
535        if self.modifiers.contains(&Modifier::Sharp11) && !known_name.contains("(♯11)") {
536            name.push_str("(♯11)");
537        }
538
539        if self.modifiers.contains(&Modifier::Flat13) && !known_name.contains("(♭13)") {
540            name.push_str("(♭13)");
541        }
542
543        // Add extensions.
544        if !self.extensions.is_empty() {
545            for e in &self.extensions {
546                name.push_str(&format!("({})", e.static_name()));
547            }
548        }
549
550        // Add slash note.
551        if let Some(slash) = self.slash {
552            name.push_str(&format!("/{}", slash.static_name()));
553        }
554
555        // Add special information about the chord.
556
557        name
558    }
559}
560
561impl HasPreciseName for Chord {
562    fn precise_name(&self) -> String {
563        let mut name = String::new();
564
565        name.push_str(&self.name());
566
567        // Add octave modifier.
568        if self.root.octave() != Octave::Four {
569            name.push_str(&format!("@{}", self.root.octave().static_name()));
570        }
571
572        // Add inversion modifier.
573        if self.inversion != 0 {
574            name.push_str(&format!("^{}", self.inversion));
575        }
576
577        // Add crunchy modifier.
578        if self.is_crunchy {
579            name.push('!');
580        }
581
582        name
583    }
584}
585
586impl HasRoot for Chord {
587    fn root(&self) -> Note {
588        self.root
589    }
590}
591
592impl HasSlash for Chord {
593    fn slash(&self) -> Note {
594        self.slash.unwrap_or(self.root)
595    }
596}
597
598impl HasModifiers for Chord {
599    fn modifiers(&self) -> &HashSet<Modifier> {
600        &self.modifiers
601    }
602}
603
604impl HasExtensions for Chord {
605    fn extensions(&self) -> &HashSet<Extension> {
606        &self.extensions
607    }
608}
609
610impl HasInversion for Chord {
611    fn inversion(&self) -> u8 {
612        self.inversion
613    }
614}
615
616impl HasIsCrunchy for Chord {
617    fn is_crunchy(&self) -> bool {
618        self.is_crunchy
619    }
620}
621
622impl Display for Chord {
623    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
624        let scale = self.scale().iter().map(HasStaticName::static_name).collect::<Vec<_>>().join(", ");
625        let chord = self.chord().iter().map(HasStaticName::static_name).collect::<Vec<_>>().join(", ");
626
627        writeln!(f, "{}", self.precise_name())?;
628        writeln!(f, "   {}", self.description())?;
629        writeln!(f, "   {}", scale)?;
630        write!(f, "   {}", chord)?;
631
632        Ok(())
633    }
634}
635
636impl Chordable for Chord {
637    fn with_modifier(mut self, modifier: Modifier) -> Chord {
638        // Augmented modifiers trump b5 and dim modifiers.
639        if modifier == Modifier::Augmented5 {
640            self.modifiers.remove(&Modifier::Flat5);
641            self.modifiers.remove(&Modifier::Diminished);
642        }
643
644        if (modifier == Modifier::Diminished || modifier == Modifier::Flat5) && self.modifiers.contains(&Modifier::Augmented5) {
645            return self;
646        }
647
648        self.modifiers.insert(modifier);
649
650        self
651    }
652
653    fn with_modifiers(self, modifiers: &[Modifier]) -> Chord {
654        let mut chord = self;
655
656        for m in modifiers {
657            chord = chord.with_modifier(*m);
658        }
659
660        chord
661    }
662
663    fn with_extension(mut self, extension: Extension) -> Chord {
664        self.extensions.insert(extension);
665
666        self
667    }
668
669    fn with_extensions(self, extensions: &[Extension]) -> Chord {
670        let mut chord = self;
671
672        for e in extensions {
673            chord = chord.with_extension(*e);
674        }
675
676        chord
677    }
678
679    fn with_inversion(mut self, inversion: u8) -> Chord {
680        self.inversion = inversion;
681
682        self
683    }
684
685    fn with_slash(mut self, slash: Note) -> Chord {
686        self.slash = Some(slash);
687
688        self
689    }
690
691    fn with_octave(self, octave: Octave) -> Self {
692        let root = Note::new(self.root.named_pitch(), octave);
693
694        Chord { root, ..self }
695    }
696
697    fn with_crunchy(self, is_crunchy: bool) -> Chord {
698        Chord { is_crunchy, ..self }
699    }
700
701    // Modifiers.
702
703    fn minor(self) -> Chord {
704        self.with_modifier(Modifier::Minor)
705    }
706
707    fn flat5(self) -> Chord {
708        self.with_modifier(Modifier::Flat5)
709    }
710
711    fn flat_five(self) -> Chord {
712        self.flat5()
713    }
714
715    fn augmented(self) -> Chord {
716        self.with_modifier(Modifier::Augmented5)
717    }
718
719    fn aug(self) -> Chord {
720        self.augmented()
721    }
722
723    fn major7(self) -> Chord {
724        self.with_modifier(Modifier::Major7)
725    }
726
727    fn major_seven(self) -> Chord {
728        self.major7()
729    }
730
731    fn maj7(self) -> Chord {
732        self.major7()
733    }
734
735    fn dominant7(self) -> Chord {
736        self.with_modifier(Modifier::Dominant(Degree::Seven))
737    }
738
739    fn seven(self) -> Chord {
740        self.dominant7()
741    }
742
743    fn dominant9(self) -> Chord {
744        self.with_modifier(Modifier::Dominant(Degree::Nine))
745    }
746
747    fn nine(self) -> Chord {
748        self.dominant9()
749    }
750
751    fn dominant11(self) -> Chord {
752        self.with_modifier(Modifier::Dominant(Degree::Eleven))
753    }
754
755    fn eleven(self) -> Chord {
756        self.dominant11()
757    }
758
759    fn dominant13(self) -> Chord {
760        self.with_modifier(Modifier::Dominant(Degree::Thirteen))
761    }
762
763    fn thirteen(self) -> Chord {
764        self.dominant13()
765    }
766
767    fn dominant(self, dominant: Degree) -> Chord {
768        self.with_modifier(Modifier::Dominant(dominant))
769    }
770
771    fn flat9(self) -> Chord {
772        self.with_modifier(Modifier::Flat9)
773    }
774
775    fn flat_nine(self) -> Chord {
776        self.flat9()
777    }
778
779    fn sharp9(self) -> Chord {
780        self.with_modifier(Modifier::Sharp9)
781    }
782
783    fn sharp_nine(self) -> Chord {
784        self.sharp9()
785    }
786
787    fn sharp11(self) -> Chord {
788        self.with_modifier(Modifier::Sharp11)
789    }
790
791    fn sharp_eleven(self) -> Chord {
792        self.sharp11()
793    }
794
795    // Special.
796
797    fn diminished(self) -> Chord {
798        self.with_modifier(Modifier::Diminished)
799    }
800
801    fn dim(self) -> Chord {
802        self.diminished()
803    }
804
805    fn half_diminished(self) -> Chord {
806        self.minor().seven().flat5()
807    }
808
809    fn half_dim(self) -> Chord {
810        self.half_diminished()
811    }
812
813    // Extensions.
814
815    fn sus2(self) -> Chord {
816        self.with_extension(Extension::Sus2)
817    }
818
819    fn sus_two(self) -> Chord {
820        self.sus2()
821    }
822
823    fn sus4(self) -> Chord {
824        self.with_extension(Extension::Sus4)
825    }
826
827    fn sus_four(self) -> Chord {
828        self.sus4()
829    }
830
831    fn sustain(self) -> Chord {
832        self.sus4()
833    }
834
835    fn sus(self) -> Chord {
836        self.sustain()
837    }
838
839    fn flat11(self) -> Chord {
840        self.with_extension(Extension::Flat11)
841    }
842
843    fn flat_eleven(self) -> Chord {
844        self.flat11()
845    }
846
847    fn flat13(self) -> Chord {
848        self.with_modifier(Modifier::Flat13)
849    }
850
851    fn flat_thirteen(self) -> Chord {
852        self.flat13()
853    }
854
855    fn sharp13(self) -> Chord {
856        self.with_extension(Extension::Sharp13)
857    }
858
859    fn sharp_thirteen(self) -> Chord {
860        self.sharp13()
861    }
862
863    fn add2(self) -> Chord {
864        self.with_extension(Extension::Add2)
865    }
866
867    fn add_two(self) -> Chord {
868        self.add2()
869    }
870
871    fn add4(self) -> Chord {
872        self.with_extension(Extension::Add4)
873    }
874
875    fn add_four(self) -> Chord {
876        self.add4()
877    }
878
879    fn add6(self) -> Chord {
880        self.with_extension(Extension::Add6)
881    }
882
883    fn add_six(self) -> Chord {
884        self.add6()
885    }
886
887    fn add9(self) -> Chord {
888        self.with_extension(Extension::Add9)
889    }
890
891    fn add_nine(self) -> Chord {
892        self.add9()
893    }
894
895    fn add11(self) -> Chord {
896        self.with_extension(Extension::Add11)
897    }
898
899    fn add_eleven(self) -> Chord {
900        self.add11()
901    }
902
903    fn add13(self) -> Chord {
904        self.with_extension(Extension::Add13)
905    }
906
907    fn add_thirteen(self) -> Chord {
908        self.add13()
909    }
910}
911
912impl HasKnownChord for Chord {
913    fn known_chord(&self) -> KnownChord {
914        let modifiers = &self.modifiers;
915        let degree = self.dominant_degree();
916
917        let contains_dominant = degree.is_some();
918        let degree = degree.unwrap_or(Degree::Seven);
919
920        if modifiers.contains(&Modifier::Diminished) {
921            KnownChord::Diminished
922        } else if modifiers.contains(&Modifier::Minor) {
923            if modifiers.contains(&Modifier::Major7) {
924                return KnownChord::MinorMajor7;
925            }
926
927            if contains_dominant {
928                if modifiers.contains(&Modifier::Flat5) {
929                    return KnownChord::HalfDiminished(degree);
930                }
931
932                if modifiers.contains(&Modifier::Flat13) {
933                    if modifiers.contains(&Modifier::Flat9) {
934                        return KnownChord::MinorDominantFlat9Flat13(degree);
935                    }
936
937                    return KnownChord::MinorDominantFlat13(degree);
938                }
939
940                return KnownChord::MinorDominant(degree);
941            }
942
943            KnownChord::Minor
944        } else {
945            if modifiers.contains(&Modifier::Augmented5) {
946                if modifiers.contains(&Modifier::Major7) {
947                    return KnownChord::AugmentedMajor7;
948                }
949
950                if contains_dominant {
951                    if modifiers.contains(&Modifier::Flat9) {
952                        return KnownChord::AugmentedDominantFlat9(degree);
953                    }
954
955                    return KnownChord::AugmentedDominant(degree);
956                }
957
958                return KnownChord::Augmented;
959            }
960
961            if self.modifiers.contains(&Modifier::Major7) {
962                return KnownChord::Major7;
963            }
964
965            if contains_dominant {
966                if modifiers.contains(&Modifier::Flat9) {
967                    return KnownChord::DominantFlat9(degree);
968                }
969
970                if modifiers.contains(&Modifier::Sharp9) {
971                    return KnownChord::DominantSharp9(degree);
972                }
973
974                if modifiers.contains(&Modifier::Sharp11) {
975                    return KnownChord::DominantSharp11(degree);
976                }
977
978                return KnownChord::Dominant(degree);
979            }
980
981            // This is a special case where the sharp 11 has to be "alone".
982            if self.modifiers.contains(&Modifier::Sharp11) && modifiers.len() == 1 {
983                return KnownChord::Sharp11;
984            }
985
986            KnownChord::Major
987        }
988    }
989}
990
991impl HasDescription for Chord {
992    fn description(&self) -> &'static str {
993        self.known_chord().description()
994    }
995}
996
997impl Chord {
998    /// Returns the static interval candidates for this chord
999    pub fn scale_interval_candidates(&self) -> &'static [IntervalCandidate] {
1000        self.known_chord().scale_interval_candidates()
1001    }
1002}
1003
1004use crate::core::interval::HasIntervals;
1005
1006impl HasIntervals for Chord {
1007    fn intervals(&self) -> &'static [Interval] {
1008        self.known_chord().intervals()
1009    }
1010}
1011
1012impl HasScaleCandidates for Chord {
1013    fn scale_candidates(&self) -> Vec<ScaleCandidate> {
1014        self.scale_interval_candidates().iter().map(IntervalCandidate::to_scale_candidate).collect()
1015    }
1016}
1017
1018impl HasRelativeScale for Chord {
1019    fn relative_scale(&self) -> Vec<Interval> {
1020        self.known_chord().relative_scale()
1021    }
1022}
1023
1024impl HasRelativeChord for Chord {
1025    fn relative_chord(&self) -> Vec<Interval> {
1026        let mut result = self.known_chord().relative_chord();
1027        let modifiers = &self.modifiers;
1028        let extensions = &self.extensions;
1029
1030        // Dominant extensions.
1031
1032        if modifiers.contains(&Modifier::Dominant(Degree::Nine)) {
1033            result.push(Interval::MajorNinth);
1034        } else if modifiers.contains(&Modifier::Dominant(Degree::Eleven)) {
1035            result.push(Interval::MajorNinth);
1036            result.push(Interval::PerfectEleventh);
1037        } else if modifiers.contains(&Modifier::Dominant(Degree::Thirteen)) {
1038            result.push(Interval::MajorNinth);
1039            result.push(Interval::PerfectEleventh);
1040            result.push(Interval::MajorThirteenth);
1041        }
1042
1043        // Special modifiers that can also be extensions.
1044
1045        if modifiers.contains(&Modifier::Flat5) {
1046            result.remove(2);
1047            result.push(Interval::DiminishedFifth);
1048        }
1049
1050        if modifiers.contains(&Modifier::Augmented5) {
1051            result.remove(2);
1052            result.push(Interval::AugmentedFifth);
1053        }
1054
1055        if modifiers.contains(&Modifier::Flat9) {
1056            result.push(Interval::MinorNinth);
1057        }
1058
1059        if modifiers.contains(&Modifier::Sharp9) {
1060            result.push(Interval::AugmentedNinth);
1061        }
1062
1063        if modifiers.contains(&Modifier::Sharp11) {
1064            result.push(Interval::AugmentedEleventh);
1065        }
1066
1067        if modifiers.contains(&Modifier::Flat13) {
1068            result.push(Interval::MinorThirteenth);
1069        }
1070
1071        // Extensions.
1072
1073        if extensions.contains(&Extension::Sus2) {
1074            result.remove(1);
1075            result.push(Interval::MajorSecond);
1076        }
1077
1078        if extensions.contains(&Extension::Sus4) {
1079            result.remove(1);
1080            result.push(Interval::PerfectFourth);
1081        }
1082
1083        if extensions.contains(&Extension::Flat11) {
1084            result.push(Interval::DiminishedEleventh);
1085        }
1086
1087        if extensions.contains(&Extension::Sharp13) {
1088            result.push(Interval::AugmentedThirteenth);
1089        }
1090
1091        if extensions.contains(&Extension::Add2) {
1092            result.push(Interval::MajorSecond);
1093        }
1094
1095        if extensions.contains(&Extension::Add4) {
1096            result.push(Interval::PerfectFourth);
1097        }
1098
1099        if extensions.contains(&Extension::Add6) {
1100            result.push(Interval::MajorSixth);
1101        }
1102
1103        if extensions.contains(&Extension::Add9) {
1104            result.push(Interval::MajorNinth);
1105        }
1106
1107        if extensions.contains(&Extension::Add11) {
1108            result.push(Interval::PerfectEleventh);
1109        }
1110
1111        if extensions.contains(&Extension::Add13) {
1112            result.push(Interval::MajorThirteenth);
1113        }
1114
1115        // Keep everything in order.
1116        result.sort();
1117        result.dedup();
1118
1119        result
1120    }
1121}
1122
1123impl HasScale for Chord {
1124    fn scale(&self) -> Vec<Note> {
1125        // Get the first (primary) interval candidate and root it at self.root()
1126        let candidates = self.scale_interval_candidates();
1127        if let Some(candidate) = candidates.first() {
1128            match candidate.kind {
1129                IntervalCollectionKind::Mode(kind) => crate::core::mode::Mode::new(self.root, kind).notes(),
1130                IntervalCollectionKind::Scale(kind) => crate::core::scale::Scale::new(self.root, kind).notes(),
1131            }
1132        } else {
1133            // Fallback to relative_scale if no candidates (shouldn't happen except for Unknown)
1134            self.relative_scale().into_iter().map(|i| self.root + i).collect()
1135        }
1136    }
1137}
1138
1139impl HasChord for Chord {
1140    fn chord(&self) -> Vec<Note> {
1141        let mut result: Vec<_> = self.relative_chord().into_iter().map(|i| self.root + i).collect();
1142
1143        // Perform inversions.
1144        for _ in 0..self.inversion {
1145            let mut note = result.remove(0);
1146
1147            while note < *result.last().unwrap_or(&CZero) {
1148                note += Interval::PerfectOctave;
1149            }
1150
1151            result.push(note);
1152        }
1153
1154        // If this chord is crunchy, bring all "octave" intervals down to the first octave frame.
1155        if self.is_crunchy {
1156            let bottom = *result.first().unwrap_or(&CZero);
1157            let top = bottom.with_octave(bottom.octave() + 1);
1158
1159            for note in &mut result {
1160                while *note > top {
1161                    *note = note.with_octave(note.octave() - 1);
1162                }
1163            }
1164        }
1165
1166        // Add slash note.
1167        if let Some(mut slash) = self.slash {
1168            // Fix slash note (it should be less than, or equal to, one octave away from the bottom tone).
1169            let bottom = *result.first().unwrap_or(&CZero);
1170            let floor = Note::new(bottom.named_pitch(), bottom.octave() - 1);
1171
1172            slash = slash.with_octave(Octave::Zero);
1173            while slash < floor {
1174                slash += Interval::PerfectOctave;
1175            }
1176
1177            result.insert(0, slash);
1178        }
1179
1180        // Crunchiness, etc. can introduce changes, so resort, and dedup.
1181        result.sort();
1182        result.dedup();
1183
1184        result
1185    }
1186}
1187
1188impl HasDomninantDegree for Chord {
1189    fn dominant_degree(&self) -> Option<Degree> {
1190        let modifiers = &self.modifiers;
1191        if !modifiers.contains(&Modifier::Dominant(Degree::Seven))
1192            && !modifiers.contains(&Modifier::Dominant(Degree::Nine))
1193            && !modifiers.contains(&Modifier::Dominant(Degree::Eleven))
1194            && !modifiers.contains(&Modifier::Dominant(Degree::Thirteen))
1195        {
1196            return None;
1197        }
1198
1199        Some(match modifiers.iter().find(|m| m.is_dominant()) {
1200            Some(Modifier::Dominant(d)) => *d,
1201            _ => Degree::Seven,
1202        })
1203    }
1204}
1205
1206impl Parsable for Chord {
1207    fn parse(input: &str) -> Res<Self>
1208    where
1209        Self: Sized,
1210    {
1211        let root = ChordParser::parse(Rule::chord, input)?.next().unwrap();
1212
1213        assert_eq!(Rule::chord, root.as_rule());
1214
1215        let mut components = root.into_inner();
1216
1217        let note = components.next().unwrap();
1218
1219        assert_eq!(Rule::note, note.as_rule());
1220
1221        let mut result = Chord::new(note_str_to_note(note.into_inner().as_str())?);
1222
1223        while let Some(component) = components.next() {
1224            match component.as_rule() {
1225                Rule::maj7_modifier => {
1226                    result = result.major7();
1227                }
1228                Rule::minor => {
1229                    result = result.minor();
1230                }
1231                Rule::augmented => {
1232                    result = result.augmented();
1233                }
1234                Rule::diminished => {
1235                    result = result.diminished();
1236                }
1237                Rule::half_diminished => {
1238                    result = result.half_diminished();
1239                }
1240                Rule::dominant_modifier => match component.as_str() {
1241                    "7" => {
1242                        result = result.seven();
1243                    }
1244                    "9" => {
1245                        result = result.nine();
1246                    }
1247                    "11" => {
1248                        result = result.eleven();
1249                    }
1250                    "13" => {
1251                        result = result.thirteen();
1252                    }
1253                    _ => {
1254                        return Err(anyhow::Error::msg(format!("Unknown dominant modifier: {}", component.as_str())));
1255                    }
1256                },
1257                Rule::modifier => match component.as_str() {
1258                    "sus2" => {
1259                        result = result.sus2();
1260                    }
1261                    "sus4" => {
1262                        result = result.sus4();
1263                    }
1264                    "add2" => {
1265                        result = result.add2();
1266                    }
1267                    "add4" => {
1268                        result = result.add4();
1269                    }
1270                    "add6" | "6" => {
1271                        result = result.add6();
1272                    }
1273                    "b5" | "♭5" => {
1274                        result = result.flat5();
1275                    }
1276                    "#5" | "♯5" => {
1277                        result = result.augmented();
1278                    }
1279                    "add9" => {
1280                        result = result.add9();
1281                    }
1282                    "b9" | "♭9" => {
1283                        result = result.flat9();
1284                    }
1285                    "#9" | "♯9" => {
1286                        result = result.sharp9();
1287                    }
1288                    "add11" => {
1289                        result = result.add11();
1290                    }
1291                    "b11" | "♭11" => {
1292                        result = result.flat11();
1293                    }
1294                    "#11" | "♯11" => {
1295                        result = result.sharp11();
1296                    }
1297                    "add13" => {
1298                        result = result.add13();
1299                    }
1300                    "b13" | "♭13" => {
1301                        result = result.flat13();
1302                    }
1303                    "#13" | "♯13" => {
1304                        result = result.sharp13();
1305                    }
1306                    _ => {
1307                        return Err(anyhow::Error::msg(format!("Unknown modifier: {}", component.as_str())));
1308                    }
1309                },
1310                Rule::slash => {
1311                    let note = note_str_to_note(components.next().unwrap().as_str())?;
1312
1313                    result = result.with_slash(note);
1314                }
1315                Rule::at => {
1316                    let octave = octave_str_to_octave(components.next().unwrap().as_str())?;
1317
1318                    result = result.with_octave(octave);
1319                }
1320                Rule::hat => {
1321                    let inversion = components.next().unwrap().as_str().parse::<u8>()?;
1322
1323                    result = result.with_inversion(inversion);
1324                }
1325                Rule::bang => {
1326                    result = result.with_crunchy(true);
1327                }
1328                Rule::EOI => {}
1329                _ => {
1330                    return Err(anyhow::Error::msg(format!("Unknown rule in chord parser: {:?}", component.as_rule())));
1331                }
1332            }
1333        }
1334
1335        Ok(result)
1336    }
1337}
1338
1339#[cfg(feature = "audio")]
1340use super::base::{Playable, PlaybackHandle};
1341
1342#[cfg(feature = "audio")]
1343impl Playable for Chord {
1344    #[coverage(off)]
1345    fn play(&self, delay: std::time::Duration, length: std::time::Duration, fade_in: std::time::Duration) -> Res<PlaybackHandle> {
1346        use rodio::{source::SineWave, OutputStreamBuilder, Sink, Source};
1347
1348        let chord_tones = self.chord();
1349
1350        if length.as_secs_f32() <= chord_tones.len() as f32 * delay.as_secs_f32() {
1351            return Err(anyhow::Error::msg(
1352                "The delay is too long for the length of play (i.e., the number of chord tones times the delay is longer than the length).",
1353            ));
1354        }
1355
1356        let stream = OutputStreamBuilder::open_default_stream()?;
1357
1358        let mut sinks = vec![];
1359
1360        for (k, n) in chord_tones.into_iter().enumerate() {
1361            let sink = Sink::connect_new(stream.mixer());
1362
1363            let d = delay * k as u32;
1364
1365            let source = SineWave::new(n.frequency()).take_duration(length - d).buffered().delay(d).fade_in(fade_in).amplify(0.20);
1366
1367            sink.append(source);
1368
1369            sinks.push(sink);
1370        }
1371
1372        Ok(PlaybackHandle::new(stream, sinks))
1373    }
1374}
1375
1376impl Default for Chord {
1377    fn default() -> Self {
1378        Chord::new(super::note::C)
1379    }
1380}
1381
1382impl Chord {
1383    /// Formats the chord with full scale/mode candidate recommendations.
1384    ///
1385    /// This returns a verbose string representation that includes:
1386    /// - Chord name, description, scale notes, and chord tones
1387    /// - Complete list of recommended scales/modes with rankings, reasons, notes, and descriptions
1388    ///
1389    /// Use this when you want comprehensive improvisation guidance.
1390    /// For minimal output, use `Display` instead (via `to_string()` or `format!("{}", chord)`).
1391    pub fn format_with_scale_candidates(&self) -> String {
1392        use std::fmt::Write;
1393
1394        let mut result = String::new();
1395
1396        let scale = self.scale().iter().map(HasStaticName::static_name).collect::<Vec<_>>().join(", ");
1397        let chord = self.chord().iter().map(HasStaticName::static_name).collect::<Vec<_>>().join(", ");
1398
1399        writeln!(&mut result, "{}", self.precise_name()).unwrap();
1400        writeln!(&mut result, "   {}", self.description()).unwrap();
1401        writeln!(&mut result, "   {}", scale).unwrap();
1402        writeln!(&mut result, "   {}", chord).unwrap();
1403
1404        // Add scale/mode candidates
1405        let candidates = self.scale_candidates();
1406        if !candidates.is_empty() {
1407            writeln!(&mut result).unwrap();
1408            writeln!(&mut result, "   Recommended scales/modes:").unwrap();
1409            for candidate in candidates {
1410                let notes = candidate.notes(self.root());
1411                let notes_str = notes.iter().map(HasStaticName::static_name).collect::<Vec<_>>().join(", ");
1412                writeln!(&mut result, "     {}. {} - {} ({})", candidate.rank(), candidate.name(), candidate.reason(), notes_str).unwrap();
1413                writeln!(&mut result, "        {}", candidate.description()).unwrap();
1414            }
1415        }
1416
1417        result
1418    }
1419}
1420
1421// Tests.
1422
1423#[cfg(test)]
1424mod tests {
1425    use super::*;
1426    use crate::core::{note::*, octave::HasOctave};
1427    use pretty_assertions::assert_eq;
1428
1429    #[test]
1430    fn test_text() {
1431        assert_eq!(Chord::new(C).flat9().sharp9().sharp11().add13().with_slash(E).name(), "C(♭9)(♯9)(♯11)(add13)/E");
1432        assert_eq!(Chord::new(C).flat5().name(), "C(♭5)");
1433        assert_eq!(Chord::new(C).minor().augmented().name(), "Cm(♯5)");
1434        assert_eq!(Chord::new(C).with_octave(Octave::Six).precise_name(), "C@6");
1435
1436        // Test Display is minimal (no scale candidates)
1437        let display_output = format!("{}", Chord::new(C).minor().seven().flat_five());
1438        assert!(display_output.contains("Cm7(♭5)"));
1439        assert!(display_output.contains("half diminished"));
1440        assert!(display_output.contains("C, D♭, E♭, F, G♭, A♭, B♭"));
1441        assert!(display_output.contains("C, E♭, G♭, B♭"));
1442        assert!(!display_output.contains("Recommended scales/modes:"));
1443
1444        // Test format_with_scale_candidates includes recommendations
1445        let verbose_output = Chord::new(C).minor().seven().flat_five().format_with_scale_candidates();
1446        assert!(verbose_output.contains("Recommended scales/modes:"));
1447        assert!(verbose_output.contains("locrian"));
1448    }
1449
1450    #[test]
1451    fn test_display_format() {
1452        // Test that Display output is minimal and stable (no scale candidates)
1453        let chord = Chord::new(C);
1454        let output = format!("{}", chord);
1455        let expected = "C\n   major\n   C, D, E, F, G, A, B\n   C, E, G";
1456        assert_eq!(output, expected);
1457
1458        // Test that format_with_scale_candidates includes recommendations
1459        let verbose_output = chord.format_with_scale_candidates();
1460        assert!(verbose_output.contains("Recommended scales/modes:"));
1461        assert!(verbose_output.contains("ionian"));
1462        assert!(verbose_output.contains("major pentatonic"));
1463    }
1464
1465    #[test]
1466    fn test_properties() {
1467        assert_eq!(Chord::new(C).seven().flat9().root(), C);
1468        assert_eq!(Chord::new(C).with_slash(E).slash(), E);
1469        assert_eq!(Chord::new(C).slash(), C);
1470        assert_eq!(Chord::new(C).flat9().add13().with_slash(E).modifiers(), &vec![Modifier::Flat9].into_iter().collect::<HashSet<_>>());
1471        assert_eq!(Chord::new(C).flat9().add13().with_slash(E).extensions(), &vec![Extension::Add13].into_iter().collect::<HashSet<_>>());
1472        assert_eq!(Chord::new(C).flat9().add13().with_slash(E).seven().dominant_degree(), Some(Degree::Seven));
1473        assert_eq!(Chord::new(C).flat9().add13().with_slash(E).nine().dominant_degree(), Some(Degree::Nine));
1474        assert_eq!(Chord::new(C).flat9().with_inversion(1).inversion(), 1);
1475        assert_eq!(Chord::new(C).flat9().with_octave(Octave::Three).root().octave(), Octave::Three);
1476    }
1477
1478    #[test]
1479    fn test_known_chords() {
1480        assert_eq!(Chord::new(C).known_chord(), KnownChord::Major);
1481        assert_eq!(Chord::new(C).minor().known_chord(), KnownChord::Minor);
1482        assert_eq!(Chord::new(C).major7().known_chord(), KnownChord::Major7);
1483        assert_eq!(Chord::new(C).minor().major7().known_chord(), KnownChord::MinorMajor7);
1484        assert_eq!(Chord::new(C).minor().dominant(Degree::Seven).known_chord(), KnownChord::MinorDominant(Degree::Seven));
1485        assert_eq!(Chord::new(C).minor().eleven().known_chord(), KnownChord::MinorDominant(Degree::Eleven));
1486        assert_eq!(Chord::new(C).seven().known_chord(), KnownChord::Dominant(Degree::Seven));
1487        assert_eq!(Chord::new(C).eleven().known_chord(), KnownChord::Dominant(Degree::Eleven));
1488        assert_eq!(Chord::new(C).thirteen().known_chord(), KnownChord::Dominant(Degree::Thirteen));
1489        assert_eq!(Chord::new(C).diminished().known_chord(), KnownChord::Diminished);
1490        assert_eq!(Chord::new(C).dim().known_chord(), KnownChord::Diminished);
1491        assert_eq!(Chord::new(C).minor().seven().flat5().known_chord(), KnownChord::HalfDiminished(Degree::Seven));
1492        assert_eq!(Chord::new(C).augmented().known_chord(), KnownChord::Augmented);
1493        assert_eq!(Chord::new(C).aug().major7().known_chord(), KnownChord::AugmentedMajor7);
1494        assert_eq!(Chord::new(C).augmented().seven().known_chord(), KnownChord::AugmentedDominant(Degree::Seven));
1495        assert_eq!(Chord::new(C).seven().sharp11().known_chord(), KnownChord::DominantSharp11(Degree::Seven));
1496        assert_eq!(Chord::new(C).seven().flat9().known_chord(), KnownChord::DominantFlat9(Degree::Seven));
1497        assert_eq!(Chord::new(C).seven().sharp9().known_chord(), KnownChord::DominantSharp9(Degree::Seven));
1498        assert_eq!(Chord::new(C).seven().flat9().augmented().known_chord(), KnownChord::AugmentedDominantFlat9(Degree::Seven));
1499
1500        assert_eq!(Chord::new(C).sus2().known_chord(), KnownChord::Major);
1501        assert_eq!(Chord::new(C).sus4().known_chord(), KnownChord::Major);
1502        assert_eq!(Chord::new(C).sustain().known_chord(), KnownChord::Major);
1503        assert_eq!(Chord::new(C).seven().sus().known_chord(), KnownChord::Dominant(Degree::Seven));
1504    }
1505
1506    #[test]
1507    fn test_scales() {
1508        // Basic.
1509
1510        assert_eq!(Chord::new(C).scale(), vec![C, D, E, F, G, A, B]);
1511        assert_eq!(Chord::new(C).minor().scale(), vec![C, D, EFlat, F, G, AFlat, BFlat]);
1512        assert_eq!(Chord::new(C).major_seven().scale(), vec![C, D, E, F, G, A, B]);
1513        assert_eq!(Chord::new(C).minor().maj7().scale(), vec![C, D, EFlat, F, G, A, B]);
1514        assert_eq!(Chord::new(C).minor().seven().scale(), vec![C, D, EFlat, F, G, A, BFlat]);
1515        assert_eq!(Chord::new(C).minor().eleven().scale(), vec![C, D, EFlat, F, G, A, BFlat]);
1516        assert_eq!(Chord::new(C).seven().scale(), vec![C, D, E, F, G, A, BFlat]);
1517        assert_eq!(Chord::new(C).eleven().scale(), vec![C, D, E, F, G, A, BFlat]);
1518        assert_eq!(Chord::new(C).thirteen().scale(), vec![C, D, E, F, G, A, BFlat]);
1519        assert_eq!(Chord::new(C).diminished().scale(), vec![C, D, EFlat, F, GFlat, AFlat, BDoubleFlat, B]);
1520        assert_eq!(Chord::new(C).dim().scale(), vec![C, D, EFlat, F, GFlat, AFlat, BDoubleFlat, B]);
1521        assert_eq!(Chord::new(C).minor().seven().flat5().scale(), vec![C, DFlat, EFlat, F, GFlat, AFlat, BFlat]);
1522        assert_eq!(Chord::new(C).augmented().scale(), vec![C, D, E, F, GSharp, A, B]);
1523        assert_eq!(Chord::new(C).augmented().major7().scale(), vec![C, D, E, FSharp, GSharp, A, B]);
1524        assert_eq!(Chord::new(C).augmented().seven().scale(), vec![C, D, E, FSharp, GSharp, ASharp]);
1525        assert_eq!(Chord::new(C).seven().sharp_eleven().scale(), vec![C, D, E, FSharp, G, A, BFlat]);
1526        assert_eq!(Chord::new(C).seven().flat_nine().scale(), vec![C, DFlat, EFlat, E, FSharp, G, A, BFlat]);
1527        assert_eq!(Chord::new(C).seven().sharp_nine().scale(), vec![C, DFlat, EFlat, FFlat, GFlat, AFlat, BFlat]);
1528
1529        // Others.
1530
1531        assert_eq!(Chord::new(DFlat).scale(), vec![DFlat, EFlat, F, GFlat, AFlat, BFlat, CFive]);
1532        assert_eq!(Chord::new(DFlat).seven().scale(), vec![DFlat, EFlat, F, GFlat, AFlat, BFlat, CFlatFive]);
1533        assert_eq!(Chord::new(DFlat).dim().scale(), vec![DFlat, EFlat, FFlat, GFlat, ADoubleFlat, BDoubleFlat, CDoubleFlatFive, CFive]);
1534    }
1535
1536    #[test]
1537    fn test_chords() {
1538        // Basic.
1539
1540        assert_eq!(Chord::new(C).chord(), vec![C, E, G]);
1541        assert_eq!(Chord::new(C).minor().chord(), vec![C, EFlat, G]);
1542        assert_eq!(Chord::new(C).major7().chord(), vec![C, E, G, B]);
1543        assert_eq!(Chord::new(C).minor().major7().chord(), vec![C, EFlat, G, B]);
1544        assert_eq!(Chord::new(C).minor().seven().chord(), vec![C, EFlat, G, BFlat]);
1545        assert_eq!(Chord::new(C).minor().eleven().chord(), vec![C, EFlat, G, BFlat, DFive, FFive]);
1546        assert_eq!(Chord::new(C).seven().chord(), vec![C, E, G, BFlat]);
1547        assert_eq!(Chord::new(C).eleven().chord(), vec![C, E, G, BFlat, DFive, FFive]);
1548        assert_eq!(Chord::new(C).thirteen().chord(), vec![C, E, G, BFlat, DFive, FFive, AFive]);
1549        assert_eq!(Chord::new(C).diminished().chord(), vec![C, EFlat, GFlat, BDoubleFlat]);
1550        assert_eq!(Chord::new(C).dim().chord(), vec![C, EFlat, GFlat, BDoubleFlat]);
1551        assert_eq!(Chord::new(C).minor().seven().flat5().chord(), vec![C, EFlat, GFlat, BFlat]);
1552        assert_eq!(Chord::new(C).half_diminished().chord(), vec![C, EFlat, GFlat, BFlat]);
1553        assert_eq!(Chord::new(C).half_dim().chord(), vec![C, EFlat, GFlat, BFlat]);
1554        assert_eq!(Chord::new(C).augmented().chord(), vec![C, E, GSharp]);
1555        assert_eq!(Chord::new(C).augmented().major7().chord(), vec![C, E, GSharp, B]);
1556        assert_eq!(Chord::new(C).augmented().seven().chord(), vec![C, E, GSharp, BFlat]);
1557        assert_eq!(Chord::new(C).seven().sharp11().chord(), vec![C, E, G, BFlat, FSharpFive]);
1558        assert_eq!(Chord::new(C).seven().flat_nine().chord(), vec![C, E, G, BFlat, DFlatFive]);
1559        assert_eq!(Chord::new(C).seven().sharp_nine().chord(), vec![C, E, G, BFlat, DSharpFive]);
1560
1561        // Extensions.
1562
1563        assert_eq!(Chord::new(C).nine().sus2().chord(), vec![C, D, G, BFlat, DFive]);
1564        assert_eq!(Chord::new(C).nine().sus_two().chord(), vec![C, D, G, BFlat, DFive]);
1565        assert_eq!(Chord::new(C).nine().sus4().chord(), vec![C, F, G, BFlat, DFive]);
1566        assert_eq!(Chord::new(C).nine().sus_four().chord(), vec![C, F, G, BFlat, DFive]);
1567        assert_eq!(Chord::new(C).nine().sustain().chord(), vec![C, F, G, BFlat, DFive]);
1568        assert_eq!(Chord::new(C).seven().sus().chord(), vec![C, F, G, BFlat]);
1569        assert_eq!(Chord::new(C).seven().add2().chord(), vec![C, D, E, G, BFlat]);
1570        assert_eq!(Chord::new(C).seven().add_two().chord(), vec![C, D, E, G, BFlat]);
1571        assert_eq!(Chord::new(C).seven().add4().chord(), vec![C, E, F, G, BFlat]);
1572        assert_eq!(Chord::new(C).seven().add_four().chord(), vec![C, E, F, G, BFlat]);
1573        assert_eq!(Chord::new(C).add6().chord(), vec![C, E, G, A]);
1574        assert_eq!(Chord::new(C).seven().add9().chord(), vec![C, E, G, BFlat, DFive]);
1575        assert_eq!(Chord::new(C).seven().add_nine().chord(), vec![C, E, G, BFlat, DFive]);
1576        assert_eq!(Chord::new(C).seven().add11().chord(), vec![C, E, G, BFlat, FFive]);
1577        assert_eq!(Chord::new(C).seven().add_eleven().chord(), vec![C, E, G, BFlat, FFive]);
1578        assert_eq!(Chord::new(C).seven().add13().chord(), vec![C, E, G, BFlat, AFive]);
1579        assert_eq!(Chord::new(C).seven().add_thirteen().chord(), vec![C, E, G, BFlat, AFive]);
1580        assert_eq!(Chord::new(C).seven().add2().add4().chord(), vec![C, D, E, F, G, BFlat]);
1581        assert_eq!(Chord::new(C).seven().add6().chord(), vec![C, E, G, A, BFlat]);
1582        assert_eq!(Chord::new(C).seven().add_six().chord(), vec![C, E, G, A, BFlat]);
1583        assert_eq!(Chord::new(C).seven().flat11().chord(), vec![C, E, G, BFlat, FFlatFive]);
1584        assert_eq!(Chord::new(C).seven().flat_eleven().chord(), vec![C, E, G, BFlat, FFlatFive]);
1585        assert_eq!(Chord::new(C).seven().flat13().chord(), vec![C, E, G, BFlat, AFlatFive]);
1586        assert_eq!(Chord::new(C).seven().flat_thirteen().chord(), vec![C, E, G, BFlat, AFlatFive]);
1587        assert_eq!(Chord::new(C).seven().sharp13().chord(), vec![C, E, G, BFlat, ASharpFive]);
1588        assert_eq!(Chord::new(C).seven().sharp_thirteen().chord(), vec![C, E, G, BFlat, ASharpFive]);
1589
1590        // Crunchy.
1591
1592        assert_eq!(Chord::new(C).seven().sharp9().with_crunchy(true).chord(), vec![C, DSharp, E, G, BFlat]);
1593
1594        // Slashes.
1595
1596        assert_eq!(Chord::new(C).with_slash(D).chord(), vec![DThree, C, E, G]);
1597        assert_eq!(Chord::new(CFive).with_slash(D).chord(), vec![DFour, CFive, EFive, GFive]);
1598
1599        // Inversions.
1600
1601        assert_eq!(C.into_chord().with_inversion(1).chord(), vec![E, G, CFive]);
1602        assert_eq!(C.into_chord().with_inversion(2).chord(), vec![G, CFive, EFive]);
1603        assert_eq!(C.into_chord().maj7().with_inversion(3).chord(), vec![B, CFive, EFive, GFive]);
1604        assert_eq!(BFlatThree.into_chord().seven().flat9().with_inversion(1).chord(), vec![D, F, AFlat, CFlatFive, BFlatFive]);
1605
1606        // Weird.
1607        assert_eq!(C.into_chord().flat5().aug().chord(), vec![C, E, GSharp]);
1608    }
1609
1610    #[test]
1611    fn test_parse() {
1612        assert_eq!(Chord::parse("C").unwrap().chord(), vec![C, E, G]);
1613        assert_eq!(Chord::parse("Cm").unwrap().chord(), vec![C, EFlat, G]);
1614        assert_eq!(Chord::parse("Cm7").unwrap().chord(), vec![C, EFlat, G, BFlat]);
1615        assert_eq!(Chord::parse("Cm7b5").unwrap().chord(), vec![C, EFlat, GFlat, BFlat]);
1616        assert_eq!(Chord::parse("C7").unwrap().chord(), vec![C, E, G, BFlat]);
1617        assert_eq!(Chord::parse("C7b9").unwrap().chord(), vec![C, E, G, BFlat, DFlatFive]);
1618        assert_eq!(Chord::parse("C7b9#11").unwrap().chord(), vec![C, E, G, BFlat, DFlatFive, FSharpFive]);
1619        assert_eq!(Chord::parse("C(add6)").unwrap().chord(), vec![C, E, G, A]);
1620        assert_eq!(Chord::parse("Em(#5)").unwrap().chord(), vec![E, G, BSharp]);
1621        assert_eq!(Chord::parse("D+11").unwrap().chord(), vec![D, FSharp, ASharp, CFive, EFive, GFive]);
1622        assert_eq!(Chord::parse("Dm13b5").unwrap().chord(), vec![D, F, AFlat, CFive, EFive, GFive, BFive]);
1623        assert_eq!(Chord::parse("Dsus2").unwrap().chord(), vec![D, E, A]);
1624        assert_eq!(Chord::parse("Dsus4").unwrap().chord(), vec![D, G, A]);
1625        assert_eq!(Chord::parse("Dadd2").unwrap().chord(), vec![D, E, FSharp, A]);
1626        assert_eq!(Chord::parse("Dadd4").unwrap().chord(), vec![D, FSharp, G, A]);
1627        assert_eq!(Chord::parse("Dadd9").unwrap().chord(), vec![D, FSharp, A, EFive]);
1628        assert_eq!(Chord::parse("Dadd11").unwrap().chord(), vec![D, FSharp, A, GFive]);
1629        assert_eq!(Chord::parse("Dadd13").unwrap().chord(), vec![D, FSharp, A, BFive]);
1630        assert_eq!(Chord::parse("Dm#9").unwrap().chord(), vec![D, F, A, ESharpFive]);
1631        assert_eq!(Chord::parse("Dmb11").unwrap().chord(), vec![D, F, A, GFlatFive]);
1632        assert_eq!(Chord::parse("D(b13)").unwrap().chord(), vec![D, FSharp, A, BFlatFive]);
1633        assert_eq!(Chord::parse("D(#13)").unwrap().chord(), vec![D, FSharp, A, BSharpFive]);
1634    }
1635
1636    #[test]
1637    fn test_guess() {
1638        assert_eq!(
1639            Chord::try_from_notes(&[EThree, C, EFlat, FSharp, ASharp, DFive]).unwrap().first().unwrap().chord(),
1640            Chord::parse("Cm9b5/E").unwrap().chord()
1641        );
1642        assert_eq!(Chord::try_from_notes(&[C, E, G]).unwrap().first().unwrap().chord(), Chord::parse("C").unwrap().chord());
1643        assert_eq!(
1644            Chord::try_from_notes(&[C, E, G, BFlat, DFive, FFive]).unwrap().first().unwrap().chord(),
1645            Chord::parse("C11").unwrap().chord()
1646        );
1647        assert_eq!(
1648            Chord::try_from_notes(&[C, E, G, BFlat, DFive, FFive, AFive]).unwrap().first().unwrap().chord(),
1649            Chord::parse("C13").unwrap().chord()
1650        );
1651        assert_eq!(Chord::try_from_notes(&[C, EFlat, GFlat, A]).unwrap().first().unwrap().chord(), Chord::parse("Cdim").unwrap().chord());
1652    }
1653
1654    #[test]
1655    #[should_panic(expected = "Must have at least three notes to guess a chord.")]
1656    fn test_chord_from_notes_failure() {
1657        Chord::try_from_notes(&[C, E]).unwrap();
1658    }
1659}