diff --git a/src/importexport/musicxml/internal/import/importmusicxmlpass2.cpp b/src/importexport/musicxml/internal/import/importmusicxmlpass2.cpp index 6a29b5685df66..7e300891297b0 100644 --- a/src/importexport/musicxml/internal/import/importmusicxmlpass2.cpp +++ b/src/importexport/musicxml/internal/import/importmusicxmlpass2.cpp @@ -65,6 +65,7 @@ #include "engraving/dom/measure.h" #include "engraving/dom/mscore.h" #include "engraving/dom/note.h" +#include "engraving/dom/ornament.h" #include "engraving/dom/ottava.h" #include "engraving/dom/part.h" #include "engraving/dom/pedal.h" @@ -118,6 +119,53 @@ static std::shared_ptr configuration( return muse::modularity::globalIoc()->resolve("iex_musicxml"); } +static void setOrnamentIntervalFromAccidental(Ornament* ornam, Note* mainNote, AccidentalType accType, bool above) +{ + if (!mainNote) { + return; + } + + // Desired alter value + AccidentalVal desiredAlter = Accidental::subtype2value(accType); + + // Try different interval types + // MAJOR, MINOR, PERFECT, AUGMENTED, DIMINISHED + static const std::vector types = { + IntervalType::MAJOR, IntervalType::MINOR, + IntervalType::AUGMENTED, IntervalType::DIMINISHED, + IntervalType::PERFECT + }; + + for (IntervalType it : types) { + OrnamentInterval oi; + oi.type = it; + oi.step = IntervalStep::SECOND; + + Interval interval = Interval::fromOrnamentInterval(oi); + if (!above) { + interval.flip(); + } + + int tpc = mainNote->tpc(); + int transposedTpc = Transpose::transposeTpc(tpc, interval, true); + if (tpcIsValid(transposedTpc)) { + if (tpc2alter(transposedTpc) == desiredAlter) { + if (above) { + ornam->setIntervalAbove(oi); + } else { + ornam->setIntervalBelow(oi); + } + ornam->setShowAccidental(OrnamentShowAccidental::ALWAYS); + return; + } + } + } + + // Fallback: if no match found with SECOND step, try UNISON for unusual accidentals? + // Or just force the accidental visibility anyway. + ornam->setShowAccidental(OrnamentShowAccidental::ALWAYS); +} + static std::shared_ptr engravingFonts() { return muse::modularity::globalIoc()->resolve("iex_musicxml"); @@ -1360,6 +1408,25 @@ static void addMordentToChord(const Notation& notation, ChordRest* cr) mordent->setVisible(notation.visible()); colorItem(mordent, Color::fromString(notation.attribute(u"color"))); cr->add(mordent); + + const String accidAbove = notation.attribute(u"above"); + if (!accidAbove.empty()) { + const AccidentalType type = musicXmlString2accidentalType(accidAbove, notation.attribute(u"above-smufl")); + if (type != AccidentalType::NONE) { + Chord* chord = cr->isChord() ? toChord(cr) : nullptr; + Note* mainNote = chord ? chord->upNote() : nullptr; + setOrnamentIntervalFromAccidental(mordent, mainNote, type, true); + } + } + const String accidBelow = notation.attribute(u"below"); + if (!accidBelow.empty()) { + const AccidentalType type = musicXmlString2accidentalType(accidBelow, notation.attribute(u"below-smufl")); + if (type != AccidentalType::NONE) { + Chord* chord = cr->isChord() ? toChord(cr) : nullptr; + Note* mainNote = chord ? chord->upNote() : nullptr; + setOrnamentIntervalFromAccidental(mordent, mainNote, type, false); + } + } } else { LOGD("unknown ornament: name '%s' long '%s' approach '%s' departure '%s'", muPrintable(name), muPrintable(attrLong), muPrintable(attrAppr), muPrintable(attrDep)); // TODO @@ -1377,6 +1444,9 @@ static void addMordentToChord(const Notation& notation, ChordRest* cr) static void addTurnToChord(const Notation& notation, ChordRest* cr) { SymId turnSym = notation.symId(); + const String accidAbove = notation.attribute(u"above"); + const String accidBelow = notation.attribute(u"below"); + const Color color = Color::fromString(notation.attribute(u"color")); const String place = notation.attribute(u"placement"); if (notation.attribute(u"slash") == "yes") { // TODO: currently this is the only available SMuFL turn with a slash @@ -1394,6 +1464,23 @@ static void addTurnToChord(const Notation& notation, ChordRest* cr) turn->setVisible(notation.visible()); colorItem(turn, Color::fromString(notation.attribute(u"color"))); cr->add(turn); + + if (!accidAbove.empty()) { + const AccidentalType type = musicXmlString2accidentalType(accidAbove, notation.attribute(u"above-smufl")); + if (type != AccidentalType::NONE) { + Chord* chord = cr->isChord() ? toChord(cr) : nullptr; + Note* mainNote = chord ? chord->upNote() : nullptr; + setOrnamentIntervalFromAccidental(turn, mainNote, type, true); + } + } + if (!accidBelow.empty()) { + const AccidentalType type = musicXmlString2accidentalType(accidBelow, notation.attribute(u"below-smufl")); + if (type != AccidentalType::NONE) { + Chord* chord = cr->isChord() ? toChord(cr) : nullptr; + Note* mainNote = chord ? chord->upNote() : nullptr; + setOrnamentIntervalFromAccidental(turn, mainNote, type, false); + } + } } //--------------------------------------------------------- @@ -1417,6 +1504,25 @@ static void addOtherOrnamentToChord(const Notation& notation, ChordRest* cr) ornam->setVisible(notation.visible()); colorItem(ornam, Color::fromString(notation.attribute(u"color"))); cr->add(ornam); + + const String accidAbove = notation.attribute(u"above"); + if (!accidAbove.empty()) { + const AccidentalType type = musicXmlString2accidentalType(accidAbove, notation.attribute(u"above-smufl")); + if (type != AccidentalType::NONE) { + Chord* chord = cr->isChord() ? toChord(cr) : nullptr; + Note* mainNote = chord ? chord->upNote() : nullptr; + setOrnamentIntervalFromAccidental(ornam, mainNote, type, true); + } + } + const String accidBelow = notation.attribute(u"below"); + if (!accidBelow.empty()) { + const AccidentalType type = musicXmlString2accidentalType(accidBelow, notation.attribute(u"below-smufl")); + if (type != AccidentalType::NONE) { + Chord* chord = cr->isChord() ? toChord(cr) : nullptr; + Note* mainNote = chord ? chord->upNote() : nullptr; + setOrnamentIntervalFromAccidental(ornam, mainNote, type, false); + } + } } else { LOGD("unknown ornament: name '%s': '%s'.", muPrintable(name), muPrintable(symname)); } @@ -8552,6 +8658,8 @@ void MusicXmlParserNotations::articulations() void MusicXmlParserNotations::ornaments() { bool trillMark = false; + String trillAccidAbove, trillAccidBelow, trillAccidAboveSmufl, trillAccidBelowSmufl; + // while (m_e.readNextStartElement()) { SymId id { SymId::noSym }; @@ -8563,6 +8671,9 @@ void MusicXmlParserNotations::ornaments() m_e.skipCurrentElement(); // skip but don't log } else if (m_e.name() == "trill-mark") { trillMark = true; + Notation notation = Notation::notationWithAttributes(u"trill-mark", m_e.attributes(), u"ornaments", SymId::ornamentTrill); + notation.setVisible(m_visible); + m_notations.push_back(notation); m_e.skipCurrentElement(); // skip but don't log } else if (m_e.name() == "wavy-line") { bool wavyLineTypeWasStart = (m_wavyLineType == "start"); @@ -8581,6 +8692,13 @@ void MusicXmlParserNotations::ornaments() if (wavyLineTypeWasStart && m_wavyLineType == u"stop") { m_wavyLineType = u"startstop"; } + // If it is a start, it might be associated with a preceding trill-mark's accidentals + if (m_wavyLineType == u"start" || m_wavyLineType == u"startstop") { + m_wavyLineAccidAbove = trillAccidAbove; + m_wavyLineAccidBelow = trillAccidBelow; + m_wavyLineAccidAboveSmufl = trillAccidAboveSmufl; + m_wavyLineAccidBelowSmufl = trillAccidBelowSmufl; + } m_e.skipCurrentElement(); // skip but don't log } else if (m_e.name() == "tremolo") { m_hasTremolo = true; @@ -8597,17 +8715,59 @@ void MusicXmlParserNotations::ornaments() notation.setVisible(m_visible); m_notations.push_back(notation); m_e.skipCurrentElement(); // skip but don't log + } else if (m_e.name() == "accidental-mark") { + if (!m_notations.empty()) { + Notation& lastNotation = m_notations.back(); + String placement = m_e.attribute("placement"); + String smufl = m_e.attribute("smufl"); + if (lastNotation.parent() == u"ornaments") { + if (placement.empty()) { + if (lastNotation.name() == u"turn" || lastNotation.name() == u"inverted-turn") { + if (lastNotation.attribute(u"above").empty()) { + placement = u"above"; + } else { + placement = u"below"; + } + } else { + placement = (lastNotation.name() == u"mordent" ? u"below" : u"above"); + } + } + String text = m_e.readText(); + lastNotation.addAttribute(placement, text); + if (!smufl.empty()) { + lastNotation.addAttribute(placement + u"-smufl", smufl); + } + + // Also store if it's a trill-mark, for possible wavy-line association + if (lastNotation.name() == u"trill-mark") { + if (placement == u"above") { + trillAccidAbove = text; + trillAccidAboveSmufl = smufl; + } else { + trillAccidBelow = text; + trillAccidBelowSmufl = smufl; + } + } + } else { + m_e.skipCurrentElement(); + } + } else { + m_e.skipCurrentElement(); // skip but don't log + } } else { skipLogCurrElem(); } } // note that mscore wavy line already implicitly includes a trillsym - // so don't add an additional one - if (trillMark && m_wavyLineType != "start" && m_wavyLineType != "startstop") { - Notation ornament = Notation::notationWithAttributes(u"trill-mark", m_e.attributes(), u"ornaments", SymId::ornamentTrill); - ornament.setVisible(m_visible); - m_notations.push_back(ornament); + // so remove it if it was added + if (trillMark && (m_wavyLineType == "start" || m_wavyLineType == "startstop")) { + for (auto it = m_notations.begin(); it != m_notations.end(); ++it) { + if (it->name() == u"trill-mark") { + m_notations.erase(it); + break; + } + } } } @@ -9150,7 +9310,9 @@ static void addTie(const Notation& notation, Note* note, const track_idx_t track static void addWavyLine(ChordRest* cr, const Fraction& tick, const int wavyLineNo, const String& wavyLineType, MusicXmlSpannerMap& spanners, TrillStack& trills, - MusicXmlLogger* logger, const XmlStreamReader* const xmlreader) + MusicXmlLogger* logger, const XmlStreamReader* const xmlreader, + const String& accidAbove = {}, const String& accidBelow = {}, + const String& accidAboveSmufl = {}, const String& accidBelowSmufl = {}) { if (!wavyLineType.empty()) { const Fraction ticks = cr->ticks(); @@ -9167,6 +9329,22 @@ static void addWavyLine(ChordRest* cr, const Fraction& tick, trill->setOrnament(Factory::createOrnament(cr)); trill->ornament()->setAnchor(ArticulationAnchor::AUTO); + Chord* chord = cr->isChord() ? toChord(cr) : nullptr; + Note* mainNote = chord ? chord->upNote() : nullptr; + + if (!accidAbove.empty()) { + const AccidentalType type = musicXmlString2accidentalType(accidAbove, accidAboveSmufl); + if (type != AccidentalType::NONE) { + setOrnamentIntervalFromAccidental(trill->ornament(), mainNote, type, true); + } + } + if (!accidBelow.empty()) { + const AccidentalType type = musicXmlString2accidentalType(accidBelow, accidBelowSmufl); + if (type != AccidentalType::NONE) { + setOrnamentIntervalFromAccidental(trill->ornament(), mainNote, type, false); + } + } + if (wavyLineType == u"start") { spanners[trill] = std::pair(tick.ticks(), -1); // LOGD("trill=%p inserted at first tick %d", trill, tick); @@ -9449,7 +9627,8 @@ void MusicXmlParserNotations::addToScore(ChordRest* const cr, Note* const note, DelayedArpMap& delayedArps) { addArpeggio(cr, m_arpeggioType, m_arpeggioNo, m_arpeggioColor, arpMap, delayedArps); - addWavyLine(cr, tick, m_wavyLineNo, m_wavyLineType, spanners, trills, m_logger, &m_e); + addWavyLine(cr, tick, m_wavyLineNo, m_wavyLineType, spanners, trills, m_logger, &m_e, + m_wavyLineAccidAbove, m_wavyLineAccidBelow, m_wavyLineAccidAboveSmufl, m_wavyLineAccidBelowSmufl); for (const Notation& notation : m_notations) { if (notation.symId() != SymId::noSym) { diff --git a/src/importexport/musicxml/internal/import/importmusicxmlpass2.h b/src/importexport/musicxml/internal/import/importmusicxmlpass2.h index 66d2e6df3e895..18d8aa1c549df 100644 --- a/src/importexport/musicxml/internal/import/importmusicxmlpass2.h +++ b/src/importexport/musicxml/internal/import/importmusicxmlpass2.h @@ -394,6 +394,10 @@ class MusicXmlParserNotations engraving::Color m_tremoloColor; muse::String m_wavyLineType; int m_wavyLineNo = 0; + muse::String m_wavyLineAccidAbove; + muse::String m_wavyLineAccidBelow; + muse::String m_wavyLineAccidAboveSmufl; + muse::String m_wavyLineAccidBelowSmufl; muse::String m_arpeggioType; int m_arpeggioNo = 0; engraving::Color m_arpeggioColor; diff --git a/src/importexport/musicxml/internal/shared/musicxmlsupport.h b/src/importexport/musicxml/internal/shared/musicxmlsupport.h index 02cea2212cf09..bae850a0f310c 100644 --- a/src/importexport/musicxml/internal/shared/musicxmlsupport.h +++ b/src/importexport/musicxml/internal/shared/musicxmlsupport.h @@ -56,7 +56,7 @@ extern muse::String accSymId2MusicXmlString(const engraving::SymId id); extern muse::String accSymId2SmuflMusicXmlString(const engraving::SymId id); extern muse::String accidentalType2MusicXmlString(const engraving::AccidentalType type); extern muse::String accidentalType2SmuflMusicXmlString(const engraving::AccidentalType type); -extern engraving::AccidentalType musicXmlString2accidentalType(const muse::String mxmlName, const muse::String smufl); +extern engraving::AccidentalType musicXmlString2accidentalType(const muse::String mxmlName, const muse::String smufl = {}); extern muse::String musicXmlAccidentalTextToChar(const muse::String mxmlName); extern engraving::SymId musicXmlString2accSymId(const muse::String mxmlName, const muse::String smufl = {}); extern engraving::AccidentalType microtonalGuess(double val); diff --git a/src/importexport/musicxml/tests/data/testOrnamentAccids.xml b/src/importexport/musicxml/tests/data/testOrnamentAccids.xml new file mode 100644 index 0000000000000..b95eb1979c326 --- /dev/null +++ b/src/importexport/musicxml/tests/data/testOrnamentAccids.xml @@ -0,0 +1,178 @@ + + + + + Ornaments with accidentals + + + Klaus Rettinghaus + + MuseScore 0.7.0 + 2007-09-10 + + + + + + + + + + Flute + Fl. + + Flute + + + + 1 + 74 + 78.7402 + 0 + + + + + + + 1 + + 0 + + + + G + 2 + + + + + B + 4 + + 4 + 1 + whole + + + + sharp + sharp + + + + + + + + B + 4 + + 4 + 1 + whole + + + + sharp + sharp + + + + + + + + B + 4 + + 4 + 1 + whole + + + + sharp + sharp + + + + + + + + B + 4 + + 4 + 1 + whole + + + + sharp + + + + + + + + B + 4 + + 4 + 1 + whole + + + + sharp + + + + + + + + B + 4 + + 4 + 1 + whole + + + + sharp + sharp + + + + + + + + B + 4 + + 4 + 1 + whole + + + + sharp + sharp + + + + + light-heavy + + + + diff --git a/src/importexport/musicxml/tests/musicxml_tests.cpp b/src/importexport/musicxml/tests/musicxml_tests.cpp index cc0a0e7f7b8e3..d120c9fa35546 100644 --- a/src/importexport/musicxml/tests/musicxml_tests.cpp +++ b/src/importexport/musicxml/tests/musicxml_tests.cpp @@ -1048,6 +1048,9 @@ TEST_F(MusicXml_Tests, numerals) { TEST_F(MusicXml_Tests, ornaments) { musicXmlIoTest("testOrnaments"); } +TEST_F(MusicXml_Tests, ornamentAccids) { + musicXmlIoTest("testOrnamentAccids"); +} TEST_F(MusicXml_Tests, overlappingSpanners) { musicXmlIoTest("testOverlappingSpanners"); }