Skip to content

feat: preserve source voice names in mx::api - #422

Open
webern wants to merge 2 commits into
claude/mx-api-roundtrip-corpus-9pkrg6-04-staff-countfrom
claude/mx-api-roundtrip-corpus-9pkrg6-05-voice-label
Open

feat: preserve source voice names in mx::api#422
webern wants to merge 2 commits into
claude/mx-api-roundtrip-corpus-9pkrg6-04-staff-countfrom
claude/mx-api-roundtrip-corpus-9pkrg6-05-voice-label

Conversation

@webern

@webern webern commented Aug 23, 2026

Copy link
Copy Markdown
Owner

Human Summary

Allows voices to be labeled instead of numbered.

Summary

mx::api keys the voices of a staff by index, and the writer named every voice from that index. A voice whose <voice> element said something else lost its name. MusicXML does not require a voice to be named with a number at all -- <voice>FirstVoice</voice> is legal, and some programs write it -- and a part whose voices are numbered straight through its staves rather than restarting on each one does not survive renumbering either.

VoiceData gains std::string label. Empty, the default, numbers the voice from its index exactly as before. The reader fills it in from the <voice> element and then drops it again wherever it is just the number the writer would have used anyway, so the common case comes back empty and the field stays out of the way.

The name belongs on the voice, not on each note: containment is what states voice membership in this api, and a per-note name would let notes in one voice disagree about which voice they are in.

Which notes get a <voice> element does not change, so a source that leaves it off a chord member (Sibelius does this) still does. The one addition is a voice where no note carried a number -- an authored score, or a voice named with a word -- since there the label is the only thing that can ask for the element.

Non-breaking: one new field defaulting to the behavior mx had before. NoteData::userRequestedVoiceNumber is untouched.

Round-trip corpus

Adds 2 files to roundtrip-baseline.txt (405 -> 407): musuite/testChordNoVoice.xml and musuite/testStringVoiceName.xml.

musuite/testVoiceMapper1.xml, the third file the classifier listed under value:voice, is not fixed here, and this is not the change that will fix it. That file writes voices 4, 5, 4 back to back with no <backup> between them. mx's reader buckets voices by counting backups, so all three notes land in one api voice, and one api voice can only carry one name. Splitting them into separate voices would be correct but would make the writer emit <forward>/<backup> between them, which the source does not have. Getting that file to round-trip means changing how the reader buckets voices and how the writer interleaves them -- a separate piece of work, worth its own issue.

Testing

  • New VoiceLabelApiTest: unlabeled voices numbered from their index, word names round-tripped, a lone voice named 5 written as 5 and stable on a second write, and a name that matches the index dropped on read (*VoiceLabel*: 18 assertions in 4 test cases)
  • make api-test (6332 assertions in 586 test cases)
  • make api-roundtrip (407 passed, 0 failed of 407 pinned)
  • make api-roundtrip-discover reports exactly the 2 new passes and no regressions
  • make fmt

References

mx::api keys the voices of a staff by index and the writer named every voice
from that index, so a voice whose <voice> element said something else lost its
name. MusicXML does not require a voice to be named with a number, and a part
whose voices are numbered straight through its staves rather than restarting on
each one does not survive renumbering either.

VoiceData gains a label. Empty, the default, numbers the voice from its index
as before. The reader fills it in from the <voice> element and then drops it
again wherever it is just the number the writer would have used anyway, so the
common case stays empty.

Which notes get a <voice> element does not change, so a source that leaves it
off a chord member still does. The one addition is a voice where no note
carried a number: there the label is the only thing that can ask for the
element, so it does.

Pins two more files in the api round-trip baseline.
@webern webern added feature new feature request non-breaking fixes or implementation that do not require breaking changes api Affects the mx::api layer impl Affects the mx::impl layer ai Issues opened by, or through, a coding agent. labels Aug 23, 2026 — with Claude
…nto claude/mx-api-roundtrip-corpus-9pkrg6-05-voice-label
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai Issues opened by, or through, a coding agent. api Affects the mx::api layer feature new feature request impl Affects the mx::impl layer non-breaking fixes or implementation that do not require breaking changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant