From d33d472bc9582c1b161b9a859914884deb2ec8da Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Fri, 23 Aug 2024 17:04:40 +0200 Subject: [PATCH 1/7] export and import numerals --- .../musicxml/internal/musicxml/exportxml.cpp | 56 +++++++++++++++++-- .../internal/musicxml/importmxmlpass2.cpp | 45 +++++++++++++-- 2 files changed, 90 insertions(+), 11 deletions(-) diff --git a/src/importexport/musicxml/internal/musicxml/exportxml.cpp b/src/importexport/musicxml/internal/musicxml/exportxml.cpp index 480cb00500f1b..fa78a7109358c 100644 --- a/src/importexport/musicxml/internal/musicxml/exportxml.cpp +++ b/src/importexport/musicxml/internal/musicxml/exportxml.cpp @@ -134,6 +134,9 @@ #include "global/realfn.h" #include "engraving/iengravingconfiguration.h" +#include +#include + #include "log.h" using namespace mu; @@ -8694,16 +8697,57 @@ void ExportMusicXml::harmony(Harmony const* const h, FretDiagram const* const fd const String textName = h->hTextName(); switch (h->harmonyType()) { case HarmonyType::NASHVILLE: { - m_xml.tag("function", h->hFunction()); - m_xml.tag("kind", { { "text", textName } }, "none"); + String alter; + String functionText = h->hFunction(); + if (functionText.empty()) { + // we just dump the text as deprecated function + m_xml.tag("function", h->musicXmlText()); + break; + } + m_xml.startElement("numeral"); + if (!functionText.at(0).isDigit()) { + alter = functionText.at(0); + functionText = functionText.at(1); + } + m_xml.tag("numeral-root", functionText); + if (alter == u"b") { + m_xml.tag("numeral-alter", "-1"); + } else if (alter == u"#") { + m_xml.tag("numeral-alter", "1"); + } + m_xml.endElement(); + if (!h->xmlKind().isEmpty()) { + String s = u"kind"; + String kindText = h->musicXmlText(); + if (h->musicXmlText() != "") { + s += u" text=\"" + kindText + u"\""; + } + if (h->xmlSymbols() == "yes") { + s += u" use-symbols=\"yes\""; + } + if (h->xmlParens() == "yes") { + s += u" parentheses-degrees=\"yes\""; + } + m_xml.tagRaw(s, h->xmlKind()); + } else { + m_xml.tag("kind", "none"); + } } break; case HarmonyType::ROMAN: { - // TODO: parse? - m_xml.tag("function", h->hTextName()); // note: HTML escape done by tag() - m_xml.tag("kind", { { "text", "" } }, "none"); + QRegularExpression romanRegex("[iv]+", QRegularExpression::CaseInsensitiveOption); + QRegularExpressionMatch romanMatch = romanRegex.match(textName); + if (romanMatch.capturedTexts().size()) { + String rootText = romanMatch.capturedTexts()[0]; + m_xml.startElement("numeral"); + m_xml.tag("numeral-root", { { "text", rootText } }, "1"); + m_xml.endElement(); + // only check for major or minor + m_xml.tag("kind", rootText.at(0).isUpper() ? "major" : "minor"); + break; + } } - break; + // fallthrough case HarmonyType::STANDARD: default: { m_xml.startElement("root"); diff --git a/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp b/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp index fa0c364b7776f..7cd80ba13b69d 100644 --- a/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp +++ b/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp @@ -7299,7 +7299,7 @@ void MusicXMLParserPass2::harmony(const String& partId, Measure* measure, const const String placement = m_e.attribute("placement"); const bool printObject = m_e.asciiAttribute("print-object") != "no"; - String kind, kindText, functionText, symbols, parens; + String kind, kindText, functionText, inversionText, symbols, parens; std::list degreeList; FretDiagram* fd = nullptr; @@ -7342,8 +7342,33 @@ void MusicXMLParserPass2::harmony(const String& partId, Measure* measure, const ha->setRootTpc(Tpc::TPC_INVALID); ha->setBaseTpc(Tpc::TPC_INVALID); functionText = m_e.readText(); - // TODO: parse to decide between ROMAN and NASHVILLE ha->setHarmonyType(HarmonyType::ROMAN); + } else if (m_e.name() == "numeral") { + ha->setRootTpc(Tpc::TPC_INVALID); + ha->setBaseTpc(Tpc::TPC_INVALID); + while (m_e.readNextStartElement()) { + if (m_e.name() == "numeral-root") { + String numeralRoot = m_e.readText(); + String numeralRootText = m_e.attribute("text"); + // TODO analyze text and import as roman numerals + ha->setHarmonyType(HarmonyType::NASHVILLE); + ha->setFunction(numeralRoot); + } else if (m_e.name() == "numeral-alter") { + const int alter = m_e.readText().toInt(); + switch (alter) { + case -1: + ha->setFunction(u"b" + ha->hFunction()); + break; + case 1: + ha->setFunction(u"#" + ha->hFunction()); + break; + default: + break; + } + } else { + skipLogCurrElem(); + } + } } else if (m_e.name() == "kind") { // attributes: use-symbols yes-no // text, stack-degrees, parentheses-degree, bracket-degrees, @@ -7356,8 +7381,16 @@ void MusicXMLParserPass2::harmony(const String& partId, Measure* measure, const ha->setRootTpc(Tpc::TPC_INVALID); } } else if (m_e.name() == "inversion") { - // attributes: print-style - skipLogCurrElem(); + const int inversion = m_e.readText().toInt(); + switch (inversion) { + case 1: inversionText = "6"; + break; + case 2: inversionText = "64"; + break; + default: + inversionText = ""; + break; + } } else if (m_e.name() == "bass") { String step; int alter = 0; @@ -7427,13 +7460,15 @@ void MusicXMLParserPass2::harmony(const String& partId, Measure* measure, const const ChordDescription* d = nullptr; if (ha->rootTpc() != Tpc::TPC_INVALID) { d = ha->fromXml(kind, kindText, symbols, parens, degreeList); + } else if (!ha->hFunction().empty()) { + d = ha->fromXml(kind, kindText, symbols, parens, degreeList); } if (d) { ha->setId(d->id); ha->setTextName(d->names.front()); } else { ha->setId(-1); - String textName = functionText + kindText; + String textName = functionText + kindText + inversionText; ha->setTextName(textName); } ha->render(); From 9118ea18170d2251861d0d01edf824275e81e920 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Fri, 23 Aug 2024 17:04:56 +0200 Subject: [PATCH 2/7] update and add test --- .../musicxml/tests/data/testHarmony6_ref.xml | 30 +- .../musicxml/tests/data/testHarmony9.xml | 274 ++++++++++++++++++ .../musicxml/tests/musicxml_tests.cpp | 3 + 3 files changed, 295 insertions(+), 12 deletions(-) create mode 100644 src/importexport/musicxml/tests/data/testHarmony9.xml diff --git a/src/importexport/musicxml/tests/data/testHarmony6_ref.xml b/src/importexport/musicxml/tests/data/testHarmony6_ref.xml index aaa30548bf54f..aecd7815189c4 100644 --- a/src/importexport/musicxml/tests/data/testHarmony6_ref.xml +++ b/src/importexport/musicxml/tests/data/testHarmony6_ref.xml @@ -74,8 +74,10 @@ containing recognized text, unrecognized plaintext and unrecognized text requiri - i - none + + 1 + + minor @@ -93,8 +95,10 @@ containing recognized text, unrecognized plaintext and unrecognized text requiri - 1 - none + + 1 + + minor @@ -134,8 +138,10 @@ containing recognized text, unrecognized plaintext and unrecognized text requiri - xyz - none + + C + + none @@ -153,8 +159,7 @@ containing recognized text, unrecognized plaintext and unrecognized text requiri - - none + xyz @@ -194,8 +199,10 @@ containing recognized text, unrecognized plaintext and unrecognized text requiri - <>&" - none + + C + + none @@ -213,8 +220,7 @@ containing recognized text, unrecognized plaintext and unrecognized text requiri - - none + <>&" diff --git a/src/importexport/musicxml/tests/data/testHarmony9.xml b/src/importexport/musicxml/tests/data/testHarmony9.xml new file mode 100644 index 0000000000000..07b49df3bebcf --- /dev/null +++ b/src/importexport/musicxml/tests/data/testHarmony9.xml @@ -0,0 +1,274 @@ + + + + + Nashville Number System + + + Klaus Rettinghaus + + MuseScore 0.7.0 + 2007-09-10 + + + + + + + + + + Akustischer Bass + Bass + + Akustischer Bass + + + + 1 + 37 + 78.7402 + 0 + + + + + + + 1 + + 2 + + + + F + 4 + + + 0 + 0 + -1 + + + + + 1 + + major + + + + D + 3 + + 1 + 1 + quarter + down + + + + 7 + + half-diminished + + + + C + 1 + 3 + + 1 + 1 + quarter + up + + + + 6 + + minor + + + + B + 2 + + 1 + 1 + quarter + up + + + + 4 + + minor + + + + B + -1 + 2 + + 1 + 1 + quarter + flat + up + + + + + + 5 + + dominant + + + + A + 2 + + 1 + 1 + quarter + up + + + + 1 + + major-sixth + + + + D + 3 + + 1 + 1 + quarter + down + + + + 2 + -1 + + major + + + + E + -1 + 3 + + 1 + 1 + quarter + flat + down + + + + 3 + -1 + + major + + + + F + 3 + + 1 + 1 + quarter + natural + down + + + + + + 1 + + power + + + + D + 3 + + 1 + 1 + quarter + down + + + + 6 + + major-seventh + + + + D + 1 + 3 + + 1 + 1 + quarter + sharp + down + + + + 2 + + minor-sixth + + + + E + 3 + + 1 + 1 + quarter + down + + + + 5 + + suspended-fourth + + + + A + 2 + + 1 + 1 + quarter + up + + + + + + 4 + 1 + + + light-heavy + + + + diff --git a/src/importexport/musicxml/tests/musicxml_tests.cpp b/src/importexport/musicxml/tests/musicxml_tests.cpp index 37a648477c034..3f7a1f017905a 100644 --- a/src/importexport/musicxml/tests/musicxml_tests.cpp +++ b/src/importexport/musicxml/tests/musicxml_tests.cpp @@ -697,6 +697,9 @@ TEST_F(Musicxml_Tests, harmony7) { TEST_F(Musicxml_Tests, harmony8) { mxmlIoTest("testHarmony8"); } +TEST_F(Musicxml_Tests, harmony9) { + mxmlIoTest("testHarmony9"); +} // chordnames without chordrest TEST_F(Musicxml_Tests, hello) { mxmlIoTest("testHello"); } From 75c0c1d3bbab08bf9eb3a147596357069d7f0738 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Fri, 23 Aug 2024 18:25:23 +0200 Subject: [PATCH 3/7] simplify --- .../musicxml/internal/musicxml/importmxmlpass2.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp b/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp index 7cd80ba13b69d..1f9ca04b8ad02 100644 --- a/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp +++ b/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp @@ -7458,9 +7458,7 @@ void MusicXMLParserPass2::harmony(const String& partId, Measure* measure, const } const ChordDescription* d = nullptr; - if (ha->rootTpc() != Tpc::TPC_INVALID) { - d = ha->fromXml(kind, kindText, symbols, parens, degreeList); - } else if (!ha->hFunction().empty()) { + if (ha->rootTpc() != Tpc::TPC_INVALID || !ha->hFunction().empty()) { d = ha->fromXml(kind, kindText, symbols, parens, degreeList); } if (d) { From f1f7227e7caf7911d0b19b8621f7e3038559a24e Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Sat, 24 Aug 2024 13:52:43 +0200 Subject: [PATCH 4/7] switch to std::regex --- .../musicxml/internal/musicxml/exportxml.cpp | 21 +++++++++---------- .../musicxml/tests/data/testHarmony6_ref.xml | 2 ++ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/importexport/musicxml/internal/musicxml/exportxml.cpp b/src/importexport/musicxml/internal/musicxml/exportxml.cpp index fa78a7109358c..3e4b723a2d263 100644 --- a/src/importexport/musicxml/internal/musicxml/exportxml.cpp +++ b/src/importexport/musicxml/internal/musicxml/exportxml.cpp @@ -8701,14 +8701,14 @@ void ExportMusicXml::harmony(Harmony const* const h, FretDiagram const* const fd String functionText = h->hFunction(); if (functionText.empty()) { // we just dump the text as deprecated function - m_xml.tag("function", h->musicXmlText()); + m_xml.tag("function", textName); + m_xml.tag("kind", "none"); break; - } - m_xml.startElement("numeral"); - if (!functionText.at(0).isDigit()) { + } else if (!functionText.at(0).isDigit()) { alter = functionText.at(0); functionText = functionText.at(1); } + m_xml.startElement("numeral"); m_xml.tag("numeral-root", functionText); if (alter == u"b") { m_xml.tag("numeral-alter", "-1"); @@ -8730,20 +8730,19 @@ void ExportMusicXml::harmony(Harmony const* const h, FretDiagram const* const fd } m_xml.tagRaw(s, h->xmlKind()); } else { - m_xml.tag("kind", "none"); + // default is major + m_xml.tag("kind", "major"); } } break; case HarmonyType::ROMAN: { - QRegularExpression romanRegex("[iv]+", QRegularExpression::CaseInsensitiveOption); - QRegularExpressionMatch romanMatch = romanRegex.match(textName); - if (romanMatch.capturedTexts().size()) { - String rootText = romanMatch.capturedTexts()[0]; + static const std::regex roman("[iv]+|[IV]+"); + if (std::regex_match(textName.toStdString(), roman)) { m_xml.startElement("numeral"); - m_xml.tag("numeral-root", { { "text", rootText } }, "1"); + m_xml.tag("numeral-root", { { "text", textName } }, "1"); m_xml.endElement(); // only check for major or minor - m_xml.tag("kind", rootText.at(0).isUpper() ? "major" : "minor"); + m_xml.tag("kind", textName.at(0).isUpper() ? "major" : "minor"); break; } } diff --git a/src/importexport/musicxml/tests/data/testHarmony6_ref.xml b/src/importexport/musicxml/tests/data/testHarmony6_ref.xml index aecd7815189c4..c20825af11ac7 100644 --- a/src/importexport/musicxml/tests/data/testHarmony6_ref.xml +++ b/src/importexport/musicxml/tests/data/testHarmony6_ref.xml @@ -160,6 +160,7 @@ containing recognized text, unrecognized plaintext and unrecognized text requiri xyz + none @@ -221,6 +222,7 @@ containing recognized text, unrecognized plaintext and unrecognized text requiri <>&" + none From f83d3668c4a26946686ce4522fef354f36901489 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Sun, 25 Aug 2024 16:57:52 +0200 Subject: [PATCH 5/7] remove wrong imports --- src/importexport/musicxml/internal/musicxml/exportxml.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/importexport/musicxml/internal/musicxml/exportxml.cpp b/src/importexport/musicxml/internal/musicxml/exportxml.cpp index 3e4b723a2d263..51cf3a0011f97 100644 --- a/src/importexport/musicxml/internal/musicxml/exportxml.cpp +++ b/src/importexport/musicxml/internal/musicxml/exportxml.cpp @@ -134,9 +134,6 @@ #include "global/realfn.h" #include "engraving/iengravingconfiguration.h" -#include -#include - #include "log.h" using namespace mu; From 901a9b35c067f4381b456fe15accbc63cfcd5637 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Sun, 25 Aug 2024 16:58:19 +0200 Subject: [PATCH 6/7] use string empty --- .../musicxml/internal/musicxml/exportxml.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/importexport/musicxml/internal/musicxml/exportxml.cpp b/src/importexport/musicxml/internal/musicxml/exportxml.cpp index 51cf3a0011f97..b0ca526517f94 100644 --- a/src/importexport/musicxml/internal/musicxml/exportxml.cpp +++ b/src/importexport/musicxml/internal/musicxml/exportxml.cpp @@ -5907,35 +5907,35 @@ static void directionJump(XmlWriter& xml, const Jump* const jp) bool isDaCapo = false; bool isDalSegno = false; if (jtp == JumpType::DC) { - if (jp->xmlText() == "") { + if (jp->xmlText().empty()) { words = u"D.C."; } else { words = jp->xmlText(); } isDaCapo = true; } else if (jtp == JumpType::DC_AL_FINE) { - if (jp->xmlText() == "") { + if (jp->xmlText().empty()) { words = u"D.C. al Fine"; } else { words = jp->xmlText(); } isDaCapo = true; } else if (jtp == JumpType::DC_AL_CODA) { - if (jp->xmlText() == "") { + if (jp->xmlText().empty()) { words = u"D.C. al Coda"; } else { words = jp->xmlText(); } isDaCapo = true; } else if (jtp == JumpType::DS_AL_CODA) { - if (jp->xmlText() == "") { + if (jp->xmlText().empty()) { words = u"D.S. al Coda"; } else { words = jp->xmlText(); } isDalSegno = true; } else if (jtp == JumpType::DS_AL_FINE) { - if (jp->xmlText() == "") { + if (jp->xmlText().empty()) { words = u"D.S. al Fine"; } else { words = jp->xmlText(); @@ -5957,7 +5957,7 @@ static void directionJump(XmlWriter& xml, const Jump* const jp) if (isDaCapo) { sound = u"dacapo=\"yes\""; } else if (isDalSegno) { - if (jp->jumpTo() == "") { + if (jp->xmlText().empty()) { sound = u"dalsegno=\"1\""; } else { sound = u"dalsegno=\"" + jp->jumpTo() + u"\""; @@ -8596,7 +8596,7 @@ void ExportMusicXml::harmony(Harmony const* const h, FretDiagram const* const fd if (!h->xmlKind().isEmpty()) { String s = u"kind"; String kindText = h->musicXmlText(); - if (h->musicXmlText() != u"") { + if (!h->musicXmlText().empty()) { s += u" text=\"" + kindText + u"\""; } if (h->xmlSymbols() == u"yes") { @@ -8716,7 +8716,7 @@ void ExportMusicXml::harmony(Harmony const* const h, FretDiagram const* const fd if (!h->xmlKind().isEmpty()) { String s = u"kind"; String kindText = h->musicXmlText(); - if (h->musicXmlText() != "") { + if (!h->musicXmlText().empty()) { s += u" text=\"" + kindText + u"\""; } if (h->xmlSymbols() == "yes") { From a3ac0c5b710bd33596932d42051916728e9410b1 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Sun, 25 Aug 2024 17:14:30 +0200 Subject: [PATCH 7/7] fix strings --- .../musicxml/internal/musicxml/importmxmlpass2.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp b/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp index 1f9ca04b8ad02..b54648017386e 100644 --- a/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp +++ b/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp @@ -7383,12 +7383,12 @@ void MusicXMLParserPass2::harmony(const String& partId, Measure* measure, const } else if (m_e.name() == "inversion") { const int inversion = m_e.readText().toInt(); switch (inversion) { - case 1: inversionText = "6"; + case 1: inversionText = u"6"; break; - case 2: inversionText = "64"; + case 2: inversionText = u"64"; break; default: - inversionText = ""; + inversionText = u""; break; } } else if (m_e.name() == "bass") {