From fac7767390fa3d3a724d9ee8650ef86774925703 Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Sat, 24 Aug 2024 20:04:24 +0200 Subject: [PATCH] MusicXML: add support for numerals Backport of #24174 --- importexport/musicxml/exportxml.cpp | 54 +++++++++++++++++++---- importexport/musicxml/importmxmlpass2.cpp | 46 ++++++++++++++++--- mtest/musicxml/io/testHarmony6_ref.xml | 28 +++++++----- 3 files changed, 105 insertions(+), 23 deletions(-) diff --git a/importexport/musicxml/exportxml.cpp b/importexport/musicxml/exportxml.cpp index cf3a88c30535b..9054604b4dbc5 100644 --- a/importexport/musicxml/exportxml.cpp +++ b/importexport/musicxml/exportxml.cpp @@ -7819,18 +7819,56 @@ void ExportMusicXml::harmony(Harmony const* const h, FretDiagram const* const fd const QString textNameEscaped = h->hTextName().toHtmlEscaped(); switch (h->harmonyType()) { case HarmonyType::NASHVILLE: { - _xml.tag("function", h->hFunction()); - QString k = "kind text=\"" + textNameEscaped + "\""; - _xml.tag(k, "none"); + QString alter; + QString functionText = h->hFunction(); + if (functionText.isEmpty()) { + // we just dump the text as deprecated function + _xml.tag("function", h->musicXmlText()); + _xml.tag("kind", "none"); + break; + } + else if (!functionText.at(0).isDigit()) { + alter = functionText.at(0); + functionText = functionText.at(1); + } + _xml.stag("numeral"); + _xml.tag("numeral-root", functionText); + if (alter == "b") + _xml.tag("numeral-alter", "-1"); + else if (alter == "#") + _xml.tag("numeral-alter", "1"); + _xml.etag(); + if (!h->xmlKind().isEmpty()) { + QString s = "kind"; + QString kindText = h->musicXmlText(); + if (h->musicXmlText() != "") + s += " text=\"" + kindText + "\""; + if (h->xmlSymbols() == "yes") + s += " use-symbols=\"yes\""; + if (h->xmlParens() == "yes") + s += " parentheses-degrees=\"yes\""; + _xml.tag(s, h->xmlKind()); + } + else { + // default is major + _xml.tag("kind", "major"); + } } break; case HarmonyType::ROMAN: { - // TODO: parse? - _xml.tag("function", h->hTextName()); // note: HTML escape done by tag() - QString k = "kind text=\"\""; - _xml.tag(k, "none"); + static QRegularExpression romanRegex("[iv]+", QRegularExpression::CaseInsensitiveOption); + QRegularExpressionMatch romanMatch = romanRegex.match(h->hTextName()); + if (romanMatch.capturedTexts().size()) { + _xml.stag("numeral"); + QString k = "numeral-root text =\"" + h->hTextName() + "\""; + _xml.tag(k, "1"); + _xml.etag(); + // only check for major or minor + _xml.tag("kind", h->hTextName().at(0).isUpper() ? "major" : "minor"); + break; + } } - break; + // fallthrough case HarmonyType::STANDARD: default: { _xml.stag("root"); diff --git a/importexport/musicxml/importmxmlpass2.cpp b/importexport/musicxml/importmxmlpass2.cpp index d9742f9d26ac8..4068150c71e97 100644 --- a/importexport/musicxml/importmxmlpass2.cpp +++ b/importexport/musicxml/importmxmlpass2.cpp @@ -6893,7 +6893,7 @@ void MusicXMLParserPass2::harmony(const QString& partId, Measure* measure, const //QString printStyle = _e.attributes().value("print-style").toString(); const QColor color { _e.attributes().value("color").toString() }; - QString kind, kindText, functionText, symbols, parens; + QString kind, kindText, functionText, inversionText, symbols, parens; QList degreeList; FretDiagram* fd = nullptr; @@ -6943,7 +6943,34 @@ void MusicXMLParserPass2::harmony(const QString& partId, Measure* measure, const ha->setHarmonyType(HarmonyType::ROMAN); } //else if (_e.name() == "function") { // MusicXML 4.0 replacement for "function" - // TODO: parse to decide between ROMAN and NASHVILLE + else if (_e.name() == "numeral") { + ha->setRootTpc(Tpc::TPC_INVALID); + ha->setBaseTpc(Tpc::TPC_INVALID); + while (_e.readNextStartElement()) { + if (_e.name() == "numeral-root") { + QString numeralRoot = _e.readElementText(); + //QString numeralRootText = _e.attributes().value("text").toString(); + // TODO analyze text and import as roman numerals + ha->setHarmonyType(HarmonyType::NASHVILLE); + ha->setFunction(numeralRoot); + } + else if (_e.name() == "numeral-alter") { + const int alter = _e.readElementText().toInt(); + switch (alter) { + case -1: + ha->setFunction("b" + ha->hFunction()); + break; + case 1: + ha->setFunction("#" + ha->hFunction()); + break; + default: + break; + } + } + else + skipLogCurrElem(); + } + } else if (_e.name() == "kind") { // attributes: use-symbols yes-no // text, stack-degrees, parentheses-degree, bracket-degrees, @@ -6957,7 +6984,16 @@ void MusicXMLParserPass2::harmony(const QString& partId, Measure* measure, const } } else if (_e.name() == "inversion") { - skipLogCurrElem(); + const int inversion = _e.readElementText().toInt(); + switch (inversion) { + case 1: inversionText = "6"; + break; + case 2: inversionText = "64"; + break; + default: + inversionText = ""; + break; + } } else if (_e.name() == "bass") { QString step; @@ -7030,7 +7066,7 @@ void MusicXMLParserPass2::harmony(const QString& partId, Measure* measure, const } const ChordDescription* d = nullptr; - if (ha->rootTpc() != Tpc::TPC_INVALID) + if (ha->rootTpc() != Tpc::TPC_INVALID || !ha->hFunction().isEmpty()) d = ha->fromXml(kind, kindText, symbols, parens, degreeList); if (d) { ha->setId(d->id); @@ -7038,7 +7074,7 @@ void MusicXMLParserPass2::harmony(const QString& partId, Measure* measure, const } else { ha->setId(-1); - QString textName = functionText + kindText; + QString textName = functionText + kindText + inversionText; ha->setTextName(textName); } ha->render(); diff --git a/mtest/musicxml/io/testHarmony6_ref.xml b/mtest/musicxml/io/testHarmony6_ref.xml index aaa30548bf54f..2d6f62123f3a0 100644 --- a/mtest/musicxml/io/testHarmony6_ref.xml +++ b/mtest/musicxml/io/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 @@ -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