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.ToString() writes an altered tension straight after the root: "C(#11)" prints as "C#11" and reparses as a C# dominant eleventh #336
For a chord with no seventh, Chord.ToString() (Semantics.Music/Chord.cs:532-545) writes nothing between the root and the next token. AppendSixth (:597-602) and AppendTensions (:629-647) then write b6, b9, #9, #11 or b13 immediately after the root name. On reparse, TryParseRoot reads that leading b or # as the root's accidental, so the chord comes back with a different root and different notes.
Reproduced on net10.0 against HEAD 9c64b67. Each input goes through Chord.Parse(x).ToString(), and that output is parsed again:
Input
ToString()
Reparsed as
C(#11)
C#11
C# dominant 11. Tones go from [0,4,7,18] to [0,4,7,10,14,17]
C(b9)
Cb9
B9 (prints B79)
C(#9)
C#9
C#9
C(b13)
Cb13
B13
C(b6)
Cb6
B6
B(b9)
Bb9
A#9
Cadd9(#11) round-trips correctly, because add9 sits between the root and #11. The same wrong text appears in Progression.ToString(), so a progression containing any of these chords does not round-trip either.
Why it matters
The XML doc on ToString says "The formatter is the inverse of Parse". Here the chord round-trips to a different root and a different set of notes, so saving and reloading a chart changes the harmony. The round-trip corpus has no major triad that carries an altered tension without a seventh, which is why the tests don't catch it. This is a different case from #300, which covers a seventh combined with add11/add13.
Open PR #335 solves the same problem for the new flat-five triad: it writes C(b5), "because Cb5 would read back as a C-flat power chord". The tensions and the flat sixth need the same treatment.
Suggested fix
When nothing has been written after the root, emit altered tensions and the flat sixth in a form the parser cannot mistake for an accidental. The parser already accepts the parenthesised form: C(#11), C(b9), C(b6). Leave the seventh-bearing forms (C7#11) unchanged.
Acceptance criteria
C(#11), C(b9), C(#9), C(b13), C(b6) and B(b9) are added to ChordRoundTripTests.Corpus, and each one reparses equal to the original.
Existing round-trip cases keep their current canonical text.
Priority: High. ToString() is documented as the inverse of Parse, yet it silently produces text that reparses to a different root and different notes. Saving and reloading a chord or progression changes the harmony with no error, which makes this data corruption, not just bad display text.
Activity
matt-edmondson commentedon Sep 30, 2026
Triage
ToString()is documented as the inverse ofParse, yet it silently produces text that reparses to a different root and different notes. Saving and reloading a chord or progression changes the harmony with no error, which makes this data corruption, not just bad display text.Semantics.Music/Chord.cs(ToString,AppendSixth,AppendTensions).C(b5)parenthesised form is the pattern to follow here.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