What's wrong
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.
What's wrong
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) andAppendTensions(:629-647) then writeb6,b9,#9,#11orb13immediately after the root name. On reparse,TryParseRootreads that leadingbor#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:ToString()C(#11)C#11[0,4,7,18]to[0,4,7,10,14,17]C(b9)Cb9B79)C(#9)C#9C(b13)Cb13C(b6)Cb6B(b9)Bb9Cadd9(#11)round-trips correctly, becauseadd9sits between the root and#11. The same wrong text appears inProgression.ToString(), so a progression containing any of these chords does not round-trip either.Why it matters
The XML doc on
ToStringsays "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 withadd11/add13.Open PR #335 solves the same problem for the new flat-five triad: it writes
C(b5), "becauseCb5would 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)andB(b9)are added toChordRoundTripTests.Corpus, and each one reparses equal to the original.