Skip to content

Chord.ToString() writes an altered tension straight after the root: "C(#11)" prints as "C#11" and reparses as a C# dominant eleventh #336

Description

@matt-edmondson

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.

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