From 2e48f82b85017dddd1d5b845cbae776bf3ae19ee Mon Sep 17 00:00:00 2001 From: Matthew James Briggs Date: Tue, 18 Aug 2026 08:31:07 +0000 Subject: [PATCH] feat: expose MusicXML id attributes in mx::api Beginning with MusicXML 3.1 most elements can carry an id attribute that names that one element within the document. mx::api dropped nearly all of them. Add an optional id member to every api type that maps one-to-one onto an element with an id, and read and write it in mx::impl. New id fields: NoteData, MeasureData, ClefData, KeyData, TimeChoice, TransposeData, BarlineData, LyricData, DirectionData, WordsData, SymbolData, RehearsalData, WedgeStart, WedgeStop, SpannerStart, SpannerStop, PedalLineData, CurveStart, CurveContinue, CurveStop, TieLetRing, TupletStart, TupletStop, SoundData and FiguredBassData. A new IdFunctions.h holds the two impl helpers that read and write the attribute. ApiCommon.h explains what ids are for and the uniqueness rule that mx does not enforce. Left out are the elements where placing the id needs a design decision: the grouping elements mx::api does not model (direction-type, notations, articulations, ornaments, technical, beam), the marks folded into MarkData (dynamics, fermata, accidental-mark), harmony and frame (one DirectionData holds several ChordData that serialize into a single harmony), credit (both credit and credit-words carry an id and PageTextData models the pair), and the elements mx::api does not model at all (print, measure-style, grouping, for-part, glissando, slide). synthetic/bracket.3.1.xml and synthetic/dashes.3.1.xml now survive the strict api round trip and are pinned in roundtrip-baseline.txt. --- src/include/mx/api/ApiCommon.h | 11 + src/include/mx/api/BarlineData.h | 6 +- src/include/mx/api/ClefData.h | 8 + src/include/mx/api/CurveData.h | 24 +- src/include/mx/api/DirectionData.h | 7 +- src/include/mx/api/FiguredBassData.h | 7 +- src/include/mx/api/KeyData.h | 9 +- src/include/mx/api/LyricData.h | 9 +- src/include/mx/api/MeasureData.h | 4 + src/include/mx/api/NoteData.h | 11 +- src/include/mx/api/PedalLineData.h | 11 +- src/include/mx/api/RehearsalData.h | 9 +- src/include/mx/api/SoundData.h | 8 +- src/include/mx/api/SpannerData.h | 15 +- src/include/mx/api/SymbolData.h | 6 +- src/include/mx/api/TimeChoice.h | 6 + src/include/mx/api/TransposeData.h | 7 + src/include/mx/api/TupletData.h | 15 +- src/include/mx/api/WedgeData.h | 15 +- src/include/mx/api/WordsData.h | 9 +- src/private/mx/api/TimeChoice.cpp | 2 +- src/private/mx/impl/Converter.cpp | 5 +- src/private/mx/impl/CurveFunctions.h | 11 + src/private/mx/impl/DirectionReader.cpp | 9 + src/private/mx/impl/DirectionWriter.cpp | 11 + src/private/mx/impl/IdFunctions.h | 37 ++ src/private/mx/impl/MeasureReader.cpp | 12 + src/private/mx/impl/MeasureWriter.cpp | 4 + src/private/mx/impl/NotationsWriter.cpp | 3 + src/private/mx/impl/NoteFunctions.cpp | 2 + src/private/mx/impl/NoteReader.cpp | 2 + src/private/mx/impl/NoteWriter.cpp | 3 + src/private/mx/impl/PropertiesWriter.cpp | 5 + src/private/mx/impl/SoundFunctions.cpp | 5 + src/private/mx/impl/SpannerFunctions.h | 8 +- src/private/mx/impl/TimeReader.cpp | 2 + src/private/mx/impl/TupletReader.cpp | 3 + src/private/mxtest/api/IdAttributeApiTest.cpp | 429 ++++++++++++++++++ src/private/mxtest/api/roundtrip-baseline.txt | 5 + 39 files changed, 729 insertions(+), 26 deletions(-) create mode 100644 src/private/mx/impl/IdFunctions.h create mode 100644 src/private/mxtest/api/IdAttributeApiTest.cpp diff --git a/src/include/mx/api/ApiCommon.h b/src/include/mx/api/ApiCommon.h index 3ff4433aa..6efabd4b0 100644 --- a/src/include/mx/api/ApiCommon.h +++ b/src/include/mx/api/ApiCommon.h @@ -32,6 +32,17 @@ constexpr int NUMBER_LEVEL_UNSPECIFIED = -1; // MusicXML 'number' attributes, e. constexpr int VALUE_UNSPECIFIED = -1; // other absent-able ints, e.g. DirectionData::voice constexpr Double DOUBLE_UNSPECIFIED = -1.0; // absent-able doubles, e.g. StaffData::staffSize +// MusicXML lets most elements carry an optional id attribute, a name that identifies that one +// element within the document. Software uses it to point at a particular note, measure, or +// marking -- to line playback up with the score, to hang an annotation on a note, or to link one +// file to another. Every mx::api type that models such an element has an `id` member. Leave it +// empty and no id attribute is written. +// +// An id must be unique within the document and must follow the XML name rules: a letter or an +// underscore first, then letters, digits, dots, hyphens, or underscores. mx repairs an id that +// breaks the name rules when it writes the file, but it does not check uniqueness, so an id you +// invent must not collide with one already in the score. + // Intentional ternary: absent-able bools use Bool::unspecified, not std::optional. // See "mx::api conventions" in AGENTS.md. enum class Bool diff --git a/src/include/mx/api/BarlineData.h b/src/include/mx/api/BarlineData.h index 967a5112b..05857f979 100644 --- a/src/include/mx/api/BarlineData.h +++ b/src/include/mx/api/BarlineData.h @@ -116,10 +116,13 @@ class BarlineData RepeatWinged repeatWinged; HorizontalAlignment location; + // The element's id attribute (see ApiCommon.h). + std::optional id; + BarlineData() : tickTimePosition{0}, barlineType{BarlineType::normal}, ending{}, repeat{false}, repeatTimes{0}, repeatDirection{RepeatDirection::unspecified}, repeatAfterJump{Bool::unspecified}, - repeatWinged{RepeatWinged::unspecified}, location{HorizontalAlignment::unspecified} + repeatWinged{RepeatWinged::unspecified}, location{HorizontalAlignment::unspecified}, id{} { } }; @@ -134,6 +137,7 @@ MXAPI_EQUALS_MEMBER(repeatDirection) MXAPI_EQUALS_MEMBER(repeatAfterJump) MXAPI_EQUALS_MEMBER(repeatWinged) MXAPI_EQUALS_MEMBER(location) +MXAPI_EQUALS_MEMBER(id) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(BarlineData); } // namespace api diff --git a/src/include/mx/api/ClefData.h b/src/include/mx/api/ClefData.h index c4feba6cd..81a788a87 100644 --- a/src/include/mx/api/ClefData.h +++ b/src/include/mx/api/ClefData.h @@ -6,6 +6,9 @@ #include "mx/api/ApiCommon.h" +#include +#include + namespace mx { namespace api @@ -67,6 +70,10 @@ class ClefData // Visibility of the clef via the MusicXML print-object attribute. // unspecified -> omit the attribute, yes/no -> write print-object verbatim. Bool printObject; + + // The element's id attribute (see ApiCommon.h). + std::optional id; + std::string toString() const; // convenience - set symbol, line and octave for common clefs @@ -99,6 +106,7 @@ MXAPI_EQUALS_MEMBER(tickTimePosition) MXAPI_EQUALS_MEMBER(location) MXAPI_EQUALS_MEMBER(additional) MXAPI_EQUALS_MEMBER(printObject) +MXAPI_EQUALS_MEMBER(id) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(ClefData); } // namespace api diff --git a/src/include/mx/api/CurveData.h b/src/include/mx/api/CurveData.h index 8b9670a9d..178919df6 100644 --- a/src/include/mx/api/CurveData.h +++ b/src/include/mx/api/CurveData.h @@ -10,6 +10,9 @@ #include "mx/api/PositionData.h" #include "mx/api/SpannerNumber.h" +#include +#include + // MusicXML Documentation for Bezier Attributes Group // The bezier attribute group is used to indicate the curvature of slurs // and ties, representing the control points for a cubic bezier curve. @@ -100,9 +103,13 @@ struct CurveStart bool isColorSpecified; ColorData colorData; + // The id attribute of the element this curve end is written as -- for a slur, + // for a tie (see ApiCommon.h). + std::optional id; + CurveStart(CurveType inCurveType) : curveType{inCurveType}, number{}, curvePoints{}, curveOrientation{CurveOrientation::unspecified}, - placement{Placement::unspecified}, lineData{}, isColorSpecified{false}, colorData{} + placement{Placement::unspecified}, lineData{}, isColorSpecified{false}, colorData{}, id{} { } }; @@ -119,9 +126,13 @@ struct CurveContinue bool isBezierOffset2Specified; double bezierOffset2; + // The id attribute of the element this curve end is written as -- for a slur, + // for a tie (see ApiCommon.h). + std::optional id; + CurveContinue(CurveType inCurveType) : curveType{inCurveType}, number{}, curvePoints{}, isBezierX2Specified{false}, bezierX2{0.0}, - isBezierY2Specified{false}, bezierY2{0.0}, isBezierOffset2Specified{false}, bezierOffset2{0.0} + isBezierY2Specified{false}, bezierY2{0.0}, isBezierOffset2Specified{false}, bezierOffset2{0.0}, id{} { } }; @@ -132,7 +143,11 @@ struct CurveStop SpannerNumber number; CurvePoints curvePoints; - CurveStop(CurveType inCurveType) : curveType{inCurveType}, number{}, curvePoints{} + // The id attribute of the element this curve end is written as -- for a slur, + // for a tie (see ApiCommon.h). + std::optional id; + + CurveStop(CurveType inCurveType) : curveType{inCurveType}, number{}, curvePoints{}, id{} { } }; @@ -157,6 +172,7 @@ MXAPI_EQUALS_MEMBER(placement) MXAPI_EQUALS_MEMBER(lineData) MXAPI_EQUALS_MEMBER(isColorSpecified) MXAPI_EQUALS_MEMBER(colorData) +MXAPI_EQUALS_MEMBER(id) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(CurveStart); @@ -170,6 +186,7 @@ MXAPI_EQUALS_MEMBER(isBezierY2Specified) MXAPI_EQUALS_MEMBER(bezierY2) MXAPI_EQUALS_MEMBER(isBezierOffset2Specified) MXAPI_EQUALS_MEMBER(bezierOffset2) +MXAPI_EQUALS_MEMBER(id) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(CurveContinue); @@ -177,6 +194,7 @@ MXAPI_EQUALS_BEGIN(CurveStop) MXAPI_EQUALS_MEMBER(curveType) MXAPI_EQUALS_MEMBER(number) MXAPI_EQUALS_MEMBER(curvePoints) +MXAPI_EQUALS_MEMBER(id) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(CurveStop); } // namespace api diff --git a/src/include/mx/api/DirectionData.h b/src/include/mx/api/DirectionData.h index a4f61fae6..6981d2200 100644 --- a/src/include/mx/api/DirectionData.h +++ b/src/include/mx/api/DirectionData.h @@ -11,6 +11,7 @@ #include "mx/api/SoundData.h" #include +#include namespace mx { @@ -80,10 +81,13 @@ struct DirectionData // serialize as their own elements, not as direction-type content. std::vector figuredBasses; + // The element's id attribute (see ApiCommon.h). + std::optional id; + DirectionData() : tickTimePosition{0}, placement{Placement::unspecified}, systemRelation{SystemRelation::unspecified}, offset{}, voice{VALUE_UNSPECIFIED}, isStaffValueSpecified{true}, isSoundDataSpecified{false}, soundData{}, - directionTypes{}, chords{}, figuredBasses{} + directionTypes{}, chords{}, figuredBasses{}, id{} { } }; @@ -106,6 +110,7 @@ MXAPI_EQUALS_MEMBER(soundData) MXAPI_EQUALS_MEMBER(directionTypes) MXAPI_EQUALS_MEMBER(chords) MXAPI_EQUALS_MEMBER(figuredBasses) +MXAPI_EQUALS_MEMBER(id) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(DirectionData); } // namespace api diff --git a/src/include/mx/api/FiguredBassData.h b/src/include/mx/api/FiguredBassData.h index 26263fbdd..a4ff4d9eb 100644 --- a/src/include/mx/api/FiguredBassData.h +++ b/src/include/mx/api/FiguredBassData.h @@ -6,6 +6,7 @@ #include "mx/api/ApiCommon.h" +#include #include #include @@ -48,7 +49,10 @@ class FiguredBassData // The optional , in ticks. A value less than 0 means 'unspecified' (no duration child). int durationTimeTicks; - FiguredBassData() : figures{}, parentheses{Bool::unspecified}, durationTimeTicks{VALUE_UNSPECIFIED} + // The element's id attribute (see ApiCommon.h). + std::optional id; + + FiguredBassData() : figures{}, parentheses{Bool::unspecified}, durationTimeTicks{VALUE_UNSPECIFIED}, id{} { } }; @@ -57,6 +61,7 @@ MXAPI_EQUALS_BEGIN(FiguredBassData) MXAPI_EQUALS_MEMBER(figures) MXAPI_EQUALS_MEMBER(parentheses) MXAPI_EQUALS_MEMBER(durationTimeTicks) +MXAPI_EQUALS_MEMBER(id) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(FiguredBassData); diff --git a/src/include/mx/api/KeyData.h b/src/include/mx/api/KeyData.h index f4551b371..ff4666ae0 100644 --- a/src/include/mx/api/KeyData.h +++ b/src/include/mx/api/KeyData.h @@ -6,6 +6,9 @@ #include "mx/api/ApiCommon.h" #include "mx/api/KeyComponent.h" +#include +#include + namespace mx { namespace api @@ -121,9 +124,12 @@ struct KeyData // alterations. When custom is non-empty, then fifths and mode are ignored. std::vector nonTraditional; + // The element's id attribute (see ApiCommon.h). + std::optional id; + KeyData() : fifths{0}, cancel{0}, cancelLocation{CancelLocation::unspecified}, mode{KeyMode::unspecified}, - tickTimePosition{0}, staffIndex{INDEX_UNSPECIFIED}, nonTraditional{} + tickTimePosition{0}, staffIndex{INDEX_UNSPECIFIED}, nonTraditional{}, id{} { } }; @@ -136,6 +142,7 @@ MXAPI_EQUALS_MEMBER(mode) MXAPI_EQUALS_MEMBER(tickTimePosition) MXAPI_EQUALS_MEMBER(staffIndex) MXAPI_EQUALS_MEMBER(nonTraditional) +MXAPI_EQUALS_MEMBER(id) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(KeyData); diff --git a/src/include/mx/api/LyricData.h b/src/include/mx/api/LyricData.h index f88c6f216..e08993b94 100644 --- a/src/include/mx/api/LyricData.h +++ b/src/include/mx/api/LyricData.h @@ -8,6 +8,9 @@ #include "mx/api/PositionData.h" #include "mx/api/PrintData.h" +#include +#include + namespace mx { namespace api @@ -38,7 +41,7 @@ class LyricData public: LyricData() : text{}, verseNumber{}, verseName{}, syllabic{LyricSyllabic::unspecified}, hasExtend{false}, - extendType{LyricExtendType::unspecified}, positionData{}, printData{} + extendType{LyricExtendType::unspecified}, positionData{}, printData{}, id{} { } @@ -50,6 +53,9 @@ class LyricData LyricExtendType extendType; PositionData positionData; PrintData printData; + + // The element's id attribute (see ApiCommon.h). + std::optional id; }; MXAPI_EQUALS_BEGIN(LyricData) @@ -61,6 +67,7 @@ MXAPI_EQUALS_MEMBER(hasExtend) MXAPI_EQUALS_MEMBER(extendType) MXAPI_EQUALS_MEMBER(positionData) MXAPI_EQUALS_MEMBER(printData) +MXAPI_EQUALS_MEMBER(id) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(LyricData); } // namespace api diff --git a/src/include/mx/api/MeasureData.h b/src/include/mx/api/MeasureData.h index 8a6cde682..6968b6daa 100644 --- a/src/include/mx/api/MeasureData.h +++ b/src/include/mx/api/MeasureData.h @@ -111,6 +111,9 @@ class MeasureData // before this field existed. std::vector transpositions; + // The element's id attribute (see ApiCommon.h). + std::optional id; + MeasureData() : staves{}, timeSignature{}, number{}, measureNumbering{MeasureNumbering::unspecified}, measureNumberingMultipleRestAlways{Bool::unspecified}, measureNumberingMultipleRestRange{Bool::unspecified}, @@ -141,6 +144,7 @@ MXAPI_EQUALS_MEMBER(keys) MXAPI_EQUALS_MEMBER(barlines) MXAPI_EQUALS_MEMBER(partSymbol) MXAPI_EQUALS_MEMBER(transpositions) +MXAPI_EQUALS_MEMBER(id) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(MeasureData); } // namespace api diff --git a/src/include/mx/api/NoteData.h b/src/include/mx/api/NoteData.h index 79129b482..d746d18b7 100644 --- a/src/include/mx/api/NoteData.h +++ b/src/include/mx/api/NoteData.h @@ -82,7 +82,11 @@ struct TieLetRing bool isColorSpecified; ColorData colorData; - TieLetRing() : positionData{}, curveOrientation{CurveOrientation::unspecified}, isColorSpecified{false}, colorData{} + // The element's id attribute (see ApiCommon.h). + std::optional id; + + TieLetRing() + : positionData{}, curveOrientation{CurveOrientation::unspecified}, isColorSpecified{false}, colorData{}, id{} { } }; @@ -92,6 +96,7 @@ MXAPI_EQUALS_MEMBER(positionData) MXAPI_EQUALS_MEMBER(curveOrientation) MXAPI_EQUALS_MEMBER(isColorSpecified) MXAPI_EQUALS_MEMBER(colorData) +MXAPI_EQUALS_MEMBER(id) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(TieLetRing); @@ -202,6 +207,9 @@ class NoteData // similarly when parsing if we see ##misc-data## in this element, it // will be parsed into these strings. do not use commas in your misc // data strings as these are the delimiter + + // The element's id attribute (see ApiCommon.h). + std::optional id; std::vector miscData; }; @@ -234,6 +242,7 @@ MXAPI_EQUALS_MEMBER(printData) MXAPI_EQUALS_MEMBER(noteAttachmentData) MXAPI_EQUALS_MEMBER(lyrics) MXAPI_EQUALS_MEMBER(miscData) +MXAPI_EQUALS_MEMBER(id) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(NoteData); } // namespace api diff --git a/src/include/mx/api/PedalLineData.h b/src/include/mx/api/PedalLineData.h index 53c5c6c64..8606bcfc9 100644 --- a/src/include/mx/api/PedalLineData.h +++ b/src/include/mx/api/PedalLineData.h @@ -7,6 +7,9 @@ #include "mx/api/ApiCommon.h" #include "mx/api/PositionData.h" +#include +#include + namespace mx { namespace api @@ -50,11 +53,14 @@ struct PedalLineData int tickTimePosition; PositionData positionData; - PedalLineData() : kind{PedalLineKind::unspecified}, tickTimePosition{0}, positionData{} + // The element's id attribute (see ApiCommon.h). + std::optional id; + + PedalLineData() : kind{PedalLineKind::unspecified}, tickTimePosition{0}, positionData{}, id{} { } - PedalLineData(PedalLineKind inKind) : kind{inKind}, tickTimePosition{0}, positionData{} + PedalLineData(PedalLineKind inKind) : kind{inKind}, tickTimePosition{0}, positionData{}, id{} { } }; @@ -63,6 +69,7 @@ MXAPI_EQUALS_BEGIN(PedalLineData) MXAPI_EQUALS_MEMBER(kind) MXAPI_EQUALS_MEMBER(tickTimePosition) MXAPI_EQUALS_MEMBER(positionData) +MXAPI_EQUALS_MEMBER(id) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(PedalLineData); } // namespace api diff --git a/src/include/mx/api/RehearsalData.h b/src/include/mx/api/RehearsalData.h index 73685bcfd..a34430155 100644 --- a/src/include/mx/api/RehearsalData.h +++ b/src/include/mx/api/RehearsalData.h @@ -9,6 +9,9 @@ #include "mx/api/FontData.h" #include "mx/api/PositionData.h" +#include +#include + namespace mx { namespace api @@ -32,9 +35,12 @@ class RehearsalData // and says which edge of the text the position refers to; MusicXML defines both. HorizontalAlignment justify; + // The element's id attribute (see ApiCommon.h). + std::optional id; + RehearsalData() : text{}, positionData{}, isColorSpecified{false}, colorData{}, fontData{}, enclosure{Enclosure::unspecified}, - justify{HorizontalAlignment::unspecified} + justify{HorizontalAlignment::unspecified}, id{} { } }; @@ -47,6 +53,7 @@ MXAPI_EQUALS_MEMBER(colorData) MXAPI_EQUALS_MEMBER(fontData) MXAPI_EQUALS_MEMBER(enclosure) MXAPI_EQUALS_MEMBER(justify) +MXAPI_EQUALS_MEMBER(id) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(RehearsalData); } // namespace api diff --git a/src/include/mx/api/SoundData.h b/src/include/mx/api/SoundData.h index 010080e37..121a12999 100644 --- a/src/include/mx/api/SoundData.h +++ b/src/include/mx/api/SoundData.h @@ -87,10 +87,13 @@ struct SoundData std::optional swing; + // The element's id attribute (see ApiCommon.h). + std::optional id; + SoundData() : tempo{DOUBLE_UNSPECIFIED}, dynamics{DOUBLE_UNSPECIFIED}, dacapo{Bool::unspecified}, forwardRepeat{Bool::unspecified}, pizzicato{Bool::unspecified}, segno{}, dalsegno{}, coda{}, tocoda{}, fine{}, - swing{} + swing{}, id{} { } @@ -98,7 +101,7 @@ struct SoundData { return tempo >= 0.0 || dynamics >= 0.0 || dacapo != Bool::unspecified || forwardRepeat != Bool::unspecified || pizzicato != Bool::unspecified || !segno.empty() || !dalsegno.empty() || !coda.empty() || - !tocoda.empty() || !fine.empty() || swing.has_value(); + !tocoda.empty() || !fine.empty() || swing.has_value() || id.has_value(); } }; @@ -123,6 +126,7 @@ MXAPI_EQUALS_MEMBER(coda) MXAPI_EQUALS_MEMBER(tocoda) MXAPI_EQUALS_MEMBER(fine) MXAPI_EQUALS_MEMBER(swing) +MXAPI_EQUALS_MEMBER(id) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(SoundData); } // namespace api diff --git a/src/include/mx/api/SpannerData.h b/src/include/mx/api/SpannerData.h index 51645990f..dd7c55d8a 100644 --- a/src/include/mx/api/SpannerData.h +++ b/src/include/mx/api/SpannerData.h @@ -10,6 +10,7 @@ #include "mx/api/PrintData.h" #include "mx/api/SpannerNumber.h" +#include #include namespace mx @@ -25,7 +26,11 @@ struct SpannerStart PrintData printData; LineData lineData; - SpannerStart() : number{}, tickTimePosition{0}, positionData{}, printData{}, lineData{} + // The id attribute of the element this spanner end is written as -- , + // , or (see ApiCommon.h). + std::optional id; + + SpannerStart() : number{}, tickTimePosition{0}, positionData{}, printData{}, lineData{}, id{} { } }; @@ -37,7 +42,11 @@ struct SpannerStop PositionData positionData; LineData lineData; - SpannerStop() : number{}, tickTimePosition{0}, positionData{}, lineData{} + // The id attribute of the element this spanner end is written as -- , + // , or (see ApiCommon.h). + std::optional id; + + SpannerStop() : number{}, tickTimePosition{0}, positionData{}, lineData{}, id{} { } }; @@ -48,6 +57,7 @@ MXAPI_EQUALS_MEMBER(tickTimePosition) MXAPI_EQUALS_MEMBER(positionData) MXAPI_EQUALS_MEMBER(printData) MXAPI_EQUALS_MEMBER(lineData) +MXAPI_EQUALS_MEMBER(id) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(SpannerStart); @@ -56,6 +66,7 @@ MXAPI_EQUALS_MEMBER(number) MXAPI_EQUALS_MEMBER(tickTimePosition) MXAPI_EQUALS_MEMBER(positionData) MXAPI_EQUALS_MEMBER(lineData) +MXAPI_EQUALS_MEMBER(id) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(SpannerStop); } // namespace api diff --git a/src/include/mx/api/SymbolData.h b/src/include/mx/api/SymbolData.h index 8b6d88742..3b215534b 100644 --- a/src/include/mx/api/SymbolData.h +++ b/src/include/mx/api/SymbolData.h @@ -41,9 +41,12 @@ class SymbolData // symbol-formatting attributes mirror the text-formatting ones on ``. HorizontalAlignment justify; + // The element's id attribute (see ApiCommon.h). + std::optional id; + SymbolData() : smufl{}, positionData{}, fontData{}, color{}, enclosure{Enclosure::unspecified}, - justify{HorizontalAlignment::unspecified} + justify{HorizontalAlignment::unspecified}, id{} { } }; @@ -55,6 +58,7 @@ MXAPI_EQUALS_MEMBER(fontData) MXAPI_EQUALS_MEMBER(color) MXAPI_EQUALS_MEMBER(enclosure) MXAPI_EQUALS_MEMBER(justify) +MXAPI_EQUALS_MEMBER(id) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(SymbolData); } // namespace api diff --git a/src/include/mx/api/TimeChoice.h b/src/include/mx/api/TimeChoice.h index a16a3fcb1..d26ba44e2 100644 --- a/src/include/mx/api/TimeChoice.h +++ b/src/include/mx/api/TimeChoice.h @@ -8,6 +8,8 @@ #include "mx/api/ComplexTimeSignature.h" #include "mx/api/TimeSignatureData.h" +#include +#include #include namespace mx @@ -57,6 +59,10 @@ class TimeChoice // Print/hide the time signature (