mx::impl::NoteWriter does not use api::DurationData::timeModificationNormalType. Instead it infers <normal-type> by scanning sibling notes for exactly one TupletStart and exactly one TupletStop. Two consequences follow, both measurable against Finale's own export of the same file.
Over-emission on ordinary tuplets. MusicXML reads an absent <normal-type> as the note's own <type>, so it only needs writing when the tuplet's normal note value differs. An author that sets the field only in that case still gets one on every note of every tuplet. For one Finale test document:
|
<normal-type> count |
Requested through DurationData |
11 |
| Written to the file |
253 |
Of those, merely repeating the note's own <type> |
242 |
| Finale's own export of the same music |
4 |
Redundant rather than incorrect, but it makes a diff against a reference export unreadable, and it overrides a deliberate choice the API already lets the author express.
Silent omission on nested tuplets. When a note opens two tuplets the sibling search matches nothing, so no <normal-type> is written at all, regardless of what the API asked for. For a 3:2 eighth tuplet inside a 3:2 quarter tuplet the omission happens to be right, because the cumulative 9:4 is expressed in the eighths the notes already are. That is luck rather than design: nothing in the writer establishes it, and a nested tuplet whose normal value does differ from the note type has no way to say so.
DurationData already models the whole cumulative time modification:
timeModificationActualNotes
timeModificationNormalNotes
timeModificationNormalType
timeModificationNormalTypeDots
Since <time-modification> is cumulative across nesting while <tuplet> notations are per-tuplet and visual, the two should not be derived from each other.
Suggested shape
- Write
<normal-type> from DurationData::timeModificationNormalType (and its dots) when set, and omit it when unspecified. Treat the field as the author's statement rather than a hint.
- Pair
TupletStart / TupletStop by numberLevel instead of assuming one of each per note, so nested tuplets are handled rather than skipped.
Scope
This issue is <normal-type> only. Two neighbouring tuplet matters are separate and neither is caused by this one:
An earlier revision of this issue conflated the third point with #429; the comment below records that correction.
mx::impl::NoteWriterdoes not useapi::DurationData::timeModificationNormalType. Instead it infers<normal-type>by scanning sibling notes for exactly oneTupletStartand exactly oneTupletStop. Two consequences follow, both measurable against Finale's own export of the same file.Over-emission on ordinary tuplets. MusicXML reads an absent
<normal-type>as the note's own<type>, so it only needs writing when the tuplet's normal note value differs. An author that sets the field only in that case still gets one on every note of every tuplet. For one Finale test document:<normal-type>countDurationData<type>Redundant rather than incorrect, but it makes a diff against a reference export unreadable, and it overrides a deliberate choice the API already lets the author express.
Silent omission on nested tuplets. When a note opens two tuplets the sibling search matches nothing, so no
<normal-type>is written at all, regardless of what the API asked for. For a 3:2 eighth tuplet inside a 3:2 quarter tuplet the omission happens to be right, because the cumulative9:4is expressed in the eighths the notes already are. That is luck rather than design: nothing in the writer establishes it, and a nested tuplet whose normal value does differ from the note type has no way to say so.DurationDataalready models the whole cumulative time modification:Since
<time-modification>is cumulative across nesting while<tuplet>notations are per-tuplet and visual, the two should not be derived from each other.Suggested shape
<normal-type>fromDurationData::timeModificationNormalType(and its dots) when set, and omit it when unspecified. Treat the field as the author's statement rather than a hint.TupletStart/TupletStopbynumberLevelinstead of assuming one of each per note, so nested tuplets are handled rather than skipped.Scope
This issue is
<normal-type>only. Two neighbouring tuplet matters are separate and neither is caused by this one:NotationsWriterwrites all tuplet stops before all tuplet starts, which inverts a tuplet contained in a single note.TupletStart/TupletStopcarry a rawnumberLevelint rather than anapi::SpannerNumber, so tuplets get none of the writer-side number assignment the other spanner families received in feat: unified spanner numbering with writer-side assignment #320, and an author has to allocate and recycle levels itself. Worth doing, but no known defect depends on it.An earlier revision of this issue conflated the third point with #429; the comment below records that correction.