From d289145e2fce18bdaf005a6d64a66a44de86ac6e Mon Sep 17 00:00:00 2001 From: Klaus Rettinghaus Date: Thu, 11 Apr 2024 12:01:04 +0200 Subject: [PATCH] MusicXML: Add support for part-name-display Backport of #22322 --- importexport/musicxml/exportxml.cpp | 58 ++- importexport/musicxml/importmxml.cpp | 29 -- importexport/musicxml/importmxmlpass1.cpp | 32 +- importexport/musicxml/musicxmlsupport.cpp | 24 + importexport/musicxml/musicxmlsupport.h | 1 + mtest/musicxml/io/testBarlineSpan.xml | 14 +- mtest/musicxml/io/testKeysig2.xml | 14 +- mtest/musicxml/io/testPartNames2.xml | 569 ++++++++++++++++++++++ mtest/musicxml/io/testTextLines_ref.xml | 14 +- mtest/musicxml/io/tst_mxml_io.cpp | 1 + 10 files changed, 704 insertions(+), 52 deletions(-) create mode 100644 mtest/musicxml/io/testPartNames2.xml diff --git a/importexport/musicxml/exportxml.cpp b/importexport/musicxml/exportxml.cpp index 48f52306dc2e3..91dc6ee17dffd 100644 --- a/importexport/musicxml/exportxml.cpp +++ b/importexport/musicxml/exportxml.cpp @@ -2614,6 +2614,31 @@ static void writeAccidental(XmlWriter& xml, const QString& tagName, const Accide } } +//--------------------------------------------------------- +// writeDisplayName +//--------------------------------------------------------- + +static void writeDisplayName(XmlWriter& xml, const QString& partName) + { + QString displayText; + for (int i = 0; i < partName.size(); ++i) { + QChar ch = partName.at(i); + if (ch != u'♭' && ch != u'♯') + displayText += ch; + else { + if (!displayText.isEmpty()) + xml.tag("display-text", displayText); + if (ch == u'♭') + xml.tag("accidental-text", "flat"); + else if (ch == u'♯') + xml.tag("accidental-text", "sharp"); + displayText.clear(); + } + } + if (!displayText.isEmpty()) + xml.tag("display-text", displayText); + } + //--------------------------------------------------------- // wavyLineStart //--------------------------------------------------------- @@ -6701,21 +6726,30 @@ static void partList(XmlWriter& xml, Score* score, MxmlInstrumentMap& instrMap) xml.stag(QString("score-part id=\"P%1\"").arg(idx+1)); initInstrMap(instrMap, part->instruments(), score); + static const QRegExp acc("[♭♯]"); + QString attributes; // by default export the parts long name as part-name - if (!part->longName().isEmpty()) - xml.tag("part-name", MScoreTextToMXML::toPlainText(part->longName())); - else { - if (!part->partName().isEmpty()) { - // use the track name if no part long name - // to prevent an empty track name on import - xml.tag("part-name print-object=\"no\"", MScoreTextToMXML::toPlainText(part->partName())); + QString partName = part->longName(); + // use the track name if no part long name + if (partName.isEmpty()) { + partName = part->partName(); + if (!partName.isEmpty()) + attributes = " print-object=\"no\""; + } + xml.tag("part-name" + attributes, MScoreTextToMXML::toPlainText(partName).replace(u'♭', 'b').replace(u'♯', '#')); + if (partName.contains(acc)) { + xml.stag("part-name-display"); + writeDisplayName(xml, partName); + xml.etag(); + } + if (!part->shortName().isEmpty()) { + xml.tag("part-abbreviation", MScoreTextToMXML::toPlainText(part->shortName()).replace(u'♭', 'b').replace(u'♯', '#')); + if (part->shortName().contains(acc)) { + xml.stag("part-abbreviation-display"); + writeDisplayName(xml, part->shortName()); + xml.etag(); } - else - // part-name is required - xml.tag("part-name", ""); } - if (!part->shortName().isEmpty()) - xml.tag("part-abbreviation", MScoreTextToMXML::toPlainText(part->shortName())); if (part->instrument()->useDrumset()) { const Drumset* drumset = part->instrument()->drumset(); diff --git a/importexport/musicxml/importmxml.cpp b/importexport/musicxml/importmxml.cpp index d7b62755efc6e..2170f7a7b34c7 100644 --- a/importexport/musicxml/importmxml.cpp +++ b/importexport/musicxml/importmxml.cpp @@ -90,34 +90,6 @@ static int musicXMLImportErrorDialog(QString text, QString detailedText) return errorDialog.exec(); } -#if 0 -static void updateNamesForAccidentals(Instrument* inst) - { - auto replace = [](QString name) { - name = name.replace(QRegExp( - R"(((?:^|\s)([A-Ga-g]|[Uu][Tt]|[Dd][Oo]|[Rr][EeÉé]|[MmSsTt][Ii]|[FfLl][Aa]|[Ss][Oo][Ll]))b(?=\s|$))"), - QString::fromStdString(R"($1♭)")); - - name = name.replace(QRegExp( - R"(((?:^|\s)([A-Ga-g]|[Uu][Tt]|[Dd][Oo]|[Rr][EeÉé]|[MmSsTt][Ii]|[FfLl][Aa]|[Ss][Oo][Ll]))#(?=\s|$))"), - QString::fromStdString(R"($1♯)")); - - return name; - }; - // change staff names from simple text (eg 'Eb') to text using accidental symbols (eg 'E♭') - - // Instrument::longNames() is const af so we need to make a deep copy, update it, and then set it again - QList longNamesCopy = inst->longNames(); - for (StaffName& sn : longNamesCopy) - sn.setName(replace(sn.name())); - QList shortNamesCopy = inst->shortNames(); - for (StaffName& sn : shortNamesCopy) - sn.setName(replace(sn.name())); - inst->setLongNames(longNamesCopy); - inst->setShortNames(shortNamesCopy); - } -#endif - //--------------------------------------------------------- // importMusicXMLfromBuffer //--------------------------------------------------------- @@ -154,7 +126,6 @@ Score::FileError importMusicXMLfromBuffer(Score* score, const QString& /*name*/, for (const Part* part : score->parts()) { for (const auto& pair : *part->instruments()) { pair.second->updateInstrumentId(); - updateNamesForAccidentals(pair.second); } } #endif diff --git a/importexport/musicxml/importmxmlpass1.cpp b/importexport/musicxml/importmxmlpass1.cpp index 5ad541e373655..8bcdebce8d988 100644 --- a/importexport/musicxml/importmxmlpass1.cpp +++ b/importexport/musicxml/importmxmlpass1.cpp @@ -2142,8 +2142,17 @@ void MusicXMLParserPass1::scorePart() _parts[id].setName(name); } else if (_e.name() == "part-name-display") { - // TODO - _e.skipCurrentElement(); // skip but don't log + QString name; + while (_e.readNextStartElement()) { + if (_e.name() == "display-text") + name += _e.readElementText(); + else if (_e.name() == "accidental-text") + name += mxmlAccidentalTextToChar(_e.readElementText()); + else + skipLogCurrElem(); + } + if (!name.isEmpty()) + _parts[id].setName(name); } else if (_e.name() == "part-abbreviation") { // Element part-name contains the displayed (abbreviated) part name @@ -2154,10 +2163,25 @@ void MusicXMLParserPass1::scorePart() QString name = _e.readElementText(); _parts[id].setAbbr(name); } - else if (_e.name() == "part-abbreviation-display") - _e.skipCurrentElement(); // skip but don't log + else if (_e.name() == "part-abbreviation-display") { + QString name; + while (_e.readNextStartElement()) { + if (_e.name() == "display-text") + name += _e.readElementText(); + else if (_e.name() == "accidental-text") + name += mxmlAccidentalTextToChar(_e.readElementText()); + else + skipLogCurrElem(); + } + if (!name.isEmpty()) + _parts[id].setAbbr(name); + } + else if (_e.name() == "group") // TODO + _e.skipCurrentElement(); // skip but don't log else if (_e.name() == "score-instrument") scoreInstrument(id); + else if (_e.name() == "player") // unsupported + _e.skipCurrentElement(); // skip but don't log else if (_e.name() == "midi-device") { if (!_e.attributes().hasAttribute("port")) { _e.readElementText(); // empty string diff --git a/importexport/musicxml/musicxmlsupport.cpp b/importexport/musicxml/musicxmlsupport.cpp index fb65a27f45baa..e18501b1252e3 100644 --- a/importexport/musicxml/musicxmlsupport.cpp +++ b/importexport/musicxml/musicxmlsupport.cpp @@ -722,6 +722,30 @@ AccidentalType mxmlString2accidentalType(const QString mxmlName, const QString s return AccidentalType::NONE; } +//--------------------------------------------------------- +// mxmlAccidentalTextToChar +//--------------------------------------------------------- + +/** + Convert a MusicXML accidental text to a accidental character. + */ + +QString mxmlAccidentalTextToChar(const QString mxmlName) + { + static QMap map; // map MusicXML accidental name to MuseScore enum AccidentalType + if (map.empty()) { + map["sharp"] = "♯"; + map["natural"] = "♮"; + map["flat"] = "♭"; + } + + if (map.contains(mxmlName)) + return map.value(mxmlName); + else + qDebug("mxmlAccidentalTextToChar: unsupported accidental '%s'", qPrintable(mxmlName)); + return ""; + } + //--------------------------------------------------------- // isAppr //--------------------------------------------------------- diff --git a/importexport/musicxml/musicxmlsupport.h b/importexport/musicxml/musicxmlsupport.h index a02e6799fba22..d62f77e3cf0cd 100644 --- a/importexport/musicxml/musicxmlsupport.h +++ b/importexport/musicxml/musicxmlsupport.h @@ -211,6 +211,7 @@ extern QString accSymId2SmuflMxmlString(const SymId id); extern QString accidentalType2MxmlString(const AccidentalType type); extern QString accidentalType2SmuflMxmlString(const AccidentalType type); extern AccidentalType mxmlString2accidentalType(const QString mxmlName, const QString smufl); +extern QString mxmlAccidentalTextToChar(const QString mxmlName); extern SymId mxmlString2accSymId(const QString mxmlName, const QString smufl = QString()); extern AccidentalType microtonalGuess(double val); extern bool isLaissezVibrer(const SymId id); diff --git a/mtest/musicxml/io/testBarlineSpan.xml b/mtest/musicxml/io/testBarlineSpan.xml index c89e41865bfd2..737e6b717c450 100644 --- a/mtest/musicxml/io/testBarlineSpan.xml +++ b/mtest/musicxml/io/testBarlineSpan.xml @@ -64,9 +64,17 @@ - Clarinet in B♭ - Cl. in B♭ - + Clarinet in Bb + + Clarinet in B + flat + + Cl. in Bb + + Cl. in B + flat + + Clarinet diff --git a/mtest/musicxml/io/testKeysig2.xml b/mtest/musicxml/io/testKeysig2.xml index 3d1e36e7d1adc..208a25fa40f39 100644 --- a/mtest/musicxml/io/testKeysig2.xml +++ b/mtest/musicxml/io/testKeysig2.xml @@ -19,8 +19,18 @@ - B♭ Clarinet - B♭ Cl. + Bb Clarinet + + B + flat + Clarinet + + Bb Cl. + + B + flat + Cl. + B♭ Clarinet diff --git a/mtest/musicxml/io/testPartNames2.xml b/mtest/musicxml/io/testPartNames2.xml new file mode 100644 index 0000000000000..7154e9b2cd934 --- /dev/null +++ b/mtest/musicxml/io/testPartNames2.xml @@ -0,0 +1,569 @@ + + + + + Part names + + + + MuseScore 0.7.0 + 2007-09-10 + + + + + + + + + + bracket + yes + + + Eb Bass + + E + flat + Bass + + Eb Bs. + + E + flat + Bs. + + + E♭ Tuba (Treble Clef) + + + + 6 + 59 + 78.7402 + 0 + + + + Bb Bass + + B + flat + Bass + + Bb Bs. + + B + flat + Bs. + + + B♭ Tuba (Treble Clef) + + + + 7 + 59 + 78.7402 + 0 + + + + + + + + 1 + + 3 + + + + G + 2 + + + -5 + -9 + -1 + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + light-heavy + + + + + + + 1 + + 2 + + + + G + 2 + + + -1 + -2 + -2 + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + + + + 4 + 1 + + + light-heavy + + + + diff --git a/mtest/musicxml/io/testTextLines_ref.xml b/mtest/musicxml/io/testTextLines_ref.xml index 8b400f9bd06fa..9e7363f453f05 100644 --- a/mtest/musicxml/io/testTextLines_ref.xml +++ b/mtest/musicxml/io/testTextLines_ref.xml @@ -28,8 +28,18 @@ - B♭ Clarinet - B♭ Cl. + Bb Clarinet + + B + flat + Clarinet + + Bb Cl. + + B + flat + Cl. + B♭ Clarinet diff --git a/mtest/musicxml/io/tst_mxml_io.cpp b/mtest/musicxml/io/tst_mxml_io.cpp index baac77a6af2de..751e11970ebe4 100644 --- a/mtest/musicxml/io/tst_mxml_io.cpp +++ b/mtest/musicxml/io/tst_mxml_io.cpp @@ -232,6 +232,7 @@ private slots: void numberedLyrics() { mxmlIoTestRef("testNumberedLyrics"); } void overlappingSpanners() { mxmlIoTest("testOverlappingSpanners"); } void partNames() { mxmlImportTestRef("testPartNames"); } + void partNames2() { mxmlIoTest("testPartNames2"); } void pedalChanges() { mxmlIoTest("testPedalChanges"); } void pedalChangesBroken() { mxmlImportTestRef("testPedalChangesBroken"); } void pedalStyles() { mxmlIoTest("testPedalStyles"); }