diff --git a/importexport/musicxml/exportxml.cpp b/importexport/musicxml/exportxml.cpp
index e709d477e5327..3fd31f13703f0 100644
--- a/importexport/musicxml/exportxml.cpp
+++ b/importexport/musicxml/exportxml.cpp
@@ -3097,6 +3097,12 @@ static QString symIdToTechn(const SymId sid)
case SymId::brassHarmonMuteStemOpen:
return "harmon-mute";
break;
+ case SymId::windClosedHole:
+ case SymId::windHalfClosedHole1:
+ case SymId::windHalfClosedHole2:
+ case SymId::windHalfClosedHole3:
+ case SymId::windOpenHole:
+ return "hole";
case SymId::guitarGolpe:
return "golpe";
break;
@@ -3352,6 +3358,25 @@ void ExportMusicXml::chordAttributes(Chord* chord, Notations& notations, Technic
_xml.tag("harmon-closed" + location, harmonClosedValue);
_xml.etag();
}
+ else if (mxmlTechn.startsWith(u"hole")) {
+ _xml.stag(mxmlTechn);
+ QString location = {};
+ QString holeClosedValue;
+ switch (sid) {
+ case SymId::windClosedHole:
+ holeClosedValue = "yes";
+ break;
+ case SymId::windOpenHole:
+ holeClosedValue = "no";
+ break;
+ default:
+ holeClosedValue = "half";
+ location = QString(" location=\"%1\"").arg(sid == SymId::windHalfClosedHole1 ? "right" : "bottom");
+ break;
+ }
+ _xml.tag("hole-closed" + location, holeClosedValue);
+ _xml.etag();
+ }
else
_xml.tagE(mxmlTechn);
}
diff --git a/importexport/musicxml/importmxmlpass2.cpp b/importexport/musicxml/importmxmlpass2.cpp
index 287611c09bc8c..e737375769372 100644
--- a/importexport/musicxml/importmxmlpass2.cpp
+++ b/importexport/musicxml/importmxmlpass2.cpp
@@ -1277,7 +1277,7 @@ static bool convertArticulationToSymId(const QString& mxmlName, SymId& id)
map["stopped"] = SymId::brassMuteClosed;
map["snap-pizzicato"] = SymId::pluckedSnapPizzicatoAbove;
map["heal"] = SymId::keyboardPedalHeel1 ;
- map["toe"] = SymId::keyboardPedalToe1 ;
+ map["toe"] = SymId::keyboardPedalToe2 ;
map["fingernails"] = SymId::pluckedWithFingernails ;
map["brass-bend"] = SymId::brassBend ;
map["flip"] = SymId::brassFlip;
@@ -7600,6 +7600,17 @@ void MusicXMLParserNotations::articulations()
_notations.push_back(artic);
_e.skipCurrentElement(); // skip but don't log
}
+ else if (_e.name() == "other-articulation") {
+ const QString smufl = _e.attributes().value("smufl").toString();
+
+ if (!smufl.isEmpty()) {
+ SymId id = SymId::noSym;
+ Notation artic = Notation::notationWithAttributes(_e.name().toString(),
+ _e.attributes(), "articulations", id);
+ _notations.push_back(artic);
+ }
+ _e.skipCurrentElement(); // skip but don't log
+ }
else {
skipLogCurrElem();
}
@@ -7619,7 +7630,7 @@ void MusicXMLParserNotations::ornaments()
bool trillMark = false;
//
while (_e.readNextStartElement()) {
- SymId id { SymId::noSym };
+ SymId id = SymId::noSym;
if (convertArticulationToSymId(_e.name().toString(), id)) {
Notation notation = Notation::notationWithAttributes(_e.name().toString(),
_e.attributes(), "articulations", id);
@@ -7687,7 +7698,7 @@ void MusicXMLParserNotations::ornaments()
void MusicXMLParserNotations::technical()
{
while (_e.readNextStartElement()) {
- SymId id { SymId::noSym };
+ SymId id = SymId::noSym;
if (convertArticulationToSymId(_e.name().toString(), id)) {
Notation notation = Notation::notationWithAttributes(_e.name().toString(),
_e.attributes(), "technical", id);
@@ -7704,6 +7715,8 @@ void MusicXMLParserNotations::technical()
harmonic();
else if (_e.name() == "harmon-mute")
harmonMute();
+ else if (_e.name() == "hole")
+ hole();
else if (_e.name() == "other-technical")
otherTechnical();
else
@@ -7714,7 +7727,18 @@ void MusicXMLParserNotations::technical()
void MusicXMLParserNotations::otherTechnical()
{
- QString text = _e.readElementText();
+ const QString smufl = _e.attributes().value("smufl").toString();
+
+ if (!smufl.isEmpty()) {
+ SymId id = SymId::noSym;
+ Notation notation = Notation::notationWithAttributes(_e.name().toString(),
+ _e.attributes(), "technical", id);
+ _notations.push_back(notation);
+ _e.skipCurrentElement();
+ return;
+ }
+
+ const QString text = _e.readElementText();
if (text == "z") {
// Buzz roll
@@ -7789,6 +7813,42 @@ void MusicXMLParserNotations::harmonMute()
_notations.push_back(Notation::notationWithAttributes("harmon-closed", _e.attributes(), "technical", mute));
}
+//---------------------------------------------------------
+// hole
+//---------------------------------------------------------
+
+/**
+ Parse the /score-partwise/part/measure/note/notations/technical/hole node.
+ */
+
+void MusicXMLParserNotations::hole()
+ {
+ SymId hole = SymId::noSym;
+ while (_e.readNextStartElement()) {
+ if (_e.name() == "hole-closed") {
+ const QString location = _e.attributes().value("location").toString();
+ const QString value = _e.readElementText();
+ if (value == "yes")
+ hole = SymId::windClosedHole;
+ else if (value == "no")
+ hole = SymId::windOpenHole;
+ else if (value == "half") {
+ if (location == "bottom")
+ hole = SymId::windHalfClosedHole2;
+ else if (location == "right")
+ hole = SymId::windHalfClosedHole1;
+ else {
+ _logger->logError(QString("unsupported hole-closed location '%1'").arg(location), &_e);
+ hole = SymId::windHalfClosedHole3;
+ }
+ }
+ }
+ else
+ _e.skipCurrentElement();
+ }
+ _notations.push_back(Notation::notationWithAttributes("hole-closed", _e.attributes(), "technical", hole));
+ }
+
//---------------------------------------------------------
// addTechnical
//---------------------------------------------------------
diff --git a/importexport/musicxml/importmxmlpass2.h b/importexport/musicxml/importmxmlpass2.h
index b323e5ebe7b16..fbf4b1c073ff5 100644
--- a/importexport/musicxml/importmxmlpass2.h
+++ b/importexport/musicxml/importmxmlpass2.h
@@ -243,6 +243,7 @@ class MusicXMLParserNotations {
void addTechnical(const Notation& notation, Note* note);
void harmonic();
void harmonMute();
+ void hole();
void articulations();
void dynamics();
void fermata();
diff --git a/mtest/musicxml/io/testHoles.xml b/mtest/musicxml/io/testHoles.xml
new file mode 100644
index 0000000000000..92459968952c9
--- /dev/null
+++ b/mtest/musicxml/io/testHoles.xml
@@ -0,0 +1,120 @@
+
+
+
+
+ Holes test
+
+
+ Klaus Rettinghaus
+
+ MuseScore 0.7.0
+ 2007-09-10
+
+
+
+
+
+
+
+
+
+ Flute
+ Fl.
+
+ Flute
+
+
+
+ 1
+ 58
+ 78.7402
+ 0
+
+
+
+
+
+
+ 1
+
+ 0
+
+
+ G
+ 2
+
+
+
+
+ C
+ 5
+
+ 1
+ 1
+ quarter
+ down
+
+
+
+ no
+
+
+
+
+
+
+ C
+ 5
+
+ 1
+ 1
+ quarter
+ down
+
+
+
+ yes
+
+
+
+
+
+
+ C
+ 5
+
+ 1
+ 1
+ quarter
+ down
+
+
+
+ half
+
+
+
+
+
+
+ C
+ 5
+
+ 1
+ 1
+ quarter
+ down
+
+
+
+ half
+
+
+
+
+
+ light-heavy
+
+
+
+
diff --git a/mtest/musicxml/io/tst_mxml_io.cpp b/mtest/musicxml/io/tst_mxml_io.cpp
index a5de697cdc573..686f3ac98a550 100644
--- a/mtest/musicxml/io/tst_mxml_io.cpp
+++ b/mtest/musicxml/io/tst_mxml_io.cpp
@@ -164,6 +164,7 @@ private slots:
void hello() { mxmlIoTest("testHello"); }
void helloReadCompr() { mxmlReadTestCompr("testHello"); }
void helloReadWriteCompr() { mxmlReadWriteTestCompr("testHello"); }
+ void holes() { mxmlIoTest("testHoles"); }
void implicitMeasure1() { mxmlIoTest("testImplicitMeasure1"); }
void incompleteTuplet() { mxmlIoTestRef("testIncompleteTuplet"); }
void incorrectStaffNumber1() { mxmlIoTestRef("testIncorrectStaffNumber1"); }