What's wrong
The parser takes a capital M as the major-seventh marker only when a 7 follows it. When a bare extension follows it, as in M9, M11 or M13, the M is dropped and the chord becomes a dominant.
Trace for Chord.Parse("CM9") at 1ddaf24:
- The body is
"M9". DetermineQuality finds no min, no - and no leading m, so the quality is Major.
DetermineSeventh (Semantics.Music/Chord.cs:367-388) sets hasMaj7 only for "maj", "M7" or 'Δ'. "M9" matches none of these, and the body has no '7', so the seventh is None.
ApplyExtensions (Chord.cs:390-424) takes "9". Because the seventh is None, it sets it to SeventhType.Dominant.
IsQualityVocabulary("M") (Chord.cs:182-193) allows 'M', so the parse succeeds.
The result is Major quality, a Dominant seventh and a Nine tension. ChordTones() returns 0, 4, 7, 10, 14, which is C E G Bb D, and ToString() prints C9. The intended chord is C E G B D.
The same thing happens in three more cases:
CM11 and CM13 become C11 and C13.
CmM9 becomes Minor with a Dominant seventh, which is Cm9, not the minor-major ninth.
The parser is also inconsistent with itself. CM7 is read as a major seventh, and CΔ9 and Cmaj9 are read as major ninths, so only the M + extension spelling is affected.
Why it matters
CM9 is a standard lead-sheet spelling. Parsing it succeeds without any error but returns a chord with a different seventh, so ChordTones(), Roman-numeral analysis and anything else downstream are wrong for that chord.
Relationship to existing issues
This is related to #281 but separate from it. #281 covers CMaj7 and CmMaj7, where the capitalised word Maj is not recognised. Matching "Maj" would fix those, but it would not fix CM9 or CmM9, because they contain no Maj.
Suggested fix
- In
DetermineSeventh, also treat a capital M followed directly by 9, 11 or 13 as the major-seventh marker. That can be done with "M9", "M11" and "M13" checks, or by detecting a capital M that is not the start of "min" or part of "maj".
- Add
ChordTests cases that assert both the SeventhType and the pitch set for:
CM9 → [0, 4, 7, 11, 14]
CM11
CM13
CmM9 → [0, 3, 7, 11, 14]
- Add a round-trip test to confirm the parse agrees with what
ToString() prints.
What's wrong
The parser takes a capital
Mas the major-seventh marker only when a7follows it. When a bare extension follows it, as inM9,M11orM13, theMis dropped and the chord becomes a dominant.Trace for
Chord.Parse("CM9")at1ddaf24:"M9".DetermineQualityfinds nomin, no-and no leadingm, so the quality is Major.DetermineSeventh(Semantics.Music/Chord.cs:367-388) setshasMaj7only for"maj","M7"or'Δ'."M9"matches none of these, and the body has no'7', so the seventh isNone.ApplyExtensions(Chord.cs:390-424) takes"9". Because the seventh isNone, it sets it toSeventhType.Dominant.IsQualityVocabulary("M")(Chord.cs:182-193) allows'M', so the parse succeeds.The result is Major quality, a Dominant seventh and a Nine tension.
ChordTones()returns0, 4, 7, 10, 14, which is C E G Bb D, andToString()printsC9. The intended chord is C E G B D.The same thing happens in three more cases:
CM11andCM13become C11 and C13.CmM9becomes Minor with a Dominant seventh, which is Cm9, not the minor-major ninth.The parser is also inconsistent with itself.
CM7is read as a major seventh, andCΔ9andCmaj9are read as major ninths, so only theM+ extension spelling is affected.Why it matters
CM9is a standard lead-sheet spelling. Parsing it succeeds without any error but returns a chord with a different seventh, soChordTones(), Roman-numeral analysis and anything else downstream are wrong for that chord.Relationship to existing issues
This is related to #281 but separate from it. #281 covers
CMaj7andCmMaj7, where the capitalised wordMajis not recognised. Matching"Maj"would fix those, but it would not fixCM9orCmM9, because they contain noMaj.Suggested fix
DetermineSeventh, also treat a capitalMfollowed directly by9,11or13as the major-seventh marker. That can be done with"M9","M11"and"M13"checks, or by detecting a capitalMthat is not the start of"min"or part of"maj".ChordTestscases that assert both theSeventhTypeand the pitch set for:CM9→[0, 4, 7, 11, 14]CM11CM13CmM9→[0, 3, 7, 11, 14]ToString()prints.