diff --git a/data/corpus.xml b/data/corpus.xml index 7496c6c9a..0719fdbaf 100644 --- a/data/corpus.xml +++ b/data/corpus.xml @@ -397,6 +397,7 @@ synthetic/transpose.3.1.xml synthetic/tremolo.3.0.xml synthetic/tremolo.3.1.xml + synthetic/tremolo.unmeasured.3.1.xml synthetic/trill-mark.3.0.xml synthetic/triple-tongue.3.0.xml synthetic/tuplet-dot.3.0.xml @@ -811,6 +812,7 @@ synthetic/transpose.3.1.xml synthetic/tremolo.3.0.xml synthetic/tremolo.3.1.xml + synthetic/tremolo.unmeasured.3.1.xml synthetic/trill-mark.3.0.xml synthetic/triple-tongue.3.0.xml synthetic/tuplet-dot.3.0.xml @@ -1222,6 +1224,7 @@ synthetic/transpose.3.1.xml synthetic/tremolo.3.0.xml synthetic/tremolo.3.1.xml + synthetic/tremolo.unmeasured.3.1.xml synthetic/trill-mark.3.0.xml synthetic/triple-tongue.3.0.xml synthetic/tuplet-dot.3.0.xml @@ -1646,6 +1649,7 @@ synthetic/transpose.3.1.xml synthetic/tremolo.3.0.xml synthetic/tremolo.3.1.xml + synthetic/tremolo.unmeasured.3.1.xml synthetic/trill-mark.3.0.xml synthetic/triple-tongue.3.0.xml synthetic/tuplet-dot.3.0.xml @@ -2060,6 +2064,7 @@ synthetic/transpose.3.1.xml synthetic/tremolo.3.0.xml synthetic/tremolo.3.1.xml + synthetic/tremolo.unmeasured.3.1.xml synthetic/trill-mark.3.0.xml synthetic/triple-tongue.3.0.xml synthetic/tuplet-dot.3.0.xml @@ -2474,6 +2479,7 @@ synthetic/transpose.3.1.xml synthetic/tremolo.3.0.xml synthetic/tremolo.3.1.xml + synthetic/tremolo.unmeasured.3.1.xml synthetic/trill-mark.3.0.xml synthetic/triple-tongue.3.0.xml synthetic/tuplet-dot.3.0.xml @@ -2873,6 +2879,7 @@ synthetic/toe.3.0.xml synthetic/tremolo.3.0.xml synthetic/tremolo.3.1.xml + synthetic/tremolo.unmeasured.3.1.xml synthetic/trill-mark.3.0.xml synthetic/triple-tongue.3.0.xml synthetic/tuplet-dot.3.0.xml @@ -3424,6 +3431,7 @@ synthetic/toe.3.0.xml synthetic/tremolo.3.0.xml synthetic/tremolo.3.1.xml + synthetic/tremolo.unmeasured.3.1.xml synthetic/trill-mark.3.0.xml synthetic/triple-tongue.3.0.xml synthetic/tuplet-dot.3.0.xml @@ -3673,6 +3681,7 @@ synthetic/toe.3.0.xml synthetic/tremolo.3.0.xml synthetic/tremolo.3.1.xml + synthetic/tremolo.unmeasured.3.1.xml synthetic/trill-mark.3.0.xml synthetic/triple-tongue.3.0.xml synthetic/tuplet-dot.3.0.xml @@ -3917,6 +3926,7 @@ synthetic/toe.3.0.xml synthetic/tremolo.3.0.xml synthetic/tremolo.3.1.xml + synthetic/tremolo.unmeasured.3.1.xml synthetic/trill-mark.3.0.xml synthetic/triple-tongue.3.0.xml synthetic/tuplet-dot.3.0.xml @@ -4161,6 +4171,7 @@ synthetic/toe.3.0.xml synthetic/tremolo.3.0.xml synthetic/tremolo.3.1.xml + synthetic/tremolo.unmeasured.3.1.xml synthetic/trill-mark.3.0.xml synthetic/triple-tongue.3.0.xml synthetic/tuplet-dot.3.0.xml @@ -4739,6 +4750,7 @@ synthetic/toe.3.0.xml synthetic/tremolo.3.0.xml synthetic/tremolo.3.1.xml + synthetic/tremolo.unmeasured.3.1.xml synthetic/trill-mark.3.0.xml synthetic/triple-tongue.3.0.xml synthetic/tuplet-dot.3.0.xml @@ -5862,7 +5874,7 @@ synthetic/word-font.3.0.xml - + custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml @@ -5878,7 +5890,7 @@ foundsuite/Invention_12.xml - + synthetic/accent.3.0.xml synthetic/accidental-mark.3.0.xml synthetic/accidental-mark.3.1.xml @@ -6184,6 +6196,7 @@ synthetic/toe.3.0.xml synthetic/tremolo.3.0.xml synthetic/tremolo.3.1.xml + synthetic/tremolo.unmeasured.3.1.xml synthetic/trill-mark.3.0.xml synthetic/triple-tongue.3.0.xml synthetic/tuplet-dot.3.0.xml @@ -7210,7 +7223,7 @@ synthetic/fermata.3.1.xml - + @@ -7229,7 +7242,7 @@ foundsuite/Invention_4.xml - + synthetic/accidental-mark.3.0.xml synthetic/accidental-mark.3.1.xml synthetic/delayed-inverted-turn.3.0.xml @@ -7246,6 +7259,7 @@ synthetic/shake.3.0.xml synthetic/tremolo.3.0.xml synthetic/tremolo.3.1.xml + synthetic/tremolo.unmeasured.3.1.xml synthetic/trill-mark.3.0.xml synthetic/turn.3.0.xml synthetic/vertical-turn.3.0.xml @@ -7549,6 +7563,7 @@ synthetic/toe.3.0.xml synthetic/tremolo.3.0.xml synthetic/tremolo.3.1.xml + synthetic/tremolo.unmeasured.3.1.xml synthetic/trill-mark.3.0.xml synthetic/triple-tongue.3.0.xml synthetic/tuplet-dot.3.0.xml @@ -7711,10 +7726,10 @@ - + - + custom/transposition.musicxml @@ -7731,7 +7746,7 @@ lysuite/ly14a_StaffDetails_LineChanges.xml - + synthetic/accent.3.0.xml synthetic/accidental-mark.3.0.xml synthetic/accidental-mark.3.1.xml @@ -8091,6 +8106,7 @@ synthetic/transpose.3.1.xml synthetic/tremolo.3.0.xml synthetic/tremolo.3.1.xml + synthetic/tremolo.unmeasured.3.1.xml synthetic/trill-mark.3.0.xml synthetic/triple-tongue.3.0.xml synthetic/tuplet-dot.3.0.xml @@ -9358,20 +9374,20 @@ synthetic/staccatissimo.3.0.xml - + - - - + + + - - - - + + + + foundsuite/Berlioz_Le_Corsaire.xml @@ -9387,9 +9403,10 @@ musuite/testTremolo.xml recsuite/ActorPreludeSample.xml - + synthetic/tremolo.3.0.xml synthetic/tremolo.3.1.xml + synthetic/tremolo.unmeasured.3.1.xml diff --git a/data/synthetic/tremolo.unmeasured.3.1.features.xml b/data/synthetic/tremolo.unmeasured.3.1.features.xml new file mode 100644 index 000000000..5c15ba635 --- /dev/null +++ b/data/synthetic/tremolo.unmeasured.3.1.features.xml @@ -0,0 +1,84 @@ + + + + synthetic/tremolo.unmeasured.3.1.xml + 3.1 + + + grace + + + measure + + number + + + + notations + + + note + + + octave + + + ornaments + + + part + + id + + + + part-group + + type + + + + part-list + + + part-name + + + pitch + + + score-part + + id + + + + score-partwise + + version + + + + staff + + + step + + + tremolo + + default-x + default-y + placement + relative-x + relative-y + smufl + type + + + + voice + + + diff --git a/data/synthetic/tremolo.unmeasured.3.1.xml b/data/synthetic/tremolo.unmeasured.3.1.xml new file mode 100644 index 000000000..f61bf4f56 --- /dev/null +++ b/data/synthetic/tremolo.unmeasured.3.1.xml @@ -0,0 +1,30 @@ + + + + + + + x + + + + + + + + + + A + 1 + + 1 + 1 + + + 0 + + + + + + diff --git a/src/include/mx/api/MarkData.h b/src/include/mx/api/MarkData.h index b3a57b6dd..84a5a539d 100644 --- a/src/include/mx/api/MarkData.h +++ b/src/include/mx/api/MarkData.h @@ -89,13 +89,21 @@ enum class MarkType mordent, invertedMordent, schleifer, - tremoloSingleOne, ///< A tremolo on a single note (a glyph, not a spanner) with 1 slash - tremoloSingleTwo, ///< A tremolo on a single note (a glyph, not a spanner) with 2 slashes - tremoloSingleThree, ///< A tremolo on a single note (a glyph, not a spanner) with 3 slashes - tremoloSingleFour, ///< A tremolo on a single note (a glyph, not a spanner) with 4 slashes - tremoloSingleFive, ///< A tremolo on a single note (a glyph, not a spanner) with 5 slashes + // One-note tremolos: slashes struck through the stem of a single note, not a spanner across + // two. MusicXML allows 1 through 8 of them. SMuFL provides a precomposed glyph for only the + // first five (tremolo1 through tremolo5); because those are combining marks, a renderer builds + // the higher counts by stacking them. + tremoloSingleOne, ///< A one-note tremolo with 1 slash + tremoloSingleTwo, ///< A one-note tremolo with 2 slashes + tremoloSingleThree, ///< A one-note tremolo with 3 slashes, the customary number + tremoloSingleFour, ///< A one-note tremolo with 4 slashes + tremoloSingleFive, ///< A one-note tremolo with 5 slashes + tremoloSingleSix, ///< A one-note tremolo with 6 slashes + tremoloSingleSeven, ///< A one-note tremolo with 7 slashes + tremoloSingleEight, ///< A one-note tremolo with 8 slashes tremoloStart, ///< The first note of a measured (two-note) tremolo; slash count is in MarkData::choice tremoloStop, ///< The second note of a measured (two-note) tremolo; slash count is in MarkData::choice + tremoloUnmeasured, ///< An unmeasured tremolo (buzz roll); its glyph is named in MarkData::choice otherOrnament, ///< MusicXML's 'other-ornament' value unknownOrnament, ///< Error state @@ -272,10 +280,9 @@ struct MarkData Bool fingeringAlternate; // Payload for mark types whose data does not fit the common fields above. Its Kind SHOULD - // correspond to markType (e.g. MarkDataChoice::Kind::tremolo pairs with - // MarkType::tremoloStart/tremoloStop), but this is a convention that MarkData does not - // enforce. New mark-specific data belongs here, as a new MarkDataChoice alternative -- see - // MarkDataChoice.h. + // correspond to markType (e.g. MarkDataChoice::Kind::tremolo pairs with the MarkType::tremolo* + // values), but this is a convention that MarkData does not enforce. New mark-specific data + // belongs here, as a new MarkDataChoice alternative -- see MarkDataChoice.h. MarkDataChoice choice; MarkData(); diff --git a/src/include/mx/api/MarkDataChoice.h b/src/include/mx/api/MarkDataChoice.h index f801bb526..c7838f560 100644 --- a/src/include/mx/api/MarkDataChoice.h +++ b/src/include/mx/api/MarkDataChoice.h @@ -15,17 +15,32 @@ namespace mx namespace api { -// Payload for MarkType::tremoloStart / MarkType::tremoloStop: the measured-tremolo slash count -// (MusicXML text value, 0-8). Absent means "not specified" (the writer falls back to a -// default). The tremoloSingle* mark types encode their slash count in the enumerator itself and do -// not use this payload. +// Payload for the tremolo mark types. Which kind of tremolo a mark is -- a one-note tremolo, one +// end of a two-note (measured) tremolo, or an unmeasured tremolo -- is stated by the MarkType; this +// payload carries what the MarkType cannot say. struct TremoloMarkData { + // How many slashes are drawn through the stem of a two-note tremolo, 0 to 8. Set this on the + // MarkType::tremoloStart and MarkType::tremoloStop marks that bracket the pair, using the same + // count on both. Leave it absent to accept a three-slash default. + // + // A one-note tremolo states its slash count in the MarkType itself + // (MarkType::tremoloSingleOne through MarkType::tremoloSingleFive), and an unmeasured tremolo + // has no slash count, so neither uses this field. std::optional tremoloMarks; + + // The canonical SMuFL glyph name to draw for an unmeasured tremolo (MarkType::tremoloUnmeasured), + // from SMuFL's Tremolos range -- for example "pendereckiTremolo" or "unmeasuredTremolo". Leave + // it absent to accept SMuFL's "buzzRoll", the glyph MusicXML assumes when no name is given. + // + // MusicXML allows this on a one-note or two-note tremolo too, but defines no meaning for it + // there, and most applications ignore it. + std::optional smufl; }; MXAPI_EQUALS_BEGIN(TremoloMarkData) MXAPI_EQUALS_MEMBER(tremoloMarks) +MXAPI_EQUALS_MEMBER(smufl) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(TremoloMarkData); @@ -93,8 +108,8 @@ MXAPI_NOT_EQUALS_AND_VECTORS(NonArpeggiateMarkData); // unclear which fields apply to which mark. New mark-specific payloads belong here, as a new // alternative, rather than as a new direct field on MarkData. // -// The choice's Kind SHOULD correspond to MarkData::markType (e.g. Kind::tremolo pairs with -// MarkType::tremoloStart/tremoloStop), but this is a convention that this class does not enforce. +// The choice's Kind SHOULD correspond to MarkData::markType (e.g. Kind::tremolo pairs with the +// MarkType::tremolo* values), but this is a convention that this class does not enforce. // // Defaults to none (no mark-specific payload). class MarkDataChoice diff --git a/src/private/mx/api/MarkData.cpp b/src/private/mx/api/MarkData.cpp index ca546eb77..0f46f3d2a 100644 --- a/src/private/mx/api/MarkData.cpp +++ b/src/private/mx/api/MarkData.cpp @@ -122,7 +122,9 @@ bool isMarkOrnament(MarkType markType) (markType == MarkType::schleifer) || (markType == MarkType::tremoloSingleOne) || (markType == MarkType::tremoloSingleTwo) || (markType == MarkType::tremoloSingleThree) || (markType == MarkType::tremoloSingleFour) || (markType == MarkType::tremoloSingleFive) || - (markType == MarkType::tremoloStart) || (markType == MarkType::tremoloStop) || + (markType == MarkType::tremoloSingleSix) || (markType == MarkType::tremoloSingleSeven) || + (markType == MarkType::tremoloSingleEight) || (markType == MarkType::tremoloStart) || + (markType == MarkType::tremoloStop) || (markType == MarkType::tremoloUnmeasured) || (markType == MarkType::otherOrnament) || (markType == MarkType::unknownOrnament); } @@ -167,8 +169,10 @@ bool isMarkTremolo(MarkType markType) { return (markType == MarkType::tremoloSingleOne) || (markType == MarkType::tremoloSingleTwo) || (markType == MarkType::tremoloSingleThree) || (markType == MarkType::tremoloSingleFour) || - (markType == MarkType::tremoloSingleFive) || (markType == MarkType::tremoloStart) || - (markType == MarkType::tremoloStop); + (markType == MarkType::tremoloSingleFive) || (markType == MarkType::tremoloSingleSix) || + (markType == MarkType::tremoloSingleSeven) || (markType == MarkType::tremoloSingleEight) || + (markType == MarkType::tremoloStart) || (markType == MarkType::tremoloStop) || + (markType == MarkType::tremoloUnmeasured); } bool isMarkCustom(MarkType markType) @@ -201,29 +205,26 @@ MarkType getMarkTypeFromCustomString(const std::string &inString) int numTremoloSlashes(MarkType markType) { - if (markType == MarkType::tremoloSingleOne) + switch (markType) { + case MarkType::tremoloSingleOne: return 1; - } - - if (markType == MarkType::tremoloSingleTwo) - { + case MarkType::tremoloSingleTwo: return 2; - } - - if (markType == MarkType::tremoloSingleThree) - { + case MarkType::tremoloSingleThree: return 3; - } - - if (markType == MarkType::tremoloSingleFour) - { + case MarkType::tremoloSingleFour: return 4; - } - - if (markType == MarkType::tremoloSingleFive) - { + case MarkType::tremoloSingleFive: return 5; + case MarkType::tremoloSingleSix: + return 6; + case MarkType::tremoloSingleSeven: + return 7; + case MarkType::tremoloSingleEight: + return 8; + default: + break; } return VALUE_UNSPECIFIED; diff --git a/src/private/mx/impl/NotationsWriter.cpp b/src/private/mx/impl/NotationsWriter.cpp index 49f1613ca..b6efc9299 100644 --- a/src/private/mx/impl/NotationsWriter.cpp +++ b/src/private/mx/impl/NotationsWriter.cpp @@ -39,6 +39,7 @@ #include "mx/core/generated/PlacementText.h" #include "mx/core/generated/ShowTuplet.h" #include "mx/core/generated/Slur.h" +#include "mx/core/generated/SmuflGlyphName.h" #include "mx/core/generated/String.h" #include "mx/core/generated/StringNumber.h" #include "mx/core/generated/StrongAccent.h" @@ -696,17 +697,30 @@ void NotationsWriter::addOrnament(const api::MarkData &mark, core::Ornaments &ou case core::OrnamentsGroupChoice::Kind::tremolo: { core::Tremolo tremolo; setAttributesFromPositionData(mark.positionData, tremolo); + const auto tremoloData = mark.choice.isTremolo() ? mark.choice.tremolo() : api::TremoloMarkData{}; + if (mark.markType == api::MarkType::tremoloStart || mark.markType == api::MarkType::tremoloStop) { tremolo.setType(mark.markType == api::MarkType::tremoloStart ? core::TremoloType::start() : core::TremoloType::stop()); - tremolo.setValue(core::TremoloMarks{mark.choice.tremolo().tremoloMarks.value_or(3)}); + tremolo.setValue(core::TremoloMarks{tremoloData.tremoloMarks.value_or(3)}); + } + else if (mark.markType == api::MarkType::tremoloUnmeasured) + { + tremolo.setType(core::TremoloType::unmeasured()); + tremolo.setValue(core::TremoloMarks{0}); } else { tremolo.setType(core::TremoloType::single()); tremolo.setValue(core::TremoloMarks{api::numTremoloSlashes(mark.markType)}); } + + if (tremoloData.smufl.has_value() && !tremoloData.smufl->empty()) + { + tremolo.setSmufl(core::SmuflGlyphName{*tremoloData.smufl}); + } + group.setChoice(core::OrnamentsGroupChoice::tremolo(tremolo)); break; } diff --git a/src/private/mx/impl/OrnamentsFunctions.cpp b/src/private/mx/impl/OrnamentsFunctions.cpp index e5176ae42..2b7bf5840 100644 --- a/src/private/mx/impl/OrnamentsFunctions.cpp +++ b/src/private/mx/impl/OrnamentsFunctions.cpp @@ -161,52 +161,72 @@ void OrnamentsFunctions::parseOrnament(const core::OrnamentsGroupChoice &choiceO case core::OrnamentsGroupChoice::Kind::tremolo: { const auto &tremolo = choiceObj.asTremolo(); const auto type = tremolo.type().value_or(core::TremoloType::single()).tag(); + outMark.name = "tremolo"; + parseMarkDataAttributes(tremolo, outMark); + + api::TremoloMarkData tremoloData; + + if (tremolo.smufl().has_value()) + { + tremoloData.smufl = tremolo.smufl()->value(); + } if (type == core::TremoloType::Tag::start || type == core::TremoloType::Tag::stop) { - outMark.name = "tremolo"; - parseMarkDataAttributes(tremolo, outMark); outMark.markType = (type == core::TremoloType::Tag::start) ? api::MarkType::tremoloStart : api::MarkType::tremoloStop; - outMark.choice = api::TremoloMarkData{tremolo.value().value()}; + tremoloData.tremoloMarks = tremolo.value().value(); + outMark.choice = tremoloData; break; } - if (type != core::TremoloType::Tag::single) + if (type == core::TremoloType::Tag::unmeasured) { - // unmeasured tremolo -- not yet representable - outMark.name = "this tremolo is not a mark"; - outMark.markType = api::MarkType::unknownOrnament; - return; + // An unmeasured tremolo has no slash count; MusicXML fixes its element text at 0. + outMark.markType = api::MarkType::tremoloUnmeasured; + outMark.choice = tremoloData; + break; } - outMark.name = "tremolo"; - parseMarkDataAttributes(tremolo, outMark); - const auto numSlashes = tremolo.value().value(); - - if (numSlashes == 0) + if (tremoloData.smufl.has_value()) { - outMark.markType = api::MarkType::tremoloSingleThree; + outMark.choice = tremoloData; } - else if (numSlashes == 1) + + switch (tremolo.value().value()) { + case 1: outMark.markType = api::MarkType::tremoloSingleOne; - } - else if (numSlashes == 2) - { + break; + case 2: outMark.markType = api::MarkType::tremoloSingleTwo; - } - else if (numSlashes == 3) - { - outMark.markType = api::MarkType::tremoloSingleThree; - } - else if (numSlashes == 4) - { + break; + case 4: outMark.markType = api::MarkType::tremoloSingleFour; - } - else if (numSlashes == 5) - { + break; + case 5: outMark.markType = api::MarkType::tremoloSingleFive; + break; + case 6: + outMark.markType = api::MarkType::tremoloSingleSix; + break; + case 7: + outMark.markType = api::MarkType::tremoloSingleSeven; + break; + case 8: + outMark.markType = api::MarkType::tremoloSingleEight; + break; + default: + // Three slashes, the customary one-note tremolo. Reached by a count of 0, which is a + // legal but degenerate single-type tremolo that would draw nothing -- an unmeasured + // tremolo is also written with a count of 0, but says so with type="unmeasured" and is + // handled above. + // + // TODO: the remap is silent and lossy -- a count of 0 reads back as 3 and is written + // out that way. mx has no warning channel, so there is nowhere to report it. Log the + // downgrade here if a logging framework is ever added. + outMark.markType = api::MarkType::tremoloSingleThree; + break; } break; diff --git a/src/private/mxtest/api/NoteDataTest.cpp b/src/private/mxtest/api/NoteDataTest.cpp index 1749a7f4e..51228ace0 100644 --- a/src/private/mxtest/api/NoteDataTest.cpp +++ b/src/private/mxtest/api/NoteDataTest.cpp @@ -588,6 +588,12 @@ TEST(tremolos, NoteData) marks.emplace_back(mark); mark = MarkData{MarkType::tremoloSingleFive}; marks.emplace_back(mark); + mark = MarkData{MarkType::tremoloSingleSix}; + marks.emplace_back(mark); + mark = MarkData{MarkType::tremoloSingleSeven}; + marks.emplace_back(mark); + mark = MarkData{MarkType::tremoloSingleEight}; + marks.emplace_back(mark); // round trip it through xml auto &mgr = DocumentManager::getInstance(); @@ -614,7 +620,10 @@ TEST(tremolos, NoteData) const auto &onote = ovoice.notes.back(); const auto &omarks = onote.noteAttachmentData.marks; - for (int i = 1; i <= 5; ++i) + // MusicXML's tremolo-marks type allows 1 through 8 slashes. + REQUIRE(8 == omarks.size()); + + for (int i = 1; i <= 8; ++i) { const auto &markData = omarks.at(static_cast(i - 1)); CHECK_EQUAL(i, numTremoloSlashes(markData.markType)); @@ -700,6 +709,70 @@ TEST(measuredTremoloFromSyntheticFile, NoteData) T_END; +TEST(unmeasuredTremolo, NoteData) +{ + ScoreData score; + score.ticksPerQuarter = 1; + score.parts.emplace_back(); + auto &part = score.parts.back(); + part.measures.emplace_back(); + auto &measure = part.measures.back(); + measure.staves.emplace_back(); + auto &staff = measure.staves.back(); + auto &voice = staff.voices[0]; + + voice.notes.emplace_back(); + auto ¬e = voice.notes.back(); + note.durationData.durationName = DurationName::quarter; + note.durationData.durationTimeTicks = 1; + note.tickTimePosition = 0; + MarkData mark{MarkType::tremoloUnmeasured}; + TremoloMarkData tremoloData; + tremoloData.smufl = "pendereckiTremolo"; + mark.choice = tremoloData; + note.noteAttachmentData.marks.emplace_back(mark); + + const auto out = mxtest::roundTrip(score); + + const auto &omarks = + out.parts.back().measures.back().staves.back().voices.at(0).notes.back().noteAttachmentData.marks; + REQUIRE(1 == omarks.size()); + CHECK(MarkType::tremoloUnmeasured == omarks.at(0).markType); + REQUIRE(omarks.at(0).choice.isTremolo()); + CHECK(!omarks.at(0).choice.tremolo().tremoloMarks.has_value()); + REQUIRE(omarks.at(0).choice.tremolo().smufl.has_value()); + CHECK_EQUAL("pendereckiTremolo", *omarks.at(0).choice.tremolo().smufl); +} + +T_END; + +// Parse the synthetic unmeasured-tremolo file and confirm mx::api surfaces it as a +// tremoloUnmeasured mark carrying the file's smufl glyph name. This pins the core -> api read path +// for unmeasured tremolos, which were previously dropped. +TEST(unmeasuredTremoloFromSyntheticFile, NoteData) +{ + const std::string path = mxtest::getResourcesDirectoryPath() + "synthetic/tremolo.unmeasured.3.1.xml"; + auto &docMgr = DocumentManager::getInstance(); + const auto docIdResult = docMgr.createFromFile(path); + REQUIRE(docIdResult.ok()); + const int docId = docIdResult.value(); + const auto scoreResult = docMgr.getData(docId); + docMgr.destroyDocument(docId); + REQUIRE(scoreResult.ok()); + const auto &score = scoreResult.value(); + + const auto &marks = + score.parts.back().measures.back().staves.back().voices.at(0).notes.back().noteAttachmentData.marks; + REQUIRE(1 == marks.size()); + CHECK(MarkType::tremoloUnmeasured == marks.at(0).markType); + REQUIRE(marks.at(0).choice.isTremolo()); + CHECK(!marks.at(0).choice.tremolo().tremoloMarks.has_value()); + REQUIRE(marks.at(0).choice.tremolo().smufl.has_value()); + CHECK_EQUAL("pendereckiTremolo", *marks.at(0).choice.tremolo().smufl); +} + +T_END; + TEST(miscFields, NoteData) { ScoreData score; diff --git a/src/private/mxtest/api/roundtrip-baseline.txt b/src/private/mxtest/api/roundtrip-baseline.txt index 685c286e9..aa4be00ef 100644 --- a/src/private/mxtest/api/roundtrip-baseline.txt +++ b/src/private/mxtest/api/roundtrip-baseline.txt @@ -540,6 +540,12 @@ musuite/testMultiMeasureRest2.xml musuite/testMultiMeasureRest3.xml synthetic/multiple-rest.3.0.xml +# Unmeasured tremolos: MarkType::tremoloUnmeasured plus TremoloMarkData::smufl. The reader +# previously discarded as "not a mark" and neither direction touched +# the smufl attribute. The sibling tremolo.3.0/3.1 fixtures still fail, on the unrelated fact that +# MarkData carries no font or color data. +synthetic/tremolo.unmeasured.3.1.xml + # Passed on macOS but not on Linux until ScoreData::sort() was made stable. It is # 14 chords, and every member of a chord shares one tick, so an unstable sort let # the standard library choose which one came first -- and MusicXML spells a chord diff --git a/src/private/mxtest/corert/CoreRoundtripTest.cpp b/src/private/mxtest/corert/CoreRoundtripTest.cpp index 0608f8e76..ecfe343e6 100644 --- a/src/private/mxtest/corert/CoreRoundtripTest.cpp +++ b/src/private/mxtest/corert/CoreRoundtripTest.cpp @@ -127,12 +127,12 @@ const CoreRoundtripRegistrar g_coreRoundtripRegistrar; } // namespace -// Pinned counts: 837 eligible files, none skipped. Count drift is a failure +// Pinned counts: 838 eligible files, none skipped. Count drift is a failure // even with zero individual fails, so a corpus or version-gate change is a // conscious decision, not silent decay. Registered last (registration is // discovery order; "zz" keeps it last alphabetically for shuffled runs too). TEST_CASE("zz-corert-pinned-counts", "[core-roundtrip]") { - CHECK(mxtest::corert::discoverInputFiles().size() == 837); + CHECK(mxtest::corert::discoverInputFiles().size() == 838); CHECK(g_skippedCount == 0); }