Skip to main content

music21_rs/roman/
mod.rs

1use crate::{
2    chord::{Chord, root::pitch_class},
3    chordsymbol::{ChordQuality, ChordSymbol},
4    defaults::{FloatType, IntegerType},
5    error::{Error, Result},
6    figuredbass::{Figure, Notation},
7    interval::Interval,
8    key::Key,
9    pitch::{Accidental, Pitch},
10};
11use std::fmt;
12
13mod analysis;
14mod figure;
15mod realize;
16
17pub use analysis::{
18    analyze_chord, analyze_chord_with_root, correct_rn_alteration_for_minor,
19    correct_suffix_for_chord_quality, figure_tuples, identify_as_tonic_or_dominant,
20    roman_inversion_name, roman_numeral_from_chord,
21};
22pub(crate) use figure::degree_to_roman;
23pub use figure::{
24    adjust_minor_vi_and_vii_by_quality, bass_scale_degree_from_notation, expand_shorthand,
25    parse_numeral_alone, secondary_key, split_roman_accidental_prefix, split_roman_prefix,
26    split_secondary, take_added_steps, take_bracketed_alterations, take_omitted_steps,
27};
28pub use realize::match_pitches_to_quality;
29
30use figure::*;
31use realize::*;
32
33/// How a figure on the sixth or seventh degree of a minor key decides
34/// between the natural and the raised degree: music21's `Minor67Default`.
35///
36/// Minor is two scales at once, and a `vi` might mean either of two chords.
37/// music21 lets a caller say which reading to use, and the readings differ
38/// in what they do with an accidental the figure already carries.
39#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
40#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
41pub enum Minor67Default {
42    /// Read it from the chord the figure asks for: a minor triad on the
43    /// sixth or a diminished chord on the seventh needs the raised degree,
44    /// and a major one needs the natural. This is music21's default and the
45    /// reading most scores assume.
46    #[default]
47    Quality,
48    /// Always the natural degree, whatever the figure asks for. A sharp
49    /// written in front still raises it.
50    Flat,
51    /// Always the raised degree. A flat written in front still lowers it.
52    Sharp,
53    /// As `Quality`, but an accidental already written in front is read as a
54    /// caution rather than as a further change — so `#vi` and `vi` are the
55    /// same chord, and so are `bVI` and `VI`.
56    Cautionary,
57}
58
59/// A parsed Roman numeral in a key.
60#[derive(Clone, Debug)]
61#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
62#[must_use]
63pub struct RomanNumeral {
64    figure: String,
65    key: Key,
66    degree: u8,
67    accidental: i8,
68    /// The alteration as it was written in front of the numeral, before the
69    /// minor sixth and seventh were read against the chord asked for.
70    ///
71    /// music21 keeps the two apart: `vi` in C minor reports `#vi` as its
72    /// numeral, because its root is a raised sixth degree, while the figure
73    /// it was written with carries no accidental at all.
74    written_accidental: i8,
75    inversion: u8,
76    seventh: bool,
77    quality: RomanQuality,
78    /// The quality the figure states, as music21 reads it off the symbol in
79    /// front of the inversion digits or off the case of the numeral. It is
80    /// what the notes read from the scale are respelled to.
81    implied_quality: ImpliedQuality,
82    /// The figured-bass column the digits stand for, with the omissions,
83    /// additions and alterations written in brackets beside them.
84    figures: FiguredBass,
85    secondary: Option<String>,
86    kind: RomanKind,
87    /// How the sixth and seventh degrees of a minor key are read.
88    sixth_minor: Minor67Default,
89    seventh_minor: Minor67Default,
90    /// The collection the figure is read over, when it is not a key at all.
91    ///
92    /// music21 takes a `ConcreteScale` wherever it takes a key, and reads
93    /// every degree off that instead — which is how a numeral means
94    /// something in a collection no key signature can write, such as the
95    /// octatonic. The key is still carried, as the major of the same tonic,
96    /// because everything that asks a numeral for its key expects one.
97    scale: Option<crate::scale::Scale>,
98    /// Whether an upper-case numeral means a major chord and a lower-case
99    /// one a minor chord.
100    ///
101    /// music21's `caseMatters`. The older figured-bass tradition writes every
102    /// numeral in upper case and lets the key say what the chord is, and a
103    /// numeral read that way states no quality at all: its notes are whatever
104    /// the scale spells, and the sixth and seventh of a minor key are left
105    /// where the key signature put them.
106    case_matters: bool,
107}
108
109#[derive(Clone, Copy, Debug, Eq, PartialEq)]
110#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
111enum RomanKind {
112    Diatonic,
113    AugmentedSixth(AugmentedSixthKind),
114}
115
116#[derive(Clone, Copy, Debug, Eq, PartialEq)]
117#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
118enum AugmentedSixthKind {
119    Italian,
120    French,
121    German,
122    Swiss,
123}
124
125#[derive(Clone, Copy, Debug, Eq, PartialEq)]
126#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
127enum RomanQuality {
128    Major,
129    Minor,
130    Diminished,
131    HalfDiminished,
132    Augmented,
133}
134
135impl AugmentedSixthKind {
136    /// The augmented sixth a figure names, if it names one.
137    ///
138    /// music21 reads these as the country's name, an optional `+`, and
139    /// whatever inversion figure follows — so `It`, `It+`, `It6`, `It+6` and
140    /// `Ger6/5` are all augmented sixths, and the inversion is read off
141    /// afterwards.
142    fn from_figure(figure: &str) -> Option<Self> {
143        let figure = figure.trim();
144        let kind = augmented_sixth_prefix(figure)?;
145        let name = match kind {
146            Self::Italian => "It",
147            Self::French => "Fr",
148            Self::German => "Ger",
149            Self::Swiss => "Sw",
150        };
151        let rest = figure[name.len()..].trim_start_matches('+');
152        unslash_inversion(rest)
153            .chars()
154            .all(|written| written.is_ascii_digit())
155            .then_some(kind)
156    }
157
158    fn from_common_name(name: &str) -> Option<Self> {
159        if name.contains("Italian augmented sixth chord") {
160            Some(Self::Italian)
161        } else if name.contains("French augmented sixth chord") {
162            Some(Self::French)
163        } else if name.contains("German augmented sixth chord") {
164            Some(Self::German)
165        } else if name.contains("Swiss augmented sixth chord") {
166            Some(Self::Swiss)
167        } else {
168            None
169        }
170    }
171
172    /// The inversion figure music21 reads one of these in when the figure
173    /// names no digits: a German sixth written `Ger` is `Ger65`.
174    fn default_inversion(self) -> &'static str {
175        match self {
176            Self::Italian => "6",
177            Self::French | Self::Swiss => "43",
178            Self::German => "65",
179        }
180    }
181
182    /// The digits an augmented-sixth figure writes, once its name and the
183    /// `+` are off it. music21's `_parseRNAloneAmidstAug6`: a figure naming
184    /// no digits takes the usual inversion, and one naming a plain `6` takes
185    /// it too, since `Fr6` is how `Fr43` is usually written.
186    fn written_figure(self, figure: &str) -> String {
187        let rest = figure
188            .trim()
189            .trim_start_matches(['I', 't', 'G', 'e', 'r', 'F', 'S', 'w'])
190            .trim_start_matches('+');
191        // A figure written `6/5` is the same as `65`.
192        let rest = unslash_inversion(rest);
193        let rest = rest.as_str();
194        let leading_digit = rest
195            .chars()
196            .next()
197            .is_some_and(|first| first.is_ascii_digit());
198        if !leading_digit {
199            return format!("{}{rest}", self.default_inversion());
200        }
201        if self != Self::Italian
202            && rest.starts_with('6')
203            && !rest[1..].starts_with(|c: char| c.is_ascii_digit())
204        {
205            return format!("{}{}", self.default_inversion(), &rest[1..]);
206        }
207        rest.to_string()
208    }
209
210    /// The scale degree music21 reads one of these on: the fourth for the
211    /// Italian and German sixths, the second for the French and Swiss.
212    fn degree(self) -> u8 {
213        match self {
214            Self::Italian | Self::German => 4,
215            Self::French | Self::Swiss => 2,
216        }
217    }
218
219    /// The alteration music21 records in front of it, in semitones. It is
220    /// written down and not applied — the sharp that makes the chord is the
221    /// bracketed one.
222    fn written_alteration(self) -> i8 {
223        match self {
224            Self::French => 0,
225            _ => 1,
226        }
227    }
228
229    /// The alterations that make the chord augmented: music21's
230    /// `bracketedAlterations`, a sharp on the root for every kind but the
231    /// French, and one on the third for the French and the Swiss.
232    fn bracketed_alterations(self) -> Vec<(i8, u8)> {
233        let mut alterations = Vec::new();
234        if self != Self::French {
235            alterations.push((1, 1));
236        }
237        if matches!(self, Self::French | Self::Swiss) {
238            alterations.push((1, 3));
239        }
240        alterations
241    }
242
243    fn figure(self) -> &'static str {
244        match self {
245            Self::Italian => "It+6",
246            Self::French => "Fr+6",
247            Self::German => "Ger+6",
248            Self::Swiss => "Sw+6",
249        }
250    }
251}
252
253impl RomanNumeral {
254    /// Parses a Roman numeral figure in a key.
255    ///
256    /// Supports ordinary figures such as `V7/V` and augmented-sixth figures
257    /// such as `It+6`, `Fr+6`, `Ger+6`, and `Sw+6`.
258    pub fn new(figure: impl Into<String>, key: Key) -> Result<Self> {
259        Self::with_minor_defaults(
260            figure,
261            key,
262            Minor67Default::default(),
263            Minor67Default::default(),
264        )
265    }
266
267    /// The same, saying how the sixth and seventh degrees of a minor key are
268    /// to be read: music21's `sixthMinor` and `seventhMinor`.
269    pub fn with_minor_defaults(
270        figure: impl Into<String>,
271        key: Key,
272        sixth_minor: Minor67Default,
273        seventh_minor: Minor67Default,
274    ) -> Result<Self> {
275        Self::with_options(figure, key, sixth_minor, seventh_minor, true)
276    }
277
278    /// The same again, saying whether the case of the numeral states the
279    /// chord's quality: music21's `caseMatters`.
280    pub fn with_options(
281        figure: impl Into<String>,
282        key: Key,
283        sixth_minor: Minor67Default,
284        seventh_minor: Minor67Default,
285        case_matters: bool,
286    ) -> Result<Self> {
287        Self::over_scale(figure, key, None, sixth_minor, seventh_minor, case_matters)
288    }
289
290    /// The same again over a scale that is not a key: music21's numerals
291    /// read against a `ConcreteScale`.
292    ///
293    /// The key is still needed — a numeral reports one, and a secondary
294    /// numeral establishes one — so pass the major key of the scale's tonic,
295    /// which is what music21 falls back on.
296    pub fn over_scale(
297        figure: impl Into<String>,
298        key: Key,
299        scale: Option<crate::scale::Scale>,
300        sixth_minor: Minor67Default,
301        seventh_minor: Minor67Default,
302        case_matters: bool,
303    ) -> Result<Self> {
304        let figure = figure.into();
305        let written = fold_figure_symbols(figure.trim());
306        let trimmed = written.as_str();
307        if trimmed.is_empty() {
308            return Err(Error::Chord("roman numeral cannot be empty".to_string()));
309        }
310        validate_figure(trimmed)?;
311
312        // The applied part comes off first, so that `Ger6/vi` is a German
313        // sixth read in the key its `vi` establishes — but a slash inside
314        // the inversion figure is not an applied part at all, and `Ger6/5`
315        // is the German sixth in the position it is usually written in.
316        let (aug6_primary, aug6_secondary) = if AugmentedSixthKind::from_figure(trimmed).is_some() {
317            (trimmed, None)
318        } else {
319            split_secondary(trimmed)
320        };
321        if let Some(kind) = AugmentedSixthKind::from_figure(aug6_primary) {
322            // music21's `_parseRNAloneAmidstAug6`: an augmented sixth is a
323            // figured-bass column over an altered degree, read in the
324            // parallel minor. The alteration in front of it is *recorded*
325            // and not applied — what makes the chord augmented is the
326            // bracketed sharp, which the ordinary path puts on afterwards.
327            let column = kind.written_figure(aug6_primary);
328            return Ok(Self {
329                // The figure is kept as written, since music21 names these
330                // several ways and reports back the one it was given.
331                figure: written.clone(),
332                key,
333                degree: kind.degree(),
334                accidental: 0,
335                written_accidental: kind.written_alteration(),
336                inversion: parse_inversion(&column),
337                seventh: suffix_has_seventh(&column),
338                quality: RomanQuality::from(ImpliedQuality::Unstated),
339                implied_quality: ImpliedQuality::Unstated,
340                figures: FiguredBass {
341                    column: Notation::parse(&expand_shorthand(&column).join(","))?,
342                    written: column,
343                    bracketed: kind.bracketed_alterations(),
344                    ..FiguredBass::default()
345                },
346                secondary: aug6_secondary,
347                kind: RomanKind::AugmentedSixth(kind),
348                sixth_minor,
349                seventh_minor,
350                case_matters,
351                scale,
352            });
353        }
354
355        let (primary, secondary) = (aug6_primary, aug6_secondary);
356
357        // music21 writes a few chords by name rather than by numeral: the
358        // Neapolitan and the cadential six-four. Each is read as the figure
359        // it stands for, while the numeral keeps the name it was given — and
360        // read in the key it actually sounds in, so the `Cad64` of `Cad64/V`
361        // in C minor is the major tonic of G and not the minor one of C.
362        let reading = match &secondary {
363            Some(secondary) => {
364                secondary_key(&key, secondary, sixth_minor, seventh_minor, case_matters)?
365            }
366            None => key.clone(),
367        };
368        let mut working = named_figure(primary, &reading);
369
370        // The brackets come off before anything reads the digits, so a
371        // `[no3]` cannot be mistaken for a diminished mark.
372        let omitted = take_omitted_steps(&mut working);
373        let added = take_added_steps(&mut working);
374        let bracketed = take_bracketed_alterations(&mut working);
375
376        let (accidental, rest) = split_roman_accidental_prefix(&working);
377        let (roman, suffix) = split_roman_prefix(rest)?;
378        let degree = roman_degree(roman)?;
379        let (implied_quality, mut column) =
380            implied_quality_from_string(roman, suffix, case_matters);
381        let inversion = parse_inversion(&column);
382        let seventh = suffix_has_seventh(&column);
383
384        let mut numeral = Self {
385            figure: written.clone(),
386            key,
387            degree,
388            accidental,
389            written_accidental: accidental,
390            inversion,
391            seventh,
392            quality: RomanQuality::from(implied_quality),
393            implied_quality,
394            figures: FiguredBass::default(),
395            secondary,
396            kind: RomanKind::Diatonic,
397            sixth_minor,
398            seventh_minor,
399            case_matters,
400            scale,
401        };
402        numeral.raise_minor_sixth_and_seventh(&mut column)?;
403        let written = column.clone();
404        // This crate writes an addition as `add(13)` where music21 writes a
405        // bracket, and it is not part of the figured-bass column: an added
406        // thirteenth is a note beside the chord, not a figure over the bass.
407        let column = strip_roman_addition_groups(&column);
408        numeral.figures = FiguredBass {
409            column: Notation::parse(&expand_shorthand(&column).join(","))?,
410            written,
411            omitted,
412            added,
413            bracketed,
414        };
415        Ok(numeral)
416    }
417
418    /// The alterations written in square brackets, as the semitones each
419    /// moves its chord step by and the step it moves.
420    pub fn bracketed_alterations(&self) -> &[(i8, u8)] {
421        &self.figures.bracketed
422    }
423
424    /// The chord steps the figure leaves out, as `[no3]`.
425    pub fn omitted_steps(&self) -> &[u8] {
426        &self.figures.omitted
427    }
428
429    /// The notes the figure puts in beside the chord, as `[add4]`: the
430    /// alteration in semitones and how far above the root each stands.
431    pub fn added_steps(&self) -> &[(i8, u8)] {
432        &self.figures.added
433    }
434
435    /// The scale the figure is read over, where it is not a key at all.
436    ///
437    /// A numeral read this way spells its chord where the scale stands, so
438    /// nothing downstream has to place it.
439    pub fn scale(&self) -> Option<&crate::scale::Scale> {
440        self.scale.as_ref()
441    }
442
443    /// The quality the figure states, which is what the notes read off the
444    /// scale are respelled to.
445    pub fn implied_quality(&self) -> ImpliedQuality {
446        self.implied_quality
447    }
448
449    /// The numbers of the figured-bass column the figure's digits stand for,
450    /// written high to low and expanded out of music21's shorthand.
451    pub fn figure_numbers(&self) -> Vec<u8> {
452        self.figures.numbers()
453    }
454
455    /// Whether the case of the numeral states the chord's quality.
456    pub fn case_matters(&self) -> bool {
457        self.case_matters
458    }
459
460    /// How this numeral reads the sixth degree of a minor key.
461    pub fn sixth_minor(&self) -> Minor67Default {
462        self.sixth_minor
463    }
464
465    /// How it reads the seventh.
466    pub fn seventh_minor(&self) -> Minor67Default {
467        self.seventh_minor
468    }
469
470    /// Returns the original figure.
471    /// The digits written under the numeral, as music21's `figuresWritten`:
472    /// the figure with the numeral, its accidental and its quality symbol
473    /// taken off it, and nothing expanded.
474    pub fn figures_written(&self) -> &str {
475        &self.figures.written
476    }
477
478    /// The figured-bass column the numeral's digits stand for, expanded out
479    /// of the shorthand they were written in: music21's `figuresNotationObj`.
480    pub fn figures_notation(&self) -> &Notation {
481        &self.figures.column
482    }
483
484    /// The figure the numeral was written with, as given: music21's `figure`.
485    pub fn figure(&self) -> &str {
486        &self.figure
487    }
488
489    /// Returns the one-based scale degree.
490    pub fn degree(&self) -> u8 {
491        self.degree
492    }
493
494    /// Returns the chromatic alteration of the scale degree in semitones.
495    ///
496    /// Negative values are flats and positive values are sharps, so `bII`
497    /// returns `-1` and `#iv` returns `1`.
498    pub fn written_accidental(&self) -> i8 {
499        self.written_accidental
500    }
501
502    /// The alteration the numeral reports, once the sixth and seventh
503    /// degrees of a minor key have been read against the chord asked for.
504    pub fn accidental(&self) -> i8 {
505        self.accidental
506    }
507
508    /// Returns the inversion number, where root position is `0`.
509    pub fn inversion(&self) -> u8 {
510        self.inversion
511    }
512
513    /// Returns the secondary/applied target figure, if any.
514    pub fn secondary(&self) -> Option<&str> {
515        self.secondary.as_deref()
516    }
517
518    /// Returns the key context.
519    pub fn key(&self) -> &Key {
520        &self.key
521    }
522
523    /// The numeral with its front alteration and nothing else: music21's
524    /// `romanNumeral`, so `bII6` is `bII` and `V65/V` is `V`. An augmented
525    /// sixth answers its nationality, `It`, `Fr`, `Ger` or `Sw`.
526    pub fn roman_numeral(&self) -> String {
527        let numeral = self.roman_numeral_alone();
528        if matches!(self.kind, RomanKind::AugmentedSixth(_)) {
529            return numeral;
530        }
531        let prefix = match self.accidental {
532            0 => String::new(),
533            sharps if sharps > 0 => "#".repeat(sharps.unsigned_abs() as usize),
534            flats => "b".repeat(flats.unsigned_abs() as usize),
535        };
536        format!("{prefix}{numeral}")
537    }
538
539    /// The numeral with nothing written in front of it: music21's
540    /// `romanNumeralAlone`, so `bVII65/V` is `VII`.
541    pub fn roman_numeral_alone(&self) -> String {
542        if let RomanKind::AugmentedSixth(kind) = self.kind {
543            return kind.figure().trim_end_matches(['+', '6']).to_string();
544        }
545        let primary = self.figure.split('/').next().unwrap_or_default();
546        // A chord written by name is read as the figure it stands for
547        // first, so the numeral alone of `N6` is `II` and of `Cad64` is the
548        // tonic — which is what music21 answers.
549        let reading = self.effective_key().unwrap_or_else(|_| self.key.clone());
550        let named = named_figure(primary, &reading);
551        let (_, unaltered) = split_roman_accidental_prefix(&named);
552        unaltered
553            .chars()
554            .take_while(|c| matches!(c, 'i' | 'v' | 'x' | 'I' | 'V' | 'X'))
555            .collect()
556    }
557
558    /// The figure and its key together: music21's `figureAndKey`,
559    /// `bII6 in a minor`.
560    pub fn figure_and_key(&self) -> String {
561        format!(
562            "{} in {} {}",
563            self.figure,
564            self.key.tonic_pitch_name_with_case(),
565            self.key.mode()
566        )
567    }
568
569    /// The scale degree with the alteration in front of it, as sharps
570    /// (positive) or flats (negative): music21's `scaleDegreeWithAlteration`.
571    pub fn scale_degree_with_alteration(&self) -> (u8, i8) {
572        (self.degree, self.accidental)
573    }
574
575    /// How strongly the figure implies its function, from music21's
576    /// `functionalityScores` table: `I` is 100, `V7` 80, an unknown figure 0.
577    /// A secondary figure multiplies the scores of its halves.
578    /// An augmented sixth is looked up by music21's spelling, `It6` rather
579    /// than the `It+6` this crate writes.
580    pub fn functionality_score(&self) -> u8 {
581        if self.secondary.is_some() {
582            let score = self.figure.split('/').fold(100.0, |score, part| {
583                score * f64::from(functionality_score_of(part).unwrap_or(0)) / 100.0
584            });
585            return score as u8;
586        }
587        functionality_score_of(&self.figure.replace('+', "")).unwrap_or(0)
588    }
589
590    /// Whether this is a Neapolitan chord: a major triad on the flattened
591    /// second degree, in first inversion unless `require_first_inversion` is
592    /// off.
593    pub fn is_neapolitan(&self, require_first_inversion: bool) -> bool {
594        self.degree == 2
595            && self.accidental == -1
596            && self.quality == RomanQuality::Major
597            && (!require_first_inversion || self.inversion == 1)
598    }
599
600    /// Whether the chord is borrowed from the parallel mode: music21's
601    /// `isMixture`, so `iv` and `bVI` in a major key are mixture and `IV` in a
602    /// minor key is. With `evaluate_secondary` a secondary figure is judged by
603    /// the numeral after the slash.
604    pub fn is_mixture(&self, evaluate_secondary: bool) -> Result<bool> {
605        if evaluate_secondary && let Some(secondary) = &self.secondary {
606            return RomanNumeral::new(secondary.clone(), self.key.clone())?.is_mixture(true);
607        }
608        if self.kind != RomanKind::Diatonic || !(1..=7).contains(&self.degree) {
609            return Ok(false);
610        }
611        // music21 asks the *chord* for its quality here, and a chord answers
612        // for its triad — so a half-diminished seventh reads as diminished,
613        // and the seventh above the triad is what the degree-seven rule below
614        // then asks about separately.
615        let quality = match self.quality {
616            RomanQuality::Diminished | RomanQuality::HalfDiminished => "diminished",
617            RomanQuality::Minor => "minor",
618            RomanQuality::Major => "major",
619            RomanQuality::Augmented => return Ok(false),
620        };
621        let front = match self.accidental {
622            0 => "natural",
623            1 => "sharp",
624            -1 => "flat",
625            _ => "other",
626        };
627        let entry = (self.degree, quality, front);
628        Ok(match self.key.mode() {
629            "major" => {
630                MAJOR_KEY_MIXTURES.contains(&entry)
631                    || (self.degree == 7
632                        && self.seventh
633                        && self.quality == RomanQuality::Diminished)
634            }
635            "minor" => {
636                MINOR_KEY_MIXTURES.contains(&entry)
637                    || (self.degree == 7
638                        && self.seventh
639                        && self.quality == RomanQuality::HalfDiminished)
640            }
641            _ => false,
642        })
643    }
644
645    /// The same figure in the key transposed by `interval`.
646    pub fn transpose(&self, interval: &Interval) -> Result<Self> {
647        RomanNumeral::new(self.figure.clone(), self.key.transpose(interval)?)
648    }
649
650    /// The key the figure is actually read in: the key it was given, or the
651    /// one a secondary numeral establishes — the `V` of `V/V` in G major is
652    /// read in D major.
653    pub fn effective_key_of(&self) -> Result<Key> {
654        self.effective_key()
655    }
656
657    fn effective_key(&self) -> Result<Key> {
658        let Some(secondary) = &self.secondary else {
659            return Ok(self.key.clone());
660        };
661
662        secondary_key(
663            &self.key,
664            secondary,
665            self.sixth_minor,
666            self.seventh_minor,
667            self.case_matters,
668        )
669    }
670}
671
672impl fmt::Display for RomanNumeral {
673    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
674        formatter.write_str(self.figure())
675    }
676}
677
678/// music21's `functionalityScores`: how strongly a figure implies its
679/// harmonic function, on a hundred-point scale, in music21's order.
680pub const FUNCTIONALITY_SCORES: [(&str, u8); 48] = [
681    ("I", 100),
682    ("i", 90),
683    ("V7", 80),
684    ("V", 70),
685    ("V65", 68),
686    ("I6", 65),
687    ("V6", 63),
688    ("V43", 61),
689    ("I64", 60),
690    ("IV", 59),
691    ("i6", 58),
692    ("viio7", 57),
693    ("V42", 55),
694    ("viio65", 53),
695    ("viio6", 52),
696    ("#viio65", 51),
697    ("ii", 50),
698    ("#viio6", 49),
699    ("ii65", 48),
700    ("ii43", 47),
701    ("ii42", 46),
702    ("IV6", 45),
703    ("ii6", 43),
704    ("VI", 42),
705    ("#VI", 41),
706    ("vi", 40),
707    ("viio", 39),
708    ("#viio", 38),
709    ("iio", 37),
710    ("iio42", 36),
711    ("bII6", 35),
712    ("It6", 34),
713    ("Ger65", 33),
714    ("iio43", 32),
715    ("iio65", 31),
716    ("Fr43", 30),
717    ("#vio", 28),
718    ("#vio6", 27),
719    ("III", 22),
720    ("Sw43", 21),
721    ("v", 20),
722    ("VII", 19),
723    ("VII7", 18),
724    ("IV65", 17),
725    ("IV7", 16),
726    ("iii", 15),
727    ("iii6", 12),
728    ("vi6", 10),
729];
730
731fn functionality_score_of(figure: &str) -> Option<u8> {
732    FUNCTIONALITY_SCORES
733        .iter()
734        .find(|(known, _)| *known == figure)
735        .map(|(_, score)| *score)
736}
737
738const MAJOR_KEY_MIXTURES: [(u8, &str, &str); 7] = [
739    (1, "minor", "natural"),
740    (2, "diminished", "natural"),
741    (3, "major", "flat"),
742    (4, "minor", "natural"),
743    (5, "minor", "natural"),
744    (6, "major", "flat"),
745    (7, "major", "flat"),
746];
747
748const MINOR_KEY_MIXTURES: [(u8, &str, &str); 5] = [
749    (1, "major", "natural"),
750    (2, "minor", "natural"),
751    (3, "minor", "sharp"),
752    (4, "major", "natural"),
753    (6, "minor", "sharp"),
754];
755
756/// Reads a chord as the tonic or the dominant of a key, with its inversion:
757/// A pitch as a figure over a reference pitch: the scale step it stands
758/// above the reference, how far it is altered from the key's own spelling of
759/// that degree, and the accidental written in front of a figure for it.
760/// music21's `FigureTuple`.
761#[derive(Clone, Debug, PartialEq)]
762#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
763#[must_use]
764pub struct FigureTuple {
765    /// The generic step above the reference pitch, `1` to `7`.
766    pub deg_from_ref_pitch: u8,
767    /// Semitones away from the key's own spelling of the degree.
768    pub alter: FloatType,
769    /// The accidental written in front of a figure for it: `#`, `b`, `##`
770    /// and so on, or nothing.
771    pub prefix: String,
772}
773
774/// A [`FigureTuple`] beside the pitch it describes: music21's
775/// `PitchFigureTuple`.
776#[derive(Clone, Debug, PartialEq)]
777#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
778#[must_use]
779pub struct PitchFigureTuple {
780    /// The figure.
781    pub figure: FigureTuple,
782    /// The pitch it was read from.
783    pub pitch: Pitch,
784}
785
786/// The figured-bass column a roman numeral's digits stand for, and what the
787/// brackets beside them say.
788///
789/// This is music21's `figuredBass.notation.Notation` reduced to what a
790/// numeral needs — the numbers above the bass and their accidentals, already
791/// expanded out of the shorthand a figure is usually written in — together
792/// with the omissions, additions and alterations music21 parses out of the
793/// figure before the column is read.
794#[derive(Clone, Debug, Default, PartialEq)]
795#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
796struct FiguredBass {
797    /// The column itself, expanded out of the shorthand it was written in.
798    column: Notation,
799    /// The digits as the figure wrote them, before the shorthand was
800    /// expanded and after everything that is not a digit has been read off:
801    /// `V65` writes `65`, `I7#5b3` writes `7#5b3`, and `V` writes nothing.
802    written: String,
803    /// Chord steps the figure asks to be left out, as `[no3]`.
804    omitted: Vec<u8>,
805    /// Notes the figure asks to be added, as `[add4]`: the alteration in
806    /// semitones and how far above the *root* the note stands.
807    added: Vec<(i8, u8)>,
808    /// Alterations written in square brackets, as `[#7]`: the alteration in
809    /// semitones and the chord step it applies to.
810    bracketed: Vec<(i8, u8)>,
811}
812
813impl FiguredBass {
814    /// The numbers of the column, high to low.
815    fn numbers(&self) -> Vec<u8> {
816        self.column
817            .numbers()
818            .iter()
819            .filter_map(|number| number.map(|number| number as u8))
820            .collect()
821    }
822
823    /// The figures of the column, high to low.
824    fn figures(&self) -> &[Figure] {
825        self.column.figures()
826    }
827
828    /// Whether an accidental was written beside a given number, which is what
829    /// stops the implied quality from correcting the note back.
830    fn alters(&self, number: u8) -> bool {
831        self.column.figures().iter().any(|figure| {
832            figure.number() == Some(IntegerType::from(number))
833                && figure
834                    .modifier()
835                    .accidental()
836                    .is_some_and(|accidental| accidental.alter() != 0.0)
837        })
838    }
839}
840
841/// The quality a figure says its chord has, whatever the scale spells.
842///
843/// music21's `impliedQuality`: the symbol in front of the inversion digits,
844/// or — when there is none — the case the numeral was written in. What it is
845/// for is respelling the third, the fifth and the seventh once they have been
846/// read off the scale.
847#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
848#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
849pub enum ImpliedQuality {
850    /// No quality was implied, and the scale's own spelling stands.
851    #[default]
852    Unstated,
853    /// A major triad.
854    Major,
855    /// A minor triad.
856    Minor,
857    /// A diminished triad, or a fully diminished seventh.
858    Diminished,
859    /// A half-diminished seventh: a diminished triad under a minor seventh.
860    HalfDiminished,
861    /// An augmented triad.
862    Augmented,
863    /// A major triad under a minor seventh.
864    DominantSeventh,
865}
866
867impl From<ImpliedQuality> for RomanQuality {
868    /// The quality a numeral reports, which is the stated one with the two
869    /// readings that only say how a seventh is spelled folded onto major.
870    fn from(quality: ImpliedQuality) -> Self {
871        match quality {
872            ImpliedQuality::Minor => Self::Minor,
873            ImpliedQuality::Diminished => Self::Diminished,
874            ImpliedQuality::HalfDiminished => Self::HalfDiminished,
875            ImpliedQuality::Augmented => Self::Augmented,
876            ImpliedQuality::Unstated | ImpliedQuality::Major | ImpliedQuality::DominantSeventh => {
877                Self::Major
878            }
879        }
880    }
881}
882
883impl ImpliedQuality {
884    /// The quality music21 names in a string, as it writes the names.
885    pub fn from_name(name: &str) -> Self {
886        match name {
887            "major" => Self::Major,
888            "minor" => Self::Minor,
889            "diminished" => Self::Diminished,
890            "half-diminished" => Self::HalfDiminished,
891            "augmented" => Self::Augmented,
892            "minor-seventh" | "dominant-seventh" => Self::DominantSeventh,
893            _ => Self::Unstated,
894        }
895    }
896
897    /// The name music21 writes it under, which is the empty string for a
898    /// quality nobody stated.
899    pub fn name(self) -> &'static str {
900        match self {
901            Self::Unstated => "",
902            Self::Major => "major",
903            Self::Minor => "minor",
904            Self::Diminished => "diminished",
905            Self::HalfDiminished => "half-diminished",
906            Self::Augmented => "augmented",
907            Self::DominantSeventh => "dominant-seventh",
908        }
909    }
910
911    /// How many semitones the third, the fifth and — where the quality says
912    /// so — the seventh stand above the root.
913    pub fn correct_semitones(self) -> &'static [u8] {
914        match self {
915            Self::Unstated => &[],
916            Self::Major => &[4, 7],
917            Self::Minor => &[3, 7],
918            Self::Diminished => &[3, 6, 9],
919            Self::HalfDiminished => &[3, 6, 10],
920            Self::Augmented => &[4, 8],
921            Self::DominantSeventh => &[4, 7, 10],
922        }
923    }
924}
925
926/// The numeral a figure opens with, and everything it implies.
927///
928/// This is what music21's `_parseRNAloneAmidstAug6` reads: usually just the
929/// roman letters and the scale degree they name, but an augmented sixth is
930/// written by nationality rather than by numeral and carries its own degree,
931/// its own alteration and the accidentals that make it augmented — and it is
932/// always read in the minor of the key it is written in.
933#[derive(Clone, Debug, PartialEq, Eq)]
934pub struct NumeralAlone {
935    /// The numeral as written, or the augmented sixth's name.
936    pub numeral: String,
937    /// What is left of the figure once the numeral is off it.
938    pub rest: String,
939    /// The scale degree the numeral stands on.
940    pub degree: u8,
941    /// The alteration in front of it, in semitones, where the numeral itself
942    /// implies one.
943    pub alteration: i8,
944    /// Whether the figure has to be read in the parallel minor, as every
945    /// augmented sixth is.
946    pub minor: bool,
947    /// The alterations that make an augmented sixth augmented.
948    pub bracketed: Vec<(i8, u8)>,
949}
950
951#[cfg(test)]
952mod tests {
953    #[test]
954    fn a_numeral_reports_how_it_was_read() {
955        use super::{
956            ImpliedQuality, Minor67Default, NumeralAlone, RomanNumeral, analyze_chord_with_root,
957            parse_numeral_alone, secondary_key,
958        };
959        use crate::{Chord, Key, Pitch};
960
961        let key = Key::from_tonic("C").unwrap();
962        let numeral = RomanNumeral::new("V7[no3][add4][#5]/ii", key.clone()).unwrap();
963        assert_eq!(numeral.key().tonic().name(), "C");
964        assert!(numeral.scale().is_none());
965        assert!(numeral.case_matters());
966        assert_eq!(numeral.sixth_minor(), Minor67Default::Quality);
967        assert_eq!(numeral.seventh_minor(), Minor67Default::Quality);
968        assert_eq!(numeral.implied_quality(), ImpliedQuality::Major);
969        assert_eq!(numeral.written_accidental(), 0);
970        assert_eq!(numeral.omitted_steps(), [3]);
971        assert_eq!(numeral.added_steps(), [(0, 4)]);
972        assert_eq!(numeral.bracketed_alterations(), [(1, 5)]);
973        // A bare `7` expands to the seventh, fifth and third.
974        assert_eq!(numeral.figures_notation().numbers().len(), 3);
975        assert_eq!(numeral.effective_key_of().unwrap().tonic().name(), "D");
976        assert_eq!(numeral.roman_numeral_alone(), "V");
977        assert_eq!(numeral.to_string(), "V7[no3][add4][#5]/ii");
978        assert_eq!(numeral.figure_and_key(), "V7[no3][add4][#5]/ii in C major");
979
980        assert_eq!(
981            ImpliedQuality::from_name("half-diminished").name(),
982            "half-diminished"
983        );
984        assert_eq!(
985            ImpliedQuality::from_name("nonsense"),
986            ImpliedQuality::Unstated
987        );
988        assert_eq!(ImpliedQuality::Unstated.name(), "");
989
990        let alone: NumeralAlone = parse_numeral_alone("VI6").unwrap();
991        assert_eq!((alone.numeral.as_str(), alone.rest.as_str()), ("VI", "6"));
992        assert_eq!((alone.degree, alone.alteration, alone.minor), (6, 0, false));
993        let german = parse_numeral_alone("Ger").unwrap();
994        assert_eq!(
995            (german.numeral.as_str(), german.rest.as_str()),
996            ("Ger", "65")
997        );
998        assert!(german.minor);
999        assert!(parse_numeral_alone("").is_err());
1000
1001        let dominant_of_dominant = secondary_key(
1002            &key,
1003            "V",
1004            Minor67Default::Quality,
1005            Minor67Default::Quality,
1006            true,
1007        )
1008        .unwrap();
1009        assert_eq!(dominant_of_dominant.tonic().name(), "G");
1010        assert_eq!(dominant_of_dominant.mode(), "major");
1011        let of_supertonic = secondary_key(
1012            &key,
1013            "ii",
1014            Minor67Default::Quality,
1015            Minor67Default::Quality,
1016            true,
1017        )
1018        .unwrap();
1019        assert_eq!(of_supertonic.mode(), "minor");
1020
1021        let chord = Chord::new("E4 G4 C5").unwrap();
1022        let root = Pitch::from_name("C4").unwrap();
1023        let analyzed = analyze_chord_with_root(&chord, key, &root)
1024            .unwrap()
1025            .unwrap();
1026        assert_eq!(analyzed.figure(), "I6");
1027    }
1028
1029    /// music21's own examples for `FigureTuple.fromPitchAndReference`,
1030    /// `figureTuples`, `correctRNAlterationForMinor` and
1031    /// `correctSuffixForChordQuality`.
1032    #[test]
1033    fn figures_are_read_above_a_reference_pitch_and_corrected_for_minor() {
1034        use super::{
1035            FigureTuple, correct_rn_alteration_for_minor, correct_suffix_for_chord_quality,
1036            figure_tuples,
1037        };
1038        use crate::{Chord, Key, Pitch};
1039
1040        let c_major = Key::from_tonic("C").unwrap();
1041        let c_minor = Key::from_tonic("c").unwrap();
1042        let figure = |name: &str, key: &Key, reference: &str| {
1043            FigureTuple::from_pitch_and_reference(
1044                &Pitch::from_name(name).unwrap(),
1045                key,
1046                &Pitch::from_name(reference).unwrap(),
1047            )
1048            .unwrap()
1049        };
1050        let read = |figure: FigureTuple| (figure.deg_from_ref_pitch, figure.alter, figure.prefix);
1051        assert_eq!(
1052            read(figure("A-3", &c_major, "F#2")),
1053            (3, -1.0, "b".to_string())
1054        );
1055        assert_eq!(
1056            read(figure("E--4", &c_minor, "C3")),
1057            (3, -1.0, "b".to_string())
1058        );
1059        assert_eq!(read(figure("E-4", &c_minor, "C3")), (3, 0.0, String::new()));
1060        assert_eq!(
1061            read(figure("E#4", &c_minor, "C3")),
1062            (3, 2.0, "##".to_string())
1063        );
1064        assert_eq!(
1065            read(figure("A4", &c_minor, "C3")),
1066            (6, 1.0, "#".to_string())
1067        );
1068        assert_eq!(
1069            read(figure("B5", &c_minor, "C3")),
1070            (7, 1.0, "#".to_string())
1071        );
1072
1073        let tuples = figure_tuples(&Chord::new("F#2 D3 A-3 C#4").unwrap(), &c_minor).unwrap();
1074        let read_all: Vec<(u8, FloatType, String)> = tuples
1075            .iter()
1076            .map(|tuple| read(tuple.figure.clone()))
1077            .collect();
1078        assert_eq!(
1079            read_all,
1080            [
1081                (1, 1.0, "#".to_string()),
1082                (6, 0.0, String::new()),
1083                (3, 0.0, String::new()),
1084                (5, 1.0, "#".to_string()),
1085            ]
1086        );
1087        assert_eq!(tuples[2].pitch.name_with_octave(), "A-3");
1088        assert!(
1089            figure_tuples(&Chord::new("").unwrap(), &c_minor)
1090                .unwrap()
1091                .is_empty()
1092        );
1093
1094        let sixth = |alter: FloatType, prefix: &str| FigureTuple {
1095            deg_from_ref_pitch: 6,
1096            alter,
1097            prefix: prefix.to_string(),
1098        };
1099        assert_eq!(
1100            read(correct_rn_alteration_for_minor(
1101                &sixth(-1.0, ""),
1102                &c_minor,
1103                false
1104            )),
1105            (6, -1.0, "b".to_string())
1106        );
1107        assert_eq!(
1108            read(correct_rn_alteration_for_minor(
1109                &sixth(0.0, ""),
1110                &c_minor,
1111                false
1112            )),
1113            (6, 0.0, "b".to_string())
1114        );
1115        let raised_seventh = figure("B5", &c_minor, "C3");
1116        assert_eq!(
1117            read(correct_rn_alteration_for_minor(
1118                &raised_seventh,
1119                &c_minor,
1120                false
1121            )),
1122            (7, 0.0, String::new())
1123        );
1124        assert_eq!(
1125            read(correct_rn_alteration_for_minor(
1126                &raised_seventh,
1127                &c_minor,
1128                true
1129            )),
1130            (7, 1.0, "#".to_string())
1131        );
1132        assert_eq!(
1133            read(correct_rn_alteration_for_minor(
1134                &sixth(2.0, "##"),
1135                &c_minor,
1136                false
1137            )),
1138            (6, 1.0, "#".to_string())
1139        );
1140        let in_major = sixth(-1.0, "b");
1141        assert_eq!(
1142            correct_rn_alteration_for_minor(&in_major, &c_major, false),
1143            in_major
1144        );
1145        let fourth = FigureTuple {
1146            deg_from_ref_pitch: 4,
1147            alter: -1.0,
1148            prefix: "b".to_string(),
1149        };
1150        assert_eq!(
1151            correct_rn_alteration_for_minor(&fourth, &c_minor, false),
1152            fourth
1153        );
1154
1155        assert_eq!(
1156            correct_suffix_for_chord_quality(&Chord::new("E3 C4 G4").unwrap(), "6"),
1157            "6"
1158        );
1159        assert_eq!(
1160            correct_suffix_for_chord_quality(&Chord::new("E3 C4 G-4").unwrap(), "6"),
1161            "o6"
1162        );
1163        assert_eq!(
1164            correct_suffix_for_chord_quality(&Chord::new("C4 E4 G#4").unwrap(), ""),
1165            "+"
1166        );
1167        assert_eq!(
1168            correct_suffix_for_chord_quality(&Chord::new("B3 D4 F4 A4").unwrap(), "7"),
1169            "ø7"
1170        );
1171        assert_eq!(
1172            correct_suffix_for_chord_quality(&Chord::new("B3 D4 F4 A-4").unwrap(), "o7"),
1173            "o7"
1174        );
1175    }
1176
1177    /// music21's own examples: in C minor a minor `vi` is raised and a
1178    /// major `VI` is not, and in a major key nothing moves.
1179    #[test]
1180    fn the_sixth_and_seventh_of_a_minor_key_are_raised_by_quality() {
1181        use super::{ImpliedQuality, Minor67Default, adjust_minor_vi_and_vii_by_quality};
1182        use crate::Key;
1183
1184        let minor = Key::from_tonic("c").unwrap();
1185        let major = Key::from_tonic("C").unwrap();
1186        let adjust = |key: &Key, reading, quality, accidental| {
1187            adjust_minor_vi_and_vii_by_quality(key, reading, quality, accidental)
1188        };
1189        assert_eq!(
1190            adjust(&minor, Minor67Default::Quality, ImpliedQuality::Minor, 0),
1191            1
1192        );
1193        assert_eq!(
1194            adjust(&minor, Minor67Default::Quality, ImpliedQuality::Major, 0),
1195            0
1196        );
1197        assert_eq!(
1198            adjust(&major, Minor67Default::Quality, ImpliedQuality::Minor, 0),
1199            0
1200        );
1201        assert_eq!(
1202            adjust(&minor, Minor67Default::Flat, ImpliedQuality::Diminished, 0),
1203            0
1204        );
1205        assert_eq!(
1206            adjust(&minor, Minor67Default::Sharp, ImpliedQuality::Major, 0),
1207            1
1208        );
1209        // A caution already written says nothing more; a flat cancels the
1210        // raise and the two meet at the natural degree.
1211        assert_eq!(
1212            adjust(&minor, Minor67Default::Cautionary, ImpliedQuality::Minor, 1),
1213            1
1214        );
1215        assert_eq!(
1216            adjust(
1217                &minor,
1218                Minor67Default::Cautionary,
1219                ImpliedQuality::Major,
1220                -1
1221            ),
1222            0
1223        );
1224        assert_eq!(
1225            adjust(&minor, Minor67Default::Cautionary, ImpliedQuality::Minor, 0),
1226            1
1227        );
1228    }
1229
1230    #[test]
1231    fn a_numeral_reports_the_digits_it_was_written_with() {
1232        let major = Key::from_tonic("C").unwrap();
1233        for (figure, written) in [
1234            ("V65", "65"),
1235            ("V", ""),
1236            ("I7#5b3", "7#5b3"),
1237            ("viio6", "6"),
1238            ("vii\u{f8}7", "7"),
1239        ] {
1240            let numeral = RomanNumeral::new(figure, major.clone()).unwrap();
1241            assert_eq!(numeral.figures_written(), written, "{figure}");
1242        }
1243        let minor = Key::from_tonic("c").unwrap();
1244        for (figure, written) in [("Fr43", "43"), ("Fr+6", "43"), ("It+6", "6"), ("Ger", "65")] {
1245            let numeral = RomanNumeral::new(figure, minor.clone()).unwrap();
1246            assert_eq!(numeral.figures_written(), written, "{figure}");
1247        }
1248    }
1249
1250    #[test]
1251    fn a_minor_key_raises_its_sixth_and_seventh_where_the_figure_asks() {
1252        let minor = Key::from_tonic_mode("a", Some("minor")).unwrap();
1253        // The diminished seventh on the leading note is the raised seventh
1254        // degree: G#, not G with three flats hung off it.
1255        let leading = RomanNumeral::new("viio7", minor.clone()).unwrap();
1256        assert_eq!(
1257            leading.to_chord().unwrap().pitch_names(),
1258            ["G#", "B", "D", "F"]
1259        );
1260        assert_eq!(leading.roman_numeral(), "#vii");
1261        assert_eq!(leading.roman_numeral_alone(), "vii");
1262        assert_eq!(leading.scale_degree_with_alteration(), (7, 1));
1263
1264        // A minor triad on the sixth degree is the raised one too.
1265        assert_eq!(
1266            RomanNumeral::new("vi", minor.clone())
1267                .unwrap()
1268                .to_chord()
1269                .unwrap()
1270                .pitch_names(),
1271            ["F#", "A", "C#"]
1272        );
1273        // The natural degrees are what the upper-case figures ask for.
1274        assert_eq!(
1275            RomanNumeral::new("VI", minor.clone())
1276                .unwrap()
1277                .to_chord()
1278                .unwrap()
1279                .pitch_names(),
1280            ["F", "A", "C"]
1281        );
1282        // And a major key is left alone entirely.
1283        let major = Key::from_tonic_mode("C", Some("major")).unwrap();
1284        assert_eq!(
1285            RomanNumeral::new("vi", major)
1286                .unwrap()
1287                .to_chord()
1288                .unwrap()
1289                .pitch_names(),
1290            ["A", "C", "E"]
1291        );
1292    }
1293
1294    #[test]
1295    fn the_four_readings_of_a_minor_sixth_and_seventh() {
1296        let minor = Key::from_tonic_mode("c", Some("minor")).unwrap();
1297        let read = |figure: &str, sixth: Minor67Default| {
1298            RomanNumeral::with_minor_defaults(figure, minor.clone(), sixth, sixth)
1299                .unwrap()
1300                .to_chord()
1301                .unwrap()
1302                .pitch_names()
1303                .join(" ")
1304        };
1305
1306        // By quality, which is the default: the chord the figure asks for
1307        // says which sixth it is built on.
1308        assert_eq!(read("vi", Minor67Default::Quality), "A C E");
1309        assert_eq!(read("VI", Minor67Default::Quality), "A- C E-");
1310
1311        // Flat is always the natural degree, whatever the figure asks for,
1312        // and a sharp written in front still raises it.
1313        assert_eq!(read("vi", Minor67Default::Flat), "A- C- E-");
1314        assert_eq!(read("#vi", Minor67Default::Flat), "A C E");
1315
1316        // Sharp is always the raised one, and a flat still lowers it.
1317        assert_eq!(read("VI", Minor67Default::Sharp), "A C# E");
1318        assert_eq!(read("bVI", Minor67Default::Sharp), "A- C E-");
1319
1320        // Cautionary reads the quality, but an accidental already written is
1321        // a caution rather than a further change, so `#vi` is `vi` and
1322        // `bVI` is `VI`.
1323        assert_eq!(read("#vi", Minor67Default::Cautionary), "A C E");
1324        assert_eq!(read("vi", Minor67Default::Cautionary), "A C E");
1325        assert_eq!(read("bVI", Minor67Default::Cautionary), "A- C E-");
1326        assert_eq!(read("VI", Minor67Default::Cautionary), "A- C E-");
1327    }
1328
1329    #[test]
1330    fn the_neapolitan_can_be_written_by_name() {
1331        // music21 writes the flattened second degree as `N`, and in first
1332        // inversion — the way it is nearly always used — as `N6`.
1333        let key = Key::from_tonic_mode("c#", Some("minor")).unwrap();
1334        let named = RomanNumeral::new("N6", key.clone()).unwrap();
1335        assert_eq!(named.figure(), "N6");
1336        assert_eq!(named.degree(), 2);
1337        assert_eq!(named.accidental(), -1);
1338        assert_eq!(named.inversion(), 1);
1339        assert!(named.is_neapolitan(true));
1340
1341        let spelled = RomanNumeral::new("bII6", key.clone()).unwrap();
1342        assert_eq!(
1343            named.to_chord().unwrap().pitch_names(),
1344            spelled.to_chord().unwrap().pitch_names()
1345        );
1346
1347        // `N53` is the same chord in root position.
1348        let root_position = RomanNumeral::new("N53", key).unwrap();
1349        assert_eq!(root_position.inversion(), 0);
1350    }
1351
1352    #[test]
1353    fn inversion_names_and_tonic_dominant_reading_match_music21() {
1354        let chord = |notes: &str| Chord::new(notes).unwrap();
1355        assert_eq!(roman_inversion_name(&chord("C4 E4 G4"), None), "");
1356        assert_eq!(roman_inversion_name(&chord("E4 G4 C5"), None), "6");
1357        assert_eq!(roman_inversion_name(&chord("G3 C4 E4"), None), "64");
1358        assert_eq!(roman_inversion_name(&chord("C4 E4 G4 B-4"), None), "7");
1359        assert_eq!(roman_inversion_name(&chord("E4 G4 B-4 C5"), None), "65");
1360        assert_eq!(roman_inversion_name(&chord("G3 B-3 C4 E4"), None), "43");
1361        assert_eq!(roman_inversion_name(&chord("B-3 C4 E4 G4"), None), "42");
1362        assert_eq!(roman_inversion_name(&chord("C4 E4 G4"), Some(2)), "64");
1363        assert_eq!(roman_inversion_name(&chord("C4 E4 G4"), Some(3)), "");
1364        assert_eq!(roman_inversion_name(&Chord::empty(), None), "");
1365
1366        let cases = [
1367            ("C4 E4 G4", "C", Some("I")),
1368            ("E4 G4 C5", "C", Some("I6")),
1369            ("G3 B3 D4 F4", "C", Some("V7")),
1370            ("D4 F4 A4", "C", Some("V43")),
1371            ("A3 C4 E4", "a", Some("i")),
1372            ("E3 G#3 B3", "a", Some("V")),
1373            ("C4 E4", "C", Some("I")),
1374            ("G4 D5", "C", Some("V")),
1375            ("B3 D4", "C", Some("V")),
1376            ("F#4 A4", "C", None),
1377        ];
1378        for (notes, key, expected) in cases {
1379            let reading =
1380                identify_as_tonic_or_dominant(&chord(notes), &Key::from_tonic(key).unwrap())
1381                    .unwrap();
1382            assert_eq!(reading.as_deref(), expected, "{notes} in {key}");
1383        }
1384    }
1385
1386    #[test]
1387    #[allow(clippy::type_complexity)]
1388    fn numeral_names_scores_and_mixture_match_music21() {
1389        let cases: [(&str, &str, &str, &str, (u8, i8), u8, bool, bool, bool); 18] = [
1390            (
1391                "V7",
1392                "C",
1393                "V",
1394                "V7 in C major",
1395                (5, 0),
1396                80,
1397                false,
1398                false,
1399                false,
1400            ),
1401            (
1402                "bII6",
1403                "C",
1404                "bII",
1405                "bII6 in C major",
1406                (2, -1),
1407                35,
1408                true,
1409                true,
1410                false,
1411            ),
1412            (
1413                "bII",
1414                "C",
1415                "bII",
1416                "bII in C major",
1417                (2, -1),
1418                0,
1419                false,
1420                true,
1421                false,
1422            ),
1423            (
1424                "bII6",
1425                "a",
1426                "bII",
1427                "bII6 in a minor",
1428                (2, -1),
1429                35,
1430                true,
1431                true,
1432                false,
1433            ),
1434            (
1435                "viio7",
1436                "C",
1437                "vii",
1438                "viio7 in C major",
1439                (7, 0),
1440                57,
1441                false,
1442                false,
1443                true,
1444            ),
1445            (
1446                "I",
1447                "C",
1448                "I",
1449                "I in C major",
1450                (1, 0),
1451                100,
1452                false,
1453                false,
1454                false,
1455            ),
1456            (
1457                "i",
1458                "a",
1459                "i",
1460                "i in a minor",
1461                (1, 0),
1462                90,
1463                false,
1464                false,
1465                false,
1466            ),
1467            (
1468                "V65/V",
1469                "C",
1470                "V",
1471                "V65/V in C major",
1472                (5, 0),
1473                47,
1474                false,
1475                false,
1476                false,
1477            ),
1478            (
1479                "iv",
1480                "C",
1481                "iv",
1482                "iv in C major",
1483                (4, 0),
1484                0,
1485                false,
1486                false,
1487                true,
1488            ),
1489            (
1490                "bVI",
1491                "C",
1492                "bVI",
1493                "bVI in C major",
1494                (6, -1),
1495                0,
1496                false,
1497                false,
1498                true,
1499            ),
1500            (
1501                "I",
1502                "a",
1503                "I",
1504                "I in a minor",
1505                (1, 0),
1506                100,
1507                false,
1508                false,
1509                true,
1510            ),
1511            (
1512                "III",
1513                "a",
1514                "III",
1515                "III in a minor",
1516                (3, 0),
1517                22,
1518                false,
1519                false,
1520                false,
1521            ),
1522            (
1523                "#iii",
1524                "a",
1525                "#iii",
1526                "#iii in a minor",
1527                (3, 1),
1528                0,
1529                false,
1530                false,
1531                true,
1532            ),
1533            (
1534                "ii",
1535                "C",
1536                "ii",
1537                "ii in C major",
1538                (2, 0),
1539                50,
1540                false,
1541                false,
1542                false,
1543            ),
1544            (
1545                "iio",
1546                "C",
1547                "ii",
1548                "iio in C major",
1549                (2, 0),
1550                37,
1551                false,
1552                false,
1553                true,
1554            ),
1555            (
1556                "IV",
1557                "a",
1558                "IV",
1559                "IV in a minor",
1560                (4, 0),
1561                59,
1562                false,
1563                false,
1564                true,
1565            ),
1566            (
1567                "bVII",
1568                "C",
1569                "bVII",
1570                "bVII in C major",
1571                (7, -1),
1572                0,
1573                false,
1574                false,
1575                true,
1576            ),
1577            (
1578                "v",
1579                "C",
1580                "v",
1581                "v in C major",
1582                (5, 0),
1583                20,
1584                false,
1585                false,
1586                true,
1587            ),
1588        ];
1589        for (figure, key, numeral, figure_and_key, degree, score, neapolitan, loose, mixture) in
1590            cases
1591        {
1592            let rn = RomanNumeral::new(figure, Key::from_tonic(key).unwrap()).unwrap();
1593            assert_eq!(rn.roman_numeral(), numeral, "{figure} in {key}");
1594            assert_eq!(rn.figure_and_key(), figure_and_key, "{figure} in {key}");
1595            assert_eq!(
1596                rn.scale_degree_with_alteration(),
1597                degree,
1598                "{figure} in {key}"
1599            );
1600            assert_eq!(rn.functionality_score(), score, "{figure} in {key}");
1601            assert_eq!(rn.is_neapolitan(true), neapolitan, "{figure} in {key}");
1602            assert_eq!(rn.is_neapolitan(false), loose, "{figure} in {key}");
1603            assert_eq!(rn.is_mixture(false).unwrap(), mixture, "{figure} in {key}");
1604            assert_eq!(rn.is_mixture(true).unwrap(), mixture, "{figure} in {key}");
1605        }
1606        // A half-diminished seventh on the leading note is mixture in a minor
1607        // key and not in a major one, which only works because its quality
1608        // reads as its triad's — diminished — the way music21's chord does.
1609        for (key, mixture) in [("a", true), ("A", false)] {
1610            let half = RomanNumeral::new("viiø7", Key::from_tonic(key).unwrap()).unwrap();
1611            assert_eq!(half.is_mixture(false).unwrap(), mixture, "viiø7 in {key}");
1612        }
1613
1614        let italian = RomanNumeral::new("It6", Key::from_tonic("C").unwrap()).unwrap();
1615        assert_eq!(italian.roman_numeral(), "It");
1616        assert_eq!(italian.functionality_score(), 34);
1617        assert!(!italian.is_mixture(false).unwrap());
1618        let transposed = RomanNumeral::new("bII6", Key::from_tonic("a").unwrap())
1619            .unwrap()
1620            .transpose(&Interval::from_name("M2").unwrap())
1621            .unwrap();
1622        assert_eq!(transposed.figure_and_key(), "bII6 in b minor");
1623    }
1624
1625    #[test]
1626    fn functionality_scores_table_has_no_duplicates() {
1627        let mut figures: Vec<&str> = FUNCTIONALITY_SCORES.iter().map(|(f, _)| *f).collect();
1628        figures.sort_unstable();
1629        figures.dedup();
1630        assert_eq!(figures.len(), FUNCTIONALITY_SCORES.len());
1631    }
1632    use super::*;
1633
1634    #[test]
1635    fn secondary_dominant_resolves_to_chord() {
1636        let key = Key::from_tonic_mode("C", "major").unwrap();
1637        let rn = RomanNumeral::new("V7/V", key).unwrap();
1638        assert_eq!(rn.degree(), 5);
1639        assert_eq!(rn.secondary(), Some("V"));
1640        assert_eq!(
1641            rn.to_chord().unwrap().pitched_common_name(),
1642            "D-dominant seventh chord"
1643        );
1644    }
1645
1646    /// Every answer here is music21's own, read off `romanNumeralFromChord`.
1647    #[test]
1648    fn a_chord_is_named_the_way_music21_names_it() {
1649        let cases: &[(&str, &str, &str)] = &[
1650            ("C E G", "C", "I"),
1651            ("G B D F", "C", "V43"),
1652            ("C E- G-", "C", "io5b3"),
1653            ("G B D F", "a", "bVII43"),
1654            ("B D F A", "C", "viiø65"),
1655            ("F A C", "C", "IV64"),
1656            ("A C E", "C", "vi6"),
1657            ("A C E", "a", "i6"),
1658            ("F A C", "a", "bVI64"),
1659            ("C E G#", "a", "III+"),
1660            ("D- F A-", "C", "bII"),
1661            ("B4 D5 F5 A-5", "c", "viio7"),
1662            ("A-3 C4 F#4", "c", "It6"),
1663            ("A-3 C4 D4 F#4", "c", "Fr43"),
1664            ("A-3 C4 E-4 F#4", "c", "Ger65"),
1665            ("A-3 C4 E-4 F#4", "C", "Ger65"),
1666            ("C4 E4 G4 B-4", "F", "V7"),
1667            ("D4 F4 A4 C5", "C", "ii7"),
1668            ("D4 F4 A4 C#5", "C", "ii#7"),
1669            ("F4 A4 C5 E5", "C", "IV7"),
1670            ("E4 G4 B4 D5", "C", "iii7"),
1671            ("C4 E-4 G4 B4", "c", "i#7"),
1672            ("C4 E4 G4 B4", "C", "I7"),
1673            ("E-4 G4 B-4 D5", "c", "III7"),
1674            ("C4 E4 G4 B4 D5", "C", "I7532"),
1675            ("G3 C4 E4", "C", "I64"),
1676            ("E3 G3 C4", "C", "I6"),
1677            ("F#3 A3 C4 E-4", "G", "viiob753"),
1678            ("D F# A", "G", "V"),
1679            ("C E G B-", "C", "Ib753"),
1680        ];
1681        for (chord, key, figure) in cases {
1682            let key = Key::from_tonic(key).unwrap();
1683            let numeral = roman_numeral_from_chord(&Chord::new(*chord).unwrap(), Some(&key))
1684                .unwrap()
1685                .unwrap();
1686            assert_eq!(
1687                numeral.figure(),
1688                *figure,
1689                "{chord} in {}",
1690                key.tonic_pitch_name_with_case()
1691            );
1692        }
1693        for (chord, figure, key_name) in [
1694            ("D F# A", "I", "D major"),
1695            ("D F A", "i", "d minor"),
1696            ("E G# B D", "Ib8642", "E major"),
1697            ("C E G", "I", "C major"),
1698            ("A-3 C4 E-4 F#4", "Ger65", "c minor"),
1699            ("G B D F", "I64b3", "G major"),
1700        ] {
1701            let numeral = roman_numeral_from_chord(&Chord::new(chord).unwrap(), None)
1702                .unwrap()
1703                .unwrap();
1704            assert_eq!(numeral.figure(), figure, "{chord} with no key");
1705            let key = numeral.key();
1706            assert_eq!(
1707                format!("{} {}", key.tonic_pitch_name_with_case(), key.mode()),
1708                key_name,
1709                "{chord} with no key"
1710            );
1711        }
1712        assert!(
1713            roman_numeral_from_chord(&Chord::empty(), None)
1714                .unwrap()
1715                .is_none()
1716        );
1717    }
1718
1719    #[test]
1720    fn analyzes_chord_in_key() {
1721        let key = Key::from_tonic_mode("C", "major").unwrap();
1722        let chord = Chord::new("G B D F").unwrap();
1723        let rn = RomanNumeral::analyze(&chord, key).unwrap().unwrap();
1724        assert_eq!(rn.figure(), "V7");
1725    }
1726
1727    #[test]
1728    fn analyzes_accidentals_inversions_and_half_diminished_quality() {
1729        let key = Key::from_tonic_mode("C", "major").unwrap();
1730
1731        let neapolitan = Chord::new("D- F A-").unwrap();
1732        let rn = RomanNumeral::analyze(&neapolitan, key.clone())
1733            .unwrap()
1734            .unwrap();
1735        assert_eq!(rn.figure(), "bII");
1736        assert_eq!(rn.degree(), 2);
1737        assert_eq!(rn.accidental(), -1);
1738
1739        let first_inversion = Chord::new("E4 G4 C5").unwrap();
1740        let rn = RomanNumeral::analyze(&first_inversion, key.clone())
1741            .unwrap()
1742            .unwrap();
1743        assert_eq!(rn.figure(), "I6");
1744
1745        let leading_tone = Chord::new("B D F A").unwrap();
1746        let rn = RomanNumeral::analyze(&leading_tone, key).unwrap().unwrap();
1747        assert_eq!(rn.figure(), "vii\u{00f8}7");
1748    }
1749
1750    #[test]
1751    fn analyzes_with_explicit_root_for_browser_style_sets() {
1752        let key = Key::from_tonic_mode("C", "major").unwrap();
1753        let root = Pitch::from_name("C").unwrap();
1754        let chord = Chord::new("C E G").unwrap();
1755        let rn = RomanNumeral::analyze_with_root(&chord, key.clone(), &root)
1756            .unwrap()
1757            .unwrap();
1758        assert_eq!(rn.figure(), "I");
1759
1760        let seventh = Chord::new("C E G B-").unwrap();
1761        let rn = RomanNumeral::analyze_with_root(&seventh, key, &root)
1762            .unwrap()
1763            .unwrap();
1764        assert_eq!(rn.figure(), "I7");
1765    }
1766
1767    #[test]
1768    fn analyzes_augmented_sixth_chords_functionally() {
1769        let key = Key::from_tonic_mode("C", "minor").unwrap();
1770        let root = Pitch::from_name("C").unwrap();
1771        let french = Chord::new("C D F# A-").unwrap();
1772        let rn = RomanNumeral::analyze_with_root(&french, key.clone(), &root)
1773            .unwrap()
1774            .unwrap();
1775        assert_eq!(rn.figure(), "Fr+6");
1776
1777        let german = Chord::new("A- C E- F#").unwrap();
1778        let rn = RomanNumeral::analyze(&german, key).unwrap().unwrap();
1779        assert_eq!(rn.figure(), "Ger+6");
1780    }
1781
1782    #[test]
1783    fn figured_bass_shorthands_give_music21_inversions() {
1784        // Captured from music21's RomanNumeral(...).inversion(). `V642` holds
1785        // `64` inside it and is a third inversion all the same.
1786        let key = Key::from_tonic_mode("C", "major").unwrap();
1787        for (figure, expected) in [
1788            ("V", 0),
1789            ("V6", 1),
1790            ("V64", 2),
1791            ("V7", 0),
1792            ("V65", 1),
1793            ("V43", 2),
1794            ("V42", 3),
1795            ("V642", 3),
1796            ("V653", 1),
1797            ("V643", 2),
1798            ("V63", 1),
1799            ("V53", 0),
1800            ("V9", 0),
1801        ] {
1802            let numeral = RomanNumeral::new(figure, key.clone())
1803                .unwrap_or_else(|err| panic!("{figure} should parse: {err}"));
1804            assert_eq!(numeral.inversion(), expected, "{figure}");
1805        }
1806    }
1807
1808    #[test]
1809    fn a_figure_expands_into_a_figured_bass_column() {
1810        // music21's shorthand: a bare `7` is a seventh chord in root
1811        // position, and `43` is the same chord over its fifth.
1812        let numbers = |figure: &str| {
1813            RomanNumeral::new(figure, Key::from_tonic("C").unwrap())
1814                .unwrap()
1815                .figure_numbers()
1816        };
1817        assert_eq!(numbers("V"), vec![5, 3]);
1818        assert_eq!(numbers("V7"), vec![7, 5, 3]);
1819        assert_eq!(numbers("V65"), vec![6, 5, 3]);
1820        assert_eq!(numbers("V43"), vec![6, 4, 3]);
1821        assert_eq!(numbers("V9"), vec![9, 7, 5, 3]);
1822        // A column written out is left as written, alterations and all.
1823        assert_eq!(numbers("V7#5b3"), vec![7, 5, 3]);
1824    }
1825
1826    #[test]
1827    fn a_column_says_which_degree_is_in_the_bass() {
1828        // A sixth and a third over the bass is a triad in first inversion, so
1829        // the bass of a `V6` is the seventh degree.
1830        assert_eq!(bass_scale_degree_from_notation(5, &[6, 3]).unwrap(), 7);
1831        assert_eq!(bass_scale_degree_from_notation(1, &[5, 3]).unwrap(), 1);
1832        assert_eq!(bass_scale_degree_from_notation(2, &[6, 5, 3]).unwrap(), 4);
1833        // A column that implies no root leaves the degree where it was.
1834        assert_eq!(bass_scale_degree_from_notation(5, &[5, 4]).unwrap(), 5);
1835    }
1836
1837    #[test]
1838    fn a_figure_alters_one_note_rather_than_naming_another_chord() {
1839        let names = |figure: &str, key: &str| {
1840            RomanNumeral::new(figure, Key::from_tonic(key).unwrap())
1841                .unwrap()
1842                .to_chord()
1843                .unwrap()
1844                .pitch_names()
1845        };
1846        // Writing the fifth out says the column is `7,b5` and nothing else,
1847        // so the third music21 would have implied is gone with it.
1848        assert_eq!(names("V7b5", "C"), ["G", "D-", "F"]);
1849        assert_eq!(names("V[no3]", "F"), ["C", "G"]);
1850        assert_eq!(names("I[add4][no3]", "C"), ["C", "F", "G"]);
1851        // The sharp of `i#7` raises the flattened seventh of a minor key to a
1852        // natural, rather than spelling a `B#`.
1853        assert_eq!(names("i#7", "c"), ["C", "E-", "G", "B"]);
1854        assert_eq!(names("i#7", "C"), ["C", "E-", "G", "B#"]);
1855    }
1856
1857    #[test]
1858    fn a_numeral_can_be_read_over_a_scale_that_is_not_a_key() {
1859        // music21 reads a numeral against any concrete scale, and an
1860        // octatonic one has eight degrees rather than seven.
1861        let scale = crate::scale::Scale::new(
1862            crate::scale::ScaleType::Octatonic,
1863            Pitch::from_name("C2").unwrap(),
1864        );
1865        let numeral = RomanNumeral::over_scale(
1866            "I9",
1867            Key::from_tonic("C").unwrap(),
1868            Some(scale),
1869            Minor67Default::Quality,
1870            Minor67Default::Quality,
1871            false,
1872        )
1873        .unwrap();
1874        let pitches: Vec<String> = numeral
1875            .to_chord()
1876            .unwrap()
1877            .pitches()
1878            .iter()
1879            .map(Pitch::name_with_octave)
1880            .collect();
1881        assert_eq!(pitches, ["C2", "E-2", "G-2", "A2", "C3"]);
1882    }
1883
1884    #[test]
1885    fn roman_numerals_parse_inversions_and_qualities() {
1886        let key = Key::from_tonic_mode("C", "major").unwrap();
1887        let first_inversion = RomanNumeral::new("I6", key.clone()).unwrap();
1888        assert_eq!(first_inversion.inversion(), 1);
1889        assert_eq!(
1890            first_inversion
1891                .to_chord()
1892                .unwrap()
1893                .pitches()
1894                .into_iter()
1895                .map(|pitch| pitch.name())
1896                .collect::<Vec<_>>(),
1897            vec!["E", "G", "C"]
1898        );
1899
1900        let diminished = RomanNumeral::new("viio7", key.clone()).unwrap();
1901        assert_eq!(diminished.degree(), 7);
1902        assert!(
1903            diminished
1904                .to_chord()
1905                .unwrap()
1906                .common_name()
1907                .contains("diminished")
1908        );
1909
1910        let half_diminished = RomanNumeral::new("vii\u{00f8}7", key.clone()).unwrap();
1911        assert_eq!(half_diminished.degree(), 7);
1912        assert_eq!(half_diminished.accidental(), 0);
1913        assert!(
1914            half_diminished
1915                .to_chord()
1916                .unwrap()
1917                .common_name()
1918                .contains("half-diminished")
1919        );
1920
1921        let borrowed = RomanNumeral::new("bII", key.clone()).unwrap();
1922        assert_eq!(borrowed.degree(), 2);
1923        assert_eq!(borrowed.accidental(), -1);
1924
1925        let added_thirteenth = RomanNumeral::new("I add(13)", key.clone()).unwrap();
1926        assert_eq!(added_thirteenth.inversion(), 0);
1927        assert_eq!(
1928            added_thirteenth.to_chord().unwrap().common_name(),
1929            "major triad"
1930        );
1931
1932        let augmented = RomanNumeral::new("III+", key).unwrap();
1933        assert_eq!(
1934            augmented
1935                .to_chord()
1936                .unwrap()
1937                .pitches()
1938                .into_iter()
1939                .map(|pitch| pitch.name())
1940                .collect::<Vec<_>>(),
1941            vec!["E", "G#", "B#"]
1942        );
1943    }
1944
1945    #[test]
1946    fn roman_numerals_parse_augmented_sixth_figures() {
1947        let key = Key::from_tonic_mode("C", "minor").unwrap();
1948        let french = RomanNumeral::new("Fr+6", key).unwrap();
1949        // music21 reads a French sixth on the second degree, with nothing
1950        // written in front of it; the sharp that makes it augmented is the
1951        // bracketed one on its third.
1952        assert_eq!(french.degree(), 2);
1953        assert_eq!(french.accidental(), 0);
1954        assert_eq!(
1955            french
1956                .to_chord()
1957                .unwrap()
1958                .pitches()
1959                .into_iter()
1960                .map(|pitch| pitch.name())
1961                .collect::<Vec<_>>(),
1962            vec!["A-", "C", "D", "F#"]
1963        );
1964    }
1965
1966    #[test]
1967    fn roman_numerals_report_invalid_figures_and_empty_analysis() {
1968        let key = Key::from_tonic_mode("C", "major").unwrap();
1969
1970        assert!(RomanNumeral::new("", key.clone()).is_err());
1971        assert!(RomanNumeral::new("Q", key.clone()).is_err());
1972        assert!(analyze_chord(&Chord::empty(), key).unwrap().is_none());
1973    }
1974}