Skip to content

Key.RomanNumeralOf produces duplicate/incorrect labels for modes with more than 7 degrees #249

Description

@matt-edmondson

What's wrong

RomanNumeralOf indexes a fixed 7-entry table with RomanNumerals[(degree.Degree - 1) % RomanNumerals.Length]. degree.Degree comes from Scale.DegreeOf, which returns i + 1 where i ranges over Mode.Intervals.Count — and Mode supports 8-note modes (OctatonicHalfWhole/OctatonicWholeHalf) and the 12-note Chromatic mode. For these modes the % 7 wraps distinct scale degrees onto the same roman numeral, silently.

Why it matters (concrete failure scenario)

Key chromatic = Key.Create(PitchClass.Create(0), Mode.Chromatic);
chromatic.RomanNumeralOf(Chord.Parse("C#"));  // "II" (degree 2)
chromatic.RomanNumeralOf(Chord.Parse("Ab"));  // "II" (degree 9) — same label, different chord

A chord rooted at scale degree 2 (C♯) and one rooted at degree 9 (A♭) both resolve to "II". The tonic (degree 1) and the 8th chromatic degree similarly collide onto "I". Mode.OctatonicHalfWhole (8 degrees) has the same collision between degree 8 and the tonic. This is also inconsistent with the inverse operation: ChordFromRomanNumeral explicitly bounds-checks the degree and throws FormatException rather than wrapping — so the two "both directions" APIs disagree on how to handle non-heptatonic keys.

Suggested fix / acceptance criteria

Either (a) throw a descriptive exception from RomanNumeralOf when degree.Degree > Mode.DegreeCount for modes without a conventional 7-degree numeral system (mirroring ChordFromRomanNumeral's guard), or (b) size/select the numeral table from the mode's actual degree count so degrees beyond 7 get distinct labels instead of wrapping. Add a test asserting RomanNumeralOf never returns the same string for two distinct, non-enharmonic scale degrees within one mode, at least for Chromatic and both octatonic modes.

File: Semantics.Music/Key.cs (RomanNumeralOf, RomanNumerals)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions