You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Proposal: Support Interleaved Words and Symbols in mx::api Directions #294
(Sorry for the LLM-written issue. It was easier than thinking. 🙄)
Need
MusicXML allows a <direction-type> to contain an ordered sequence of <words> and <symbol> children. mx::api should be able to preserve and write these mixed direction
markings.
The immediate need is expressive dynamics and performance text, for example:
These are visually and semantically ordered. Splitting them into separate directions, or preserving only the words, loses useful notation intent.
Current Gap
mx::core already supports this shape: DirectionTypeChoiceChoice can be either words or symbol.
mx::api currently exposes direction words, but does not expose a corresponding ordered item for this mixed sequence. The reader also skips symbols in this group, and the
writer batches words rather than emitting an interleaved word/symbol run.
Desired Capability
mx::api should be able to read, represent, modify, and write ordered word/symbol sequences such as:
In looking further at this with my own two eyes, I am realizing that DirectionData may need a major refactor. It seems like a perfect use-case for std::variant. You could have
using DirectionTypeData = std::variant <
TempoData,
MarkData,
WedgeStart,
WedgeStop,
OttavaStart,
SpannerStart,
SpannerStop,
WordsData,
ChordData,
FiguredBassData,
SegnoData,
CodaData,
RehearsalData,
SymbolData, // added by me for this issue ;-p
>;
Then all you would need is one vector.
std::vector<DirectionTypeData> directionTypes;
And you could dispense with DirectionComponent and orderedComponents which look risky to me no matter how you slice it. The directionTypes would encode the order directly, so there would be no need for trickery to round-trip.
I certainly would not undertake a refactor like this myself without your blessing. I don't know what the ramifications might be both in-repo and for other clients.
The symbol element specifies a musical symbol using a canonical SMuFL glyph name. It is used when an occasional musical symbol is interspersed into text. It should not be used in place of semantic markup, such as metronome marks that mix text and symbols.
And, I agree, you should use the common-MusicXML-language-semantics when available:
I agree that mx::api has a gap here. The ordering workaround was sufficient for some basic cases, but it falls short of more complex directions. Here some examples of what is and is not supported.
orderedComponents = { {words, 0}, {mark, 0} } → words emit before the
mark. Works, but only because there's a single words element.
Example D (impossible: words split around another kind, e.g. "più 𝆑 ma non troppo"):
<directionplacement="below">
<direction-type><words>più</words></direction-type>
<direction-type><dynamics><f/></dynamics></direction-type>
<direction-type><words>ma non troppo</words></direction-type>
</direction>
{words,0}, {mark,0}, {words,1} doesn't work — the writer dumps all words
at the first words entry (DirectionWriter.cpp:792), yielding "più ma non
troppo 𝆑". Also impossible: anything containing <symbol> (no api type;
reader drops it).
Proposal
I think your proposal is solid except for one nit. I'm aesthetically influenced by Rust (I wish I had algebraic enums) and I don't like std::variant's undefined behavior and access methods very much, so I started adding "choice" classes in the mx::api to hide the std::variant. Thus I suggest something more like this:
I think we can go ahead with this change. I'm happy to do it if you want, or you can give it a shot. If you do, there may be some back-and-forth in review, but if you follow patterns established in:
TimeChoice — TimeSignatureData | ComplexTimeSignature; simple-equivalent complex collapses to simple.
Where MarkDataChoice is perhaps the closest template for WordsChoice / DirectionChoice (Kind enum, copy accessors, default-copy on wrong-kind). And have your agent read /mx-api-doctrine, it should probably go pretty smoothly.
I am traveling at the moment so won't be in front of a devbox computer before next week. FWIW: i have list of gaps in mx::api I was going to work through then, where I will just submit PRs for the easy ones and open issues for ones that require refactoring or other complications. So I might get around to this in a couple of weeks if you haven't already tackled it.
I ended up using what was available for encoding dynamics (as you suggested: semantically) after I posted this issue.
This week I'm on vacation and trying to restrain myself from programming. But I did want to get this one out of the way. The design and subsequent refinement were very solid and I think Fable got it perfect in one shot so I'm going to merge the PR and hopefully unblock you with that for the further refinements you mentioned that you have a backlog of.
Activity
rpatters1 commentedon Jul 3, 2026
In looking further at this with my own two eyes, I am realizing that
DirectionDatamay need a major refactor. It seems like a perfect use-case forstd::variant. You could haveThen all you would need is one vector.
And you could dispense with
DirectionComponentandorderedComponentswhich look risky to me no matter how you slice it. ThedirectionTypeswould encode the order directly, so there would be no need for trickery to round-trip.I certainly would not undertake a refactor like this myself without your blessing. I don't know what the ramifications might be both in-repo and for other clients.
feat: model all pedal types in mx::api (#364)
test: canonicalize non-semantic direction spellings and pin ly31a (#366)
webern commentedon Jul 22, 2026
Symbol
I agree with the need to support
<symbol>interleaved with words.Semantics Nit
Although, I have a nit for the files you are authoring:
The spec says this:
And, I agree, you should use the common-MusicXML-language-semantics when available:
Ordered Direction Components
I agree that
mx::apihas a gap here. The ordering workaround was sufficient for some basic cases, but it falls short of more complex directions. Here some examples of what is and is not supported.Example A (supported):
Example B (supported):
Example C (supported with the buggy ordering knob):
Example D (impossible: words split around another kind, e.g. "più 𝆑 ma non troppo"):
Proposal
I think your proposal is solid except for one nit. I'm aesthetically influenced by Rust (I wish I had algebraic enums) and I don't like
std::variant's undefined behavior and access methods very much, so I started adding "choice" classes in themx::apito hide thestd::variant. Thus I suggest something more like this:And
Conclusion
I think we can go ahead with this change. I'm happy to do it if you want, or you can give it a shot. If you do, there may be some back-and-forth in review, but if you follow patterns established in:
TimeChoice— TimeSignatureData | ComplexTimeSignature; simple-equivalent complex collapses to simple.ComplexTimeSignature— (MeteredTimeSignature | senza-misura string)MarkDataChoice— monostate | TremoloMarkData | ArpeggiateMarkData | NonArpeggiateMarkData; monostate = "none".Where
MarkDataChoiceis perhaps the closest template forWordsChoice/DirectionChoice(Kindenum, copy accessors, default-copy on wrong-kind). And have your agent read/mx-api-doctrine, it should probably go pretty smoothly.webern commentedon Jul 22, 2026
This might serve as a reasonable stress-test file to add to the corpus under
synthetic/:rpatters1 commentedon Jul 22, 2026
I am traveling at the moment so won't be in front of a devbox computer before next week. FWIW: i have list of gaps in mx::api I was going to work through then, where I will just submit PRs for the easy ones and open issues for ones that require refactoring or other complications. So I might get around to this in a couple of weeks if you haven't already tackled it.
I ended up using what was available for encoding dynamics (as you suggested: semantically) after I posted this issue.
webern commentedon Jul 26, 2026
This week I'm on vacation and trying to restrain myself from programming. But I did want to get this one out of the way. The design and subsequent refinement were very solid and I think Fable got it perfect in one shot so I'm going to merge the PR and hopefully unblock you with that for the further refinements you mentioned that you have a backlog of.