Skip to content

Add API-level spanner identity for writer-side number assignment #297

Description

@rpatters1

Problem

MusicXML uses number attributes on spanners such as <slur> and <wedge> to distinguish simultaneously open spanners of the same type.

For slurs, assigning these numbers correctly can depend on the final serialized MusicXML order, not just musical time. This is especially true when multiple voices are written using <backup>: a slur that has musically ended may still appear open to a streaming reader until the later voice containing its stop is serialized.

Currently, mx::api exposes independent start/stop objects with numberLevel, so callers must assign MusicXML number values before writing. But callers do not own the final serialization order.

Proposed API

Add API-level spanner identity metadata to start/stop objects. This metadata would not be serialized directly. It would only tell the writer which start and stop belong to the same logical spanner.

Example shape:

  struct CurveStart {
      CurveType curveType;
      int numberLevel;
      std::optional<std::string> spannerId; // API metadata only
      ...
  };

  struct CurveStop {
      CurveType curveType;
      int numberLevel;
      std::optional<std::string> spannerId; // API metadata only
      ...
  };

A similar identity field could be added to other spanner types such as wedges.

Writer Behavior

When matching spanner identity metadata is present, the writer could assign or normalize MusicXML number values using the actual serialization order it controls.

Suggested behavior:

  • Pair start/stop records by spanner type and spannerId.
  • Assign legal MusicXML number values from 1..16.
  • Track currently open spanners in serialized output order.
  • Reuse the lowest available number when possible.
  • Preserve existing explicit numberLevel behavior when no spannerId is present, or define a clear precedence rule.

Why This Matters

A musical-time overlap calculation is not always enough.

Example:

  • slur A: starts measure 1 beat 1, ends beat 3
  • slur B: starts measure 1 beat 3.5, ends next measure beat 1
  • slur C: starts next measure beat 2, ends next measure beat 3
  • Musically, B and C do not overlap.
  • But if voice 1 is serialized before voice 2 using , Slur C may be written before Slur B’s stop appears in the MusicXML stream.

In that serialized order, both slurs are simultaneously open to a streaming MusicXML reader, so they need different number values.

Because mx::api controls the final note serialization order, writer-side assignment based on API-level spanner identity would be more reliable than requiring callers to guess that order.

Here is a test case that illustrates the above scenario:

slurs_overbars-ref.musicxml.txt

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions