You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Chord.Parse reads "CM9", "CM11" and "CM13" as dominant chords (Bb, not B), and "CmM9" as a plain Cm9 #322
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.
Category: Bug (a standard chord spelling parses without error to the wrong chord)
Priority: Medium. CM9, CM11, CM13 and CmM9 get a dominant seventh instead of a major seventh, so ChordTones() and any Roman-numeral analysis are wrong for those chords. The scope is limited to the M + extension spelling, because CM7, Cmaj9 and CΔ9 already parse correctly.
Area / suggested assignment:Semantics.Music (Chord.DetermineSeventh), with new cases in ChordTests.
Suggested next step: treat a capital M followed by 9, 11 or 13 as the major-seventh marker, and add tests that assert both the pitch sets and the parse/ToString() round trip.
Activity
matt-edmondson commentedon Sep 29, 2026
Triage
CM9,CM11,CM13andCmM9get a dominant seventh instead of a major seventh, soChordTones()and any Roman-numeral analysis are wrong for those chords. The scope is limited to theM+ extension spelling, becauseCM7,Cmaj9andCΔ9already parse correctly.Semantics.Music(Chord.DetermineSeventh), with new cases inChordTests.CMaj7/CmMaj7). Both touchDetermineSeventh, so fixing them in one PR would avoid churn.Suggested next step: treat a capital
Mfollowed by9,11or13as the major-seventh marker, and add tests that assert both the pitch sets and the parse/ToString()round trip.Generated by Claude Code
matt-edmondson commentedon Sep 30, 2026
Covered by #360, which redesigns this bug cluster as a whole. Implement it through that issue rather than individually.
Generated by Claude Code
[minor] Redesign chord symbols; fixes #300, #322, #336, #340, #341, #344