LCOV - code coverage report
Current view: top level - src/importexport/mei/internal - meiimporter.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 81.5 % 1880 1533
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-Studio-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2021 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "meiimporter.h"
      24              : 
      25              : #include "engraving/dom/arpeggio.h"
      26              : #include "engraving/dom/articulation.h"
      27              : #include "engraving/dom/barline.h"
      28              : #include "engraving/dom/box.h"
      29              : #include "engraving/dom/bracket.h"
      30              : #include "engraving/dom/breath.h"
      31              : #include "engraving/dom/chord.h"
      32              : #include "engraving/dom/chordline.h"
      33              : #include "engraving/dom/clef.h"
      34              : #include "engraving/dom/dynamic.h"
      35              : #include "engraving/dom/expression.h"
      36              : #include "engraving/dom/factory.h"
      37              : #include "engraving/dom/fermata.h"
      38              : #include "engraving/dom/figuredbass.h"
      39              : #include "engraving/dom/fingering.h"
      40              : #include "engraving/dom/glissando.h"
      41              : #include "engraving/dom/hairpin.h"
      42              : #include "engraving/dom/harmony.h"
      43              : #include "engraving/dom/harppedaldiagram.h"
      44              : #include "engraving/dom/jump.h"
      45              : #include "engraving/dom/key.h"
      46              : #include "engraving/dom/keysig.h"
      47              : #include "engraving/dom/laissezvib.h"
      48              : #include "engraving/dom/layoutbreak.h"
      49              : #include "engraving/dom/lyrics.h"
      50              : #include "engraving/dom/marker.h"
      51              : #include "engraving/dom/measure.h"
      52              : #include "engraving/dom/measurerepeat.h"
      53              : #include "engraving/dom/note.h"
      54              : #include "engraving/dom/ornament.h"
      55              : #include "engraving/dom/ottava.h"
      56              : #include "engraving/dom/part.h"
      57              : #include "engraving/dom/pedal.h"
      58              : #include "engraving/dom/playtechannotation.h"
      59              : #include "engraving/dom/rehearsalmark.h"
      60              : #include "engraving/dom/rest.h"
      61              : #include "engraving/dom/score.h"
      62              : #include "engraving/dom/segment.h"
      63              : #include "engraving/dom/sig.h"
      64              : #include "engraving/dom/slur.h"
      65              : #include "engraving/dom/staff.h"
      66              : #include "engraving/dom/stafftext.h"
      67              : #include "engraving/dom/systemtext.h"
      68              : #include "engraving/dom/tempotext.h"
      69              : #include "engraving/dom/text.h"
      70              : #include "engraving/dom/textline.h"
      71              : #include "engraving/dom/tie.h"
      72              : #include "engraving/dom/timesig.h"
      73              : #include "engraving/dom/tuplet.h"
      74              : #include "engraving/dom/trill.h"
      75              : #include "engraving/dom/utils.h"
      76              : 
      77              : #include "engraving/editing/editstaffbrackets.h"
      78              : #include "engraving/editing/transpose.h"
      79              : 
      80              : #include "thirdparty/libmei/cmn.h"
      81              : #include "thirdparty/libmei/fingering.h"
      82              : #include "thirdparty/libmei/lyrics.h"
      83              : #include "thirdparty/libmei/shared.h"
      84              : #include "thirdparty/libmei/midi.h"
      85              : 
      86              : #include "pugixml.hpp"
      87              : 
      88              : using namespace muse;
      89              : using namespace mu;
      90              : using namespace mu::iex::mei;
      91              : using namespace mu::engraving;
      92              : 
      93              : #define SCOREDEF_IDX -1
      94              : 
      95              : #define MEI_BASIC_VERSION "5.1+basic"
      96              : 
      97              : #define MEI_FB_HARM "fb-harm"
      98              : 
      99              : /**
     100              :  * Read the Score from the file.
     101              :  * Return false on error.
     102              :  */
     103              : 
     104           64 : bool MeiImporter::read(const muse::io::path_t& path)
     105              : {
     106           64 :     m_uids = UIDRegister::instance();
     107           64 :     m_uids->clear();
     108           64 :     m_hasMuseScoreIds = false;
     109              : 
     110           64 :     m_lastMeasure = nullptr;
     111           64 :     m_tremoloId.clear();
     112           64 :     m_tuplet = nullptr;
     113           64 :     m_beamBeginMode = BeamMode::AUTO;
     114           64 :     m_graceBeamBeginMode = BeamMode::AUTO;
     115              : 
     116           64 :     m_readingGraceNotes = GraceNone;
     117           64 :     m_lastChord = nullptr;
     118              : 
     119           64 :     m_readingEnding = false;
     120           64 :     m_endingStart = nullptr;
     121           64 :     m_endingEnd = nullptr;
     122              : 
     123           64 :     Convert::logs.clear();
     124              : 
     125           64 :     pugi::xml_document doc;
     126           64 :     pugi::xml_parse_result result = doc.load_file(
     127           64 :         path.toStdString().c_str(), (pugi::parse_comments | pugi::parse_default) & ~pugi::parse_eol);
     128              : 
     129           64 :     if (!result) {
     130            0 :         LOGD() << "Cannot open file <" << qPrintable(path.toString()) << ">";
     131            0 :         return false;
     132              :     }
     133              : 
     134           64 :     pugi::xml_node root = doc.first_child();
     135              : 
     136           64 :     pugi::xml_attribute meiVersion = root.attribute("meiversion");
     137           64 :     if (!meiVersion || String(meiVersion.value()) != String(MEI_BASIC_VERSION)) {
     138            0 :         Convert::logs.push_back(String("The MEI file does not seem to be a MEI Basic version '%1' file").arg(String(MEI_BASIC_VERSION)));
     139              :     }
     140              : 
     141           64 :     bool success = true;
     142              : 
     143           64 :     success = success && this->readMeiHead(root);
     144              : 
     145           64 :     pugi::xml_attribute xmlId = root.attribute("xml:id");
     146           64 :     bool hasRootXmlId = false;
     147           64 :     if (xmlId && !String(xmlId.value()).empty()) {
     148            0 :         hasRootXmlId = true;
     149            0 :         String xmlIdStr = String(xmlId.value());
     150            0 :         if (xmlIdStr.startsWith(u"mscore-")) {
     151              :             // Keep a global flag since we are going to read them only if mei@xml:id is given with mscore EID
     152            0 :             m_hasMuseScoreIds = true;
     153            0 :             String valStr = xmlIdStr.remove(u"mscore-").replace('.', '/').replace('-', '+');
     154              :             // The  mei@xml:id store the score EID
     155            0 :             EID eid = EID::fromStdString(valStr.toStdString());
     156            0 :             if (eid.isValid()) {
     157            0 :                 m_score->setEID(eid);
     158              :             }
     159            0 :         } else {
     160              :             // Keep it as a seed
     161            0 :             m_score->setMetaTag(u"xml:id", xmlIdStr);
     162              :         }
     163            0 :     }
     164              : 
     165           64 :     success = success && this->readScore(root);
     166              : 
     167           64 :     if (hasRootXmlId) {
     168              :         // Do not keep a xml:id map when having a xml:id seed or MscoreIds
     169            0 :         m_uids->clear();
     170              :     }
     171              : 
     172           64 :     return success;
     173           64 : }
     174              : 
     175              : //---------------------------------------------------------
     176              : // helper methods
     177              : //---------------------------------------------------------
     178              : 
     179              : /**
     180              :  * Add a message together with a printout of the node to the log list.
     181              :  */
     182              : 
     183            0 : void MeiImporter::addLog(const std::string& msg, pugi::xml_node node)
     184              : {
     185            0 :     String nodeStr = u"&lt;";
     186            0 :     nodeStr += String(node.name());
     187            0 :     for (auto attribute : node.attributes()) {
     188            0 :         nodeStr += String(" %1='%2'").arg(String(attribute.name()), String(attribute.value()));
     189              :     }
     190            0 :     nodeStr += u" /&gt;";
     191            0 :     Convert::logs.push_back(String("Could not convert the %1 from %2").arg(String::fromStdString(msg), nodeStr));
     192            0 : }
     193              : 
     194              : /**
     195              :  * Return true if the node name matches the name parameter.
     196              :  */
     197              : 
     198          536 : bool MeiImporter::isNode(pugi::xml_node node, const String& name)
     199              : {
     200          536 :     if (!node) {
     201            0 :         return false;
     202              :     }
     203              : 
     204          536 :     String nodeName = String(node.name());
     205          536 :     return nodeName == name;
     206          536 : }
     207              : 
     208              : /**
     209              :  * Return the MuseScore staff index for the corresponding MEI staff `@n`
     210              :  * Actually useful only when reading files not written by MuseScore and that can have not continuous staff numbers
     211              :  */
     212              : 
     213         1706 : int MeiImporter::getStaffIndex(int staffN)
     214              : {
     215         1706 :     if (m_staffNs.find(staffN) == m_staffNs.end()) {
     216            0 :         return 0;
     217              :     }
     218         3412 :     return static_cast<int>(std::distance(m_staffNs.begin(), m_staffNs.find(staffN)));
     219              : }
     220              : 
     221              : /**
     222              :  * Return the voice index (0 to 3) for the corresponding MEI layer `@n`
     223              :  * Actually useful only when reading files not written by MuseScore
     224              :  */
     225              : 
     226            0 : int MeiImporter::getVoiceIndex(int staffIdx, int layerN)
     227              : {
     228            0 :     if (!m_staffLayerNs.count(staffIdx)) {
     229            0 :         return 0;
     230              :     }
     231            0 :     const std::set<int>& layerNs = m_staffLayerNs.at(staffIdx);
     232            0 :     if (layerNs.find(layerN) == layerNs.end()) {
     233            0 :         return 0;
     234              :     }
     235            0 :     return static_cast<int>(std::distance(layerNs.begin(), layerNs.find(layerN)));
     236              : }
     237              : 
     238              : /**
     239              :  * Add a new ChordRest object to a measure and called when reading a <chord>, <note> or <rest>, including grace notes.
     240              :  * When reading grace notes, add the object to the m_graceNotes list, which will eventually be added to the appropriate Chord.
     241              :  * When reading tuplet, increase the ticks value to the corrected ratio, and not at all when reading grace notes.
     242              :  */
     243              : 
     244         1643 : ChordRest* MeiImporter::addChordRest(pugi::xml_node node, Measure* measure, int track, const libmei::Element& meiElement, Fraction& ticks,
     245              :                                      bool isRest)
     246              : {
     247         1643 :     IF_ASSERT_FAILED(measure) {
     248            0 :         return nullptr;
     249              :     }
     250              : 
     251         1643 :     bool warning = false;
     252              : 
     253         1643 :     const libmei::AttAugmentDots* augmentDotsAtt = dynamic_cast<const libmei::AttAugmentDots*>(&meiElement);
     254         1643 :     const libmei::AttDurationLog* durationLogAtt = dynamic_cast<const libmei::AttDurationLog*>(&meiElement);
     255         1643 :     const libmei::AttStaffIdent* staffIdentAtt = dynamic_cast<const libmei::AttStaffIdent*>(&meiElement);
     256         1643 :     const libmei::AttTyped* typedAtt = dynamic_cast<const libmei::AttTyped*>(&meiElement);
     257              : 
     258         1643 :     IF_ASSERT_FAILED(augmentDotsAtt && durationLogAtt && staffIdentAtt && typedAtt) {
     259            0 :         return nullptr;
     260              :     }
     261              : 
     262              :     // No assert because att.beam.secondary is not available on MEI `<space>`
     263         1643 :     const libmei::AttBeamSecondary* beamSecondaryAtt = dynamic_cast<const libmei::AttBeamSecondary*>(&meiElement);
     264              : 
     265         1643 :     TDuration duration;
     266         1643 :     duration.setType(Convert::durFromMEI(durationLogAtt->GetDur(), warning));
     267         1643 :     if (node.select_node("ancestor::fTrem")) {
     268              :         // fTrem is a tremolo, so we increase the duration type
     269           48 :         duration.setType(Convert::durFromMEI(static_cast<libmei::data_DURATION>(durationLogAtt->GetDur()
     270           24 :                                                                                 + libmei::DURATION_breve), warning));
     271              :     }
     272         1643 :     if (warning) {
     273            0 :         this->addLog("duration", node);
     274              :     }
     275              : 
     276         1643 :     if (augmentDotsAtt->HasDots()) {
     277           57 :         duration.setDots(augmentDotsAtt->GetDots());
     278              :     }
     279              : 
     280              :     // grace notes are added to the chord they precede, so they get no segment of their own
     281         1643 :     Segment* segment = m_readingGraceNotes ? nullptr : measure->getSegment(SegmentType::ChordRest, ticks + measure->tick());
     282         1643 :     const DummyParentOr<Segment> parent = parentOrDummy(segment, m_score->dummy());
     283              : 
     284         1643 :     ChordRest* chordRest = nullptr;
     285         1643 :     if (isRest) {
     286           65 :         chordRest = Factory::createRest(parent);
     287              :     } else {
     288         1578 :         chordRest = Factory::createChord(parent);
     289              :     }
     290              : 
     291              :     // Do not use single note xml:id / EID for the ChordRest
     292         1643 :     if (!dynamic_cast<const libmei::Note*>(&meiElement)) {
     293          199 :         this->readXmlId(chordRest, meiElement.m_xmlId);
     294              :     }
     295              : 
     296         1643 :     if (m_startIdChordRests.count(meiElement.m_xmlId)) {
     297          274 :         m_startIdChordRests[meiElement.m_xmlId] = chordRest;
     298              :     }
     299         1643 :     if (m_endIdChordRests.count(meiElement.m_xmlId)) {
     300           83 :         m_endIdChordRests[meiElement.m_xmlId] = chordRest;
     301              :     }
     302         1643 :     if (m_plistValueChordRests.count(meiElement.m_xmlId)) {
     303            4 :         m_plistValueChordRests[meiElement.m_xmlId] = chordRest;
     304              :     }
     305              : 
     306              :     // Handle cross-staff notation
     307         1643 :     if (staffIdentAtt->HasStaff()) {
     308          102 :         int staffN = staffIdentAtt->GetStaff().front();
     309          102 :         int staffMoveIdx = this->getStaffIndex(staffN);
     310          102 :         int staffMove = (staffMoveIdx - static_cast<int>(track2staff(track)));
     311              :         // Limit staff move to one staff - also, boundaries should be OK since we use getStaffIndex and track2staff previously
     312          102 :         if (staffMove == -1 || staffMove == 1) {
     313          102 :             chordRest->setStaffMove(staffMove);
     314              :         }
     315              :     }
     316              : 
     317         1643 :     chordRest->setTrack(track);
     318         1643 :     chordRest->setTicks(duration.fraction());
     319         1643 :     chordRest->setDurationType(TDuration(duration));
     320              : 
     321         1643 :     if (m_readingGraceNotes) {
     322              :         // non critical assert
     323           47 :         assert(chordRest->isChord());
     324           47 :         m_graceNotes.push_front(toChord(chordRest));
     325              :     } else {
     326         1596 :         if (m_graceNotes.size() > 0) {
     327              :             // pre grace groups have to be added to the chord - if it is a rest they will be ignored (deleted)
     328           24 :             this->addGraceNotesToChord(chordRest);
     329              :         }
     330         1596 :         segment->add(chordRest);
     331              :         // Keep a pointer to the last chord read for adding post grace groups
     332         1596 :         if (chordRest->isChord()) {
     333         1531 :             m_lastChord = toChord(chordRest);
     334              :         }
     335              :     }
     336              : 
     337         1643 :     BeamMode beamMode = Convert::beamFromMEI(typedAtt->GetType(), BEAM_ELEMENT_TYPE, warning);
     338         1643 :     if (beamMode != BeamMode::AUTO) {
     339          116 :         chordRest->setBeamMode(beamMode);
     340              :     }
     341              :     // Set BeamMode::BEGIN16 or BeamMode::BEGIN32 (we have a distinct flag for grace notes for handling nested beams)
     342              :     else {
     343         1527 :         if (!m_readingGraceNotes) {
     344         1481 :             chordRest->setBeamMode(m_beamBeginMode);
     345         1481 :             m_beamBeginMode = BeamMode::AUTO;
     346              :         } else {
     347           46 :             chordRest->setBeamMode(m_graceBeamBeginMode);
     348           46 :             m_graceBeamBeginMode = BeamMode::AUTO;
     349              :         }
     350              :     }
     351              : 
     352         1643 :     Fraction chordTicks = chordRest->ticks();
     353              :     // For tuplet add the ChordRest to it and adjust the tick advance to the ratio
     354         1643 :     if (m_tuplet) {
     355          180 :         m_tuplet->add(chordRest);
     356          180 :         chordRest->setTuplet(m_tuplet);
     357              :         // For tuplets the tick advance is adjusted to the ratio
     358          180 :         chordTicks /= m_tuplet->ratio();
     359              :     }
     360              :     // For grace notes, no tick advance
     361         1643 :     if (!m_readingGraceNotes) {
     362         1596 :         ticks += chordTicks;
     363              :         // Check if we have an AttBeamSecondary and read it for the next ChordRest
     364         1596 :         if (beamSecondaryAtt) {
     365         1596 :             m_beamBeginMode = Convert::breaksecFromMEI(beamSecondaryAtt->GetBreaksec(), warning);
     366              :         }
     367              :     } else {
     368              :         // Check if we have an AttBeamSecondary and read it for the next ChordRest
     369           47 :         if (beamSecondaryAtt) {
     370           47 :             m_graceBeamBeginMode = Convert::breaksecFromMEI(beamSecondaryAtt->GetBreaksec(), warning);
     371              :         }
     372              :     }
     373              : 
     374         1643 :     return chordRest;
     375              : }
     376              : 
     377              : /**
     378              :  * Add grace notes to a ChordRest When a grace group was previously read and added to MeiImporter::m_graceNotes
     379              :  * Ignore (delete) the grace notes if the ChordRest is a Rest.
     380              :  * Look at m_graceNoteType for setting the acciaccatura note type (when grace notes precede only)
     381              :  */
     382              : 
     383           25 : bool MeiImporter::addGraceNotesToChord(ChordRest* chordRest, bool isAfter)
     384              : {
     385           25 :     IF_ASSERT_FAILED(chordRest) {
     386            0 :         return false;
     387              :     }
     388              : 
     389           25 :     if (!chordRest->isChord()) {
     390            0 :         this->clearGraceNotes();
     391            0 :         return false;
     392              :     } else {
     393           25 :         if (isAfter) {
     394            1 :             NoteType noteType = NoteType::GRACE8_AFTER;
     395              :             // For after grace notes, the order of insertion is flipped
     396            1 :             m_graceNotes.reverse();
     397            4 :             for (auto graceChord : m_graceNotes) {
     398            3 :                 switch (graceChord->durationType().type()) {
     399            1 :                 case (DurationType::V_EIGHTH): noteType = NoteType::GRACE8_AFTER;
     400            1 :                     break;
     401            2 :                 case (DurationType::V_16TH): noteType = NoteType::GRACE16_AFTER;
     402            2 :                     break;
     403            0 :                 case (DurationType::V_32ND): noteType = NoteType::GRACE32_AFTER;
     404            0 :                     break;
     405            0 :                 default:
     406            0 :                     LOGD() << "MeiImporter::addGraceNotesToChord unsupported grace duration type " <<
     407            0 :                         int(graceChord->durationType().type());
     408            0 :                     break;
     409              :                 }
     410            3 :                 graceChord->setNoteType(noteType);
     411            3 :                 chordRest->add(graceChord);
     412              :             }
     413              :         } else {
     414           68 :             for (auto graceChord : m_graceNotes) {
     415           44 :                 NoteType noteType = NoteType::APPOGGIATURA;
     416           44 :                 if (m_graceNoteType == NoteType::ACCIACCATURA) {
     417            7 :                     noteType = NoteType::ACCIACCATURA;
     418              :                 } else {
     419           37 :                     switch (graceChord->durationType().type()) {
     420            4 :                     case (DurationType::V_QUARTER): noteType = NoteType::GRACE4;
     421            4 :                         break;
     422            8 :                     case (DurationType::V_EIGHTH): noteType = NoteType::APPOGGIATURA;
     423            8 :                         break;
     424            8 :                     case (DurationType::V_16TH): noteType = NoteType::GRACE16;
     425            8 :                         break;
     426           17 :                     case (DurationType::V_32ND): noteType = NoteType::GRACE32;
     427           17 :                         break;
     428            0 :                     default:
     429            0 :                         LOGD("MeiImporter::addGraceNotesToChord unsupported grace duration type %d",
     430            0 :                              int(graceChord->durationType().type()));
     431            0 :                         break;
     432              :                     }
     433              :                 }
     434           44 :                 graceChord->setNoteType(noteType);
     435           44 :                 chordRest->add(graceChord);
     436              :             }
     437              :         }
     438           25 :         m_graceNotes.clear();
     439           25 :         return true;
     440              :     }
     441              : }
     442              : 
     443              : /**
     444              :  * Create a annotation (MEI control event with @startid).
     445              :  * Create the EngravingItem according to the MEI element name (e.g., "dynam", "fermata")
     446              :  * Return nullptr if the lookup fails.
     447              :  */
     448              : 
     449          114 : EngravingItem* MeiImporter::addAnnotation(const libmei::Element& meiElement, Measure* measure)
     450              : {
     451          114 :     ControlElementPosition pos = this->findStart(meiElement, measure);
     452          114 :     if (!pos.measure || (pos.chordRest && pos.chordRest->isGrace())) {
     453            0 :         return nullptr;
     454              :     }
     455              : 
     456          114 :     Segment* segment = pos.measure->getSegment(SegmentType::ChordRest, pos.tick);
     457          114 :     EngravingItem* item = nullptr;
     458              : 
     459          114 :     if (meiElement.m_name == "breath" || meiElement.m_name == "caesura") {
     460              :         // For Breath we need to add a specific segment and add the breath to it (and not to the ChordRest one)
     461           17 :         Fraction tick = (pos.chordRest) ? pos.chordRest->endTick() : pos.tick;
     462           17 :         segment = pos.measure->getSegment(SegmentType::Breath, tick);
     463           17 :         item = Factory::createBreath(segment);
     464           97 :     } else if (meiElement.m_name == "dir") {
     465            5 :         ElementType elementType = Convert::elementTypeForDir(meiElement);
     466            5 :         switch (elementType) {
     467            2 :         case (ElementType::PLAYTECH_ANNOTATION):
     468            2 :             item = Factory::createPlayTechAnnotation(segment, PlayingTechniqueType::Natural, TextStyleType::STAFF);
     469            2 :             break;
     470            1 :         case (ElementType::STAFF_TEXT):
     471            1 :             item = Factory::createStaffText(segment);
     472            1 :             break;
     473            1 :         case (ElementType::SYSTEM_TEXT):
     474            1 :             item = Factory::createSystemText(segment);
     475            1 :             break;
     476            1 :         default:
     477            1 :             item = Factory::createExpression(segment);
     478              :         }
     479           92 :     } else if (meiElement.m_name == "dynam") {
     480           10 :         item = Factory::createDynamic(segment);
     481           82 :     } else if (meiElement.m_name == "fermata") {
     482           32 :         item = Factory::createFermata(segment);
     483           50 :     } else if (meiElement.m_name == "harm") {
     484           33 :         const libmei::AttLabelled* labeledAtt = dynamic_cast<const libmei::AttLabelled*>(&meiElement);
     485           33 :         if (labeledAtt && (labeledAtt->GetLabel() == MEI_FB_HARM)) {
     486            7 :             item = Factory::createFiguredBass(segment);
     487              :         } else {
     488           26 :             item = Factory::createHarmony(segment);
     489              :         }
     490           17 :     } else if (meiElement.m_name == "harpPedal") {
     491            3 :         item = Factory::createHarpPedalDiagram(segment);
     492           14 :     } else if (meiElement.m_name == "reh") {
     493            3 :         item = Factory::createRehearsalMark(segment);
     494           11 :     } else if (meiElement.m_name == "tempo") {
     495           11 :         item = Factory::createTempoText(segment);
     496              :     } else {
     497            0 :         return nullptr;
     498              :     }
     499          114 :     this->readXmlId(item, meiElement.m_xmlId);
     500              : 
     501          114 :     item->setTrack(pos.track);
     502          114 :     segment->add(item);
     503              : 
     504          114 :     return item;
     505              : }
     506              : 
     507              : /**
     508              :  * Create a spanner (MEI control event with @startid end @endid).
     509              :  * Create the Spanner according to the MEI element name (e.g., "slur", "tie").
     510              :  * Add the corresponding pugixml::node to the m_openSpannerMap to be closed later
     511              :  * Return nullptr if the lookup fails.
     512              :  */
     513              : 
     514           58 : Spanner* MeiImporter::addSpanner(const libmei::Element& meiElement, Measure* measure, pugi::xml_node node)
     515              : {
     516           58 :     ControlElementPosition pos = this->findStart(meiElement, measure);
     517           58 :     if (!pos.measure) {
     518            0 :         return nullptr;
     519              :     }
     520              : 
     521              :     // a spanner is not attached to anything: the score manages it
     522           58 :     DummyParent* dummy = m_score->dummy();
     523              : 
     524           58 :     Spanner* item = nullptr;
     525           58 :     if (meiElement.m_name == "dir") {
     526            3 :         ElementType elementType = Convert::elementTypeForDirWithExt(meiElement);
     527            3 :         switch (elementType) {
     528            3 :         case (ElementType::HAIRPIN): item = Factory::createHairpin(dummy);
     529            3 :             break;
     530            0 :         default:
     531            0 :             item = Factory::createTextLine(dummy);
     532              :         }
     533           55 :     } else if (meiElement.m_name == "hairpin") {
     534            6 :         item = Factory::createHairpin(dummy);
     535           49 :     } else if (meiElement.m_name == "octave") {
     536            7 :         item = Factory::createOttava(dummy);
     537           42 :     } else if (meiElement.m_name == "pedal") {
     538            5 :         item = Factory::createPedal(dummy);
     539           37 :     } else if (meiElement.m_name == "slur") {
     540           33 :         item = Factory::createSlur(dummy);
     541            4 :     } else if (meiElement.m_name == "trill") {
     542            4 :         item = Factory::createTrill(dummy);
     543              :     } else {
     544            0 :         return nullptr;
     545              :     }
     546           58 :     this->readXmlId(item, meiElement.m_xmlId);
     547              : 
     548           58 :     item->setTick(pos.tick);
     549           58 :     item->setStartElement(pos.chordRest);
     550           58 :     item->setTrack(pos.track);
     551           58 :     item->setTrack2(pos.track);
     552              : 
     553           58 :     m_score->addElement(item);
     554              : 
     555              :     // Add it to the map for setting spanner end in MeiImporter::addSpannerEnds
     556           58 :     m_openSpannerMap[item] = node;
     557              : 
     558           58 :     return item;
     559              : }
     560              : 
     561              : /**
     562              :  * Create a articulation (MEI control event with @startid).
     563              :  * Create the EngravingItem according to the MEI element name (e.g., "mordent", "turn")
     564              :  * If measure is null, then chord is expected and no lookup for the chordRest will be performed (e.g., for "artic")
     565              :  * The articulation object is added to the EngravingItem (and not to the segment as for annotations)
     566              :  * Return nullptr if the lookup fails.
     567              :  */
     568              : 
     569          113 : EngravingItem* MeiImporter::addToChordRest(const libmei::Element& meiElement, Measure* measure, Chord* chord)
     570              : {
     571          113 :     ChordRest* chordRest = (!measure) ? chord : this->findStart(meiElement, measure).chordRest;
     572          113 :     if (!chordRest) {
     573            0 :         return nullptr;
     574              :     }
     575              : 
     576          113 :     EngravingItem* item = nullptr;
     577              : 
     578          115 :     static const std::vector<std::string> s_ornaments = { "mordent", "ornam", "trill", "turn" };
     579              : 
     580          113 :     if (std::find(s_ornaments.begin(), s_ornaments.end(), meiElement.m_name) != s_ornaments.end()) {
     581           37 :         item = Factory::createOrnament(chordRest);
     582           76 :     } else if (meiElement.m_name == "arpeg") {
     583           11 :         if (chordRest->isChord()) {
     584           11 :             item = Factory::createArpeggio(toChord(chordRest));
     585              :         }
     586           65 :     } else if (meiElement.m_name == "artic") {
     587           65 :         item = Factory::createArticulation(chordRest);
     588              :     } else {
     589            0 :         return nullptr;
     590              :     }
     591          113 :     this->readXmlId(item, meiElement.m_xmlId);
     592              : 
     593          113 :     item->setTrack(chordRest->track());
     594          113 :     chordRest->add(item);
     595              : 
     596          113 :     return item;
     597              : }
     598              : 
     599              : /**
     600              :  * Basic helper that removes the '#' character from a dataURI reference to \@xml:id.
     601              :  */
     602              : 
     603          426 : std::string MeiImporter::xmlIdFrom(std::string dataURI)
     604              : {
     605              :     // Basic check for the @stardid validity
     606          426 :     if (dataURI.size() < 1 || dataURI.at(0) != '#') {
     607            0 :         Convert::logs.push_back(String("Could not convert the data.URI '%1'").arg(String::fromStdString(dataURI)));
     608            0 :         return "";
     609              :     }
     610          426 :     return dataURI.erase(0, 1);
     611              : }
     612              : 
     613              : /**
     614              :  * Look for the ChordRest for an MEI element with @startid.
     615              :  * Do a lookup in the m_startIdChordRests map with the @startid value to retrieve the ChordRest to which the annotation points to.
     616              :  * If there is not @startid but a @tstamp (MEI not written by MuseScore), try to find the corresponding ChordRest
     617              :  */
     618              : 
     619          220 : ControlElementPosition MeiImporter::findStart(const libmei::Element& meiElement, Measure* measure)
     620              : {
     621          220 :     ControlElementPosition pos;
     622          220 :     const libmei::AttStartId* startIdAtt = dynamic_cast<const libmei::AttStartId*>(&meiElement);
     623          220 :     IF_ASSERT_FAILED(measure && startIdAtt) {
     624            0 :         return pos;
     625              :     }
     626              : 
     627          220 :     if (startIdAtt->HasStartid()) {
     628          210 :         std::string startId = this->xmlIdFrom(startIdAtt->GetStartid());
     629              :         // The startid corresponding ChordRest should have been added to the m_startIdChordRests previously
     630          210 :         if (!m_startIdChordRests.count(startId) || !m_startIdChordRests.at(startId)) {
     631            0 :             Convert::logs.push_back(String("Could not find element for @startid '%1'").arg(String::fromStdString(
     632            0 :                                                                                                startIdAtt->GetStartid())));
     633            0 :             return pos;
     634              :         }
     635          210 :         pos.chordRest = m_startIdChordRests.at(startId);
     636          210 :         pos.measure = pos.chordRest->measure();
     637          210 :         pos.tick = pos.chordRest->tick();
     638          210 :         pos.track = pos.chordRest->track();
     639          210 :     } else {
     640              :         // No @startid, try a lookup based on the @tstamp. This is only for files not written via MuseScore
     641           10 :         const libmei::AttTimestampLog* timestampLogAtt = dynamic_cast<const libmei::AttTimestampLog*>(&meiElement);
     642           10 :         const libmei::AttStaffIdent* staffIdentAtt = dynamic_cast<const libmei::AttStaffIdent*>(&meiElement);
     643           10 :         const libmei::AttLayerIdent* layerIdentAtt = dynamic_cast<const libmei::AttLayerIdent*>(&meiElement);
     644              : 
     645           10 :         IF_ASSERT_FAILED(timestampLogAtt && staffIdentAtt) {
     646            0 :             return pos;
     647              :         }
     648              : 
     649              :         // If no @tstamp (invalid), put it on 1.0;
     650           10 :         const double tstampValue = timestampLogAtt->HasTstamp() ? timestampLogAtt->GetTstamp() : 1.0;
     651           10 :         Fraction tstampFraction = Convert::tstampToFraction(tstampValue, measure->timesig());
     652           30 :         const int staffIdx = (staffIdentAtt->HasStaff() && staffIdentAtt->GetStaff().size() > 0) ? this->getStaffIndex(
     653           20 :             staffIdentAtt->GetStaff().at(0)) : 0;
     654           10 :         const int layer = (layerIdentAtt && layerIdentAtt->HasLayer()) ? this->getVoiceIndex(staffIdx, layerIdentAtt->GetLayer()) : 0;
     655              : 
     656           10 :         pos.measure = measure;
     657           10 :         pos.tick = measure->tick() + tstampFraction;
     658           10 :         pos.track = staffIdx * VOICES + layer;
     659           10 :         pos.chordRest = measure->findChordRest(pos.tick, pos.track);
     660              :     }
     661              : 
     662          220 :     return pos;
     663              : }
     664              : 
     665              : /**
     666              :  * Look for the ChordRest for an MEI element with @endid.
     667              :  * Do a lookup in the m_endIdChordRests map with the @endid value to retrieve the ChordRest to which the annotation points to.
     668              :  * If there is not @endid but a @tstamp2 (MEI not written by MuseScore), try to find the corresponding ChordRest
     669              :  */
     670              : 
     671           58 : ControlElementPosition MeiImporter::findEnd(pugi::xml_node controlNode, Spanner* spanner)
     672              : {
     673           58 :     ControlElementPosition pos;
     674           58 :     libmei::InstStartEndId startEndIdAtt;
     675           58 :     startEndIdAtt.ReadStartEndId(controlNode);
     676              : 
     677           58 :     if (startEndIdAtt.HasEndid()) {
     678           58 :         std::string endId = this->xmlIdFrom(startEndIdAtt.GetEndid());
     679              :         // The @endid corresponding ChordRest should have been added to the m_endIdChordRests previously
     680           58 :         if (!m_endIdChordRests.count(endId) || !m_endIdChordRests.at(endId)) {
     681            0 :             Convert::logs.push_back(String("Could not find element for @endid '%1'").arg(String::fromStdString(startEndIdAtt.GetEndid())));
     682            0 :             return pos;
     683              :         }
     684           58 :         pos.chordRest = m_endIdChordRests.at(endId);
     685           58 :         pos.measure = pos.chordRest->measure();
     686           58 :         pos.tick = pos.chordRest->tick();
     687           58 :         pos.track = pos.chordRest->track();
     688           58 :     } else {
     689              :         // No @endid, try a lookup based on the @tstamp2. This is only for files not written via MuseScore
     690            0 :         libmei::InstTimestamp2Log timestamp2LogAtt;
     691            0 :         timestamp2LogAtt.ReadTimestamp2Log(controlNode);
     692            0 :         libmei::InstStaffIdent staffIdentAtt;
     693            0 :         staffIdentAtt.ReadStaffIdent(controlNode);
     694            0 :         libmei::InstLayerIdent layerIdentAtt;
     695            0 :         layerIdentAtt.ReadLayerIdent(controlNode);
     696              : 
     697              :         // We need at least a @tstamp2 and a spanner with its startMeasure
     698            0 :         Measure* startM = (spanner->startElement())
     699            0 :                           ? spanner->startElement()->findMeasure()
     700            0 :                           : m_score->tick2measure(spanner->tick());
     701              : 
     702            0 :         if (!timestamp2LogAtt.HasTstamp2() || !startM) {
     703            0 :             return pos;
     704              :         }
     705              : 
     706            0 :         libmei::data_MEASUREBEAT tstamp2Value = timestamp2LogAtt.GetTstamp2();
     707              : 
     708              :         // Find the end Measure
     709            0 :         Measure* measure = startM;
     710            0 :         for (int i = tstamp2Value.first; i > 0; --i) {
     711            0 :             if (!measure->next() || !measure->next()->isMeasure()) {
     712            0 :                 return pos;
     713              :             }
     714            0 :             measure = toMeasure(measure->next());
     715              :         }
     716              : 
     717            0 :         pos.measure = measure;
     718            0 :         Fraction tstampFraction = Convert::tstampToFraction(tstamp2Value.second, measure->timesig());
     719              :         // Use the spanner staffIdx unless given in @staff
     720            0 :         staff_idx_t staffIdx = (staffIdentAtt.HasStaff() && staffIdentAtt.GetStaff().size() > 0) ? this->getStaffIndex(
     721            0 :             staffIdentAtt.GetStaff().at(0)) : track2staff(spanner->track());
     722              :         // Use the spanner voice unless given in @layer
     723            0 :         track_idx_t layer = (layerIdentAtt.HasLayer())
     724            0 :                             ? static_cast<track_idx_t>(this->getVoiceIndex(static_cast<int>(staffIdx),
     725              :                                                                            layerIdentAtt.GetLayer()))
     726            0 :                             : track2voice(spanner->track());
     727              : 
     728            0 :         pos.tick = measure->tick() + tstampFraction;
     729            0 :         pos.track = staffIdx * VOICES + layer;
     730            0 :         pos.chordRest = measure->findChordRest(pos.tick, pos.track);
     731            0 :     }
     732              : 
     733           58 :     return pos;
     734           58 : }
     735              : 
     736              : /**
     737              :  * Look for the Note for an MEI element with @startid.
     738              :  * Do a lookup in the m_startIdNotes map with the @startid value to retrieve the Note to which the element points to.
     739              :  */
     740              : 
     741          105 : Note* MeiImporter::findStartNote(const libmei::Element& meiElement)
     742              : {
     743          105 :     const libmei::AttStartId* startIdAtt = dynamic_cast<const libmei::AttStartId*>(&meiElement);
     744          105 :     IF_ASSERT_FAILED(startIdAtt && startIdAtt->HasStartid()) {
     745            0 :         return nullptr;
     746              :     }
     747              : 
     748          105 :     std::string startId = this->xmlIdFrom(startIdAtt->GetStartid());
     749              :     // The startid corresponding Note should have been added to the m_startIdNotes previously
     750          105 :     if (!m_startIdNotes.count(startId) || !m_startIdNotes.at(startId)) {
     751            0 :         Convert::logs.push_back(String("Could not find note for @startid '%1'").arg(String::fromStdString(startIdAtt->GetStartid())));
     752            0 :         return nullptr;
     753              :     }
     754          105 :     return m_startIdNotes.at(startId);
     755          105 : }
     756              : 
     757              : /**
     758              :  * Look for the Note for an MEI element with @endid.
     759              :  * Do a lookup in the m_endIdNotes map with the @endid value to retrieve the Note to which the element points to.
     760              :  */
     761              : 
     762           49 : Note* MeiImporter::findEndNote(pugi::xml_node controlNode)
     763              : {
     764           49 :     libmei::InstStartEndId startEndIdAtt;
     765           49 :     startEndIdAtt.ReadStartEndId(controlNode);
     766              : 
     767              :     // This should not happen because an element without an @endid will not have been added to the map
     768           49 :     if (!startEndIdAtt.HasEndid()) {
     769            0 :         return nullptr;
     770              :     }
     771              : 
     772           49 :     std::string endId = this->xmlIdFrom(startEndIdAtt.GetEndid());
     773              :     // The endid corresponding Note should have been added to the m_endIdNotes previously
     774           49 :     if (!m_endIdNotes.count(endId) || !m_endIdNotes.at(endId)) {
     775            0 :         Convert::logs.push_back(String("Could not find note for @endid '%1'").arg(String::fromStdString(startEndIdAtt.GetEndid())));
     776            0 :         return nullptr;
     777              :     }
     778           49 :     return m_endIdNotes.at(endId);
     779           49 : }
     780              : 
     781              : /**
     782              :  * Look for the ChordRest for an MEI element with @plist.
     783              :  * Do a lookup in the m_plistValueChordRests map with the @plist values to retrieve all the ChordRests to which the plist refers to.
     784              :  */
     785              : 
     786            4 : const std::vector<ChordRest*> MeiImporter::findPlistChordRests(pugi::xml_node controlNode)
     787              : {
     788            4 :     libmei::InstPlist plistAtt;
     789            4 :     plistAtt.ReadPlist(controlNode);
     790              : 
     791              :     // This should not happen because an element without a @plist will not have been added to the map
     792            4 :     if (!plistAtt.HasPlist()) {
     793            0 :         return {};
     794              :     }
     795              : 
     796            4 :     std::vector<ChordRest*> plistChordRests;
     797            8 :     for (auto& id : plistAtt.GetPlist()) {
     798            4 :         std::string plistValue = this->xmlIdFrom(id);
     799              :         // The plist corresponding Note should have been added to the m_plistValueChordRests previously
     800            4 :         if (!m_plistValueChordRests.count(plistValue) || !m_plistValueChordRests.at(plistValue)) {
     801            0 :             Convert::logs.push_back(String("Could not find note for @plist value '%1'").arg(String::fromStdString(id)));
     802            0 :             continue;
     803              :         }
     804            4 :         plistChordRests.push_back(m_plistValueChordRests.at(plistValue));
     805            8 :     }
     806            4 :     return plistChordRests;
     807            4 : }
     808              : 
     809              : /**
     810              :  * Clear grace notes that cannot be properly attached to a Chord.
     811              :  * Actually delete  the grace notes (Chord) because these have no parent.
     812              :  */
     813              : 
     814          676 : void MeiImporter::clearGraceNotes()
     815              : {
     816          676 :     for (auto graceChord : m_graceNotes) {
     817            0 :         delete graceChord;
     818              :     }
     819          676 :     m_graceNotes.clear();
     820          676 :     m_readingGraceNotes = GraceNone;
     821          676 : }
     822              : 
     823              : /**
     824              :  * Check if we have an Lyrics to extend for the given track and Lyrics no.
     825              :  */
     826              : 
     827          199 : bool MeiImporter::hasLyricsToExtend(track_idx_t track, int no)
     828              : {
     829          199 :     return (m_lyricExtenders.count(track) > 0) && (m_lyricExtenders.at(track).count(no) > 0);
     830              : }
     831              : 
     832              : /**
     833              :  * Get the Lyrics / ChordRest endpoint pair.
     834              :  * Existence of the pair need to be checked first with MeiImporter::hasLyricsToExtend.
     835              :  */
     836              : 
     837            6 : const std::pair<Lyrics*, ChordRest*>& MeiImporter::getLyricsToExtend(track_idx_t track, int no)
     838              : {
     839            6 :     return m_lyricExtenders.at(track).at(no);
     840              : }
     841              : 
     842              : /**
     843              :  * Add a ChordRest as the tentative end point for the extended Lyrics.
     844              :  * Apply it to all the Lyrics no for the ChordRest track.
     845              :  */
     846              : 
     847         1444 : void MeiImporter::addChordtoLyricsToExtend(ChordRest* chordRest)
     848              : {
     849         1444 :     track_idx_t track = chordRest->track();
     850         1444 :     if (m_lyricExtenders.count(track) > 0) {
     851           63 :         auto map = &m_lyricExtenders.at(track);
     852           94 :         for (auto& item : *map) {
     853           31 :             item.second.second = chordRest;
     854              :         }
     855              :     }
     856         1444 : }
     857              : 
     858              : /**
     859              :  * Extend a Lyrics by setting the appropriate ticks.
     860              :  * Calculate it based on the tick of the ChordRest marking the end of the extension.
     861              :  */
     862              : 
     863            7 : void MeiImporter::extendLyrics(const std::pair<Lyrics*, ChordRest*>& lyricsToExtend)
     864              : {
     865            7 :     if (lyricsToExtend.second) {
     866            7 :         Fraction ticks = lyricsToExtend.second->tick() - lyricsToExtend.first->chordRest()->tick();
     867            7 :         lyricsToExtend.first->setTicks(ticks);
     868              :     }
     869            7 : }
     870              : 
     871              : /**
     872              :  * Extend all lyrics remaining.
     873              :  * Called at the end of the import for extended lyrics closed with a following verse.
     874              :  */
     875              : 
     876           64 : void MeiImporter::extendLyrics()
     877              : {
     878           67 :     for (auto& itemTrack : m_lyricExtenders) {
     879            4 :         for (auto& itemNo : itemTrack.second) {
     880            1 :             this->extendLyrics(itemNo.second);
     881              :         }
     882              :     }
     883           64 : }
     884              : 
     885              : /**
     886              :  * Creates the accidentals (above and below) for an ornament.
     887              :  * Currently does not work if the corresponding OrnamentInterval value is not set in the Ornament.
     888              :  */
     889              : 
     890           37 : void MeiImporter::setOrnamentAccid(engraving::Ornament* ornament, const Convert::OrnamStruct& ornamSt)
     891              : {
     892           37 :     if (ornament->hasIntervalAbove() && (ornamSt.accidTypeAbove != AccidentalType::NONE)) {
     893            7 :         Accidental* accidental = Factory::createAccidental(ornament);
     894            7 :         accidental->setAccidentalType(ornamSt.accidTypeAbove);
     895            7 :         ornament->setAccidentalAbove(accidental);
     896              :     }
     897           37 :     if (ornament->hasIntervalBelow() && (ornamSt.accidTypeBelow != AccidentalType::NONE)) {
     898            2 :         Accidental* accidental = Factory::createAccidental(ornament);
     899            2 :         accidental->setAccidentalType(ornamSt.accidTypeBelow);
     900            2 :         ornament->setAccidentalBelow(accidental);
     901              :     }
     902           37 : }
     903              : 
     904         5098 : void MeiImporter::readXmlId(engraving::EngravingItem* item, const std::string& meiUID)
     905              : {
     906         5098 :     String xmlIdStr = String::fromStdString(meiUID);
     907              :     // We have a file that has MuseScore EIDs and one on this element
     908         5098 :     if (m_hasMuseScoreIds && xmlIdStr.startsWith(u"mscore-")) {
     909            0 :         String valStr = xmlIdStr.remove(u"mscore-").replace('.', '/').replace('-', '+');
     910            0 :         EID eid = EID::fromStdString(valStr.toStdString());
     911            0 :         if (!eid.isValid()) {
     912            0 :             Convert::logs.push_back(String("A valid MuseScore ID could not be extracted from '%1'").arg(xmlIdStr));
     913              :         } else {
     914            0 :             item->setEID(eid);
     915              :         }
     916            0 :     } else {
     917         5098 :         m_uids->reg(item, meiUID);
     918              :     }
     919         5098 : }
     920              : 
     921              : //---------------------------------------------------------
     922              : // parsing methods
     923              : //---------------------------------------------------------
     924              : 
     925              : /**
     926              :  * Read the <meiHead> and stores it as a custom MuseScore metatag
     927              :  * Also try to fill in some metadata
     928              :  */
     929              : 
     930           64 : bool MeiImporter::readMeiHead(pugi::xml_node root)
     931              : {
     932           64 :     pugi::xml_node headNode = root.select_node("//meiHead").node();
     933              :     // Store the MEI header in a custom meta tag
     934              : 
     935              :     // Critical error
     936           64 :     if (!headNode) {
     937            0 :         return false;
     938              :     }
     939              : 
     940           64 :     pugi::xml_document docHeader;
     941           64 :     docHeader.append_copy(headNode);
     942           64 :     unsigned int output_flags = pugi::format_default | pugi::format_no_declaration | pugi::format_raw;
     943              : 
     944           64 :     std::stringstream strStream;
     945           64 :     docHeader.save(strStream, "", output_flags);
     946           64 :     m_score->setMetaTag(u"meiHead", String::fromStdString(strStream.str()));
     947              : 
     948           64 :     pugi::xml_node firstTitleNode = root.select_node("//meiHead/fileDesc/titleStmt/title").node();
     949           64 :     if (firstTitleNode) {
     950              :         // assume the first title to be the main title
     951           64 :         m_score->setMetaTag(u"workTitle", String(firstTitleNode.text().as_string()));
     952              :     }
     953           64 :     pugi::xpath_node_set workTitleNodes = root.select_nodes("//meiHead/fileDesc/titleStmt/title[@type]");
     954           78 :     for (pugi::xpath_node workTitleNode : workTitleNodes) {
     955           14 :         const String type = String(workTitleNode.node().attribute("type").as_string());
     956           14 :         if (type == u"main") {
     957           12 :             if (m_score->metaTag(u"workTitle").isEmpty()) {
     958            0 :                 m_score->setMetaTag(u"workTitle", String(workTitleNode.node().text().as_string()));
     959              :             }
     960            2 :         } else if (type == u"subordinate") {
     961            1 :             m_score->setMetaTag(u"subtitle", String(workTitleNode.node().text().as_string()));
     962              :         } else {
     963            1 :             const String metaTag = type + u"Title";
     964            1 :             m_score->setMetaTag(metaTag, String(workTitleNode.node().text().as_string()));
     965            1 :         }
     966           14 :     }
     967              : 
     968              :     // check for dedicated elements (only for import)
     969           64 :     pugi::xml_node composer = root.select_node("//meiHead/fileDesc/titleStmt/composer").node();
     970           64 :     if (composer) {
     971            0 :         m_score->setMetaTag(u"composer", String(composer.text().as_string()));
     972              :     }
     973           64 :     pugi::xml_node lyricist = root.select_node("//meiHead/fileDesc/titleStmt/lyricist").node();
     974           64 :     if (lyricist) {
     975            0 :         m_score->setMetaTag(u"lyricist", String(lyricist.text().as_string()));
     976              :     }
     977           64 :     pugi::xml_node arranger = root.select_node("//meiHead/fileDesc/titleStmt/arranger").node();
     978           64 :     if (arranger) {
     979            0 :         m_score->setMetaTag(u"arranger", String(arranger.text().as_string()));
     980              :     }
     981              : 
     982           64 :     StringList persNames;
     983              :     // the creator types commonly found in MusicXML
     984           64 :     persNames << u"arranger" << u"composer" << u"lyricist" << u"translator";
     985          320 :     for (String tagName : persNames) {
     986          256 :         String xpath = String("//meiHead/fileDesc/titleStmt/respStmt/persName[@role='%1']").arg(tagName);
     987          256 :         pugi::xml_node persNameNode = root.select_node(xpath.toStdString().c_str()).node();
     988          256 :         if (persNameNode) {
     989           59 :             m_score->setMetaTag(tagName, String(persNameNode.text().as_string()));
     990              :         }
     991          256 :     }
     992              : 
     993           64 :     pugi::xml_node copyrightNode = root.select_node("//meiHead/fileDesc/pubStmt/availability").node();
     994           64 :     if (copyrightNode) {
     995            2 :         m_score->setMetaTag(u"copyright", String(copyrightNode.text().as_string()));
     996              :     }
     997              : 
     998           64 :     return true;
     999           64 : }
    1000              : 
    1001              : /**
    1002              :  * Read the MEI score.
    1003              :  * Previously builds a map of IDs being referred to (e.g., through `@startid` or `@endid`)
    1004              :  * Also builds a map for staff@n and layer@n when reading MEI files not produced with MuseScore.
    1005              :  * Reads the initial scoreDef before reading the section elements.
    1006              :  */
    1007              : 
    1008           64 : bool MeiImporter::readScore(pugi::xml_node root)
    1009              : {
    1010           64 :     pugi::xml_node scoreNode = root.select_node("./music//score").node();
    1011              : 
    1012           64 :     if (!scoreNode) {
    1013            0 :         return false;
    1014              :     }
    1015              : 
    1016           64 :     pugi::xml_node scoreDefNode = scoreNode.select_node("./scoreDef").node();
    1017              : 
    1018           64 :     bool success = true;
    1019              : 
    1020           64 :     success = success && this->buildIdMap(scoreNode);
    1021              : 
    1022           64 :     success = success && this->buildStaffLayerMap(scoreNode);
    1023              : 
    1024           64 :     success = success && this->readScoreDef(scoreDefNode, true);
    1025              : 
    1026           64 :     m_ticks = Fraction(0, 1);
    1027           64 :     m_lastMeasureN = 0;
    1028           64 :     m_lastMeasure = nullptr;
    1029              : 
    1030           64 :     pugi::xpath_node_set sections = scoreNode.select_nodes("./section");
    1031          128 :     for (pugi::xpath_node xpathNode : sections) {
    1032           64 :         success = success && this->readSectionElements(xpathNode.node());
    1033              :     }
    1034              : 
    1035           64 :     this->addSpannerEnds();
    1036           64 :     this->extendLyrics();
    1037              : 
    1038           64 :     return success;
    1039           64 : }
    1040              : 
    1041              : /**
    1042              :  * Read a scoreDef (initial or intermediate).
    1043              :  * For the initial scoreDef, also tries to build the part structure from the scoreDef relying on staffGrp@label and staffDef@label.
    1044              :  * Sets the time signature and key signature to the global m_timeSigs and m_keySigs maps.
    1045              :  * Uses the SCOREDEF_IDX index position for global (scoreDef) time signature and key signatures.
    1046              :  * Since the map are ordered, these will have priority over the ones read in MeiImporter::readStaffDef.
    1047              :  */
    1048              : 
    1049           82 : bool MeiImporter::readScoreDef(pugi::xml_node scoreDefNode, bool isInitial)
    1050              : {
    1051           82 :     bool warning = false;
    1052           82 :     libmei::ScoreDef meiScoreDef;
    1053              : 
    1054              :     // Critical error
    1055           82 :     if (!scoreDefNode) {
    1056            0 :         return false;
    1057              :     }
    1058              : 
    1059           82 :     bool success = true;
    1060              : 
    1061           82 :     if (isInitial) {
    1062           64 :         success = readPgHead(scoreDefNode.child("pgHead"));
    1063              :     }
    1064              : 
    1065           82 :     meiScoreDef.Read(scoreDefNode);
    1066              : 
    1067           82 :     if (meiScoreDef.HasMeterSym() || meiScoreDef.HasMeterCount()) {
    1068           14 :         m_timeSigs[SCOREDEF_IDX] = Convert::meterFromMEI(meiScoreDef, warning);
    1069           14 :         if (warning) {
    1070            0 :             this->addLog("meter signature", scoreDefNode);
    1071              :         }
    1072              :     }
    1073           82 :     if (meiScoreDef.HasKeysig()) {
    1074            0 :         m_keySigs[SCOREDEF_IDX] = Convert::keyFromMEI(meiScoreDef.GetKeysig(), warning);
    1075            0 :         if (warning) {
    1076            0 :             this->addLog("key signature", scoreDefNode);
    1077              :         }
    1078              :     }
    1079              : 
    1080           82 :     if (isInitial) {
    1081           64 :         success = success && this->buildScoreParts(scoreDefNode);
    1082              :     }
    1083              : 
    1084           82 :     success = success && this->readStaffDefs(scoreDefNode);
    1085              : 
    1086              :     // If we have a time signature, pick the first one, which will be SCOREDEF_IDX one (if given)
    1087              :     // Otherwise, it picks the first staffDef one and we expect it to be the same for all staves
    1088           82 :     if (!m_timeSigs.empty()) {
    1089           81 :         m_currentTimeSig = m_timeSigs.begin()->second.first;
    1090              :     }
    1091              : 
    1092           82 :     return success;
    1093           82 : }
    1094              : 
    1095              : /**
    1096              :  * Read the pgHead into a MuseScore initial vBox.
    1097              :  */
    1098              : 
    1099           64 : bool MeiImporter::readPgHead(pugi::xml_node pgHeadNode)
    1100              : {
    1101           64 :     if (!pgHeadNode) {
    1102            0 :         return this->buildTextFrame();
    1103              :     }
    1104              : 
    1105              :     bool warning;
    1106              : 
    1107           64 :     VBox* vBox = nullptr;
    1108              : 
    1109              :     // first level MEI /rend are positioning <rend> elements, which are ignored since positioning is based on
    1110              :     // second level MEI /rend @label based on text styles ("title", "subtitle", "composer", etc).
    1111           64 :     pugi::xpath_node_set rends = pgHeadNode.select_nodes("./rend/rend");
    1112              : 
    1113          225 :     for (pugi::xpath_node rendXpathNode : rends) {
    1114          161 :         pugi::xml_node rendNode = rendXpathNode.node();
    1115          161 :         if (!rendNode) {
    1116            0 :             continue;
    1117              :         }
    1118          161 :         libmei::Rend meiRend;
    1119          161 :         meiRend.Read(rendNode);
    1120              : 
    1121              :         // Read the string content (first level), including <lb>
    1122          161 :         StringList lines;
    1123          161 :         size_t line = 0;
    1124          161 :         this->readLines(rendNode, lines, line);
    1125              : 
    1126          161 :         TextStyleType textStyle = Convert::textFromMEI(meiRend, warning);
    1127          161 :         if (warning) {
    1128            0 :             this->addLog("text label", rendNode);
    1129              :         }
    1130              : 
    1131          161 :         if (!vBox) {
    1132           64 :             vBox = Factory::createTitleVBox(m_score);
    1133              :         }
    1134              : 
    1135          161 :         Text* text = Factory::createText(vBox, textStyle);
    1136          161 :         text->setPlainText(lines.join(u"\n"));
    1137          161 :         vBox->add(text);
    1138          161 :     }
    1139              : 
    1140           64 :     if (vBox) {
    1141           64 :         m_score->measures()->append(vBox);
    1142              :     }
    1143              : 
    1144           64 :     return true;
    1145           64 : }
    1146              : 
    1147              : /**
    1148              :  * Read the lines of a textual mixed content.
    1149              :  */
    1150              : 
    1151          456 : bool MeiImporter::readLines(pugi::xml_node parentNode, StringList& lines, size_t& line)
    1152              : {
    1153          931 :     for (pugi::xml_node child : parentNode.children()) {
    1154          475 :         if (child.type() == pugi::node_pcdata) {
    1155              :             // This is the first time we are adding text to the current line - push it
    1156          465 :             if (lines.size() <= line) {
    1157          465 :                 lines.push_back(String(child.text().as_string()));
    1158              :             }
    1159              :             // If not, then concatenate it to the current line
    1160              :             else {
    1161            0 :                 lines.at(line) += String(child.text().as_string());
    1162              :             }
    1163           10 :         } else if (this->isNode(child, u"lb")) {
    1164           10 :             line += 1;
    1165              :         } else {
    1166              :             // Try to recursively read the text content of child nodes
    1167              :             // For files not written by MuseScore with additional /rend elements
    1168            0 :             this->addLog("unsupported child element, reading text only", child);
    1169            0 :             readLines(child, lines, line);
    1170              :         }
    1171              :     }
    1172          456 :     return true;
    1173              : }
    1174              : 
    1175              : /**
    1176              :  * Read the lines of a textual mixed content.
    1177              :  * For each line, read <rend> with @glyph.auth as XML symbols.
    1178              :  * Read the text content line by line and convert each line into MuseScore xmlText (with <sym>)
    1179              :  * The method does not parse the content of <rend> recursively.
    1180              :  */
    1181              : 
    1182           14 : bool MeiImporter::readLinesWithSmufl(pugi::xml_node parentNode, StringList& lines)
    1183              : {
    1184              :     // A list of segmented text or smufl text blocks
    1185           14 :     Convert::textWithSmufl lineBlocks;
    1186           14 :     String line;
    1187              : 
    1188           40 :     for (pugi::xml_node child : parentNode.children()) {
    1189              :         // Plain text, add it as is to the text block list
    1190           26 :         if (child.type() == pugi::node_pcdata) {
    1191           16 :             lineBlocks.push_back(std::make_pair(false, String(child.text().as_string())));
    1192              :         }
    1193              :         // A rend, check is the content is smufl
    1194           10 :         else if (this->isNode(child, u"rend")) {
    1195            9 :             libmei::Rend meiRend;
    1196            9 :             meiRend.Read(child);
    1197            9 :             bool isSmufl = (meiRend.HasGlyphAuth() && meiRend.GetGlyphAuth() == SMUFL_AUTH);
    1198              :             // If smufl, add its text as smufl, otherwise as text
    1199            9 :             lineBlocks.push_back(std::make_pair(isSmufl, String(child.first_child().text().as_string())));
    1200            9 :         }
    1201              :         // A line break, already convert the current text blocks
    1202            1 :         else if (this->isNode(child, u"lb")) {
    1203            1 :             Convert::textFromMEI(line, lineBlocks);
    1204            1 :             lines.push_back(line);
    1205            1 :             lineBlocks.clear();
    1206              :         } else {
    1207            0 :             this->addLog("skipping child element", child);
    1208              :         }
    1209              :     }
    1210              : 
    1211              :     // At the end, convert the remaining text blocks
    1212           14 :     Convert::textFromMEI(line, lineBlocks);
    1213           14 :     lines.push_back(line);
    1214              : 
    1215           14 :     return true;
    1216           14 : }
    1217              : 
    1218              : /**
    1219              :  * Read the staff definition.
    1220              :  * Stores clef, time signature and key signature into the corresponding global maps.
    1221              :  */
    1222              : 
    1223           82 : bool MeiImporter::readStaffDefs(pugi::xml_node parentNode)
    1224              : {
    1225           82 :     bool warning = false;
    1226              : 
    1227           82 :     pugi::xpath_node_set staffDefs = parentNode.select_nodes(".//staffDef");
    1228              :     // No critical error here if not staffDefs is empty because we can all this from an empty <scoreDef> change
    1229              : 
    1230          219 :     for (pugi::xpath_node staffDefXpathNode : staffDefs) {
    1231          137 :         libmei::StaffDef meiStaffDef;
    1232          137 :         meiStaffDef.Read(staffDefXpathNode.node());
    1233              : 
    1234              :         // Mapped index from MEI staff@n to MuseScore index
    1235          137 :         const int staffIdx = this->getStaffIndex(meiStaffDef.GetN());
    1236              : 
    1237          137 :         pugi::xml_node clefNode = staffDefXpathNode.node().select_node(".//clef").node();
    1238          137 :         if (clefNode) {
    1239          125 :             libmei::Clef meiClef;
    1240          125 :             meiClef.Read(clefNode);
    1241          125 :             m_clefs[staffIdx] = Convert::clefFromMEI(meiClef, warning);
    1242          125 :             if (warning) {
    1243            0 :                 this->addLog("clef", clefNode);
    1244              :             }
    1245              :             //staff->setDefaultClefType(ClefTypeList(Convert::clefFromMEI(meiClef)));
    1246          137 :         } else if (meiStaffDef.HasClefShape() && meiStaffDef.HasClefLine()) {
    1247            0 :             m_clefs[staffIdx] = Convert::clefFromMEI(meiStaffDef, warning);
    1248            0 :             if (warning) {
    1249            0 :                 this->addLog("clef", staffDefXpathNode.node());
    1250              :             }
    1251              :             //staff->setDefaultClefType(ClefTypeList(Convert::clefFromMEI(meiStaffDef)));
    1252              :         }
    1253              :         //m_clefs[meiStaffDef.GetN()] = staff->defaultClefType();
    1254              : 
    1255              :         // try to import MEI from other applications
    1256          137 :         pugi::xml_node meterSigNode = staffDefXpathNode.node().select_node(".//meterSig").node();
    1257          137 :         if (meterSigNode) {
    1258            0 :             meiStaffDef.SetMeterCount(meiStaffDef.AttMeterSigDefaultLog::StrToMetercountPair(meterSigNode.attribute("count").value()));
    1259            0 :             meiStaffDef.SetMeterUnit(meterSigNode.attribute("unit").as_int());
    1260            0 :             meiStaffDef.SetMeterSym(meiStaffDef.AttMeterSigDefaultLog::StrToMetersign(meterSigNode.attribute("sym").value()));
    1261              :         }
    1262          137 :         if (meiStaffDef.HasMeterSym() || meiStaffDef.HasMeterCount()) {
    1263          129 :             m_timeSigs[staffIdx] = Convert::meterFromMEI(meiStaffDef, warning);
    1264          129 :             if (warning) {
    1265            0 :                 this->addLog("meter signature", staffDefXpathNode.node());
    1266              :             }
    1267              :         }
    1268              : 
    1269          137 :         if (meiStaffDef.HasKeysig()) {
    1270           21 :             m_keySigs[staffIdx] = Convert::keyFromMEI(meiStaffDef.GetKeysig(), warning);
    1271           21 :             if (warning) {
    1272            0 :                 this->addLog("key signature", staffDefXpathNode.node());
    1273              :             }
    1274          116 :         } else if (pugi::xml_node keySigNode = staffDefXpathNode.node().select_node(".//keySig").node()) {
    1275            0 :             m_keySigs[staffIdx] = Convert::keyFromMEI(
    1276            0 :                 meiStaffDef.AttKeySigDefaultLog::StrToKeysignature(keySigNode.attribute("sig").value()), warning);
    1277            0 :             if (warning) {
    1278            0 :                 this->addLog("key signature", keySigNode);
    1279              :             }
    1280              :         }
    1281          137 :     }
    1282              : 
    1283           82 :     return true;
    1284           82 : }
    1285              : 
    1286              : /**
    1287              :  * Read the staffGrp.
    1288              :  */
    1289              : 
    1290           99 : bool MeiImporter::readStaffGrps(pugi::xml_node parentNode, int& staffSpan, int column, size_t& idx)
    1291              : {
    1292           99 :     pugi::xpath_node_set children = parentNode.select_nodes("./*");
    1293              : 
    1294           99 :     bool success = true;
    1295              : 
    1296              :     // Loop through all staffDef and staffGrp
    1297          309 :     for (pugi::xpath_node child : children) {
    1298          210 :         if (isNode(child.node(), u"staffDef")) {
    1299          125 :             staffSpan++;
    1300          125 :             idx++;
    1301           85 :         } else if (isNode(child.node(), u"staffGrp")) {
    1302           35 :             libmei::StaffGrp meiStaffGrp;
    1303           35 :             meiStaffGrp.Read(child.node());
    1304           35 :             Convert::BracketStruct bracketSt = Convert::staffGrpFromMEI(meiStaffGrp);
    1305           35 :             if (!meiStaffGrp.HasSymbol()) {
    1306            0 :                 bracketSt.bracketType = Convert::symbolFromMEI(
    1307            0 :                     meiStaffGrp.AttStaffGroupingSym::StrToStaffGroupingSymSymbol(
    1308            0 :                         child.node().child("grpSym").attribute("symbol").value()));
    1309              :             }
    1310              : 
    1311           35 :             staff_idx_t startIdx = idx;
    1312           35 :             EditStaffBrackets::setBracketType(m_score, startIdx, column, bracketSt.bracketType);
    1313              : 
    1314           35 :             int childStaffSpan = 0;
    1315              :             // Recursive call
    1316           35 :             success = success && this->readStaffGrps(child.node(), childStaffSpan, column + 1, idx);
    1317              : 
    1318              :             // Now we know the spanning of the group
    1319           35 :             EditStaffBrackets::setBracketSpan(m_score, startIdx, column, childStaffSpan);
    1320              :             // We can also set the barline spanning - staff by staff since this is what MuseScore seems to do by default
    1321           35 :             if (bracketSt.barLineSpan > 0) {
    1322           31 :                 size_t staffIdxStart = idx - static_cast<size_t>(childStaffSpan);
    1323           31 :                 size_t staffIdxEnd = idx - 1;
    1324           83 :                 for (size_t staffIdx = staffIdxStart; staffIdx < staffIdxEnd; staffIdx++) {
    1325           52 :                     Staff* currentStaff = m_score->staff(staffIdx);
    1326           52 :                     if (currentStaff) {
    1327           52 :                         currentStaff->setBarLineSpan(true);
    1328              :                     }
    1329              :                 }
    1330              :             }
    1331              : 
    1332           35 :             staffSpan += childStaffSpan;
    1333           35 :         }
    1334              :     }
    1335              : 
    1336           99 :     return success;
    1337           99 : }
    1338              : 
    1339              : /**
    1340              :  * Read section content, including through a recursive call for nested section elements.
    1341              :  */
    1342              : 
    1343           68 : bool MeiImporter::readSectionElements(pugi::xml_node parentNode)
    1344              : {
    1345           68 :     bool success = true;
    1346              : 
    1347           68 :     libmei::Section meiSection;
    1348           68 :     meiSection.Read(parentNode);
    1349              : 
    1350           68 :     if (meiSection.HasRestart() && meiSection.GetRestart() == libmei::BOOLEAN_true) {
    1351            0 :         MeasureBase* lastMeasureBase = !m_score->measures()->empty() ? m_score->measures()->last() : nullptr;
    1352            0 :         if (lastMeasureBase) {
    1353            0 :             m_score->insertBox(ElementType::HBOX, lastMeasureBase);
    1354              :         }
    1355              :     }
    1356              : 
    1357           68 :     pugi::xpath_node_set elements = parentNode.select_nodes("./*");
    1358          494 :     for (pugi::xpath_node xpathNode : elements) {
    1359          426 :         std::string elementName = std::string(xpathNode.node().name());
    1360          426 :         if (elementName == "section") {
    1361            0 :             success = success && this->readSectionElements(xpathNode.node());
    1362          426 :         } else if (elementName == "ending") {
    1363            4 :             success = success && this->readEnding(xpathNode.node());
    1364          422 :         } else if (elementName == "measure") {
    1365          404 :             success = success && this->readMeasure(xpathNode.node());
    1366           18 :         } else if (elementName == "pb") {
    1367            0 :             success = success && this->readPb(xpathNode.node());
    1368           18 :         } else if (elementName == "sb") {
    1369            0 :             success = success && this->readSb(xpathNode.node());
    1370           18 :         } else if (elementName == "scoreDef") {
    1371           18 :             success = success && this->readScoreDef(xpathNode.node(), false);
    1372              :         }
    1373          426 :     }
    1374              : 
    1375              :     // Post-processing adjustment for the last barLine.
    1376           68 :     if (m_score->measures()->last() && m_score->measures()->last()->isMeasure()) {
    1377           68 :         Measure* measure = toMeasure(m_score->measures()->last());
    1378           68 :         if (!measure->endBarLine()) {
    1379           10 :             this->addEndBarLineToMeasure(measure, BarLineType::NORMAL);
    1380              :         }
    1381              :     }
    1382              : 
    1383           68 :     return success;
    1384           68 : }
    1385              : 
    1386              : /**
    1387              :  * Read ending and its content.
    1388              :  * Spanning of the ending is set using m_endingStart and m_endingEnd measures
    1389              :  */
    1390              : 
    1391            4 : bool MeiImporter::readEnding(pugi::xml_node endingNode)
    1392              : {
    1393            4 :     bool success = true;
    1394              : 
    1395              :     bool warning;
    1396            4 :     libmei::Ending meiEnding;
    1397            4 :     meiEnding.Read(endingNode);
    1398              : 
    1399            4 :     m_readingEnding = true;
    1400              : 
    1401            4 :     success = success && this->readSectionElements(endingNode);
    1402              : 
    1403            4 :     if (!m_endingStart || !m_endingEnd) {
    1404            0 :         success = false;
    1405              :     } else {
    1406            4 :         Volta* volta = Factory::createVolta(m_score->dummy());
    1407            4 :         Convert::endingFromMEI(volta, meiEnding, warning);
    1408            4 :         this->readXmlId(volta, meiEnding.m_xmlId);
    1409            4 :         volta->setTrack(0);
    1410            4 :         volta->setTrack2(0);
    1411            4 :         volta->setTick(m_endingStart->tick());
    1412            4 :         volta->setTick2(m_endingEnd->endTick());
    1413            4 :         m_score->addElement(volta);
    1414              :     }
    1415              : 
    1416            4 :     m_readingEnding = false;
    1417            4 :     m_endingStart = nullptr;
    1418            4 :     m_endingEnd = nullptr;
    1419              : 
    1420            4 :     return success;
    1421            4 : }
    1422              : 
    1423              : /**
    1424              :  * Read measure and its content.
    1425              :  * Sets m_endingStart and m_endingEnd pointers as appropriate.
    1426              :  * Try to manage measure offset looking at MEI measure@n (num-like values).
    1427              :  * Adjust various flags (end barline, repeat counts).
    1428              :  * Reads measure control events.
    1429              :  */
    1430              : 
    1431          404 : bool MeiImporter::readMeasure(pugi::xml_node measureNode)
    1432              : {
    1433          404 :     bool success = true;
    1434              : 
    1435              :     bool warning;
    1436          404 :     libmei::Measure meiMeasure;
    1437          404 :     meiMeasure.Read(measureNode);
    1438          404 :     Convert::MeasureStruct measureSt = Convert::measureFromMEI(meiMeasure, warning);
    1439              : 
    1440          404 :     Measure* measure = Factory::createMeasure(m_score);
    1441          404 :     this->readXmlId(measure, meiMeasure.m_xmlId);
    1442          404 :     measure->setTick(m_ticks);
    1443          404 :     measure->setTimesig(m_currentTimeSig);
    1444              : 
    1445          404 :     if (m_readingEnding) {
    1446            6 :         if (!m_endingStart) {
    1447            4 :             m_endingStart = measure;
    1448              :         }
    1449            6 :         m_endingEnd = measure;
    1450              :     }
    1451              : 
    1452              :     // Try to manage measure offsets
    1453          404 :     if (measureSt.n) {
    1454          400 :         m_lastMeasureN++;
    1455              :         // The offset might be positive or negative
    1456              :         // For example, two measure 9a and 9b, with the second with a -1 offset
    1457          400 :         if (measureSt.n != m_lastMeasureN) {
    1458            0 :             measure->setMeasureNumberOffset(measureSt.n - m_lastMeasureN);
    1459            0 :             m_lastMeasureN = measureSt.n;
    1460              :         }
    1461              :     }
    1462          404 :     measure->setExcludeFromNumbering(measureSt.irregular);
    1463              : 
    1464          404 :     Fraction measureTicks(0, 1);
    1465          404 :     success = success & this->readStaves(measureNode, measure, measureTicks);
    1466              :     // Make sure we correct empty content because this would crash MuseScore
    1467          404 :     if (measureTicks.isZero()) {
    1468            2 :         measureTicks = m_currentTimeSig;
    1469            2 :         LOGD() << "MeiImporter::readMeasure empty content in " << meiMeasure.GetN();
    1470              :     }
    1471          404 :     measure->setTicks(measureTicks);
    1472              : 
    1473          404 :     success = success & this->readControlEvents(measureNode, measure);
    1474              : 
    1475          404 :     measure->setRepeatStart(measureSt.repeatStart);
    1476          404 :     if (measureSt.repeatEnd) {
    1477            6 :         measure->setRepeatEnd(true);
    1478          398 :     } else if (measureSt.endBarLineType != BarLineType::NORMAL) {
    1479           66 :         this->addEndBarLineToMeasure(measure, measureSt.endBarLineType);
    1480              :     }
    1481              : 
    1482          404 :     if (measureSt.repeatCount) {
    1483            6 :         measure->setRepeatCount(measureSt.repeatCount);
    1484              :     }
    1485              : 
    1486          404 :     m_score->measures()->append(measure);
    1487          404 :     m_ticks += measure->ticks();
    1488              : 
    1489          404 :     m_lastMeasure = measure;
    1490              : 
    1491          404 :     return success;
    1492          404 : }
    1493              : 
    1494              : /**
    1495              :  * Read page begin.
    1496              :  * Only pb with a BREAK_TYPE `@type` will be imported as user added breaks
    1497              :  */
    1498              : 
    1499            0 : bool MeiImporter::readPb(pugi::xml_node pbNode)
    1500              : {
    1501            0 :     if (!configuration()->meiImportLayout()) {
    1502            0 :         return true;
    1503              :     }
    1504              : 
    1505            0 :     libmei::Pb meiPb;
    1506            0 :     meiPb.Read(pbNode);
    1507              : 
    1508            0 :     if (meiPb.HasType() && Convert::hasTypeValue(meiPb.GetType(), BREAK_TYPE)) {
    1509            0 :         this->addLayoutBreakToMeasure(m_lastMeasure, LayoutBreakType::PAGE);
    1510              :     }
    1511              : 
    1512            0 :     return true;
    1513            0 : }
    1514              : 
    1515              : /**
    1516              :  * Read system begin.
    1517              :  * Only sb with a BREAK_TYPE `@type` will be imported as user added breaks
    1518              :  */
    1519              : 
    1520            0 : bool MeiImporter::readSb(pugi::xml_node pbNode)
    1521              : {
    1522            0 :     if (!configuration()->meiImportLayout()) {
    1523            0 :         return true;
    1524              :     }
    1525              : 
    1526            0 :     libmei::Sb meiSb;
    1527            0 :     meiSb.Read(pbNode);
    1528              : 
    1529            0 :     if (meiSb.HasType() && Convert::hasTypeValue(meiSb.GetType(), BREAK_TYPE)) {
    1530            0 :         this->addLayoutBreakToMeasure(m_lastMeasure, LayoutBreakType::LINE);
    1531              :     }
    1532              : 
    1533            0 :     return true;
    1534            0 : }
    1535              : 
    1536              : /**
    1537              :  * Read all staves of a measure and their content.
    1538              :  * Lookup (and clear) the time signature and key signature maps to add them if necessary.
    1539              :  */
    1540              : 
    1541          404 : bool MeiImporter::readStaves(pugi::xml_node parentNode, Measure* measure, Fraction& measureTicks)
    1542              : {
    1543          404 :     IF_ASSERT_FAILED(measure) {
    1544            0 :         return false;
    1545              :     }
    1546              : 
    1547          404 :     bool success = true;
    1548              : 
    1549          404 :     pugi::xpath_node_set staves = parentNode.select_nodes("./staff");
    1550              :     // Critical error
    1551          404 :     if (staves.empty()) {
    1552            0 :         success = false;
    1553              :     }
    1554              : 
    1555         1054 :     for (pugi::xpath_node xpathNode : staves) {
    1556          650 :         libmei::Staff meiStaff;
    1557          650 :         meiStaff.Read(xpathNode.node());
    1558          650 :         const int staffIdx = this->getStaffIndex(meiStaff.GetN());
    1559              : 
    1560              :         // Either the staffDef specific value (if any), the scoreDef one (if any) or nothing
    1561          650 :         int timeSigIdx = m_timeSigs.count(staffIdx) ? staffIdx : m_timeSigs.count(SCOREDEF_IDX) ? SCOREDEF_IDX : MEI_UNSET;
    1562          650 :         if (timeSigIdx != MEI_UNSET) {
    1563          149 :             Segment* segment = measure->getSegment(SegmentType::TimeSig, Fraction::fromTicks(0) + measure->tick());
    1564          149 :             TimeSig* timeSig = Factory::createTimeSig(segment);
    1565          149 :             timeSig->setTrack(staffIdx * VOICES);
    1566          149 :             timeSig->setSig(m_timeSigs.at(timeSigIdx).first, m_timeSigs.at(timeSigIdx).second);
    1567          149 :             segment->add(timeSig);
    1568              :             // Erase the staffDef specific values (add them only for this staff).
    1569          149 :             m_timeSigs.erase(staffIdx);
    1570              :         }
    1571              : 
    1572          650 :         int keySigIdx = m_keySigs.count(staffIdx) ? staffIdx : m_keySigs.count(SCOREDEF_IDX) ? SCOREDEF_IDX : MEI_UNSET;
    1573          650 :         if (keySigIdx != MEI_UNSET) {
    1574           21 :             Segment* segment = measure->getSegment(SegmentType::KeySig, Fraction::fromTicks(0) + measure->tick());
    1575           21 :             KeySigEvent ksEvent;
    1576           21 :             ksEvent.setKey(m_keySigs.at(keySigIdx));
    1577           21 :             ksEvent.setConcertKey(m_keySigs.at(keySigIdx));
    1578           21 :             KeySig* keySig = Factory::createKeySig(segment);
    1579           21 :             keySig->setTrack(staffIdx * VOICES);
    1580           21 :             keySig->setKeySigEvent(ksEvent);
    1581           21 :             segment->add(keySig);
    1582           21 :             m_keySigs.erase(staffIdx);
    1583           21 :         }
    1584              : 
    1585          650 :         if (m_clefs.count(staffIdx)) {
    1586          125 :             Segment* segment = measure->getSegment(SegmentType::HeaderClef, Fraction::fromTicks(0) + measure->tick());
    1587          125 :             Clef* clef = Factory::createClef(segment);
    1588          125 :             clef->setClefType(m_clefs.at(staffIdx));
    1589          125 :             clef->setTrack(staffIdx * VOICES);
    1590          125 :             segment->add(clef);
    1591          125 :             m_clefs.erase(staffIdx);
    1592              :         }
    1593              : 
    1594          650 :         success = success && this->readLayers(xpathNode.node(), measure, staffIdx, measureTicks);
    1595          650 :     }
    1596              : 
    1597              :     // Erase the scoreDef values (add them only for this measure)
    1598          404 :     m_timeSigs.erase(SCOREDEF_IDX);
    1599          404 :     m_keySigs.erase(SCOREDEF_IDX);
    1600              : 
    1601          404 :     return success;
    1602          404 : }
    1603              : 
    1604              : /**
    1605              :  * Read the layer and its content.
    1606              :  * Also read grace notes not within a graceGrp for MEI files not written by MuseScore.
    1607              :  * Relies on the m_lastChord pointer for adding grace notes to the correct ChordRest
    1608              :  */
    1609              : 
    1610          650 : bool MeiImporter::readLayers(pugi::xml_node parentNode, Measure* measure, int staffN, Fraction& measureTicks)
    1611              : {
    1612          650 :     IF_ASSERT_FAILED(measure) {
    1613            0 :         return false;
    1614              :     }
    1615              : 
    1616          650 :     bool success = true;
    1617              : 
    1618          650 :     pugi::xpath_node_set layers = parentNode.select_nodes("./layer");
    1619              :     // Critical error
    1620          650 :     if (layers.empty()) {
    1621            0 :         success = false;
    1622              :     }
    1623              : 
    1624          650 :     size_t i = 0;
    1625         1326 :     for (pugi::xpath_node xpathNode : layers) {
    1626              :         // We cannot have more than 4 voices in MuseScore
    1627          676 :         if (i >= VOICES) {
    1628            0 :             Convert::logs.push_back(String("More than %1 layers in a staff in not supported. Their content will not be imported.").arg(
    1629              :                                         VOICES));
    1630            0 :             break;
    1631              :         }
    1632          676 :         libmei::Layer meiLayer;
    1633          676 :         meiLayer.Read(xpathNode.node());
    1634              : 
    1635          676 :         m_lastChord = nullptr;
    1636          676 :         Fraction ticks;
    1637          676 :         int track = staffN * VOICES + static_cast<int>(i);
    1638          676 :         success = success && this->readElements(xpathNode.node(), measure, track, ticks);
    1639          676 :         measureTicks = std::max(measureTicks, ticks);
    1640          676 :         i++;
    1641          676 :         this->clearGraceNotes();
    1642          676 :     }
    1643              : 
    1644          650 :     return success;
    1645          650 : }
    1646              : 
    1647              : /**
    1648              :  * Read the layer content, including through a recursive call for nested elements.
    1649              :  */
    1650              : 
    1651          926 : bool MeiImporter::readElements(pugi::xml_node parentNode, Measure* measure, int track, Fraction& ticks)
    1652              : {
    1653          926 :     IF_ASSERT_FAILED(measure) {
    1654            0 :         return false;
    1655              :     }
    1656              : 
    1657          926 :     bool success = true;
    1658              : 
    1659          926 :     pugi::xpath_node_set elements = parentNode.select_nodes("./*");
    1660         3047 :     for (pugi::xpath_node xpathNode : elements) {
    1661         2121 :         std::string elementName = std::string(xpathNode.node().name());
    1662         2121 :         if (elementName == "beam") {
    1663          150 :             success = success && this->readBeam(xpathNode.node(), measure, track, ticks);
    1664         1971 :         } else if (elementName == "bTrem") {
    1665           22 :             success = success && this->readBTrem(xpathNode.node(), measure, track, ticks);
    1666         1949 :         } else if (elementName == "chord") {
    1667          134 :             success = success && this->readChord(xpathNode.node(), measure, track, ticks);
    1668         1815 :         } else if (elementName == "clef" && !m_readingGraceNotes) {
    1669           31 :             success = success && this->readClef(xpathNode.node(), measure, track, ticks);
    1670         1784 :         } else if (elementName == "fTrem") {
    1671           12 :             success = success && this->readFTrem(xpathNode.node(), measure, track, ticks);
    1672         1772 :         } else if (elementName == "graceGrp" && !m_readingGraceNotes) {
    1673           25 :             success = success && this->readGraceGrp(xpathNode.node(), measure, track, ticks);
    1674         1747 :         } else if (elementName == "mRest" && !m_readingGraceNotes) {
    1675          195 :             success = success && this->readMRest(xpathNode.node(), measure, track, ticks);
    1676         1552 :         } else if (elementName == "note") {
    1677         1444 :             success = success && this->readNote(xpathNode.node(), measure, track, ticks);
    1678          108 :         } else if (elementName == "mRpt") {
    1679            2 :             success = success && this->readMRpt(xpathNode.node(), measure, track, ticks);
    1680          106 :         } else if (elementName == "rest" && !m_readingGraceNotes) {
    1681           65 :             success = success && this->readRest(xpathNode.node(), measure, track, ticks);
    1682           41 :         } else if (elementName == "space" && !m_readingGraceNotes) {
    1683            0 :             success = success && this->readSpace(xpathNode.node(), measure, track, ticks);
    1684           41 :         } else if (elementName == "tuplet" && !m_readingGraceNotes) {
    1685           41 :             success = success && this->readTuplet(xpathNode.node(), measure, track, ticks);
    1686              :         } else {
    1687            0 :             success = success && this->readElements(xpathNode.node(), measure, track, ticks);
    1688              :         }
    1689         2121 :     }
    1690          926 :     return success;
    1691          926 : }
    1692              : 
    1693              : /**
    1694              :  * Loop through the content of the MEI <note> or <chord> and read the artics
    1695              :  */
    1696              : 
    1697         1578 : bool MeiImporter::readArtics(pugi::xml_node parentNode, Chord* chord)
    1698              : {
    1699         1578 :     IF_ASSERT_FAILED(chord) {
    1700            0 :         return false;
    1701              :     }
    1702              : 
    1703         1578 :     bool success = true;
    1704              : 
    1705         1578 :     pugi::xpath_node_set elements = parentNode.select_nodes("./artic");
    1706         1643 :     for (pugi::xpath_node xpathNode : elements) {
    1707           65 :         success = success && this->readArtic(xpathNode.node(), chord);
    1708              :     }
    1709              : 
    1710         1578 :     return success;
    1711         1578 : }
    1712              : 
    1713              : /**
    1714              :  * Read an artic.
    1715              :  */
    1716              : 
    1717           65 : bool MeiImporter::readArtic(pugi::xml_node articNode, Chord* chord)
    1718              : {
    1719           65 :     IF_ASSERT_FAILED(chord) {
    1720            0 :         return false;
    1721              :     }
    1722              : 
    1723           65 :     bool warning = false;
    1724           65 :     libmei::Artic meiArtic;
    1725           65 :     meiArtic.Read(articNode);
    1726              : 
    1727           65 :     Articulation* articulation = toArticulation(this->addToChordRest(meiArtic, nullptr, chord));
    1728           65 :     if (!articulation) {
    1729              :         // Warning message given in MeiImporter::addToChordRest
    1730            0 :         return true;
    1731              :     }
    1732              : 
    1733           65 :     Convert::articFromMEI(articulation, meiArtic, warning);
    1734              : 
    1735           65 :     if (articulation->symId() == SymId::noSym) {
    1736              :         // remove the articulation if it has no symbol
    1737           12 :         chord->remove(articulation);
    1738           12 :         delete articulation;
    1739              :     }
    1740              : 
    1741           65 :     return true;
    1742           65 : }
    1743              : 
    1744           61 : bool MeiImporter::readArtic(pugi::xml_node articNode, Note* note)
    1745              : {
    1746           61 :     IF_ASSERT_FAILED(note) {
    1747            0 :         return false;
    1748              :     }
    1749              : 
    1750           61 :     bool warning = false;
    1751           61 :     libmei::Artic meiArtic;
    1752           61 :     meiArtic.Read(articNode);
    1753              : 
    1754           61 :     Chord* chord = note->chord();
    1755           61 :     ChordLine* chordLine = nullptr;
    1756              : 
    1757          183 :     if (meiArtic.HasArtic()
    1758           61 :         && (meiArtic.GetArtic().at(0) >= libmei::ARTICULATION_doit && meiArtic.GetArtic().at(0) < libmei::ARTICULATION_longfall)) {
    1759           12 :         chordLine = Factory::createChordLine(chord);
    1760              :     }
    1761              : 
    1762           61 :     if (!chordLine) {
    1763              :         // Warning message given in MeiImporter::addToChordRest
    1764           49 :         return true;
    1765              :     }
    1766              : 
    1767           12 :     this->readXmlId(chordLine, meiArtic.m_xmlId);
    1768           12 :     Convert::articFromMEI(chordLine, meiArtic, warning);
    1769           12 :     chordLine->setTrack(note->track());
    1770           12 :     chord->add(chordLine);
    1771           12 :     chordLine->setNote(note);
    1772              : 
    1773           12 :     return true;
    1774           61 : }
    1775              : 
    1776              : /**
    1777              :  * Read a beam.
    1778              :  * Set MuseScore flags based on custom `@type` values.
    1779              :  */
    1780              : 
    1781          150 : bool MeiImporter::readBeam(pugi::xml_node beamNode, Measure* measure, int track, Fraction& ticks)
    1782              : {
    1783          150 :     IF_ASSERT_FAILED(measure) {
    1784            0 :         return false;
    1785              :     }
    1786              : 
    1787          150 :     bool success = true;
    1788              : 
    1789          150 :     libmei::Beam meiBeam;
    1790          150 :     meiBeam.Read(beamNode);
    1791              : 
    1792          150 :     success = readElements(beamNode, measure, track, ticks);
    1793              : 
    1794              :     // Reset the beam BEGIN16 and BEGIN32 mode
    1795          150 :     if (!m_readingGraceNotes) {
    1796          141 :         m_beamBeginMode = BeamMode::AUTO;
    1797              :     } else {
    1798            9 :         m_graceBeamBeginMode = BeamMode::AUTO;
    1799              :     }
    1800              : 
    1801          150 :     return success;
    1802          150 : }
    1803              : 
    1804              : /**
    1805              :  * Read a bTrem.
    1806              :  * Set MuseScore TremoloType.
    1807              :  */
    1808              : 
    1809           22 : bool MeiImporter::readBTrem(pugi::xml_node bTremNode, Measure* measure, int track, Fraction& ticks)
    1810              : {
    1811           22 :     IF_ASSERT_FAILED(measure) {
    1812            0 :         return false;
    1813              :     }
    1814              : 
    1815           22 :     bool success = true;
    1816              : 
    1817           22 :     libmei::BTrem meiBTrem;
    1818           22 :     meiBTrem.Read(bTremNode);
    1819           22 :     m_tremoloId = meiBTrem.m_xmlId;
    1820              : 
    1821           22 :     success = readElements(bTremNode, measure, track, ticks);
    1822              : 
    1823           22 :     m_tremoloId.clear();
    1824              : 
    1825           22 :     return success;
    1826           22 : }
    1827              : 
    1828              : /**
    1829              :  * Read a chord and its content (note elements).
    1830              :  */
    1831              : 
    1832          134 : bool MeiImporter::readChord(pugi::xml_node chordNode, Measure* measure, int track, Fraction& ticks)
    1833              : {
    1834          134 :     IF_ASSERT_FAILED(measure) {
    1835            0 :         return false;
    1836              :     }
    1837              : 
    1838          134 :     libmei::Chord meiChord;
    1839          134 :     meiChord.Read(chordNode);
    1840              : 
    1841          134 :     Fraction chordTicks = ticks;
    1842              : 
    1843              :     // Support for @grace without <graceGrp>
    1844          134 :     this->readGracedAtt(meiChord);
    1845          134 :     Chord* chord = toChord(addChordRest(chordNode, measure, track, meiChord, ticks, false));
    1846          134 :     this->readStemsAtt(chord, meiChord);
    1847          134 :     this->readArtics(chordNode, chord);
    1848              : 
    1849          134 :     if (!m_tremoloId.empty()) {
    1850           16 :         if (isNode(chordNode.parent(), u"fTrem")) {
    1851            0 :             TremoloTwoChord* tremolo = Factory::createTremoloTwoChord(chord);
    1852            0 :             m_uids->reg(tremolo, m_tremoloId);
    1853            0 :             tremolo->setTremoloType(m_tremoloType);
    1854            0 :             chord->add(tremolo);
    1855              :         } else {
    1856           16 :             TremoloType ttype = Convert::stemModFromMEI(meiChord.GetStemMod());
    1857           16 :             TremoloSingleChord* tremolo = Factory::createTremoloSingleChord(chord);
    1858           16 :             this->readXmlId(tremolo, m_tremoloId);
    1859           16 :             tremolo->setTremoloType(ttype);
    1860           16 :             chord->add(tremolo);
    1861              :         }
    1862              :     }
    1863              : 
    1864          134 :     pugi::xpath_node_set notes = chordNode.select_nodes(".//note");
    1865          505 :     for (pugi::xpath_node xpathNode : notes) {
    1866          371 :         this->readNote(xpathNode.node(), measure, track, chordTicks, chord);
    1867              :     }
    1868              : 
    1869          134 :     return true;
    1870          134 : }
    1871              : 
    1872              : /*
    1873              :  * Read a clef.
    1874              :  */
    1875              : 
    1876           31 : bool MeiImporter::readClef(pugi::xml_node clefNode, Measure* measure, int track, Fraction& ticks)
    1877              : {
    1878           31 :     IF_ASSERT_FAILED(measure) {
    1879            0 :         return false;
    1880              :     }
    1881              : 
    1882           31 :     bool warning = false;
    1883           31 :     libmei::Clef meiClef;
    1884           31 :     meiClef.Read(clefNode);
    1885              : 
    1886           31 :     Segment* segment = measure->getSegment(SegmentType::Clef, ticks + measure->tick());
    1887           31 :     Clef* clef = Factory::createClef(segment);
    1888           31 :     Convert::colorFromMEI(clef, meiClef);
    1889           31 :     this->readXmlId(clef, meiClef.m_xmlId);
    1890           31 :     clef->setClefType(ClefTypeList(Convert::clefFromMEI(meiClef, warning)));
    1891           31 :     if (warning) {
    1892            0 :         this->addLog("clef", clefNode);
    1893              :     }
    1894              : 
    1895           31 :     clef->setTrack(track);
    1896              :     //clef->setGenerated(true);
    1897           31 :     segment->add(clef);
    1898              : 
    1899           31 :     return true;
    1900           31 : }
    1901              : 
    1902              : /**
    1903              :  * Read a fTrem.
    1904              :  * Set MuseScore TremoloType.
    1905              :  */
    1906              : 
    1907           12 : bool MeiImporter::readFTrem(pugi::xml_node fTremNode, Measure* measure, int track, Fraction& ticks)
    1908              : {
    1909           12 :     IF_ASSERT_FAILED(measure) {
    1910            0 :         return false;
    1911              :     }
    1912              : 
    1913           12 :     bool success = true;
    1914              : 
    1915           12 :     libmei::FTrem meiFTrem;
    1916           12 :     meiFTrem.Read(fTremNode);
    1917           12 :     m_tremoloId = meiFTrem.m_xmlId;
    1918              : 
    1919           12 :     bool warning = false;
    1920           12 :     m_tremoloType = Convert::unitdurFromMEI(meiFTrem, warning);
    1921              : 
    1922           12 :     success = readElements(fTremNode, measure, track, ticks);
    1923              : 
    1924           12 :     m_tremoloId.clear();
    1925              : 
    1926           12 :     return success;
    1927           12 : }
    1928              : 
    1929              : /**
    1930              :  * Read a <graceGrp> and adjust the MeiImporter::m_readingGraceNotes flag.
    1931              :  * For <graceGrp> with @attach="pre", add the grace notes read recursively (through readElements) to m_lastChord (if any)
    1932              :  * Otherwise, only reset the flag and adding the grace notes will be performed in MeiImporter::addChordRest when the next <chord> / <note> is read
    1933              :  */
    1934              : 
    1935           25 : bool MeiImporter::readGraceGrp(pugi::xml_node graceGrpNode, Measure* measure, int track, Fraction& ticks)
    1936              : {
    1937           25 :     IF_ASSERT_FAILED(measure) {
    1938            0 :         return false;
    1939              :     }
    1940              : 
    1941           25 :     libmei::GraceGrp meiGraceGrp;
    1942           25 :     meiGraceGrp.Read(graceGrpNode);
    1943              : 
    1944           25 :     bool warning = false;
    1945           25 :     auto [isAfter, noteType] = Convert::gracegrpFromMEI(meiGraceGrp.GetAttach(), meiGraceGrp.GetGrace(), warning);
    1946           25 :     m_graceNoteType = noteType;
    1947              : 
    1948           25 :     m_readingGraceNotes = GraceAsGrp;
    1949              : 
    1950           25 :     this->readElements(graceGrpNode, measure, track, ticks);
    1951              : 
    1952           25 :     if (isAfter) {
    1953            1 :         if (m_lastChord) {
    1954            1 :             this->addGraceNotesToChord(m_lastChord, true);
    1955              :         } else {
    1956            0 :             this->clearGraceNotes();
    1957              :         }
    1958              :     }
    1959              : 
    1960           25 :     m_readingGraceNotes = GraceNone;
    1961              : 
    1962           25 :     return true;
    1963           25 : }
    1964              : 
    1965              : /**
    1966              :  * Read a mRest.
    1967              :  */
    1968              : 
    1969          195 : bool MeiImporter::readMRest(pugi::xml_node mRestNode, Measure* measure, int track, Fraction& ticks)
    1970              : {
    1971          195 :     IF_ASSERT_FAILED(measure) {
    1972            0 :         return false;
    1973              :     }
    1974              : 
    1975          195 :     libmei::MRest meiMRest;
    1976          195 :     meiMRest.Read(mRestNode);
    1977              : 
    1978          195 :     TDuration duration;
    1979              : 
    1980          195 :     Segment* segment = measure->getSegment(SegmentType::ChordRest, ticks + measure->tick());
    1981          195 :     Rest* rest = Factory::createRest(segment, TDuration(DurationType::V_MEASURE));
    1982          195 :     Convert::colorFromMEI(rest, meiMRest);
    1983          195 :     this->readXmlId(rest, meiMRest.m_xmlId);
    1984          195 :     rest->setTicks(m_currentTimeSig);
    1985          195 :     rest->setDurationType(DurationType::V_MEASURE);
    1986          195 :     rest->setTrack(track);
    1987          195 :     segment->add(rest);
    1988              : 
    1989              :     // Also add mRest as @startid / @endid
    1990          195 :     if (m_startIdChordRests.count(meiMRest.m_xmlId)) {
    1991            8 :         m_startIdChordRests[meiMRest.m_xmlId] = rest;
    1992              :     }
    1993          195 :     if (m_endIdChordRests.count(meiMRest.m_xmlId)) {
    1994            0 :         m_endIdChordRests[meiMRest.m_xmlId] = rest;
    1995              :     }
    1996              : 
    1997              :     // The duration is the duration according to the timesig
    1998          195 :     ticks += rest->ticks();
    1999              : 
    2000          195 :     return true;
    2001          195 : }
    2002              : 
    2003              : /**
    2004              :  * Read a mRpt.
    2005              :  */
    2006              : 
    2007            2 : bool MeiImporter::readMRpt(pugi::xml_node mRptNode, Measure* measure, int track, Fraction& ticks)
    2008              : {
    2009            2 :     IF_ASSERT_FAILED(measure) {
    2010            0 :         return false;
    2011              :     }
    2012              : 
    2013            2 :     libmei::MRpt meiMRpt;
    2014            2 :     meiMRpt.Read(mRptNode);
    2015              : 
    2016            2 :     if (meiMRpt.GetExpand() == libmei::BOOLEAN_true) {
    2017            0 :         LOGD() << "MeiImporter::readMRpt cannot expand measure repeats";
    2018              :     }
    2019              : 
    2020            2 :     Segment* segment = measure->getSegment(SegmentType::ChordRest, ticks + measure->tick());
    2021            2 :     MeasureRepeat* measureRepeat = Factory::createMeasureRepeat(segment);
    2022            2 :     Convert::colorFromMEI(measureRepeat, meiMRpt);
    2023            2 :     this->readXmlId(measureRepeat, meiMRpt.m_xmlId);
    2024            2 :     measureRepeat->setTrack(track);
    2025            2 :     measureRepeat->setTicks(measure->ticks());
    2026            2 :     measureRepeat->setNumMeasures(1);
    2027            2 :     measure->setMeasureRepeatCount(1, track2staff(track));
    2028            2 :     segment->add(measureRepeat);
    2029              : 
    2030            2 :     return true;
    2031            2 : }
    2032              : 
    2033              : /**
    2034              :  * Read a note.
    2035              :  */
    2036              : 
    2037         1815 : bool MeiImporter::readNote(pugi::xml_node noteNode, Measure* measure, int track, Fraction& ticks, Chord* chord)
    2038              : {
    2039         1815 :     IF_ASSERT_FAILED(measure) {
    2040            0 :         return false;
    2041              :     }
    2042              : 
    2043         1815 :     bool warning = false;
    2044         1815 :     libmei::Note meiNote;
    2045         1815 :     meiNote.Read(noteNode);
    2046              : 
    2047              :     // Support for @grace without <graceGrp>
    2048         1815 :     if (!chord) {
    2049         1444 :         this->readGracedAtt(meiNote);
    2050              :     }
    2051              : 
    2052         1815 :     pugi::xml_node accidNode = noteNode.select_node(".//accid").node();
    2053         1815 :     libmei::Accid meiAccid;
    2054         1815 :     if (accidNode) {
    2055          310 :         meiAccid.Read(accidNode);
    2056              :     } else {
    2057              :         // Support for non MEI Basic accid and accid.ges encoded in <note> - this is not academic...
    2058         1505 :         meiAccid.Read(noteNode);
    2059              :         // Remove the xml:id read from the note in that case
    2060         1505 :         meiAccid.m_xmlId = "";
    2061              :     }
    2062              : 
    2063         1815 :     Staff* staff = m_score->staff(track2staff(track));
    2064         1815 :     Interval interval = staff->part()->instrument()->transpose();
    2065              : 
    2066         1815 :     Convert::PitchStruct pitchSt = Convert::pitchFromMEI(meiNote, meiAccid, interval, warning);
    2067         1815 :     if (warning) {
    2068            0 :         this->addLog("pitch", noteNode);
    2069            0 :         this->addLog("accidental", accidNode);
    2070              :     }
    2071              : 
    2072         1815 :     if (!chord) {
    2073         1444 :         chord = toChord(addChordRest(noteNode, measure, track, meiNote, ticks, false));
    2074         1444 :         this->readStemsAtt(chord, meiNote);
    2075         1444 :         this->readArtics(noteNode, chord);
    2076         1444 :         this->readVerses(noteNode, chord);
    2077         1444 :         if (!m_tremoloId.empty()) {
    2078           30 :             if (isNode(noteNode.parent(), u"fTrem")) {
    2079           24 :                 TremoloTwoChord* tremolo = Factory::createTremoloTwoChord(chord);
    2080           24 :                 m_uids->reg(tremolo, m_tremoloId);
    2081           24 :                 tremolo->setTremoloType(m_tremoloType);
    2082           24 :                 chord->add(tremolo);
    2083              :             } else {
    2084            6 :                 TremoloType ttype = Convert::stemModFromMEI(meiNote.GetStemMod());
    2085            6 :                 TremoloSingleChord* tremolo = Factory::createTremoloSingleChord(chord);
    2086            6 :                 this->readXmlId(tremolo, m_tremoloId);
    2087            6 :                 tremolo->setTremoloType(ttype);
    2088            6 :                 chord->add(tremolo);
    2089              :             }
    2090              :         }
    2091              :     }
    2092              : 
    2093         1815 :     Note* note = Factory::createNote(chord);
    2094         1815 :     Convert::colorFromMEI(note, meiNote);
    2095         1815 :     this->readXmlId(note, meiNote.m_xmlId);
    2096              : 
    2097              :     // If there is a reference to the note in the MEI, add it the maps (e.g., for ties)
    2098         1815 :     if (m_startIdChordRests.count(meiNote.m_xmlId)) {
    2099          249 :         m_startIdNotes[meiNote.m_xmlId] = note;
    2100              :     }
    2101         1815 :     if (m_endIdChordRests.count(meiNote.m_xmlId)) {
    2102           95 :         m_endIdNotes[meiNote.m_xmlId] = note;
    2103              :     }
    2104              : 
    2105         1815 :     int tpc1 = Transpose::transposeTpc(pitchSt.tpc2, interval, true);
    2106         1815 :     note->setPitch(pitchSt.pitch, tpc1, pitchSt.tpc2);
    2107              : 
    2108         1815 :     if (meiNote.HasVel()) {
    2109            0 :         note->setUserVelocity(meiNote.GetVel());
    2110              :     }
    2111              : 
    2112         1815 :     Accidental* accid = Factory::createAccidental(note);
    2113         1815 :     Convert::colorFromMEI(accid, meiAccid);
    2114         1815 :     this->readXmlId(accid, meiAccid.m_xmlId);
    2115         1815 :     accid->setAccidentalType(pitchSt.accidType);
    2116         1815 :     accid->setBracket(pitchSt.accidBracket);
    2117         1815 :     accid->setRole(pitchSt.accidRole);
    2118         1815 :     note->add(accid);
    2119              : 
    2120         1815 :     note->setTrack(track);
    2121         1815 :     chord->add(note);
    2122              : 
    2123              :     // read chord lines
    2124         1815 :     pugi::xpath_node_set elements = noteNode.select_nodes("./artic");
    2125         1876 :     for (pugi::xpath_node xpathNode : elements) {
    2126           61 :         this->readArtic(xpathNode.node(), note);
    2127              :     }
    2128              : 
    2129         1815 :     return true;
    2130         1815 : }
    2131              : 
    2132              : /**
    2133              :  * Read a rest.
    2134              :  */
    2135              : 
    2136           65 : bool MeiImporter::readRest(pugi::xml_node restNode, Measure* measure, int track, Fraction& ticks)
    2137              : {
    2138           65 :     IF_ASSERT_FAILED(measure) {
    2139            0 :         return false;
    2140              :     }
    2141              : 
    2142           65 :     libmei::Rest meiRest;
    2143           65 :     meiRest.Read(restNode);
    2144              : 
    2145           65 :     Rest* rest = toRest(addChordRest(restNode, measure, track, meiRest, ticks, true));
    2146           65 :     Convert::colorFromMEI(rest, meiRest);
    2147              : 
    2148              :     UNUSED(rest);
    2149              : 
    2150           65 :     return true;
    2151           65 : }
    2152              : 
    2153              : /**
    2154              :  * Read a space.
    2155              :  */
    2156              : 
    2157            0 : bool MeiImporter::readSpace(pugi::xml_node spaceNode, Measure* measure, int track, Fraction& ticks)
    2158              : {
    2159            0 :     IF_ASSERT_FAILED(measure) {
    2160            0 :         return false;
    2161              :     }
    2162              : 
    2163            0 :     libmei::Space meiSpace;
    2164            0 :     meiSpace.Read(spaceNode);
    2165              : 
    2166            0 :     Rest* space = toRest(addChordRest(spaceNode, measure, track, meiSpace, ticks, true));
    2167            0 :     space->setVisible(false);
    2168              : 
    2169            0 :     return true;
    2170            0 : }
    2171              : 
    2172              : /**
    2173              :  * Read a syl
    2174              :  */
    2175              : 
    2176          211 : bool MeiImporter::readSyl(pugi::xml_node sylNode, Lyrics* lyrics, Convert::textWithSmufl& textBlocks, ElisionType elision)
    2177              : {
    2178          211 :     IF_ASSERT_FAILED(lyrics) {
    2179            0 :         return false;
    2180              :     }
    2181              : 
    2182              :     bool warning;
    2183          211 :     libmei::Syl meiSyl;
    2184          211 :     meiSyl.Read(sylNode);
    2185              : 
    2186          211 :     Convert::sylFromMEI(lyrics, meiSyl, elision, warning);
    2187              : 
    2188          211 :     textBlocks.push_back(std::make_pair(false, String(sylNode.text().as_string())));
    2189              : 
    2190          211 :     return true;
    2191          211 : }
    2192              : 
    2193              : /**
    2194              :  * Read a tuplet.
    2195              :  */
    2196              : 
    2197           41 : bool MeiImporter::readTuplet(pugi::xml_node tupletNode, Measure* measure, int track, Fraction& ticks)
    2198              : {
    2199           41 :     IF_ASSERT_FAILED(measure) {
    2200            0 :         return false;
    2201              :     }
    2202              : 
    2203           41 :     if (m_tuplet) {
    2204            0 :         Convert::logs.push_back(u"Nested tuplets are not supported");
    2205            0 :         return false;
    2206              :     }
    2207              : 
    2208           41 :     bool warning = false;
    2209           41 :     bool success = true;
    2210              : 
    2211           41 :     Fraction startTicks = ticks;
    2212           41 :     libmei::Tuplet meiTuplet;
    2213           41 :     meiTuplet.Read(tupletNode);
    2214              : 
    2215           41 :     m_tuplet = Factory::createTuplet(measure);
    2216           41 :     this->readXmlId(m_tuplet, meiTuplet.m_xmlId);
    2217           41 :     Convert::tupletFromMEI(m_tuplet, meiTuplet, warning);
    2218           41 :     if (warning) {
    2219            0 :         this->addLog("tuplet", tupletNode);
    2220              :     }
    2221              : 
    2222           41 :     m_tuplet->setTrack(track);
    2223              : 
    2224           41 :     success = readElements(tupletNode, measure, track, ticks);
    2225              : 
    2226           41 :     Fraction tupletTicks = ticks - startTicks;
    2227              : 
    2228           41 :     TDuration d;
    2229           41 :     d.setVal((tupletTicks / m_tuplet->ratio().denominator()).ticks());
    2230              : 
    2231           41 :     m_tuplet->setBaseLen(d);
    2232           41 :     m_tuplet->setTick(startTicks + measure->tick());
    2233           41 :     m_tuplet->setTicks(tupletTicks);
    2234              : 
    2235           41 :     m_tuplet = nullptr;
    2236              : 
    2237           41 :     return success;
    2238           41 : }
    2239              : 
    2240              : /**
    2241              :  * Loop through the content of the MEI <note> or <chord> and read the verses
    2242              :  */
    2243              : 
    2244         1444 : bool MeiImporter::readVerses(pugi::xml_node parentNode, Chord* chord)
    2245              : {
    2246         1444 :     IF_ASSERT_FAILED(chord) {
    2247            0 :         return false;
    2248              :     }
    2249              : 
    2250         1444 :     bool success = true;
    2251              : 
    2252         1444 :     pugi::xpath_node_set elements = parentNode.select_nodes("./verse");
    2253         1643 :     for (pugi::xpath_node xpathNode : elements) {
    2254          199 :         success = success && this->readVerse(xpathNode.node(), chord);
    2255              :     }
    2256              : 
    2257              :     // The chord as a potential end point for the lyrics to extend (all lyrics no for that track)
    2258         1444 :     this->addChordtoLyricsToExtend(chord);
    2259              : 
    2260         1444 :     return success;
    2261         1444 : }
    2262              : 
    2263              : /**
    2264              :  * Read a verse.
    2265              :  */
    2266              : 
    2267          199 : bool MeiImporter::readVerse(pugi::xml_node verseNode, Chord* chord)
    2268              : {
    2269          199 :     IF_ASSERT_FAILED(chord) {
    2270            0 :         return false;
    2271              :     }
    2272              : 
    2273          199 :     libmei::Verse meiVerse;
    2274          199 :     meiVerse.Read(verseNode);
    2275              : 
    2276          199 :     int no = 0;
    2277          199 :     if (meiVerse.HasN() && std::isdigit(meiVerse.GetN().at(0))) {
    2278          199 :         no = std::stoi(meiVerse.GetN()) - 1;
    2279              :         // Make sure we have no verse number below 0;
    2280          199 :         no = std::max(0, no);
    2281              :     }
    2282              : 
    2283              :     // First check if we have a lyric extension that needs to be ended for that track / no
    2284          199 :     if (this->hasLyricsToExtend(chord->track(), no)) {
    2285            6 :         auto lyricsToExtend = this->getLyricsToExtend(chord->track(), no);
    2286            6 :         this->extendLyrics(lyricsToExtend);
    2287              :         // Remove it from the lyrics to extend
    2288            6 :         m_lyricExtenders.at(chord->track()).erase(no);
    2289              :     }
    2290              : 
    2291          199 :     Lyrics* lyrics = Factory::createLyrics(chord);
    2292          199 :     this->readXmlId(lyrics, meiVerse.m_xmlId);
    2293          199 :     Convert::colorFromMEI(lyrics, meiVerse);
    2294              : 
    2295          199 :     bool success = true;
    2296              : 
    2297              :     // If the verse has a syl with @con="u", add it to the lyrics to extend
    2298          199 :     pugi::xpath_node extender = verseNode.select_node("./syl[@con='u']");
    2299          199 :     if (extender) {
    2300            7 :         m_lyricExtenders[chord->track()][no] = std::make_pair(lyrics, nullptr);
    2301              :     }
    2302              : 
    2303              :     // @place
    2304          199 :     if (meiVerse.HasPlace()) {
    2305            0 :         lyrics->setPlacement(meiVerse.GetPlace()
    2306            0 :                              == libmei::STAFFREL_above ? engraving::PlacementV::ABOVE : engraving::PlacementV::BELOW);
    2307            0 :         lyrics->setPropertyFlags(engraving::Pid::PLACEMENT, engraving::PropertyFlags::UNSTYLED);
    2308              :     }
    2309              : 
    2310              :     // Aggregate the syllable into line blocks
    2311          199 :     Convert::textWithSmufl textBlocks;
    2312          199 :     pugi::xpath_node_set elements = verseNode.select_nodes("./label|./syl");
    2313              : 
    2314              :     // If we have more than one syl we assume to have elision
    2315          199 :     ElisionType elision = (elements.size() > 1) ? ElisionFirst : ElisionNone;
    2316          199 :     size_t sylCount = 0;
    2317              : 
    2318          410 :     for (pugi::xpath_node xpathNode : elements) {
    2319          633 :         if (xpathNode.node().name() == std::string("label")) {
    2320            0 :             textBlocks.push_back(std::make_pair(false, String(xpathNode.node().text().as_string())));
    2321            0 :             textBlocks.push_back(std::make_pair(false, u"\u00A0"));
    2322            0 :             continue;
    2323              :         }
    2324              : 
    2325          211 :         if (sylCount > 0) {
    2326           12 :             textBlocks.push_back(std::make_pair(true, u"\uE551"));
    2327              :         }
    2328              : 
    2329          211 :         success = success && this->readSyl(xpathNode.node(), lyrics, textBlocks, elision);
    2330          211 :         sylCount++;
    2331          211 :         elision = (sylCount == elements.size() - 1) ? ElisionLast : ElisionMiddle;
    2332              :     }
    2333              : 
    2334          199 :     String syllable;
    2335          199 :     Convert::textFromMEI(syllable, textBlocks);
    2336              : 
    2337          199 :     lyrics->setXmlText(syllable);
    2338          199 :     lyrics->setVerse(no);
    2339          199 :     lyrics->initTextStyleType(lyrics->isEven() ? TextStyleType::LYRICS_EVEN : TextStyleType::LYRICS_ODD, /*preserveDifferent*/ true);
    2340          199 :     lyrics->setTrack(chord->track());
    2341          199 :     chord->add(lyrics);
    2342              : 
    2343          199 :     return success;
    2344          199 : }
    2345              : 
    2346              : //---------------------------------------------------------
    2347              : // read MEI control events
    2348              : //---------------------------------------------------------
    2349              : 
    2350              : /**
    2351              :  * Loop through the content of the MEI <measure> and read the control events
    2352              :  */
    2353              : 
    2354          404 : bool MeiImporter::readControlEvents(pugi::xml_node parentNode, Measure* measure)
    2355              : {
    2356          404 :     IF_ASSERT_FAILED(measure) {
    2357            0 :         return false;
    2358              :     }
    2359              : 
    2360          404 :     bool success = true;
    2361              : 
    2362          404 :     pugi::xpath_node_set elements = parentNode.select_nodes("./*");
    2363         1404 :     for (pugi::xpath_node xpathNode : elements) {
    2364         1000 :         std::string elementName = std::string(xpathNode.node().name());
    2365         1000 :         if (elementName == "arpeg") {
    2366           11 :             success = success && this->readArpeg(xpathNode.node(), measure);
    2367          989 :         } else if (elementName == "breath") {
    2368            4 :             success = success && this->readBreath(xpathNode.node(), measure);
    2369          985 :         } else if (elementName == "caesura") {
    2370           13 :             success = success && this->readCaesura(xpathNode.node(), measure);
    2371          972 :         } else if (elementName == "dir") {
    2372            8 :             success = success && this->readDir(xpathNode.node(), measure);
    2373          964 :         } else if (elementName == "dynam") {
    2374           10 :             success = success && this->readDynam(xpathNode.node(), measure);
    2375          954 :         } else if (elementName == "fermata") {
    2376           38 :             success = success && this->readFermata(xpathNode.node(), measure);
    2377          916 :         } else if (elementName == "fing") {
    2378           50 :             success = success && this->readFing(xpathNode.node(), measure);
    2379          866 :         } else if (elementName == "gliss") {
    2380           19 :             success = success && this->readGliss(xpathNode.node(), measure);
    2381          847 :         } else if (elementName == "hairpin") {
    2382            6 :             success = success && this->readHairpin(xpathNode.node(), measure);
    2383          841 :         } else if (elementName == "harm") {
    2384           33 :             if (xpathNode.node().select_node("./fb")) {
    2385            7 :                 success = success && this->readFb(xpathNode.node(), measure);
    2386              :             } else {
    2387           26 :                 success = success && this->readHarm(xpathNode.node(), measure);
    2388              :             }
    2389          808 :         } else if (elementName == "harpPedal") {
    2390            3 :             success = success && this->readHarpPedal(xpathNode.node(), measure);
    2391          805 :         } else if (elementName == "lv") {
    2392            6 :             success = success && this->readLv(xpathNode.node(), measure);
    2393          799 :         } else if (elementName == "mordent") {
    2394           22 :             success = success && this->readMordent(xpathNode.node(), measure);
    2395          777 :         } else if (elementName == "octave") {
    2396            7 :             success = success && this->readOctave(xpathNode.node(), measure);
    2397          770 :         } else if (elementName == "ornam") {
    2398            0 :             success = success && this->readOrnam(xpathNode.node(), measure);
    2399          770 :         } else if (elementName == "pedal") {
    2400            5 :             success = success && this->readPedal(xpathNode.node(), measure);
    2401          765 :         } else if (elementName == "phrase") {
    2402              :             // there is no distinction between phrase and slur in MuseScore
    2403            0 :             success = success && this->readSlur(xpathNode.node(), measure);
    2404          765 :         } else if (elementName == "reh") {
    2405            3 :             success = success && this->readReh(xpathNode.node(), measure);
    2406          762 :         } else if (elementName == "repeatMark") {
    2407           23 :             success = success && this->readRepeatMark(xpathNode.node(), measure);
    2408          739 :         } else if (elementName == "slur") {
    2409           33 :             success = success && this->readSlur(xpathNode.node(), measure);
    2410          706 :         } else if (elementName == "tempo") {
    2411           11 :             success = success && this->readTempo(xpathNode.node(), measure);
    2412          695 :         } else if (elementName == "tie") {
    2413           30 :             success = success && this->readTie(xpathNode.node(), measure);
    2414          665 :         } else if (elementName == "trill") {
    2415            9 :             success = success && this->readTrill(xpathNode.node(), measure);
    2416          656 :         } else if (elementName == "turn") {
    2417            6 :             success = success && this->readTurn(xpathNode.node(), measure);
    2418              :         }
    2419         1000 :     }
    2420          404 :     return success;
    2421          404 : }
    2422              : 
    2423              : /**
    2424              :  * Read a arpeg.
    2425              :  */
    2426              : 
    2427           11 : bool MeiImporter::readArpeg(pugi::xml_node arpegNode, Measure* measure)
    2428              : {
    2429           11 :     IF_ASSERT_FAILED(measure) {
    2430            0 :         return false;
    2431              :     }
    2432              : 
    2433              :     bool warning;
    2434           11 :     libmei::Arpeg meiArpeg;
    2435           11 :     meiArpeg.Read(arpegNode);
    2436              : 
    2437           11 :     Arpeggio* arpeggio = toArpeggio(this->addToChordRest(meiArpeg, measure));
    2438           11 :     if (!arpeggio) {
    2439              :         // Warning message given in MeiImporter::addToChordRest
    2440            0 :         return true;
    2441              :     }
    2442              : 
    2443           11 :     Convert::arpegFromMEI(arpeggio, meiArpeg, warning);
    2444              : 
    2445           11 :     if (meiArpeg.HasPlist()) {
    2446              :         // Add the Arpeggio to the open arpeggio map, which will handle ties differently as appropriate
    2447            4 :         m_openArpegMap[arpeggio] = arpegNode;
    2448              :     }
    2449              : 
    2450           11 :     return true;
    2451           11 : }
    2452              : 
    2453              : /**
    2454              :  * Read a breath.
    2455              :  */
    2456              : 
    2457            4 : bool MeiImporter::readBreath(pugi::xml_node breathNode, Measure* measure)
    2458              : {
    2459            4 :     IF_ASSERT_FAILED(measure) {
    2460            0 :         return false;
    2461              :     }
    2462              : 
    2463              :     bool warning;
    2464            4 :     libmei::Breath meiBreath;
    2465            4 :     meiBreath.Read(breathNode);
    2466              : 
    2467            4 :     Breath* breath = toBreath(this->addAnnotation(meiBreath, measure));
    2468            4 :     if (!breath) {
    2469              :         // Warning message given in MeiImporter::addAnnotation
    2470            0 :         return true;
    2471              :     }
    2472              : 
    2473            4 :     Convert::breathFromMEI(breath, meiBreath, warning);
    2474              : 
    2475            4 :     return true;
    2476            4 : }
    2477              : 
    2478              : /**
    2479              :  * Read a caesura
    2480              :  */
    2481              : 
    2482           13 : bool MeiImporter::readCaesura(pugi::xml_node caesuraNode, Measure* measure)
    2483              : {
    2484           13 :     IF_ASSERT_FAILED(measure) {
    2485            0 :         return false;
    2486              :     }
    2487              : 
    2488              :     bool warning;
    2489           13 :     libmei::Caesura meiCaesura;
    2490           13 :     meiCaesura.Read(caesuraNode);
    2491              : 
    2492           13 :     Breath* breath = toBreath(this->addAnnotation(meiCaesura, measure));
    2493           13 :     if (!breath) {
    2494              :         // Warning message given in MeiImporter::addAnnotation
    2495            0 :         return true;
    2496              :     }
    2497              : 
    2498           13 :     Convert::caesuraFromMEI(breath, meiCaesura, warning);
    2499              : 
    2500           13 :     return true;
    2501           13 : }
    2502              : 
    2503              : /**
    2504              :  * Read a dir.
    2505              :  */
    2506              : 
    2507            8 : bool MeiImporter::readDir(pugi::xml_node dirNode, Measure* measure)
    2508              : {
    2509            8 :     IF_ASSERT_FAILED(measure) {
    2510            0 :         return false;
    2511              :     }
    2512              : 
    2513              :     bool warning;
    2514            8 :     libmei::Dir meiDir;
    2515            8 :     meiDir.Read(dirNode);
    2516              : 
    2517            8 :     StringList meiLines;
    2518            8 :     size_t meiLine = 0;
    2519            8 :     this->readLines(dirNode, meiLines, meiLine);
    2520              : 
    2521            8 :     if (Convert::isDirWithExt(meiDir)) {
    2522            3 :         TextLineBase* textLineBase = toTextLineBase(this->addSpanner(meiDir, measure, dirNode));
    2523            3 :         if (!textLineBase) {
    2524              :             // Warning message given in MeiImporter::addSpanner
    2525            0 :             return true;
    2526              :         }
    2527            3 :         Convert::dirFromMEI(textLineBase, meiLines, meiDir, warning);
    2528              :     } else {
    2529            5 :         TextBase* textBase = toTextBase(this->addAnnotation(meiDir, measure));
    2530            5 :         if (!textBase) {
    2531              :             // Warning message given in MeiImporter::addAnnotation
    2532            0 :             return true;
    2533              :         }
    2534            5 :         Convert::dirFromMEI(textBase, meiLines, meiDir, warning);
    2535              :     }
    2536              : 
    2537            8 :     return true;
    2538            8 : }
    2539              : 
    2540              : /**
    2541              :  * Read a dynam.
    2542              :  */
    2543              : 
    2544           10 : bool MeiImporter::readDynam(pugi::xml_node dynamNode, Measure* measure)
    2545              : {
    2546           10 :     IF_ASSERT_FAILED(measure) {
    2547            0 :         return false;
    2548              :     }
    2549              : 
    2550              :     bool warning;
    2551           10 :     libmei::Dynam meiDynam;
    2552           10 :     meiDynam.Read(dynamNode);
    2553              : 
    2554           10 :     Dynamic* dynamic = toDynamic(this->addAnnotation(meiDynam, measure));
    2555           10 :     if (!dynamic) {
    2556              :         // Warning message given in MeiImporter::addAnnotation
    2557            0 :         return true;
    2558              :     }
    2559              : 
    2560           10 :     StringList meiLines;
    2561           10 :     size_t meiLine = 0;
    2562           10 :     this->readLines(dynamNode, meiLines, meiLine);
    2563              : 
    2564           10 :     Convert::dynamFromMEI(dynamic, meiLines, meiDynam, warning);
    2565              : 
    2566           10 :     return true;
    2567           10 : }
    2568              : 
    2569              : /**
    2570              :  * Read a f (FiguredBassItem)
    2571              :  */
    2572              : 
    2573            8 : bool MeiImporter::readF(pugi::xml_node fNode, engraving::FiguredBass* figuredBass)
    2574              : {
    2575            8 :     IF_ASSERT_FAILED(figuredBass) {
    2576            0 :         return false;
    2577              :     }
    2578              : 
    2579              :     bool warning;
    2580            8 :     libmei::F meiF;
    2581            8 :     meiF.Read(fNode);
    2582              : 
    2583            8 :     const int line = static_cast<int>(figuredBass->itemsCount());
    2584            8 :     FiguredBassItem* figuredBassItem = figuredBass->createItem(line);
    2585            8 :     this->readXmlId(figuredBassItem, meiF.m_xmlId);
    2586            8 :     figuredBassItem->setTrack(figuredBass->track());
    2587            8 :     figuredBassItem->setOwnershipParent(figuredBass);
    2588              : 
    2589            8 :     StringList meiLines;
    2590            8 :     size_t meiLine = 0;
    2591            8 :     this->readLines(fNode, meiLines, meiLine);
    2592              : 
    2593            8 :     Convert::fFromMEI(figuredBassItem, meiLines, meiF, warning);
    2594              : 
    2595            8 :     figuredBass->appendItem(figuredBassItem);
    2596              : 
    2597            8 :     return true;
    2598            8 : }
    2599              : 
    2600              : /**
    2601              :  * Read a fb (FiguredBass).
    2602              :  */
    2603              : 
    2604            7 : bool MeiImporter::readFb(pugi::xml_node harmNode, Measure* measure)
    2605              : {
    2606              :     // Already checked in MeiImporter::readControlEvents
    2607            7 :     pugi::xml_node fbNode = harmNode.select_node("./fb").node();
    2608              : 
    2609            7 :     IF_ASSERT_FAILED(fbNode && measure) {
    2610            0 :         return false;
    2611              :     }
    2612              : 
    2613              :     bool warning;
    2614            7 :     libmei::Harm meiHarm;
    2615            7 :     meiHarm.Read(harmNode);
    2616              :     // Add a custom label for MeiImporter::addAnnotation to create a FiguredBass
    2617            7 :     meiHarm.SetLabel(MEI_FB_HARM);
    2618            7 :     libmei::Fb meiFb;
    2619            7 :     meiFb.Read(fbNode);
    2620              : 
    2621            7 :     FiguredBass* figuredBass = toFiguredBass(this->addAnnotation(meiHarm, measure));
    2622            7 :     if (!figuredBass) {
    2623              :         // Warning message given in MeiImporter::addAnnotation
    2624            0 :         return true;
    2625              :     }
    2626              :     // Needs to be registered by hand because we pass meiHarm to MeiImporter::addAnnotation
    2627            7 :     this->readXmlId(figuredBass, meiFb.m_xmlId);
    2628              : 
    2629            7 :     Convert::fbFromMEI(figuredBass, meiHarm, meiFb, warning);
    2630              : 
    2631            7 :     bool success = true;
    2632              : 
    2633            7 :     pugi::xpath_node_set fs = fbNode.select_nodes("./f");
    2634           15 :     for (pugi::xpath_node xpathNode : fs) {
    2635            8 :         success = success && this->readF(xpathNode.node(), figuredBass);
    2636              :     }
    2637              : 
    2638            7 :     return success;
    2639            7 : }
    2640              : 
    2641              : /**
    2642              :  * Read a fermata.
    2643              :  */
    2644              : 
    2645           38 : bool MeiImporter::readFermata(pugi::xml_node fermataNode, Measure* measure)
    2646              : {
    2647           38 :     IF_ASSERT_FAILED(measure) {
    2648            0 :         return false;
    2649              :     }
    2650              : 
    2651              :     bool warning;
    2652           38 :     libmei::Fermata meiFermata;
    2653           38 :     meiFermata.Read(fermataNode);
    2654              : 
    2655           38 :     Fermata* fermata = nullptr;
    2656           38 :     if (meiFermata.HasTstamp()) {
    2657            6 :         Fraction fermataPos = Convert::tstampToFraction(meiFermata.GetTstamp(), measure->timesig());
    2658            6 :         if (fermataPos == measure->ticks()) {
    2659            6 :             Segment* segment = measure->getSegment(SegmentType::EndBarLine, measure->endTick());
    2660            6 :             fermata = Factory::createFermata(segment);
    2661            6 :             this->readXmlId(fermata, meiFermata.m_xmlId);
    2662              :             const int staffIdx
    2663            6 :                 = (meiFermata.HasStaff() && meiFermata.GetStaff().size() > 0) ? this->getStaffIndex(meiFermata.GetStaff().at(0)) : 0;
    2664            6 :             fermata->setTrack(staffIdx * VOICES);
    2665            6 :             segment->add(fermata);
    2666              :         }
    2667              :     }
    2668              : 
    2669           38 :     if (!fermata) {
    2670           32 :         fermata = toFermata(this->addAnnotation(meiFermata, measure));
    2671              :     }
    2672              : 
    2673           38 :     if (!fermata) {
    2674              :         // Warning message given in MeiImporter::addAnnotation
    2675            0 :         return true;
    2676              :     }
    2677              : 
    2678           38 :     Convert::fermataFromMEI(fermata, meiFermata, warning);
    2679              : 
    2680           38 :     return true;
    2681           38 : }
    2682              : 
    2683              : /**
    2684              :  * Read a fing.
    2685              :  */
    2686              : 
    2687           50 : bool MeiImporter::readFing(pugi::xml_node fingNode, Measure* measure)
    2688              : {
    2689           50 :     IF_ASSERT_FAILED(measure) {
    2690            0 :         return false;
    2691              :     }
    2692              : 
    2693              :     bool warning;
    2694           50 :     libmei::Fing meiFing;
    2695           50 :     meiFing.Read(fingNode);
    2696              : 
    2697           50 :     Note* note = this->findStartNote(meiFing);
    2698           50 :     if (!note) {
    2699              :         // Warning message given in MeiImporter::findStartNote
    2700            0 :         return true;
    2701              :     }
    2702              : 
    2703           50 :     Fingering* fing = Factory::createFingering(note);
    2704           50 :     m_uids->reg(fing, meiFing.m_xmlId);
    2705              : 
    2706           50 :     StringList meiLines;
    2707           50 :     size_t meiLine = 0;
    2708           50 :     this->readLines(fingNode, meiLines, meiLine);
    2709              : 
    2710           50 :     Convert::fingFromMEI(fing, meiLines, meiFing, warning);
    2711              : 
    2712           50 :     note->add(fing);
    2713              : 
    2714           50 :     return true;
    2715           50 : }
    2716              : 
    2717              : /**
    2718              :  * Read a gliss.
    2719              :  */
    2720              : 
    2721           19 : bool MeiImporter::readGliss(pugi::xml_node glissNode, Measure* measure)
    2722              : {
    2723           19 :     IF_ASSERT_FAILED(measure) {
    2724            0 :         return false;
    2725              :     }
    2726              : 
    2727              :     bool warning;
    2728           19 :     libmei::Gliss meiGliss;
    2729           19 :     meiGliss.Read(glissNode);
    2730              : 
    2731              :     // We do not use addSpanner here because Gliss objects are added directly to the start and end Note objects
    2732           19 :     Note* startNote = this->findStartNote(meiGliss);
    2733           19 :     if (!startNote) {
    2734              :         // Here we could detect if it's a tied chord (for files not exported from MuseScore)
    2735              :         // We would need a dedicated list and tie each note once the second chord has been found.
    2736            0 :         return true;
    2737              :     }
    2738              : 
    2739           19 :     Glissando* gliss = Factory::createGlissando(startNote);
    2740           19 :     m_uids->reg(gliss, meiGliss.m_xmlId);
    2741           19 :     gliss->setTick(startNote->chord()->tick());
    2742           19 :     gliss->setStartElement(startNote);
    2743           19 :     gliss->setTrack(startNote->track());
    2744           19 :     gliss->setGlissandoStyle(startNote->part()->instrument(startNote->tick())->glissandoStyle());
    2745              : 
    2746           19 :     const String glissText = String(glissNode.text().as_string());
    2747           19 :     gliss->setText(glissText);
    2748              : 
    2749           19 :     m_score->addElement(gliss);
    2750              : 
    2751              :     // Still add the glissando to the open Spanner map, which will handle glissandos differently as appropriate
    2752           19 :     m_openSpannerMap[gliss] = glissNode;
    2753              : 
    2754           19 :     Convert::glissFromMEI(gliss, meiGliss, warning);
    2755              : 
    2756           19 :     return true;
    2757           19 : }
    2758              : 
    2759              : /**
    2760              :  * Read a hairpin.
    2761              :  */
    2762              : 
    2763            6 : bool MeiImporter::readHairpin(pugi::xml_node hairpinNode, Measure* measure)
    2764              : {
    2765            6 :     IF_ASSERT_FAILED(measure) {
    2766            0 :         return false;
    2767              :     }
    2768              : 
    2769              :     bool warning;
    2770            6 :     libmei::Hairpin meiHairpin;
    2771            6 :     meiHairpin.Read(hairpinNode);
    2772              : 
    2773            6 :     Hairpin* hairpin = toHairpin(this->addSpanner(meiHairpin, measure, hairpinNode));
    2774            6 :     if (!hairpin) {
    2775              :         // Warning message given in MeiImporter::addSpanner
    2776            0 :         return true;
    2777              :     }
    2778              : 
    2779            6 :     Convert::hairpinFromMEI(hairpin, meiHairpin, warning);
    2780              : 
    2781            6 :     return true;
    2782            6 : }
    2783              : 
    2784              : /**
    2785              :  * Read a harm.
    2786              :  * <harm> with <fb> are read by MeiImporter::readFb
    2787              :  */
    2788              : 
    2789           26 : bool MeiImporter::readHarm(pugi::xml_node harmNode, Measure* measure)
    2790              : {
    2791           26 :     IF_ASSERT_FAILED(measure) {
    2792            0 :         return false;
    2793              :     }
    2794              : 
    2795              :     bool warning;
    2796           26 :     libmei::Harm meiHarm;
    2797           26 :     meiHarm.Read(harmNode);
    2798              : 
    2799           26 :     Harmony* harmony = toHarmony(this->addAnnotation(meiHarm, measure));
    2800           26 :     if (!harmony) {
    2801              :         // Warning message given in MeiImporter::addAnnotation
    2802            0 :         return true;
    2803              :     }
    2804              : 
    2805           26 :     StringList meiLines;
    2806           26 :     size_t meiLine = 0;
    2807           26 :     this->readLines(harmNode, meiLines, meiLine);
    2808              : 
    2809           26 :     Convert::harmFromMEI(harmony, meiLines, meiHarm, warning);
    2810              : 
    2811           26 :     return true;
    2812           26 : }
    2813              : 
    2814              : /**
    2815              :  * Read a harpPedal.
    2816              :  */
    2817              : 
    2818            3 : bool MeiImporter::readHarpPedal(pugi::xml_node harpPedalNode, Measure* measure)
    2819              : {
    2820            3 :     IF_ASSERT_FAILED(measure) {
    2821            0 :         return false;
    2822              :     }
    2823              : 
    2824              :     bool warning;
    2825            3 :     libmei::HarpPedal meiHarpPedal;
    2826            3 :     meiHarpPedal.Read(harpPedalNode);
    2827              : 
    2828            3 :     HarpPedalDiagram* harpPedalDiagram = toHarpPedalDiagram(this->addAnnotation(meiHarpPedal, measure));
    2829            3 :     if (!harpPedalDiagram) {
    2830              :         // Warning message given in MeiImporter::addAnnotation
    2831            0 :         return true;
    2832              :     }
    2833              : 
    2834            3 :     Convert::harpPedalFromMEI(harpPedalDiagram, meiHarpPedal, warning);
    2835              : 
    2836            3 :     return true;
    2837            3 : }
    2838              : 
    2839              : /**
    2840              :  * Read a instrDef (instrument definition).
    2841              :  */
    2842              : 
    2843           97 : bool MeiImporter::readInstrDef(pugi::xml_node instrDefNode, Part* part)
    2844              : {
    2845           97 :     IF_ASSERT_FAILED(part) {
    2846            0 :         return false;
    2847              :     }
    2848              : 
    2849           97 :     libmei::InstrDef meiInstrDef;
    2850           97 :     meiInstrDef.Read(instrDefNode);
    2851              : 
    2852           97 :     part->setMidiProgram(meiInstrDef.GetMidiInstrnum());
    2853              : 
    2854           97 :     return true;
    2855           97 : }
    2856              : 
    2857              : /**
    2858              :  * Read a lv.
    2859              :  */
    2860              : 
    2861            6 : bool MeiImporter::readLv(pugi::xml_node lvNode, Measure* measure)
    2862              : {
    2863            6 :     IF_ASSERT_FAILED(measure) {
    2864            0 :         return false;
    2865              :     }
    2866              : 
    2867              :     bool warning;
    2868            6 :     libmei::Lv meiLv;
    2869            6 :     meiLv.Read(lvNode);
    2870              : 
    2871            6 :     Note* note = this->findStartNote(meiLv);
    2872            6 :     if (!note) {
    2873            0 :         return true;
    2874              :     }
    2875              : 
    2876            6 :     LaissezVib* lv = Factory::createLaissezVib(note);
    2877            6 :     note->score()->undoAddElement(lv);
    2878            6 :     m_uids->reg(lv, meiLv.m_xmlId);
    2879              : 
    2880            6 :     Convert::lvFromMEI(lv, meiLv, warning);
    2881              : 
    2882            6 :     return true;
    2883            6 : }
    2884              : 
    2885              : /**
    2886              :  * Read a mordent.
    2887              :  */
    2888              : 
    2889           22 : bool MeiImporter::readMordent(pugi::xml_node mordentNode, Measure* measure)
    2890              : {
    2891           22 :     IF_ASSERT_FAILED(measure) {
    2892            0 :         return false;
    2893              :     }
    2894              : 
    2895              :     bool warning;
    2896           22 :     libmei::Mordent meiMordent;
    2897           22 :     meiMordent.Read(mordentNode);
    2898              : 
    2899           22 :     Ornament* ornament = toOrnament(this->addToChordRest(meiMordent, measure));
    2900           22 :     if (!ornament) {
    2901              :         // Warning message given in MeiImporter::addToChordRest
    2902            0 :         return true;
    2903              :     }
    2904              : 
    2905           22 :     Convert::OrnamStruct ornamSt = Convert::mordentFromMEI(ornament, meiMordent, warning);
    2906           22 :     this->setOrnamentAccid(ornament, ornamSt);
    2907              : 
    2908           22 :     return true;
    2909           22 : }
    2910              : 
    2911              : /**
    2912              :  * Read a octave.
    2913              :  */
    2914              : 
    2915            7 : bool MeiImporter::readOctave(pugi::xml_node octaveNode, Measure* measure)
    2916              : {
    2917            7 :     IF_ASSERT_FAILED(measure) {
    2918            0 :         return false;
    2919              :     }
    2920              : 
    2921              :     bool warning;
    2922            7 :     libmei::Octave meiOctave;
    2923            7 :     meiOctave.Read(octaveNode);
    2924              : 
    2925            7 :     Ottava* ottava = toOttava(this->addSpanner(meiOctave, measure, octaveNode));
    2926            7 :     if (!ottava) {
    2927              :         // Warning message given in MeiImporter::addSpanner
    2928            0 :         return true;
    2929              :     }
    2930              : 
    2931            7 :     Convert::octaveFromMEI(ottava, meiOctave, warning);
    2932              : 
    2933            7 :     return true;
    2934            7 : }
    2935              : 
    2936              : /**
    2937              :  * Read a ornam.
    2938              :  */
    2939              : 
    2940            0 : bool MeiImporter::readOrnam(pugi::xml_node ornamNode, Measure* measure)
    2941              : {
    2942            0 :     IF_ASSERT_FAILED(measure) {
    2943            0 :         return false;
    2944              :     }
    2945              : 
    2946              :     bool warning;
    2947            0 :     libmei::Ornam meiOrnam;
    2948            0 :     meiOrnam.Read(ornamNode);
    2949              : 
    2950            0 :     Ornament* ornament = toOrnament(this->addToChordRest(meiOrnam, measure));
    2951            0 :     if (!ornament) {
    2952              :         // Warning message given in MeiImporter::addToChordRest
    2953            0 :         return true;
    2954              :     }
    2955              : 
    2956            0 :     Convert::OrnamStruct ornamSt = Convert::ornamFromMEI(ornament, meiOrnam, warning);
    2957            0 :     this->setOrnamentAccid(ornament, ornamSt);
    2958              : 
    2959            0 :     return true;
    2960            0 : }
    2961              : 
    2962              : /**
    2963              :  * Read a pedal.
    2964              :  */
    2965              : 
    2966            5 : bool MeiImporter::readPedal(pugi::xml_node pedalNode, Measure* measure)
    2967              : {
    2968            5 :     IF_ASSERT_FAILED(measure) {
    2969            0 :         return false;
    2970              :     }
    2971              : 
    2972              :     bool warning;
    2973            5 :     libmei::Pedal meiPedal;
    2974            5 :     meiPedal.Read(pedalNode);
    2975              : 
    2976            5 :     Pedal* pedal = toPedal(this->addSpanner(meiPedal, measure, pedalNode));
    2977            5 :     if (!pedal) {
    2978              :         // Warning message given in MeiImporter::addSpanner
    2979            0 :         return true;
    2980              :     }
    2981              : 
    2982            5 :     Convert::pedalFromMEI(pedal, meiPedal, warning);
    2983              : 
    2984            5 :     return true;
    2985            5 : }
    2986              : 
    2987              : /**
    2988              :  * Read a reh.
    2989              :  */
    2990              : 
    2991            3 : bool MeiImporter::readReh(pugi::xml_node rehNode, Measure* measure)
    2992              : {
    2993            3 :     IF_ASSERT_FAILED(measure) {
    2994            0 :         return false;
    2995              :     }
    2996              : 
    2997            3 :     libmei::Reh meiReh;
    2998            3 :     meiReh.Read(rehNode);
    2999              : 
    3000            3 :     RehearsalMark* rehearsalMark = toRehearsalMark(this->addAnnotation(meiReh, measure));
    3001            3 :     if (!rehearsalMark) {
    3002              :         // Warning message given in MeiImporter::addAnnotation
    3003            0 :         return true;
    3004              :     }
    3005            3 :     Convert::colorFromMEI(rehearsalMark, meiReh);
    3006              : 
    3007            3 :     StringList meiLines;
    3008            3 :     this->readLinesWithSmufl(rehNode, meiLines);
    3009              : 
    3010              :     // text
    3011            3 :     rehearsalMark->setXmlText(meiLines.join(u"\n"));
    3012              : 
    3013            3 :     return true;
    3014            3 : }
    3015              : 
    3016              : /**
    3017              :  * Read a repeatMark and create a Jump or a Marker as appropriate.
    3018              :  * For Jump, default values for jumpTo, playUntil and continueAt are used.
    3019              :  * Similarly, default value for label is used for Marker.
    3020              :  */
    3021              : 
    3022           23 : bool MeiImporter::readRepeatMark(pugi::xml_node repeatMarkNode, Measure* measure)
    3023              : {
    3024           23 :     IF_ASSERT_FAILED(measure) {
    3025            0 :         return false;
    3026              :     }
    3027              : 
    3028              :     bool warning;
    3029           23 :     libmei::RepeatMark meiRepeatMark;
    3030           23 :     meiRepeatMark.Read(repeatMarkNode);
    3031              : 
    3032           23 :     EngravingItem* item = nullptr;
    3033           23 :     if (Convert::elementTypeFor(meiRepeatMark) == ElementType::JUMP) {
    3034           14 :         item = Factory::createJump(measure);
    3035           14 :         Convert::jumpFromMEI(toJump(item), meiRepeatMark, warning);
    3036              :     } else {
    3037            9 :         item = Factory::createMarker(measure);
    3038            9 :         Convert::markerFromMEI(toMarker(item), meiRepeatMark, warning);
    3039              :     }
    3040           23 :     this->readXmlId(item, meiRepeatMark.m_xmlId);
    3041           23 :     item->setTrack(0);
    3042           23 :     measure->add(item);
    3043              : 
    3044           23 :     return true;
    3045           23 : }
    3046              : 
    3047              : /**
    3048              :  * Read a slur.
    3049              :  */
    3050              : 
    3051           33 : bool MeiImporter::readSlur(pugi::xml_node slurNode, Measure* measure)
    3052              : {
    3053           33 :     IF_ASSERT_FAILED(measure) {
    3054            0 :         return false;
    3055              :     }
    3056              : 
    3057              :     bool warning;
    3058           33 :     libmei::Slur meiSlur;
    3059           33 :     meiSlur.Read(slurNode);
    3060              : 
    3061           33 :     Slur* slur = toSlur(this->addSpanner(meiSlur, measure, slurNode));
    3062           33 :     if (!slur) {
    3063              :         // Warning message given in MeiImporter::addSpanner
    3064            0 :         return true;
    3065              :     }
    3066              : 
    3067           33 :     Convert::slurFromMEI(slur, meiSlur, warning);
    3068              : 
    3069           33 :     return true;
    3070           33 : }
    3071              : 
    3072              : /**
    3073              :  * Read a tempo.
    3074              :  */
    3075              : 
    3076           11 : bool MeiImporter::readTempo(pugi::xml_node tempoNode, Measure* measure)
    3077              : {
    3078           11 :     IF_ASSERT_FAILED(measure) {
    3079            0 :         return false;
    3080              :     }
    3081              : 
    3082              :     bool warning;
    3083           11 :     libmei::Tempo meiTempo;
    3084           11 :     meiTempo.Read(tempoNode);
    3085              : 
    3086           11 :     TempoText* tempoText = toTempoText(this->addAnnotation(meiTempo, measure));
    3087           11 :     if (!tempoText) {
    3088              :         // Warning message given in MeiImporter::addAnnotation
    3089            0 :         return true;
    3090              :     }
    3091              : 
    3092           11 :     StringList meiLines;
    3093           11 :     this->readLinesWithSmufl(tempoNode, meiLines);
    3094              : 
    3095           11 :     Convert::tempoFromMEI(tempoText, meiLines, meiTempo, warning);
    3096              : 
    3097           11 :     return true;
    3098           11 : }
    3099              : 
    3100              : /**
    3101              :  * Read a tie.
    3102              :  */
    3103              : 
    3104           30 : bool MeiImporter::readTie(pugi::xml_node tieNode, Measure* measure)
    3105              : {
    3106           30 :     IF_ASSERT_FAILED(measure) {
    3107            0 :         return false;
    3108              :     }
    3109              : 
    3110              :     bool warning;
    3111           30 :     libmei::Tie meiTie;
    3112           30 :     meiTie.Read(tieNode);
    3113              : 
    3114              :     // We do not use addSpanner here because Tie object are added directly to the start and end Note objects
    3115           30 :     Note* startNote = this->findStartNote(meiTie);
    3116           30 :     if (!startNote) {
    3117              :         // Here we could detect if it's a tied chord (for files not exported from MuseScore)
    3118              :         // We would need a dedicated list and tie each note once the second chord has been found.
    3119            0 :         return true;
    3120              :     }
    3121              : 
    3122           30 :     Tie* tie = new Tie(startNote);
    3123           30 :     this->readXmlId(tie, meiTie.m_xmlId);
    3124           30 :     startNote->setTieFor(tie);
    3125           30 :     tie->setStartNote(startNote);
    3126           30 :     tie->setTrack(startNote->track());
    3127              : 
    3128              :     // Still add the tie to the open Spanner map, which will handle ties differently as appropriate
    3129           30 :     m_openSpannerMap[tie] = tieNode;
    3130              : 
    3131           30 :     Convert::tieFromMEI(tie, meiTie, warning);
    3132              : 
    3133           30 :     return true;
    3134           30 : }
    3135              : 
    3136              : /**
    3137              :  * Read a trill.
    3138              :  */
    3139              : 
    3140            9 : bool MeiImporter::readTrill(pugi::xml_node trillNode, Measure* measure)
    3141              : {
    3142            9 :     IF_ASSERT_FAILED(measure) {
    3143            0 :         return false;
    3144              :     }
    3145              : 
    3146              :     bool warning;
    3147            9 :     libmei::Trill meiTrill;
    3148            9 :     meiTrill.Read(trillNode);
    3149              : 
    3150            9 :     Ornament* ornament = toOrnament(this->addToChordRest(meiTrill, measure));
    3151            9 :     if (!ornament) {
    3152              :         // Warning message given in MeiImporter::addToChordRest
    3153            0 :         return true;
    3154              :     }
    3155              : 
    3156            9 :     if (meiTrill.HasEndid()) {
    3157            4 :         Trill* trill = toTrill(this->addSpanner(meiTrill, measure, trillNode));
    3158            4 :         if (trill) {
    3159              :             // move ornament to spanner
    3160            4 :             ornament->ownershipParentItem()->remove(ornament);
    3161            4 :             trill->setOrnament(ornament);
    3162              :             // @color
    3163            4 :             Convert::colorlineFromMEI(trill, meiTrill);
    3164              :         }
    3165              :     }
    3166              : 
    3167            9 :     Convert::OrnamStruct ornamSt = Convert::trillFromMEI(ornament, meiTrill, warning);
    3168            9 :     this->setOrnamentAccid(ornament, ornamSt);
    3169              : 
    3170            9 :     return true;
    3171            9 : }
    3172              : 
    3173              : /**
    3174              :  * Read a mordent.
    3175              :  */
    3176              : 
    3177            6 : bool MeiImporter::readTurn(pugi::xml_node turnNode, Measure* measure)
    3178              : {
    3179            6 :     IF_ASSERT_FAILED(measure) {
    3180            0 :         return false;
    3181              :     }
    3182              : 
    3183              :     bool warning;
    3184            6 :     libmei::Turn meiTurn;
    3185            6 :     meiTurn.Read(turnNode);
    3186              : 
    3187            6 :     Ornament* ornament = toOrnament(this->addToChordRest(meiTurn, measure));
    3188            6 :     if (!ornament) {
    3189              :         // Warning message given in MeiImporter::addToChordRest
    3190            0 :         return true;
    3191              :     }
    3192              : 
    3193            6 :     Convert::OrnamStruct ornamSt = Convert::turnFromMEI(ornament, meiTurn, warning);
    3194            6 :     this->setOrnamentAccid(ornament, ornamSt);
    3195              : 
    3196            6 :     return true;
    3197            6 : }
    3198              : 
    3199              : //---------------------------------------------------------
    3200              : // read MEI attribute classes
    3201              : //---------------------------------------------------------
    3202              : 
    3203              : /**
    3204              :  * Check the presence of `@grace` on <chord> and <note> to see if we have grace encoded not as <graceGrp>
    3205              :  * Used only when importing files not generated from MuseScore since these will use <graceGrp>.
    3206              :  * Always set the grace notes not within a <graceGrp> as attached to a following <chord> / <note>.
    3207              :  * Grace notes at the end of a measure and not in a <graceGrp> will be dropped.
    3208              :  */
    3209              : 
    3210         1578 : bool MeiImporter::readGracedAtt(libmei::AttGraced& gracedAtt)
    3211              : {
    3212              :     // if we are inside a graceGrp, ignore @grace on the chord/node
    3213         1578 :     if (m_readingGraceNotes == GraceAsGrp) {
    3214           47 :         return true;
    3215              :     }
    3216         1531 :     if (gracedAtt.HasGrace()) {
    3217            0 :         m_readingGraceNotes = GraceAsNote;
    3218            0 :         bool warning = false;
    3219            0 :         auto [isAfter, noteType] = Convert::gracegrpFromMEI(libmei::graceGrpLog_ATTACH_post, gracedAtt.GetGrace(), warning);
    3220            0 :         m_graceNoteType = noteType;
    3221              :     } else {
    3222         1531 :         m_readingGraceNotes = GraceNone;
    3223              :     }
    3224              : 
    3225         1531 :     return true;
    3226              : }
    3227              : 
    3228              : /**
    3229              :  * Read the stem direction attributes on <chord> and <note>.
    3230              :  * Attribute read are `@stem.dir` and `@stem.len` when `0.0` is converted as stem less.
    3231              :  */
    3232              : 
    3233         1578 : bool MeiImporter::readStemsAtt(Chord* chord, libmei::AttStems& stemsAtt)
    3234              : {
    3235         1578 :     IF_ASSERT_FAILED(chord) {
    3236            0 :         return false;
    3237              :     }
    3238              : 
    3239         1578 :     bool warning = false;
    3240              : 
    3241         1578 :     auto [direction, noStem] = Convert::stemFromMEI(stemsAtt, warning);
    3242         1578 :     chord->setStemDirection(direction);
    3243         1578 :     chord->setNoStem(noStem);
    3244              : 
    3245         1578 :     return true;
    3246              : }
    3247              : 
    3248              : //---------------------------------------------------------
    3249              : // content extraction methods
    3250              : //---------------------------------------------------------
    3251              : 
    3252              : /**
    3253              :  * Build a map of xml:id and corresponding EngravingItem elements.
    3254              :  * After building the map, all values are `nullptr`.
    3255              :  * They will be assigned a object when the element with the corresponding `xml:id` is read in MeiImporter::addChordRest
    3256              :  */
    3257           64 : bool MeiImporter::buildIdMap(pugi::xml_node scoreNode)
    3258              : {
    3259           64 :     pugi::xpath_node_set nodesWithStartid = scoreNode.select_nodes("//@startid");
    3260              : 
    3261          375 :     for (pugi::xpath_node elementXpathNode : nodesWithStartid) {
    3262          311 :         std::string id = elementXpathNode.attribute().value();
    3263          311 :         if (id.size() < 1 || id.at(0) != '#') {
    3264            0 :             continue;
    3265              :         }
    3266          311 :         m_startIdChordRests[id.erase(0, 1)] = nullptr;
    3267          311 :     }
    3268              : 
    3269           64 :     pugi::xpath_node_set nodesWithEndid = scoreNode.select_nodes("//@endid");
    3270              : 
    3271          171 :     for (pugi::xpath_node elementXpathNode : nodesWithEndid) {
    3272          107 :         std::string id = elementXpathNode.attribute().value();
    3273          107 :         if (id.size() < 1 || id.at(0) != '#') {
    3274            0 :             continue;
    3275              :         }
    3276          107 :         m_endIdChordRests[id.erase(0, 1)] = nullptr;
    3277          107 :     }
    3278              : 
    3279           64 :     pugi::xpath_node_set nodesWithPlist = scoreNode.select_nodes("//@plist");
    3280              : 
    3281           68 :     for (pugi::xpath_node elementXpathNode : nodesWithPlist) {
    3282            4 :         std::string plist = elementXpathNode.attribute().value();
    3283            4 :         std::istringstream iss(plist);
    3284            4 :         std::string id;
    3285            8 :         while (std::getline(iss, id, ' ')) {
    3286            4 :             if (id.size() < 1 || id.at(0) != '#') {
    3287            0 :                 continue;
    3288              :             }
    3289            4 :             m_plistValueChordRests[id.erase(0, 1)] = nullptr;
    3290              :         }
    3291            4 :     }
    3292              : 
    3293           64 :     return true;
    3294           64 : }
    3295              : 
    3296              : /**
    3297              :  * Create a mapping for <staff> `@n` and <layer> `@n`.
    3298              :  * Useful only when reading MEI files where the sequence of `@n` is not starting from 1 or not sequential.
    3299              :  * Not really useful when reading MEI files generated from MuseScore since these will have sequential numbers starting with 1.
    3300              :  */
    3301              : 
    3302           64 : bool MeiImporter::buildStaffLayerMap(pugi::xml_node node)
    3303              : {
    3304           64 :     pugi::xpath_node_set staves = node.select_nodes("//staff");
    3305              :     // Critical error
    3306           64 :     if (staves.empty()) {
    3307            0 :         return false;
    3308              :     }
    3309              : 
    3310          714 :     for (pugi::xpath_node staffXpathNode : staves) {
    3311          650 :         pugi::xml_node staff = staffXpathNode.node();
    3312          650 :         int staffN = staff.attribute("n") ? staff.attribute("n").as_int() : 1;
    3313          650 :         m_staffNs.insert(staffN);
    3314              :     }
    3315              : 
    3316           64 :     pugi::xpath_node_set layers = node.select_nodes("//layer");
    3317              :     // Critical error
    3318           64 :     if (layers.empty()) {
    3319            0 :         return false;
    3320              :     }
    3321              : 
    3322          740 :     for (pugi::xpath_node layerXpathNode : layers) {
    3323          676 :         pugi::xml_node layer = layerXpathNode.node();
    3324          676 :         int layerN = layer.attribute("n") ? layer.attribute("n").as_int() : 1;
    3325          676 :         pugi::xml_node staff = layer.parent();
    3326          676 :         if (!staff) {
    3327            0 :             continue;
    3328              :         }
    3329          676 :         const int staffN = staff.attribute("n") ? staff.attribute("n").as_int() : 1;
    3330          676 :         const int staffIdx = this->getStaffIndex(staffN);
    3331          676 :         m_staffLayerNs[staffIdx].insert(layerN);
    3332              :     }
    3333              : 
    3334           64 :     return true;
    3335           64 : }
    3336              : 
    3337              : /**
    3338              :  * Build a title frame from the metaTag values.
    3339              :  */
    3340              : 
    3341            0 : bool MeiImporter::buildTextFrame()
    3342              : {
    3343            0 :     VBox* vBox = nullptr;
    3344              : 
    3345            0 :     if (!m_score->metaTag(u"workTitle").isEmpty()) {
    3346            0 :         this->addTextToTitleFrame(vBox, m_score->metaTag(u"workTitle"), TextStyleType::TITLE);
    3347              :     }
    3348              : 
    3349            0 :     StringList creators;
    3350            0 :     if (!m_score->metaTag(u"composer").isEmpty()) {
    3351            0 :         creators << m_score->metaTag(u"composer");
    3352              :     }
    3353            0 :     if (!m_score->metaTag(u"arranger").isEmpty()) {
    3354            0 :         creators << m_score->metaTag(u"arranger");
    3355              :     }
    3356            0 :     if (!creators.empty()) {
    3357            0 :         this->addTextToTitleFrame(vBox, creators.join(u"\n"), TextStyleType::COMPOSER);
    3358              :     }
    3359              : 
    3360            0 :     StringList textCreators;
    3361            0 :     if (!m_score->metaTag(u"lyricist").isEmpty()) {
    3362            0 :         textCreators << m_score->metaTag(u"lyricist");
    3363              :     }
    3364            0 :     if (!m_score->metaTag(u"translator").isEmpty()) {
    3365            0 :         textCreators << m_score->metaTag(u"translator");
    3366              :     }
    3367            0 :     if (!textCreators.empty()) {
    3368            0 :         this->addTextToTitleFrame(vBox, textCreators.join(u"\n"), TextStyleType::LYRICIST);
    3369              :     }
    3370              : 
    3371            0 :     if (vBox) {
    3372            0 :         vBox->setTick(Fraction(0, 1));
    3373            0 :         m_score->measures()->append(vBox);
    3374              :     }
    3375              : 
    3376            0 :     return true;
    3377            0 : }
    3378              : 
    3379              : /**
    3380              :  * Build the score parts from the scoreDef staffGrp@label and staffDef@label.
    3381              :  */
    3382              : 
    3383           64 : bool MeiImporter::buildScoreParts(pugi::xml_node scoreDefNode)
    3384              : {
    3385           64 :     bool warning = false;
    3386              : 
    3387              :     // First try to build the list of parts from the labels
    3388           64 :     pugi::xpath_node_set labels = scoreDefNode.select_nodes(".//label");
    3389          161 :     for (pugi::xpath_node labelXpathNode : labels) {
    3390           97 :         pugi::xml_node labelNode = labelXpathNode.node();
    3391           97 :         if (!labelNode) {
    3392            0 :             continue;
    3393              :         }
    3394              : 
    3395           97 :         Part* part = new Part(m_score);
    3396              : 
    3397           97 :         StringList lines;
    3398           97 :         size_t line = 0;
    3399           97 :         this->readLines(labelNode, lines, line);
    3400           97 :         part->setLongName(lines.join(u"\n"));
    3401              : 
    3402           97 :         pugi::xml_node labelAbbrNode = labelNode.select_node("./following-sibling::labelAbbr").node();
    3403           97 :         if (labelAbbrNode) {
    3404           96 :             StringList abbrLines;
    3405           96 :             size_t abbrLine = 0;
    3406           96 :             this->readLines(labelAbbrNode, abbrLines, abbrLine);
    3407           96 :             part->setShortName(abbrLines.join(u"\n"));
    3408           96 :         }
    3409              : 
    3410           97 :         pugi::xml_node instrDefNode = labelNode.select_node("./following-sibling::instrDef").node();
    3411           97 :         readInstrDef(instrDefNode, part);
    3412              : 
    3413           97 :         m_score->appendPart(part);
    3414              : 
    3415              :         // If the label is a child of a staffGrp, the part contains all the child staffDefs
    3416           97 :         if (isNode(labelNode.parent(), u"staffGrp")) {
    3417           20 :             pugi::xpath_node_set staffDefs = labelNode.parent().select_nodes(".//staffDef");
    3418           62 :             for (pugi::xpath_node staffDefXpathNode : staffDefs) {
    3419           42 :                 libmei::StaffDef meiStaffDef;
    3420           42 :                 meiStaffDef.Read(staffDefXpathNode.node());
    3421           42 :                 m_staffParts[meiStaffDef.GetN()] = part;
    3422           42 :             }
    3423           20 :         }
    3424              :         // Otherwise the parent must be a staffDef and it is a part with a single staff
    3425           77 :         else if (isNode(labelNode.parent(), u"staffDef")) {
    3426           77 :             libmei::StaffDef meiStaffDef;
    3427           77 :             meiStaffDef.Read(labelNode.parent());
    3428           77 :             m_staffParts[meiStaffDef.GetN()] = part;
    3429           77 :         } else {
    3430            0 :             LOGD() << "MeiImporter::readStaffDefs unknown label part " << labelNode.text();
    3431              :         }
    3432           97 :     }
    3433              : 
    3434           64 :     pugi::xpath_node_set staffDefs = scoreDefNode.select_nodes(".//staffDef");
    3435              :     // Critical error
    3436           64 :     if (staffDefs.empty()) {
    3437            0 :         return false;
    3438              :     }
    3439              : 
    3440          189 :     for (pugi::xpath_node staffDefXpathNode : staffDefs) {
    3441          125 :         libmei::StaffDef meiStaffDef;
    3442          125 :         meiStaffDef.Read(staffDefXpathNode.node());
    3443          125 :         Convert::StaffStruct staffSt = Convert::staffFromMEI(meiStaffDef, warning);
    3444          125 :         if (warning) {
    3445            0 :             this->addLog("staff definition", staffDefXpathNode.node());
    3446              :         }
    3447              : 
    3448          125 :         Part* part = nullptr;
    3449              :         // Try to get the corresponding part from the map
    3450          125 :         if (m_staffParts.count(meiStaffDef.GetN()) > 0) {
    3451          119 :             part = m_staffParts.at(meiStaffDef.GetN());
    3452              :         }
    3453              :         // If not found, create a new part and add it to the score
    3454              :         else {
    3455            6 :             part = new Part(m_score);
    3456            6 :             m_score->appendPart(part);
    3457              :         }
    3458          125 :         Staff* staff = Factory::createStaff(part);
    3459              :         // Mapped index from MEI staff@n to MuseScore index
    3460          125 :         const int staffIdx = this->getStaffIndex(meiStaffDef.GetN());
    3461          125 :         staff->setId(staffIdx);
    3462          125 :         staff->setLines(Fraction(0, 1), staffSt.lines);
    3463          125 :         if (staffSt.color.isValid()) {
    3464            1 :             staff->staffType(Fraction(0, 1))->setColor(staffSt.color);
    3465              :         }
    3466          125 :         staff->staffType(Fraction(0, 1))->setInvisible(staffSt.invisible);
    3467          125 :         staff->staffType(Fraction(0, 1))->setUserMag(staffSt.scale / 100);
    3468          125 :         part->instrument()->setTranspose(staffSt.interval);
    3469              : 
    3470          125 :         m_score->appendStaff(staff);
    3471          125 :     }
    3472              : 
    3473           64 :     int staffSpan = 0;
    3474           64 :     size_t idx = 0;
    3475              :     // Start from the top (default) staffGrp
    3476           64 :     pugi::xml_node staffGrpNode = scoreDefNode.select_node("./staffGrp").node();
    3477              : 
    3478           64 :     bool success = this->readStaffGrps(staffGrpNode, staffSpan, 0, idx);
    3479              : 
    3480              :     // Update parts instruments IDs for playback
    3481          167 :     for (const Part* part : m_score->parts()) {
    3482          206 :         for (const auto& pair : part->instruments()) {
    3483          103 :             pair.second->updateInstrumentId();
    3484              :         }
    3485              :     }
    3486              : 
    3487           64 :     return success;
    3488           64 : }
    3489              : 
    3490              : //---------------------------------------------------------
    3491              : // content creation helper methods
    3492              : //---------------------------------------------------------
    3493              : 
    3494              : /**
    3495              :  * Add an end barline to a measure.
    3496              :  */
    3497              : 
    3498           76 : void MeiImporter::addEndBarLineToMeasure(Measure* measure, BarLineType barLineType)
    3499              : {
    3500           76 :     IF_ASSERT_FAILED(measure) {
    3501            0 :         return;
    3502              :     }
    3503              : 
    3504              :     // Here we could check if this is the last measure and not add END because MuseScore sets it automatically
    3505           76 :     Segment* segment = measure->getSegment(SegmentType::EndBarLine, measure->endTick());
    3506          235 :     for (staff_idx_t staffIdx = 0; staffIdx < m_score->nstaves(); staffIdx++) {
    3507          159 :         BarLine* barLine = Factory::createBarLine(segment);
    3508          159 :         barLine->setTrack(staffIdx * VOICES);
    3509          159 :         barLine->setBarLineType(barLineType);
    3510          159 :         barLine->setSpanStaff(m_score->staff(staffIdx)->barLineSpan());
    3511          159 :         segment->add(barLine);
    3512              :     }
    3513              : }
    3514              : 
    3515              : /**
    3516              :  * Add a layout break to a measure.
    3517              :  */
    3518              : 
    3519            0 : void MeiImporter::addLayoutBreakToMeasure(Measure* measure, LayoutBreakType layoutBreakType)
    3520              : {
    3521            0 :     if (!measure) {
    3522            0 :         LOGD() << "MeiImporter::addLayoutBreakToMeasure no measure to add the break";
    3523            0 :         return;
    3524              :     }
    3525              : 
    3526            0 :     LayoutBreak* layoutBreak = Factory::createLayoutBreak(measure);
    3527            0 :     layoutBreak->setLayoutBreakType(layoutBreakType);
    3528            0 :     layoutBreak->setTrack(0);
    3529            0 :     measure->add(layoutBreak);
    3530              : }
    3531              : 
    3532              : /**
    3533              :  *  Add some text with a textStyleType to a vBox.
    3534              :  */
    3535              : 
    3536            0 : void MeiImporter::addTextToTitleFrame(VBox*& vBox, const String& str, TextStyleType textStyleType)
    3537              : {
    3538            0 :     if (!str.isEmpty()) {
    3539            0 :         if (vBox == nullptr) {
    3540            0 :             vBox = Factory::createTitleVBox(m_score);
    3541              :         }
    3542            0 :         Text* text = Factory::createText(vBox, textStyleType);
    3543            0 :         text->setPlainText(str);
    3544            0 :         vBox->add(text);
    3545              :     }
    3546            0 : }
    3547              : 
    3548              : /**
    3549              :  * Add the end element / tick / track for spanner ends.
    3550              :  * This is happening at the end of the import because we need to make sure spanning ends have been read.
    3551              :  * Different handing for ties since these are attached to the notes
    3552              :  * Additional handling for ottava to adjust end position tricker pitch offset calculation
    3553              :  */
    3554              : 
    3555           64 : void MeiImporter::addSpannerEnds()
    3556              : {
    3557          171 :     for (auto spannerMapEntry : m_openSpannerMap) {
    3558              :         // Ties are added directly to the notes
    3559          107 :         if (spannerMapEntry.first->isTie()) {
    3560           30 :             Note* endNote = this->findEndNote(spannerMapEntry.second);
    3561           30 :             if (!endNote) {
    3562            0 :                 continue;
    3563              :             }
    3564           30 :             Tie* tie = toTie(spannerMapEntry.first);
    3565           30 :             endNote->setTieBack(tie);
    3566           30 :             tie->setEndNote(endNote);
    3567           77 :         } else if (spannerMapEntry.first->isGlissando()) {
    3568           19 :             Note* endNote = this->findEndNote(spannerMapEntry.second);
    3569           19 :             if (!endNote) {
    3570            0 :                 continue;
    3571              :             }
    3572           19 :             Glissando* gliss = toGlissando(spannerMapEntry.first);
    3573           19 :             gliss->setEndElement(endNote);
    3574           19 :             gliss->setTick2(endNote->chord()->tick());
    3575           19 :             gliss->setTrack2(endNote->track());
    3576           19 :             endNote->addSpannerBack(gliss);
    3577              : 
    3578              :             // All other Spanners
    3579              :         } else {
    3580           58 :             ControlElementPosition pos = this->findEnd(spannerMapEntry.second, spannerMapEntry.first);
    3581           58 :             if (!pos.measure) {
    3582            0 :                 continue;
    3583              :             }
    3584           58 :             spannerMapEntry.first->setTick2(pos.tick);
    3585           58 :             spannerMapEntry.first->setEndElement(pos.chordRest);
    3586           58 :             spannerMapEntry.first->setTrack2(pos.track);
    3587           58 :             if (spannerMapEntry.first->isOttava() || spannerMapEntry.first->isTrill()) {
    3588              :                 // Set the tick2 to include the duration of the ChordRest
    3589           11 :                 if (pos.chordRest) {
    3590           11 :                     spannerMapEntry.first->setTick2(pos.chordRest->endTick());
    3591              :                 }
    3592              :                 // Special handling of ottavas
    3593           11 :                 if (spannerMapEntry.first->isOttava()) {
    3594            7 :                     Ottava* ottava = toOttava(spannerMapEntry.first);
    3595              :                     // Make the staff fill the pitch offsets accordingly since we use Note::ppitch in export
    3596            7 :                     ottava->staff()->updateOttava();
    3597              :                 }
    3598              :             }
    3599              :         }
    3600              :     }
    3601           68 :     for (auto arpegMapEntry : m_openArpegMap) {
    3602            4 :         std::vector plistChordRests = findPlistChordRests(arpegMapEntry.second);
    3603              :         // Go through the list of chord rest and check if they are on a staff below
    3604            8 :         for (auto chordRest : plistChordRests) {
    3605            4 :             Arpeggio* arpeggio = arpegMapEntry.first;
    3606            4 :             int span = static_cast<int>(chordRest->track() - arpeggio->track()) + 1;
    3607              :             // Adjust the span if it is currently smaller
    3608            4 :             if (arpeggio->span() < span) {
    3609            4 :                 arpeggio->setSpan(span);
    3610              :             }
    3611              :         }
    3612            4 :     }
    3613           64 : }
        

Generated by: LCOV version 2.0-1