Skip to content

NotationsWriter emits all spanner stops before all starts, breaking single-note spanners #429

Description

@rpatters1

[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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions