diff --git a/src/importexport/musicxml/internal/musicxml/export/exportmusicxml.cpp b/src/importexport/musicxml/internal/musicxml/export/exportmusicxml.cpp index e81c475e7b651..14388e7b4d610 100644 --- a/src/importexport/musicxml/internal/musicxml/export/exportmusicxml.cpp +++ b/src/importexport/musicxml/internal/musicxml/export/exportmusicxml.cpp @@ -5946,35 +5946,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(); @@ -5996,7 +5996,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"\""; @@ -8683,7 +8683,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") { @@ -8781,16 +8781,83 @@ 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", textName); + m_xml.tag("kind", "none"); + break; + } 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"); + } 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().empty()) { + 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 { + // default is major + m_xml.tag("kind", "major"); + } } break; case HarmonyType::ROMAN: { - // TODO: parse? - m_xml.tag("function", h->hTextName()); // note: HTML escape done by tag() - m_xml.tag("kind", { { "text", "" } }, "none"); + int alter = 0; + static const std::wregex roman(L"(b|#)?([ivIV]+)"); + if (textName.contains(roman)) { + StringList matches = textName.search(roman, { 1, 2 }); + m_xml.startElement("numeral"); + if (matches.at(0) == u"b") { + alter = -1; + } else if (matches.at(0) == u"#") { + alter = 1; + } + const String numberStr = matches.at(1); + size_t harmoy = 1; + if (numberStr.contains(u"v", CaseSensitivity::CaseInsensitive)) { + if (numberStr.startsWith(u"i", CaseSensitivity::CaseInsensitive)) { + harmoy = 4; + } else { + harmoy = 4 + numberStr.size(); + } + } else { + harmoy = numberStr.size(); + } + m_xml.tag("numeral-root", { { "text", numberStr } }, harmoy); + if (alter) { + m_xml.tag("numeral-alter", alter); + } + m_xml.endElement(); + // simple check for major or minor + m_xml.tag("kind", numberStr.at(0).isUpper() ? "major" : "minor"); + // infer inversion from ending digits + if (textName.endsWith(u"64")) { + m_xml.tag("inversion", 2); + } else if (textName.endsWith(u"6")) { + m_xml.tag("inversion", 1); + } + break; + } } - break; + // fallthrough case HarmonyType::STANDARD: default: { m_xml.startElement("root"); diff --git a/src/importexport/musicxml/internal/musicxml/import/importmusicxmlpass2.cpp b/src/importexport/musicxml/internal/musicxml/import/importmusicxmlpass2.cpp index d4d3d90dd0180..b57b76736327b 100644 --- a/src/importexport/musicxml/internal/musicxml/import/importmusicxmlpass2.cpp +++ b/src/importexport/musicxml/internal/musicxml/import/importmusicxmlpass2.cpp @@ -6913,8 +6913,8 @@ Note* MusicXmlParserPass2::note(const String& partId, // handle notations if (cr) { notations.addToScore(cr, note, - noteStartTime.ticks(), m_slurs, m_glissandi, m_spanners, m_trills, m_ties, m_unstartedTieNotes, m_unendedTieNotes, arpMap, - delayedArps); + noteStartTime.ticks(), m_slurs, m_glissandi, m_spanners, m_trills, m_ties, m_unstartedTieNotes, + m_unendedTieNotes, arpMap, delayedArps); // if no tie added yet, convert the "tie" into "tied" and add it. if (note && !note->tieFor() && !note->tieBack() && !tieType.empty()) { @@ -7374,7 +7374,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; @@ -7418,8 +7418,36 @@ 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") { + functionText = m_e.attribute("text"); + const String numeralRoot = m_e.readText(); + if (functionText.isEmpty() || functionText.at(0).isDigit()) { + ha->setHarmonyType(HarmonyType::NASHVILLE); + ha->setFunction(numeralRoot); + } else { + ha->setHarmonyType(HarmonyType::ROMAN); + } + } 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, @@ -7432,8 +7460,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 = u"6"; + break; + case 2: inversionText = u"64"; + break; + default: + inversionText = u""; + break; + } } else if (m_e.name() == "bass") { String step; int alter = 0; @@ -7501,7 +7537,7 @@ void MusicXmlParserPass2::harmony(const String& partId, Measure* measure, const } const ChordDescription* d = nullptr; - if (ha->rootTpc() != Tpc::TPC_INVALID) { + if (ha->rootTpc() != Tpc::TPC_INVALID || ha->harmonyType() == HarmonyType::NASHVILLE) { d = ha->fromXml(kind, kindText, symbols, parens, degreeList); } if (d) { @@ -7509,7 +7545,7 @@ void MusicXmlParserPass2::harmony(const String& partId, Measure* measure, const ha->setTextName(d->names.front()); } else { ha->setId(-1); - String textName = functionText + kindText; + String textName = functionText + kindText + inversionText; ha->setTextName(textName); } ha->render(); diff --git a/src/importexport/musicxml/tests/data/testHarmony6_ref.xml b/src/importexport/musicxml/tests/data/testHarmony6_ref.xml index aaa30548bf54f..c20825af11ac7 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,8 @@ containing recognized text, unrecognized plaintext and unrecognized text requiri - - none + xyz + none @@ -194,8 +200,10 @@ containing recognized text, unrecognized plaintext and unrecognized text requiri - <>&" - none + + C + + none @@ -213,8 +221,8 @@ containing recognized text, unrecognized plaintext and unrecognized text requiri - - none + <>&" + 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/data/testNumerals.xml b/src/importexport/musicxml/tests/data/testNumerals.xml new file mode 100644 index 0000000000000..5ade4cbe6f22a --- /dev/null +++ b/src/importexport/musicxml/tests/data/testNumerals.xml @@ -0,0 +1,223 @@ + + + + + Numeral test file + + + + MuseScore 0.7.0 + 2007-09-10 + + + + + + + + + + Piano + Klav. + + Piano + + + + 1 + 1 + 78.7402 + 0 + + + + + + + 1 + + 0 + + + + F + 4 + + + + + 1 + + major + + + + C + 3 + + 2 + 1 + half + up + + + + 2 + + minor + + + + D + 3 + + 2 + 1 + half + down + + + + + + 3 + + minor + + + + E + 3 + + 2 + 1 + half + down + + + + 4 + + major + + + + F + 3 + + 2 + 1 + half + down + + + + + + 5 + + major + + + + G + 3 + + 4 + 1 + whole + + + + + + 1 + + major + + + + C + 3 + + 2 + 1 + half + up + + + + 2 + + minor + + + + D + 3 + + 2 + 1 + half + down + + + + + + 3 + + minor + + + + E + 3 + + 2 + 1 + half + down + + + + 4 + + major + + + + F + 3 + + 2 + 1 + half + down + + + + + + 5 + + dominant + + + + G + 3 + + 4 + 1 + whole + + + light-heavy + + + + diff --git a/src/importexport/musicxml/tests/musicxml_tests.cpp b/src/importexport/musicxml/tests/musicxml_tests.cpp index 3bff6875addfd..bcb503ae068ee 100644 --- a/src/importexport/musicxml/tests/musicxml_tests.cpp +++ b/src/importexport/musicxml/tests/musicxml_tests.cpp @@ -691,6 +691,9 @@ TEST_F(MusicXml_Tests, harmony7) { TEST_F(MusicXml_Tests, harmony8) { musicXmlIoTest("testHarmony8"); } +TEST_F(MusicXml_Tests, harmony9) { + musicXmlIoTest("testHarmony9"); +} // chordnames without chordrest TEST_F(MusicXml_Tests, hello) { musicXmlIoTest("testHello"); } @@ -950,6 +953,9 @@ TEST_F(MusicXml_Tests, notesRests2) { TEST_F(MusicXml_Tests, numberedLyrics) { musicXmlIoTestRef("testNumberedLyrics"); } +TEST_F(MusicXml_Tests, numerals) { + musicXmlIoTest("testNumerals"); +} TEST_F(MusicXml_Tests, overlappingSpanners) { musicXmlIoTest("testOverlappingSpanners"); }