Skip to content

fix: write normal-type from DurationData instead of inferring it from sibling notes - #433

Merged
webern merged 2 commits into
mainfrom
claude/normal-type-over-expression-u5d7pr
Aug 29, 2026
Merged

fix: write normal-type from DurationData instead of inferring it from sibling notes#433
webern merged 2 commits into
mainfrom
claude/normal-type-over-expression-u5d7pr

Conversation

@webern

@webern webern commented Aug 28, 2026

Copy link
Copy Markdown
Owner

Human Summary

I am not quite sure what I was going for when I had the writer attempting to derive normal-note from the tick time positions. I think I wa trying to make a more fully automatic tuplet experience, but it definitely did not work correctly. This implements the writer in the way that the api struct implies which should fix the problem. In other words, you are now expected to fill normal-note yourself when it is needed and the api has no opinion about whether you are using it correctly.

I created #434 to track the idea of a tuplet helper.

Summary

NoteWriter ignored DurationData::timeModificationNormalType and instead recomputed normal-type by scanning sibling notes for a TupletStart/TupletStop pair and measuring the tick distance between them. That wrote the element on every note of every tuplet (253 written for 11 requested in the issue's test document), and a note that opens two tuplets broke the sibling search so nothing was written no matter what the author asked for.

The writer now treats the field as the author's statement: it emits normal-type (and normal-dot) exactly when timeModificationNormalType is set and omits it when unspecified, which MusicXML reads as the note's own type. The sibling scan, findNormalNameAndDots, and the writer's note-index plumbing are deleted. (NoteWriter still receives the voice's notes, which the <voice>-element logic from #422 uses.)

For the round trip, NoteReader now records whether the source time-modification actually carried a normal-type, and NoteFunctions maps absence to unspecified. This replaces a heuristic (dots > 0 && type != note type) that dropped an explicit undotted normal-type on read, which the writer's inference then papered over on write.

Everything is in the mx::impl layer; no public header changed. Behavior of written output changes: an author who never sets timeModificationNormalType no longer gets an inferred normal-type in the output.

The two FreezingRoundTrip tests that pinned the old behavior now assert presence parity: a saved note carries normal-type exactly where the source did, with the same value. The PreserveTimeModificationNormalType.xml case also checks that a meaningful normal-type (differing from the note's own type) survives, and that an absent one is not invented.

I created issue #434 to track the idea of a tuplet helper.

Testing

  • checkMissingNormalTypeSimple asserts the fix: the source's normal-type survives and an absent one is not fabricated
  • make api-test passes (5463 assertions in 597 test cases)
  • make api-roundtrip regression gate passes (411/411 pinned files)
  • Discovery mode over the full corpus: the same 411 files pass as the pinned baseline, none newly failing
  • make fmt applied; make fmt-check passes

References

… sibling notes

NoteWriter scanned sibling notes for a TupletStart/TupletStop pair and
recomputed <normal-type> from their tick distance, writing the element on
every note of every tuplet and ignoring
DurationData::timeModificationNormalType entirely. Nested tuplets broke
the sibling search and lost the element altogether.

The writer now emits <normal-type> (and <normal-dot>) exactly when
timeModificationNormalType is set, and omits it when unspecified. On the
read side, NoteReader records whether the source <time-modification>
carried a <normal-type>, and NoteFunctions maps absence to unspecified
instead of guessing from a dots-and-type heuristic that dropped explicit
undotted values.

Closes #428
@webern webern added bug software defect breaking fixes or implementation that 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 28, 2026 — with Claude
@webern
webern merged commit 050c3dd into main Aug 29, 2026
8 checks passed
@webern
webern deleted the claude/normal-type-over-expression-u5d7pr branch August 29, 2026 07:26
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 breaking fixes or implementation that require breaking changes bug software defect impl Affects the mx::impl layer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NoteWriter infers <normal-type> from sibling notes instead of using DurationData::timeModificationNormalType

1 participant