Summary
SpannerNumberResolver (#320) pools spanner numbers per staff per class (occupied[staffIndex][number]), but MusicXML's number-level type scopes concurrency to the part: numbers distinguish "concurrent objects of the same type when the objects overlap in MusicXML document order." Its own piano example says a cross-staff slur "will need a separate number from the mid-measure slurs" even though they are on different staves.
Repro
Two-staff part, two identity slurs: staff 2 spanning m1 -> m2, staff 1 inside m2. Serialized order:
m1 staff 2: <slur type="start" number="1"/> (still open when the next start emits)
m2 staff 1: <slur type="start" number="1"/>
m2 staff 1: <slur type="stop" number="1"/>
m2 staff 2: <slur type="stop" number="1"/>
The second slur must get a different number. Readers pair slur endpoints by number within the part, so this duplicate typically pairs the m1 start with the m2 staff-1 stop — a phantom cross-staff slur — and drops the rest.
Suggested fix
Drop the staff dimension from occupied and pool per part + class only. The cross-staff "reserve in every staff pool touched" logic then becomes unnecessary.
File containing this example.
(Note: this file was created by my new Finale exporter and MX because Finale's current exporter exports every staff as a different part. That means this file exhibits the bug.)
numberlev_2staffpart.musicxml.txt
Summary
SpannerNumberResolver(#320) pools spanner numbers per staff per class (occupied[staffIndex][number]), but MusicXML'snumber-leveltype scopes concurrency to the part: numbers distinguish "concurrent objects of the same type when the objects overlap in MusicXML document order." Its own piano example says a cross-staff slur "will need a separate number from the mid-measure slurs" even though they are on different staves.Repro
Two-staff part, two identity slurs: staff 2 spanning m1 -> m2, staff 1 inside m2. Serialized order:
The second slur must get a different number. Readers pair slur endpoints by number within the part, so this duplicate typically pairs the m1 start with the m2 staff-1 stop — a phantom cross-staff slur — and drops the rest.
Suggested fix
Drop the staff dimension from
occupiedand pool per part + class only. The cross-staff "reserve in every staff pool touched" logic then becomes unnecessary.File containing this example.
(Note: this file was created by my new Finale exporter and MX because Finale's current exporter exports every staff as a different part. That means this file exhibits the bug.)
numberlev_2staffpart.musicxml.txt