From cfddbc801edd2e283a378082a1c848c0e460e78d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=B6gstr=C3=B6m?= Date: Fri, 12 Jun 2026 21:31:29 +0200 Subject: [PATCH] Import explicit MusicXML sound jump attributes regardless of inferTextType The element attributes dacapo, dalsegno, fine, segno, coda and tocoda are the explicit, machine-readable MusicXML representation of jump/marker semantics. They were only imported when the "infer text type" preference (default off) was enabled, because the entire handleRepeats() function was gated behind it, so with default settings these directions were imported as plain staff text and playback ignored the jumps. Now only the purely text-based matchRepeat() fallback depends on the preference. Additionally, the words accompanying a dacapo/dalsegno sound attribute may refine the jump type, so "D.C. al Fine" with dacapo="yes" imports as a D.C. al Fine jump (stopping at the Fine marker) instead of a plain D.C. that plays to the end. Resolves: https://github.com/musescore/MuseScore/issues/33801 Co-Authored-By: Claude Fable 5 --- .../internal/import/importmusicxmlpass2.cpp | 15 +- .../musicxml/tests/data/testDCalCoda_ref.xml | 145 ++++++++++++++++++ .../tests/data/testDSalCodaMisplaced_ref.mscx | 4 +- .../musicxml/tests/data/testDSalCoda_ref.mscx | 4 +- .../musicxml/tests/musicxml_tests.cpp | 35 ++++- 5 files changed, 191 insertions(+), 12 deletions(-) create mode 100644 src/importexport/musicxml/tests/data/testDCalCoda_ref.xml diff --git a/src/importexport/musicxml/internal/import/importmusicxmlpass2.cpp b/src/importexport/musicxml/internal/import/importmusicxmlpass2.cpp index b13fbbe51e1d7..a2b68f5993b98 100644 --- a/src/importexport/musicxml/internal/import/importmusicxmlpass2.cpp +++ b/src/importexport/musicxml/internal/import/importmusicxmlpass2.cpp @@ -4644,26 +4644,27 @@ static String countSegno(const String& plainWords) void MusicXmlParserDirection::handleRepeats(Measure* measure, const Fraction tick, bool& measureHasCoda, SegnoStack& segnos, DelayedDirectionsList& delayedDirections) { - if (!configuration()->inferTextType()) { - return; - } // Try to recognize the various repeats + // The explicit repeat attributes of the element are always honoured; + // only the purely text-based fallback depends on the inferTextType preference String repeat; const String plainWords = MScoreTextToMusicXml::toPlainText(m_wordsText.toLower().simplified()); + const String wordsRepeat = matchRepeat(plainWords); if (!m_sndCoda.empty()) { repeat = u"coda"; } else if (!m_sndDacapo.empty()) { - repeat = u"daCapo"; + // the accompanying words may refine the jump type (e.g. "D.C. al Fine") + repeat = wordsRepeat.startsWith(u"daCapo") ? wordsRepeat : u"daCapo"; } else if (!m_sndDalsegno.empty()) { - repeat = u"dalSegno"; + repeat = wordsRepeat.startsWith(u"dalSegno") ? wordsRepeat : u"dalSegno"; } else if (!m_sndFine.empty()) { repeat = u"fine"; } else if (!m_sndSegno.empty()) { repeat = u"segno"; } else if (!m_sndToCoda.empty()) { repeat = u"toCoda"; - } else { - repeat = matchRepeat(plainWords); + } else if (configuration()->inferTextType()) { + repeat = wordsRepeat; } // Check if repeat number has become detached if (repeat == u"coda" || repeat == u"segno") { diff --git a/src/importexport/musicxml/tests/data/testDCalCoda_ref.xml b/src/importexport/musicxml/tests/data/testDCalCoda_ref.xml new file mode 100644 index 0000000000000..2209e6fe166bf --- /dev/null +++ b/src/importexport/musicxml/tests/data/testDCalCoda_ref.xml @@ -0,0 +1,145 @@ + + + + + MuseScore testfile + D.C. al Coda + + + Leon Vinken + + MuseScore 0.7.0 + 2007-09-10 + + + + + + + + + + Guitar + + Guitar + + + + 1 + 1 + 78.7402 + 0 + + + + + + + 1 + + 0 + + + + G + 2 + -1 + + + + + G + 3 + + 4 + 1 + whole + + + + + + A + 3 + + 4 + 1 + whole + + + + To Coda + coda + + + + + + + + B + 3 + + 4 + 1 + whole + + + + + + C + 4 + + 4 + 1 + whole + + + + + + 100 + + + + + + + + + + + D + 4 + + 4 + 1 + whole + + + + + + E + 4 + + 4 + 1 + whole + + + + D.C. al Coda + + + + + light-heavy + + + + diff --git a/src/importexport/musicxml/tests/data/testDSalCodaMisplaced_ref.mscx b/src/importexport/musicxml/tests/data/testDSalCodaMisplaced_ref.mscx index 1c84cd381457b..fe596da9789e0 100644 --- a/src/importexport/musicxml/tests/data/testDSalCodaMisplaced_ref.mscx +++ b/src/importexport/musicxml/tests/data/testDSalCodaMisplaced_ref.mscx @@ -400,8 +400,8 @@ D.S. al Coda segno - end - + coda + codab diff --git a/src/importexport/musicxml/tests/data/testDSalCoda_ref.mscx b/src/importexport/musicxml/tests/data/testDSalCoda_ref.mscx index 1c84cd381457b..fe596da9789e0 100644 --- a/src/importexport/musicxml/tests/data/testDSalCoda_ref.mscx +++ b/src/importexport/musicxml/tests/data/testDSalCoda_ref.mscx @@ -400,8 +400,8 @@ D.S. al Coda segno - end - + coda + codab diff --git a/src/importexport/musicxml/tests/musicxml_tests.cpp b/src/importexport/musicxml/tests/musicxml_tests.cpp index cc47ad7cde8db..b1f1e21e4360f 100644 --- a/src/importexport/musicxml/tests/musicxml_tests.cpp +++ b/src/importexport/musicxml/tests/musicxml_tests.cpp @@ -23,6 +23,8 @@ #include #include "engraving/engravingerrors.h" +#include "engraving/dom/jump.h" +#include "engraving/dom/marker.h" #include "engraving/dom/masterscore.h" #include "settings.h" @@ -520,11 +522,42 @@ TEST_F(MusicXml_Tests, dalSegno) { musicXmlIoTest("testDalSegno"); } TEST_F(MusicXml_Tests, dcalCoda) { - musicXmlIoTest("testDCalCoda"); + // "D.C. al Coda" with imports as a D.C. al Coda jump, + // so the exported attribute becomes linked to the coda marker + musicXmlIoTestRef("testDCalCoda"); } TEST_F(MusicXml_Tests, dcalFine) { musicXmlIoTest("testDCalFine"); } +TEST_F(MusicXml_Tests, dcalFineNoInferTextType) { + // Explicit and attributes must be + // imported as Jump/Marker elements even when text type inference is disabled + MScore::debugMode = true; + + setValue(PREF_IMPORT_MUSICXML_INFERTEXT, Val(false)); + + MasterScore* score = readScore(XML_IO_DATA_DIR + u"testDCalFine.xml"); + ASSERT_TRUE(score); + + const Jump* jump = nullptr; + const Marker* marker = nullptr; + for (const MeasureBase* mb = score->first(); mb; mb = mb->next()) { + for (const EngravingItem* el : mb->el()) { + if (el->isJump()) { + jump = toJump(el); + } else if (el->isMarker()) { + marker = toMarker(el); + } + } + } + + ASSERT_TRUE(jump); + EXPECT_EQ(jump->jumpType(), JumpType::DC_AL_FINE); + ASSERT_TRUE(marker); + EXPECT_EQ(marker->markerType(), MarkerType::FINE); + + delete score; +} TEST_F(MusicXml_Tests, directions1) { musicXmlIoTestRef("testDirections1"); }