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