Skip to content

Key.RomanNumeralOf labels the minor-key leading-tone chord as a flattened tonic: G#dim in A minor is "bi°", not "#vii°" #304

Description

@matt-edmondson

What's wrong

Scale.DegreeOf (Semantics.Music/Scale.cs:94-103) spells a pitch class that isn't in the scale against its nearest scale neighbours, preferring the flat spelling on a tie. When the pitch is above the scale's last offset there is no upper neighbour, so the flat candidate wraps around the octave to degree 1:

int flatDegree = upperIndex >= 0 ? upperIndex + 1 : 1;
int flatAlteration = semitone - (upperIndex >= 0 ? offsets[upperIndex] : offsets[0] + 12);
...
return Math.Abs(flatAlteration) <= Math.Abs(sharpAlteration) ? flat : sharp;

Every mode with a flat seventh has this problem: aeolian, dorian, phrygian, mixolydian, locrian, and the blues and pentatonic scales. Semitone 11 is one semitone from both neighbours, so it comes out as degree 1 with alteration −1, a flattened tonic.

Harmonic analysis never reads that pitch as a flattened tonic. It is the raised seventh, the leading tone, and the most common chromatic note in minor-key harmony.

Reproduced

In a scratch net10.0 app against Semantics.Music:

Key am = Key.Parse("A aeolian");
am.FunctionOf(PitchClass.Parse("G#"));                            // ScaleDegree { Degree = 1, Alteration = -1 }
am.RomanNumeralOf(Chord.Parse("G#dim"));                          // "bi°"
am.RomanNumeralOf(Chord.Parse("G#dim7"));                         // "bi°"
Key.Parse("G mixolydian").RomanNumeralOf(Chord.Parse("F#dim"));   // "bi°"
am.ChordFromRomanNumeral("#vii°7");                               // G#dim7 — the parser accepts the conventional spelling

So the minor progression i – iv – vii°7 – i is labelled i, iv, bi°, i. ChordFromRomanNumeral accepts #vii°, which means the output of RomanNumeralOf and the input ChordFromRomanNumeral expects disagree.

Suggested fix

In DegreeOf, when the flat candidate reaches degree 1 only by wrapping (upperIndex < 0), use the sharp spelling of the last degree instead. The flat-preferring tie-break stays as it is everywhere else, so major-key bII, bIII, bVI and bVII are unchanged.

Acceptance criteria

  • Key.Parse("A aeolian").FunctionOf(PitchClass.Parse("G#")) returns (7, +1).
  • Key.Parse("A aeolian").RomanNumeralOf(Chord.Parse("G#dim")) returns "#vii°".
  • RomanNumeralOf → ChordFromRomanNumeral round-trips for that chord.
  • The existing ScaleTests.DegreeOf_ChromaticPrefersFlatOfUpperDegree still passes.

Related but separate: #289 covers the seventh suffix (° vs °7 / ø7) on this same chord.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions