Skip to content

feat: model cue-note ties as notation, stop duplicating <tied>, stabilize note sorting - #379

Open
rpatters1 wants to merge 2 commits into
webern:mainfrom
rpatters1:tie-cue-grace-notes
Open

feat: model cue-note ties as notation, stop duplicating <tied>, stabilize note sorting#379
rpatters1 wants to merge 2 commits into
webern:mainfrom
rpatters1:tie-cue-grace-notes

Conversation

@rpatters1

@rpatters1 rpatters1 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Human Summary

Closes a gap in cue-note ties through mx::api, allowing them to be emitted as notation-only ties.

Summary

MusicXML states a tie twice: <tie> for sound and <tied> for notation. Two defects came
out of how mx::impl handled that split. A third, unrelated fix is folded in below.

Cue and grace-cue notes dropped their ties entirely. Half of that was right - the schema
gives <tie> a slot in only two of the four note-choice branches (normal and grace-normal),
because a cue note is silent and a sound-level tie on it is meaningless. But <notations>
sits outside the note choice, so <tied> is legal on all four flavors, and it was being
dropped along with the <tie>. NoteWriter::addTie now gates only the core::Tie push on
isCue and always records the <tied>; NoteReader reads isTieStart / isTieStop back
from the <tied> notation for those two flavors, since they have no <tie> to read from.
Grace-normal notes already round-tripped ties and are unchanged.

The second defect: NoteWriter synthesized a bare <tied> from the tie flags while the
reader routed source <tied> into noteAttachmentData.curveStarts / curveStops, which
NotationsWriter also emits with its full attributes. Any note whose source had both
elements wrote <tied> twice - exact doubling, e.g. Adagio and Fugue in C minor, K.546.xml
went from 52 <tied type="start"> to 104. NoteWriter::hasTieCurve now suppresses the
synthesized one when a curve already covers that direction. Authoring a flag alone still
emits both encodings, so the api keeps one field for one fact (doctrine principle 3).

data/synthetic/tied.cue.4.0.xml defends both halves: measure 1 has cue and grace-cue tie
pairs (notation only), measure 2 a grace-normal pair carrying both <tie> and <tied>.

cueNoteTiesAreDropped pinned the old behavior, so it is now cueNoteTiesAreNotationOnly,
joined by tiedNotationIsNotDuplicated. New ScoreDataSortTest.cpp covers sort stability. The mx-api-doctrine skill cited "cue-note ties are silently dropped" as its example of a defined fallback; that clause is now inaccurate and was rewritten.

Separately: ScoreWriter calls ScoreData::sort(), which ordered notes, clefs, and
directions by tickTimePosition alone using std::sort. Every member of a chord shares one
tick, so they all compare equal, and std::sort is not stable - their relative order was
left to the standard library. libc++ happened to leave them alone; libstdc++ permutes them.
Since MusicXML spells a chord by omitting <chord> from its first note, that rewrites the
chord. Switched to std::stable_sort; the comparators now take const&, which
stable_sort requires and which was the right signature anyway.

This one is pre-existing and platform-dependent, not fallout from the tie work. It surfaced because ly32d_Arpeggio.xml - 14 chords - passed on macOS and was pinned on the strength of a macOS-only run, then failed the Linux CI job with text mismatch at .../note/pitch/step: expected 'C', actual 'E'. Its baseline comment now records why.

Not addressed: a normal note whose source has <tie> but no <tied> still gains a <tied>
on write (visible in ly33b_Spanners_Tie.xml). That is pre-existing principle-3
normalization, left alone deliberately.

Testing

  • make api-roundtrip: 351 passed, 0 failed - baseline grew 295 -> 351
  • Corpus discovery 297 -> 350 PASS with zero regressions, verified by capturing
    before/after pass-lists across a revert; every previously pinned file still passes
  • 54 newly passing files pinned (48 kiritan_singing, 5 lysuite, the new fixture), plus 2
    that already passed but were never pinned (ly32d_Arpeggio.xml,
    multiple-rest.3.0.xml)
  • make api-test: all tests pass (5231 assertions in 464 test cases)
  • make core-roundtrip-test: all tests pass (838 test cases); pinned count bumped to 837
    and make audit regenerated corpus.xml plus the new sidecar
  • cueNoteTiesAreNotationOnly and tiedNotationIsNotDuplicated fail before the fix,
    pass after
  • Linux CI - the sort fix verified in CI Linux build

References

@rpatters1 rpatters1 changed the title feat: model cue-note ties as notation and stop duplicating <tied> feat: model cue-note ties as notation, stop duplicating <tied>, stabilize note sorting Jul 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant