Skip to main content

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