Problem
OttavaStart::ottavaType determines the complete octave shift, including its size:
- 8va / 8vb →
size="8"
- 15ma / 15mb →
size="15"
- 22ma / 22mb →
size="22"
However, OttavaStop separately exposes std::optional<int> size. Authors must repeat the start's size on the stop when they want it emitted. This creates two fields for one musical fact, and the values can contradict each other.
Omitting the stop size is valid MusicXML, but some importers, including MuseScore, expect it to be present. Authoring interoperable output should not depend on manually copying the start size to the stop.
Using OttavaType directly on OttavaStop would not be ideal. A MusicXML stop has no up/down direction of its own, so the reader cannot distinguish 8va from 8vb using the stop element alone.
Proposed Direction
Derive an octave-shift stop's size automatically from its matching OttavaStart.
The writer already performs a part-wide spanner pass through SpannerNumberResolver. That pass could be extended, or generalized, to associate each ottava stop with its start and make the resolved size available to DirectionWriter::emitOttavaStop.
The intended authoring behavior would be:
- Set
OttavaStart::ottavaType.
- Add the corresponding
OttavaStop.
- The writer emits the appropriate stop size automatically.
OttavaStop::size could then be removed or replaced with a fidelity policy such as:
Bool writeSize = Bool::unspecified;
Possible semantics:
- unspecified: automatically write the size derived from the start.
- yes: explicitly write the derived size.
- no: omit the size attribute.
The reader could set yes or no according to whether the source stop explicitly contained size, preserving that spelling when required. Authored data would leave the field unspecified and get interoperable output automatically.
Design Considerations
- This would be a breaking public API change if OttavaStop::size is removed.
- Explicit, identity-based, and unspecified spanner numbers all need defined pairing behavior.
- Pairing must work across measures and with overlapping octave shifts.
- A dangling stop must have a harmless fallback and must not throw. Writing the MusicXML default size of 8 is one possible fallback.
- If a source stop's size contradicts its matching start, the start should probably be authoritative and the output normalized.
- MusicXML permits any positive integer for size, although mx::api models the conventional 8, 15, and 22 values. Nonstandard sizes therefore need a documented normalization policy.
A larger alternative would be to model an ottava as one span containing both endpoints and one OttavaType, but that is outside the scope of this change. (I have proposed this for other spanners as well, but it is a big refactor.)
Acceptance Criteria
Acceptance Criteria
Problem
OttavaStart::ottavaTypedetermines the complete octave shift, including its size:size="8"size="15"size="22"However,
OttavaStopseparately exposesstd::optional<int> size. Authors must repeat the start's size on the stop when they want it emitted. This creates two fields for one musical fact, and the values can contradict each other.Omitting the stop size is valid MusicXML, but some importers, including MuseScore, expect it to be present. Authoring interoperable output should not depend on manually copying the start size to the stop.
Using
OttavaTypedirectly onOttavaStopwould not be ideal. A MusicXML stop has no up/down direction of its own, so the reader cannot distinguish 8va from 8vb using the stop element alone.Proposed Direction
Derive an octave-shift stop's size automatically from its matching
OttavaStart.The writer already performs a part-wide spanner pass through
SpannerNumberResolver. That pass could be extended, or generalized, to associate each ottava stop with its start and make the resolved size available toDirectionWriter::emitOttavaStop.The intended authoring behavior would be:
OttavaStart::ottavaType.OttavaStop.OttavaStop::sizecould then be removed or replaced with a fidelity policy such as:Possible semantics:
The reader could set yes or no according to whether the source stop explicitly contained size, preserving that spelling when required. Authored data would leave the field unspecified and get interoperable output automatically.
Design Considerations
A larger alternative would be to model an ottava as one span containing both endpoints and one OttavaType, but that is outside the scope of this change. (I have proposed this for other spanners as well, but it is a big refactor.)
Acceptance Criteria
Acceptance Criteria