[NOTE: This is a more serious issue than just tuplets on single notes. See the glissando examples below.]
NotationsWriter::writeNotations writes every entry of NoteData::noteAttachmentData::tupletStops before every entry of tupletStarts:
for (const auto &tupletStop : myNoteData.noteAttachmentData.tupletStops) { ... } // all stops
for (const auto &tupletStart : myNoteData.noteAttachmentData.tupletStarts) { ... } // then all starts
That convention is right when a note closes one tuplet and opens another. It is wrong for a tuplet contained in a single note, where both ends belong to the same note and MusicXML wants start and then stop.
Symptom. A Finale document with a nested tuplet whose inner tuplet covers exactly one note (the last note of the outer tuplet) produces, on that note:
<tuplet type="stop" number="1"/>
<tuplet type="stop" number="2"/>
<tuplet type="start" number="2"/>
Number 2 closes before it opens. A reader encounters a stop for a tuplet that was never open, then a start that is never closed. The correct sequence is:
<tuplet type="start" number="2"/>
<tuplet type="stop" number="2"/>
<tuplet type="stop" number="1"/>
The numbers themselves are correct and pair correctly; only the order is wrong.
Why the author cannot work around it. tupletStarts and tupletStops are separate vectors, so there is no way to express "this start precedes that stop on the same note". Any ordering the author chooses within each vector is discarded by the two-loop structure.
Suggested shape
Order a note's tuplet notations so that a start whose matching stop is on the same note is emitted before that stop. Since numberLevel already pairs the two ends, that can be decided locally: when a TupletStart and a TupletStop on one note share a numberLevel, write the start first, and otherwise keep the existing stops-before-starts order for the tuplets that merely pass through.
Note
This is the ordering half only. <normal-type> for the same nested-tuplet territory is #428, and the two are independent. Tuplets also still carry a raw numberLevel int rather than an api::SpannerNumber, so they get none of the writer-side number assignment from #320; that is a third, separate matter and is not the cause of this bug.
[NOTE: This is a more serious issue than just tuplets on single notes. See the glissando examples below.]
NotationsWriter::writeNotationswrites every entry ofNoteData::noteAttachmentData::tupletStopsbefore every entry oftupletStarts:That convention is right when a note closes one tuplet and opens another. It is wrong for a tuplet contained in a single note, where both ends belong to the same note and MusicXML wants
startand thenstop.Symptom. A Finale document with a nested tuplet whose inner tuplet covers exactly one note (the last note of the outer tuplet) produces, on that note:
Number 2 closes before it opens. A reader encounters a stop for a tuplet that was never open, then a start that is never closed. The correct sequence is:
The numbers themselves are correct and pair correctly; only the order is wrong.
Why the author cannot work around it.
tupletStartsandtupletStopsare separate vectors, so there is no way to express "this start precedes that stop on the same note". Any ordering the author chooses within each vector is discarded by the two-loop structure.Suggested shape
Order a note's tuplet notations so that a start whose matching stop is on the same note is emitted before that stop. Since
numberLevelalready pairs the two ends, that can be decided locally: when aTupletStartand aTupletStopon one note share anumberLevel, write the start first, and otherwise keep the existing stops-before-starts order for the tuplets that merely pass through.Note
This is the ordering half only.
<normal-type>for the same nested-tuplet territory is #428, and the two are independent. Tuplets also still carry a rawnumberLevelint rather than anapi::SpannerNumber, so they get none of the writer-side number assignment from #320; that is a third, separate matter and is not the cause of this bug.