LCOV - code coverage report
Current view: top level - src/importexport/mei/internal - meiexporter.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 84.2 % 1403 1182
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 "meiexporter.h"
      24              : 
      25              : #include <random>
      26              : #include <vector>
      27              : 
      28              : #include "containers.h"
      29              : #include "log.h"
      30              : #include "types/datetime.h"
      31              : 
      32              : #include "engraving/dom/arpeggio.h"
      33              : #include "engraving/dom/beam.h"
      34              : #include "engraving/dom/box.h"
      35              : #include "engraving/dom/bracket.h"
      36              : #include "engraving/dom/breath.h"
      37              : #include "engraving/dom/chord.h"
      38              : #include "engraving/dom/chordline.h"
      39              : #include "engraving/dom/clef.h"
      40              : #include "engraving/dom/dynamic.h"
      41              : #include "engraving/dom/fermata.h"
      42              : #include "engraving/dom/figuredbass.h"
      43              : #include "engraving/dom/fingering.h"
      44              : #include "engraving/dom/glissando.h"
      45              : #include "engraving/dom/hairpin.h"
      46              : #include "engraving/dom/harmony.h"
      47              : #include "engraving/dom/harppedaldiagram.h"
      48              : #include "engraving/dom/instrument.h"
      49              : #include "engraving/dom/jump.h"
      50              : #include "engraving/dom/keysig.h"
      51              : #include "engraving/dom/laissezvib.h"
      52              : #include "engraving/dom/lyrics.h"
      53              : #include "engraving/dom/marker.h"
      54              : #include "engraving/dom/masterscore.h"
      55              : #include "engraving/dom/measure.h"
      56              : #include "engraving/dom/measurerepeat.h"
      57              : #include "engraving/dom/note.h"
      58              : #include "engraving/dom/ornament.h"
      59              : #include "engraving/dom/ottava.h"
      60              : #include "engraving/dom/page.h"
      61              : #include "engraving/dom/part.h"
      62              : #include "engraving/dom/pedal.h"
      63              : #include "engraving/dom/rehearsalmark.h"
      64              : #include "engraving/dom/rest.h"
      65              : #include "engraving/dom/score.h"
      66              : #include "engraving/dom/segment.h"
      67              : #include "engraving/dom/sig.h"
      68              : #include "engraving/dom/slur.h"
      69              : #include "engraving/dom/staff.h"
      70              : #include "engraving/dom/system.h"
      71              : #include "engraving/dom/tempotext.h"
      72              : #include "engraving/dom/text.h"
      73              : #include "engraving/dom/tie.h"
      74              : #include "engraving/dom/timesig.h"
      75              : #include "engraving/dom/trill.h"
      76              : #include "engraving/dom/tuplet.h"
      77              : #include "engraving/dom/volta.h"
      78              : 
      79              : #include "thirdparty/libmei/cmn.h"
      80              : #include "thirdparty/libmei/fingering.h"
      81              : #include "thirdparty/libmei/harmony.h"
      82              : #include "thirdparty/libmei/lyrics.h"
      83              : #include "thirdparty/libmei/midi.h"
      84              : #include "thirdparty/libmei/shared.h"
      85              : 
      86              : using namespace mu::iex::mei;
      87              : using namespace mu::engraving;
      88              : 
      89              : // Number of spaces for the XML indentation. Set to 0 for tabs
      90              : #define MEI_INDENT 3
      91              : 
      92              : // Use counter-based IDs for layer elements
      93              : #define MEI_COUNTER_BASED_IDS false
      94              : 
      95              : /**
      96              :  * Write the Score to the destination file.
      97              :  * Return false on error.
      98              :  */
      99              : 
     100           64 : bool MeiExporter::write(std::string& meiData)
     101              : {
     102           64 :     const bool useMuseScoreIds = configuration()->meiUseMuseScoreIds();
     103              : 
     104           64 :     m_uids = UIDRegister::instance();
     105           64 :     m_xmlIDCounter = 0;
     106              : 
     107           64 :     m_hasSections = false;
     108              : 
     109           64 :     m_sectionCounter = 0;
     110           64 :     m_measureCounter = 0;
     111           64 :     m_staffCounter = 0;
     112           64 :     m_layerCounter = 0;
     113           64 :     m_layerCounterFor.resize(UNSPECIFIED_L + 1);
     114           64 :     this->resetLayerIDs();
     115              : 
     116              :     try {
     117           64 :         pugi::xml_document meiDoc;
     118              : 
     119           64 :         pugi::xml_node decl = meiDoc.prepend_child(pugi::node_declaration);
     120           64 :         decl.append_attribute("version") = "1.0";
     121           64 :         decl.append_attribute("encoding") = "UTF-8";
     122              : 
     123              :         // schema processing instruction
     124           64 :         std::string schema = "https://music-encoding.org/schema/5.1/mei-basic.rng";
     125           64 :         decl = meiDoc.append_child(pugi::node_declaration);
     126           64 :         decl.set_name("xml-model");
     127           64 :         decl.append_attribute("href") = schema.c_str();
     128           64 :         decl.append_attribute("type") = "application/xml";
     129           64 :         decl.append_attribute("schematypens") = "http://relaxng.org/ns/structure/1.0";
     130              : 
     131           64 :         decl = meiDoc.append_child(pugi::node_declaration);
     132           64 :         decl.set_name("xml-model");
     133           64 :         decl.append_attribute("href") = schema.c_str();
     134           64 :         decl.append_attribute("type") = "application/xml";
     135           64 :         decl.append_attribute("schematypens") = "http://purl.oclc.org/dsdl/schematron";
     136              : 
     137           64 :         m_mei = meiDoc.append_child("mei");
     138           64 :         m_mei.append_attribute("xmlns") = "http://www.music-encoding.org/ns/mei";
     139              : 
     140              :         // Option to use MuseScore Ids has priority
     141           64 :         if (useMuseScoreIds) {
     142            0 :             std::stringstream xmlId;
     143            0 :             EID eid = m_score->masterScore()->eid();
     144            0 :             if (!eid.isValid()) {
     145            0 :                 eid = m_score->masterScore()->assignNewEID();
     146              :             }
     147            0 :             String eidStr = String::fromStdString(eid.toStdString().c_str());
     148            0 :             xmlId << "mscore-" << eidStr.replace('/', '.').replace('+', '-').toStdString();
     149            0 :             m_mei.append_attribute("xml:id") = xmlId.str().c_str();
     150            0 :         }
     151              :         // Otherwise check if we have a metaTag
     152              :         else {
     153              :             // Save xml:id metaTag's as mei@xml:id
     154           64 :             String xmlId = m_score->metaTag(u"xml:id");
     155           64 :             if (!xmlId.isEmpty()) {
     156            0 :                 m_mei.append_attribute("xml:id") = xmlId.toStdString().c_str();
     157              :             }
     158           64 :         }
     159              : 
     160           64 :         libmei::AttConverter converter;
     161           64 :         libmei::meiVersion_MEIVERSION meiVersion = libmei::meiVersion_MEIVERSION_5_1plusbasic;
     162           64 :         m_mei.append_attribute("meiversion") = (converter.MeiVersionMeiversionToStr(meiVersion)).c_str();
     163              : 
     164           64 :         this->writeHeader();
     165              : 
     166           64 :         this->writeScore();
     167              : 
     168              :         // Currently not used. To be enabled for unfolding MuseScore Jumps into `@jumpto` MEI attribute if it becomes available on MEI repeatMark
     169              :         // this->addJumpToRepeatMarks();
     170              : 
     171           64 :         unsigned int output_flags = pugi::format_default;
     172              : 
     173              :         // Tabulation of MEI_INDENT * spaces (tabs if 0)
     174           64 :         std::string indent = MEI_INDENT ? std::string(MEI_INDENT, ' ') : "\t";
     175           64 :         std::stringstream strStream;
     176           64 :         meiDoc.save(strStream, indent.c_str(), output_flags);
     177           64 :         meiData = strStream.str();
     178           64 :     }
     179            0 :     catch (char* str) {
     180              :         UNUSED(str);
     181              :         // Do something with the error message
     182            0 :         return false;
     183            0 :     }
     184              : 
     185           64 :     return true;
     186              : }
     187              : 
     188              : //---------------------------------------------------------
     189              : //   convert
     190              : //---------------------------------------------------------
     191              : 
     192              : /**
     193              :  * Write the MEI header.
     194              :  * Look for the meiHead custom meta tag (given when importing an MEI file into MuseScore).
     195              :  * Otherwise, generate the header using the common meta tags.
     196              :  */
     197              : 
     198           64 : bool MeiExporter::writeHeader()
     199              : {
     200           64 :     String headStr = m_score->metaTag(u"meiHead");
     201           64 :     if (headStr.size() > 0) {
     202           64 :         pugi::xml_document docHead;
     203           64 :         docHead.load_string(headStr.toStdString().c_str());
     204           64 :         m_mei.append_copy(docHead.first_child());
     205           64 :     } else {
     206              :         // create header
     207            0 :         pugi::xml_node meiHead = m_mei.append_child("meiHead");
     208            0 :         pugi::xml_node fileDesc = meiHead.append_child("fileDesc");
     209            0 :         pugi::xml_node titleStmt = fileDesc.append_child("titleStmt");
     210            0 :         pugi::xml_node title = titleStmt.append_child("title");
     211            0 :         if (!m_score->metaTag(u"workTitle").isEmpty()) {
     212            0 :             title.text().set(m_score->metaTag(u"workTitle").toStdString().c_str());
     213            0 :             title.append_attribute("type") = "main";
     214              :         }
     215            0 :         if (!m_score->metaTag(u"subtitle").isEmpty()) {
     216            0 :             pugi::xml_node subtitle = titleStmt.append_child("title");
     217            0 :             subtitle.text().set(m_score->metaTag(u"subtitle").toStdString().c_str());
     218            0 :             subtitle.append_attribute("type") = "subordinate";
     219              :         }
     220              : 
     221            0 :         pugi::xml_node respStmt;
     222            0 :         StringList persNames;
     223              :         // the creator types commonly found in MusicXML
     224            0 :         persNames << u"arranger" << u"composer" << u"lyricist" << u"translator";
     225            0 :         for (String tagName : persNames) {
     226            0 :             String persName = m_score->metaTag(tagName);
     227            0 :             if (!persName.isEmpty()) {
     228            0 :                 if (!respStmt) {
     229            0 :                     respStmt = titleStmt.append_child("respStmt");
     230              :                 }
     231            0 :                 pugi::xml_node persNameNode = respStmt.append_child("persName");
     232            0 :                 persNameNode.text().set(persName.toStdString().c_str());
     233            0 :                 persNameNode.append_attribute("role") = tagName.toStdString().c_str();
     234              :             }
     235            0 :         }
     236              : 
     237            0 :         pugi::xml_node pubStmt = fileDesc.append_child("pubStmt");
     238            0 :         pugi::xml_node date = pubStmt.append_child("date");
     239              : 
     240              :         // date
     241            0 :         String dateStr = muse::DateTime::currentDateTime().toString();
     242            0 :         date.append_attribute("isodate") = dateStr.toStdString().c_str();
     243              : 
     244            0 :         if (!m_score->metaTag(u"copyright").isEmpty()) {
     245            0 :             pugi::xml_node availability = pubStmt.append_child("availability");
     246            0 :             availability.text().set(m_score->metaTag(u"copyright").toStdString().c_str());
     247              :         }
     248            0 :     }
     249              : 
     250           64 :     return true;
     251           64 : }
     252              : 
     253              : /**
     254              :  * Write the MEI score.
     255              :  * First write the initial scoreDef, and then loop through pages and systems.
     256              :  * Layout information (sb and pb) is written only when the corresponding output option is selected.
     257              :  */
     258              : 
     259           64 : bool MeiExporter::writeScore()
     260              : {
     261           64 :     pugi::xml_node music = m_mei.append_child("music");
     262           64 :     m_currentNode = music.append_child("body");
     263           64 :     m_currentNode = m_currentNode.append_child("mdiv");
     264           64 :     m_currentNode = m_currentNode.append_child("score");
     265              : 
     266           64 :     this->writeScoreDef();
     267              : 
     268           64 :     m_currentNode = m_currentNode.append_child();
     269           64 :     libmei::Section meiSection;
     270           64 :     meiSection.Write(m_currentNode, this->getSectionXmlId());
     271              : 
     272           64 :     int measureN = 0;
     273           64 :     bool isFirst = true;
     274           64 :     bool wasPreviousIrregular = true;
     275              : 
     276           64 :     bool exportLayout = configuration()->meiExportLayout();
     277           64 :     bool pageBreak = false;
     278           64 :     bool lineBreak = false;
     279              :     //bool sectionBreak = false; // not implemented, but we should had additional sections elements where m_hasSections is true
     280              : 
     281          128 :     for (const Page* page : m_score->pages()) {
     282           64 :         bool firstSystem = true;
     283           64 :         if (exportLayout) {
     284            0 :             libmei::Pb pb;
     285            0 :             if (pageBreak) {
     286            0 :                 pb.SetType(BREAK_TYPE);
     287              :             }
     288            0 :             pb.Write(m_currentNode.append_child());
     289            0 :         }
     290          224 :         for (const System* system : page->systems()) {
     291          160 :             if (exportLayout && !firstSystem) {
     292            0 :                 libmei::Sb sb;
     293            0 :                 if (lineBreak) {
     294            0 :                     sb.SetType(BREAK_TYPE);
     295              :                 }
     296            0 :                 sb.Write(m_currentNode.append_child());
     297            0 :             }
     298          628 :             for (const MeasureBase* mBase : system->measures()) {
     299          468 :                 if (mBase->isMeasure()) {
     300          404 :                     const Measure* measure = toMeasure(mBase);
     301          404 :                     this->writeEnding(measure);
     302          404 :                     this->writeMeasure(measure, measureN, isFirst, wasPreviousIrregular);
     303          404 :                     this->writeEndingEnd(measure);
     304          404 :                     firstSystem = false;
     305              :                 }
     306          468 :                 lineBreak = mBase->lineBreak();
     307          468 :                 pageBreak = mBase->pageBreak();
     308              :                 //sectionBreak = mBase->sectionBreak(); // see comment above
     309              :             }
     310              :         }
     311              :     }
     312              : 
     313              :     // non critical assert
     314           64 :     assert(this->isCurrentNode(libmei::Section()));
     315           64 :     m_currentNode = m_currentNode.parent();
     316              : 
     317           64 :     return true;
     318           64 : }
     319              : 
     320              : /**
     321              :  * Write the initial score definition.
     322              :  */
     323              : 
     324           64 : bool MeiExporter::writeScoreDef()
     325              : {
     326           64 :     m_currentNode = m_currentNode.append_child("scoreDef");
     327              : 
     328              :     // If we have a VBox on the first measure, assume it to be a title frame and use it for the pgHead
     329           64 :     MeasureBase* mBase = m_score->measures()->first();
     330           64 :     if (mBase->isVBox()) {
     331           64 :         this->writePgHead(toVBox(mBase));
     332              :     }
     333              : 
     334              :     // Number of staffGrp closing at each staff
     335           64 :     std::vector<int> staffGrpEnds(m_score->staves().size(), 0);
     336              : 
     337           64 :     const Measure* measure = nullptr;
     338          532 :     for (MeasureBase* mBase2 = m_score->measures()->first(); mBase2 != nullptr; mBase2 = mBase2->next()) {
     339          468 :         if (!measure && mBase2->isMeasure()) {
     340              :             // the first actual measure we are going built the scoreDef from
     341           64 :             measure = toMeasure(mBase2);
     342              :         }
     343              :         // Also check here if we have multiple sections in the score
     344          468 :         if (mBase2->sectionBreak()) {
     345            0 :             m_hasSections = true;
     346            0 :             break;
     347              :         }
     348              :     }
     349              : 
     350              :     // Probably no music in the file (see vtest/scores/frametext.mscx)
     351           64 :     if (!measure) {
     352              :         // pop the scoreDef
     353            0 :         m_currentNode = m_currentNode.parent();
     354            0 :         return true;
     355              :     }
     356              : 
     357           64 :     m_currentNode = m_currentNode.append_child("staffGrp");
     358              : 
     359          167 :     for (Part* part : m_score->parts()) {
     360              :         // For parts with more than one staff, write the label in the staffGrp
     361          103 :         Part* staffGrpPart = (part->nstaves() > 1) ? part : nullptr;
     362              :         // Otherwise write the label in the staffDef
     363          103 :         bool isStaffDefPart = (part->nstaves() == 1) ? true : false;
     364          228 :         for (Staff* staff : part->staves()) {
     365          125 :             this->writeStaffGrpStart(staff, staffGrpEnds, staffGrpPart);
     366          125 :             this->writeStaffDef(staff, measure, part, isStaffDefPart);
     367          125 :             this->writeStaffGrpEnd(staff, staffGrpEnds);
     368              :             // We pass the part only for the first staff of the staffGrp, so set it to null after
     369          125 :             staffGrpPart = nullptr;
     370              :         }
     371              :     }
     372              : 
     373              :     // pop the staffGrp
     374           64 :     m_currentNode = m_currentNode.parent();
     375              :     // pop the scoreDef
     376           64 :     m_currentNode = m_currentNode.parent();
     377              : 
     378           64 :     return true;
     379           64 : }
     380              : 
     381              : /**
     382              :  * Write the page header.
     383              :  * Uses the first MuseScore vBox.
     384              :  */
     385              : 
     386           64 : bool MeiExporter::writePgHead(const VBox* vBox)
     387              : {
     388           64 :     IF_ASSERT_FAILED(vBox) {
     389            0 :         return false;
     390              :     }
     391              : 
     392           64 :     m_currentNode = m_currentNode.append_child();
     393              : 
     394           64 :     libmei::PgHead pgHead;
     395           64 :     pgHead.SetFunc(libmei::PGFUNC_first);
     396           64 :     pgHead.Write(m_currentNode);
     397              : 
     398         1216 :     std::list<std::pair<libmei::Rend, String> > cells[CellCount];
     399              : 
     400              :     // For each text in the frame create an MEI Rend
     401              :     // Convert::textToMEI set the size of the Rend looking at the TextStyleType
     402              :     // It also places them (cell) accordingly
     403          225 :     for (const EngravingItem* element : vBox->el()) {
     404          161 :         if (element->isText()) {
     405          161 :             const Text* text = toText(element);
     406          161 :             auto [meiRend, cell, rendText] = Convert::textToMEI(text);
     407          161 :             cells[cell].push_back(std::make_pair(meiRend, rendText));
     408          161 :         }
     409              :     }
     410              : 
     411              :     // Each cell is now a list of pairs of Rend and the corresponding text content
     412              :     // The text content is plain text but can be multiple lines separated with a "\n"
     413          640 :     for (int cell = TopLeft; cell < CellCount; cell++) {
     414          576 :         if (cells[cell].empty()) {
     415          457 :             continue;
     416              :         }
     417              :         // if the cell is not empty, create a cell Rend an place it accordingly
     418          119 :         pugi::xml_node cellNode = m_currentNode.append_child();
     419          119 :         libmei::Rend meiRendCell;
     420          119 :         if (cell < 3) {
     421           65 :             meiRendCell.SetValign(libmei::VERTICALALIGNMENT_top);
     422           54 :         } else if (cell < 6) {
     423            0 :             meiRendCell.SetValign(libmei::VERTICALALIGNMENT_middle);
     424              :         } else {
     425           54 :             meiRendCell.SetValign(libmei::VERTICALALIGNMENT_bottom);
     426              :         }
     427          119 :         if ((cell % 3) == 0) {
     428            2 :             meiRendCell.SetHalign(libmei::HORIZONTALALIGNMENT_left);
     429          117 :         } else if ((cell % 3) == 1) {
     430           64 :             meiRendCell.SetHalign(libmei::HORIZONTALALIGNMENT_center);
     431              :         } else {
     432           53 :             meiRendCell.SetHalign(libmei::HORIZONTALALIGNMENT_right);
     433              :         }
     434          119 :         meiRendCell.Write(cellNode);
     435              :         // In the cell, write each Rend
     436          119 :         bool isFirst = true;
     437          280 :         for (auto& pair : cells[cell]) {
     438              :             // If we have more than one Rend in the cell-list, add an <lb/>
     439          161 :             if (!isFirst) {
     440           42 :                 cellNode.append_child("lb");
     441              :             }
     442          161 :             pugi::xml_node rendNode = cellNode.append_child();
     443          322 :             pair.first.Write(rendNode);
     444              :             // Each Rend (as plain text) can itself be multi-line, split it with <lb/>
     445          161 :             StringList lines = pair.second.split(u"\n");
     446          161 :             this->writeLines(rendNode, lines);
     447          161 :             isFirst = false;
     448          161 :         }
     449          119 :     }
     450              : 
     451           64 :     m_currentNode = m_currentNode.parent();
     452              : 
     453           64 :     return true;
     454          704 : }
     455              : 
     456              : /**
     457              :  * Write a list of string as lines separated with line breaks
     458              :  */
     459              : 
     460          456 : bool MeiExporter::writeLines(pugi::xml_node node, const StringList& lines)
     461              : {
     462          456 :     if (lines.size() > 0) {
     463          456 :         node.text().set(lines[0].toStdString().c_str());
     464              :     }
     465          466 :     for (size_t index = 1; index < lines.size(); index++) {
     466           10 :         node.append_child("lb");
     467           10 :         pugi::xml_node textNode = node.append_child(pugi::node_pcdata);
     468           10 :         textNode.text() = lines[index].toStdString().c_str();
     469              :     }
     470          456 :     return true;
     471              : }
     472              : 
     473              : /**
     474              :  * Write a list of string as lines separated with line breaks.
     475              :  * For each line group the SMuFL symbols into <rend> with a @glyph.num
     476              :  * Convert line by line MuseScore plain text (without <sym>) into text segmented text blocks
     477              :  */
     478              : 
     479           11 : bool MeiExporter::writeLinesWithSMuFL(pugi::xml_node node, const StringList& lines)
     480              : {
     481           11 :     bool isFirst = true;
     482           23 :     for (size_t index = 0; index < lines.size(); index++) {
     483           12 :         if (!isFirst) {
     484            1 :             node.append_child("lb");
     485              :         }
     486              : 
     487           12 :         Convert::textWithSmufl lineBlocks;
     488           12 :         Convert::textToMEI(lineBlocks, lines.at(index));
     489              : 
     490           12 :         bool isFirstBlock = true;
     491           34 :         for (auto& block : lineBlocks) {
     492           22 :             if (block.first) {
     493            9 :                 pugi::xml_node rendText = node.append_child();
     494            9 :                 libmei::Rend meiRend;
     495           18 :                 meiRend.SetGlyphAuth(SMUFL_AUTH);
     496            9 :                 meiRend.Write(rendText);
     497            9 :                 rendText.text() = block.second.toStdString().c_str();
     498            9 :             } else {
     499           13 :                 if (isFirst && isFirstBlock) {
     500            5 :                     node.text().set(block.second.toStdString().c_str());
     501              :                 } else {
     502            8 :                     pugi::xml_node textNode = node.append_child(pugi::node_pcdata);
     503            8 :                     textNode.text() = block.second.toStdString().c_str();
     504              :                 }
     505              :             }
     506           22 :             isFirstBlock = false;
     507              :         }
     508           12 :         isFirst = false;
     509           12 :     }
     510           11 :     return true;
     511              : }
     512              : 
     513              : /**
     514              :  * Write a score definition change.
     515              :  * The scoreDef is preprended to the m_currentNode that currently holds the measure.
     516              :  * The changes are encoded in the scoreDef element if that is possible.
     517              :  * Otherwise, staffGrp with staffDef are encoded (e.g., when changing a key signature with transposing instruments)
     518              :  */
     519              : 
     520          340 : bool MeiExporter::writeScoreDefChange()
     521              : {
     522          340 :     if (!m_timeSig && !m_keySig) {
     523          322 :         return true;
     524              :     }
     525              : 
     526              :     // First check that the timesig change is the same at all staves
     527           18 :     const TimeSig* scoreDefTimeSig = nullptr;
     528           18 :     if (m_timeSig) {
     529           39 :         for (size_t staff = 0; staff < m_score->nstaves(); staff++) {
     530           25 :             const TimeSig* current = dynamic_cast<const TimeSig*>(m_timeSig->element(staff2track(staff)));
     531           25 :             if (scoreDefTimeSig) {
     532              :                 // Compare the current and the previous one, stop if they are different
     533            8 :                 if (!current || (*current) != (*scoreDefTimeSig)) {
     534            2 :                     scoreDefTimeSig = nullptr;
     535            2 :                     break;
     536              :                 }
     537              :             }
     538           23 :             scoreDefTimeSig = current;
     539              :             // The first staff had none, that will be different than any other one in the segment anyway
     540           23 :             if (!scoreDefTimeSig) {
     541            1 :                 break;
     542              :             }
     543              :         }
     544              :     }
     545              : 
     546              :     // Same for keysig changes (likely to be different with transposing instruments)
     547           18 :     const KeySig* scoreDefKeySig = nullptr;
     548           18 :     if (m_keySig) {
     549            4 :         for (size_t staff = 0; staff < m_score->nstaves(); staff++) {
     550            4 :             const KeySig* current = dynamic_cast<const KeySig*>(m_keySig->element(staff2track(staff)));
     551            4 :             if (scoreDefKeySig) {
     552            2 :                 if (!current || (current->key() != scoreDefKeySig->key())) {
     553            2 :                     scoreDefKeySig = nullptr;
     554            2 :                     break;
     555              :                 }
     556              :             }
     557            2 :             scoreDefKeySig = current;
     558              :             // The first staff had none, same as for time sig
     559            2 :             if (!scoreDefKeySig) {
     560            0 :                 break;
     561              :             }
     562              :         }
     563              :     }
     564              : 
     565              :     // m_currentNode is the last measure and we need to prepend the scoreDef
     566           18 :     pugi::xml_node scoreDefNode = m_currentNode.parent().insert_child_before("scoreDef", m_currentNode);
     567           18 :     libmei::ScoreDef meiScoreDef;
     568              : 
     569              :     // Single timesig change
     570           18 :     if (scoreDefTimeSig) {
     571           14 :         libmei::StaffDef timeSigDef = Convert::meterToMEI(scoreDefTimeSig->sig(), scoreDefTimeSig->timeSigType());
     572           14 :         meiScoreDef.SetMeterSym(timeSigDef.GetMeterSym());
     573           14 :         meiScoreDef.SetMeterUnit(timeSigDef.GetMeterUnit());
     574           14 :         meiScoreDef.SetMeterCount(timeSigDef.GetMeterCount());
     575           14 :     }
     576              :     // Single keysig change
     577           18 :     if (scoreDefKeySig) {
     578              :         //libmei::StaffDef keySigDef = Convert::keyToMEI(scoreDefKeySig->sig());
     579            0 :         meiScoreDef.SetKeysig(Convert::keyToMEI(scoreDefKeySig->key()));
     580              :     }
     581              :     // Otherwise, add staffGrp/staffDef
     582           18 :     if ((!scoreDefTimeSig && m_timeSig) || (!scoreDefKeySig && m_keySig)) {
     583            5 :         pugi::xml_node staffGrpNode = scoreDefNode.append_child("staffGrp");
     584           17 :         for (size_t staff = 0; staff < m_score->nstaves(); staff++) {
     585           12 :             pugi::xml_node staffDefNode = staffGrpNode.append_child();
     586           12 :             libmei::StaffDef meiStaffDef;
     587           12 :             if (!scoreDefTimeSig && m_timeSig) {
     588            6 :                 const TimeSig* timeSig = dynamic_cast<const TimeSig*>(m_timeSig->element(staff2track(staff)));
     589            6 :                 if (timeSig) {
     590            4 :                     meiStaffDef = Convert::meterToMEI(timeSig->sig(), timeSig->timeSigType());
     591              :                 }
     592              :             }
     593           12 :             if (!scoreDefKeySig && m_keySig) {
     594            6 :                 const KeySig* keySig = dynamic_cast<const KeySig*>(m_keySig->element(staff2track(staff)));
     595            6 :                 if (keySig) {
     596            6 :                     meiStaffDef.SetKeysig(Convert::keyToMEI(keySig->key()));
     597              :                 }
     598              :             }
     599           12 :             meiStaffDef.SetN(static_cast<int>(staff + 1));
     600           12 :             meiStaffDef.Write(staffDefNode);
     601           12 :         }
     602              :     }
     603              : 
     604           18 :     meiScoreDef.Write(scoreDefNode);
     605              : 
     606           18 :     return true;
     607           18 : }
     608              : 
     609              : /**
     610              :  * Write a staffGrp (opening).
     611              :  * Increments in ends the ending position of the staffGrp to be closed by MeiExporter::writeStaffGrpEnd.
     612              :  */
     613              : 
     614          125 : bool MeiExporter::writeStaffGrpStart(const Staff* staff, std::vector<int>& ends, const Part* staffGrpPart)
     615              : {
     616          125 :     IF_ASSERT_FAILED(staff) {
     617            0 :         return false;
     618              :     }
     619          125 :     const staff_idx_t staffIdx = staff->idx();
     620              : 
     621          253 :     for (size_t j = 0; j < m_score->bracketLevels(staffIdx) + 1; j++) {
     622          128 :         BracketType bracketType = m_score->bracketType(staffIdx, j);
     623          128 :         if (bracketType == BracketType::NO_BRACKET) {
     624           93 :             continue;
     625              :         }
     626              : 
     627           35 :         libmei::StaffGrp meiStaffGrp = Convert::staffGrpToMEI(bracketType, staff->barLineSpan());
     628              :         // mark at which staff we will need to close the staffGrp
     629           35 :         int end = static_cast<int>(staffIdx + m_score->bracketSpan(staffIdx, j)) - 1;
     630              :         // Something is wrong, maybe a staff was delete in the MuseScore file?
     631           35 :         if (end >= static_cast<int>(ends.size())) {
     632            0 :             continue;
     633              :         }
     634           35 :         ends.at(end)++;
     635              :         //
     636           35 :         m_currentNode = m_currentNode.append_child();
     637           35 :         meiStaffGrp.Write(m_currentNode);
     638              :         // If we have a part and reached the latest level, write the label and labelAbbr
     639           35 :         if (staffGrpPart && j == m_score->bracketLevels(staffIdx)) {
     640           20 :             this->writeLabel(m_currentNode, staffGrpPart);
     641           20 :             this->writeInstrDef(m_currentNode, staffGrpPart);
     642              :         }
     643           35 :     }
     644          125 :     return true;
     645              : }
     646              : 
     647              : /**
     648              :  * Write a staffGrp (closing).
     649              :  * Looks in ends how many staffGrp levels need to be closed for the corresponding staffIdx.
     650              :  */
     651              : 
     652          125 : bool MeiExporter::writeStaffGrpEnd(const Staff* staff, std::vector<int>& ends)
     653              : {
     654          125 :     IF_ASSERT_FAILED(staff) {
     655            0 :         return false;
     656              :     }
     657              : 
     658          125 :     size_t idx = staff->idx();
     659          160 :     for (int i = 0; i < ends.at(idx); i++) {
     660           35 :         m_currentNode = m_currentNode.parent();
     661              :     }
     662              : 
     663          125 :     return true;
     664              : }
     665              : 
     666              : /**
     667              :  * Write the initial staff definitions.
     668              :  */
     669              : 
     670          125 : bool MeiExporter::writeStaffDef(const Staff* staff, const Measure* measure, const Part* part, bool isPart)
     671              : {
     672          125 :     IF_ASSERT_FAILED(staff && measure && part) {
     673            0 :         return false;
     674              :     }
     675              : 
     676          125 :     libmei::StaffDef meiStaffDef = Convert::staffToMEI(staff);
     677          125 :     pugi::xml_node staffDefNode = m_currentNode.append_child();
     678              : 
     679          125 :     if (isPart) {
     680           83 :         this->writeLabel(staffDefNode, part);
     681           83 :         this->writeInstrDef(staffDefNode, part);
     682              :     }
     683              : 
     684          125 :     if (measure) {
     685          125 :         Fraction tick = measure->tick();
     686              : 
     687          125 :         track_idx_t startTrack = staff2track(staff->idx());
     688          125 :         track_idx_t endTrack = startTrack + VOICES;
     689              : 
     690              :         // clef
     691          125 :         Segment* clefSeg = measure->findSegment(SegmentType::HeaderClef, tick);
     692          125 :         if (clefSeg) {
     693          125 :             for (track_idx_t track = startTrack; track < endTrack; ++track) {
     694          125 :                 Clef* clef = toClef(clefSeg->element(track));
     695          125 :                 if (clef) {
     696          125 :                     libmei::Clef meiClef = Convert::clefToMEI(clef->clefType());
     697          125 :                     Convert::colorToMEI(clef, meiClef);
     698          125 :                     pugi::xml_node clefNode = staffDefNode.append_child();
     699          125 :                     meiClef.Write(clefNode);
     700          125 :                     break;
     701          125 :                 }
     702              :             }
     703              :         }
     704              :         // time signature
     705          125 :         Segment* timeSigSeg = measure->findSegment(SegmentType::TimeSig, tick);
     706          125 :         if (timeSigSeg) {
     707          125 :             for (track_idx_t track = startTrack; track < endTrack; ++track) {
     708          125 :                 TimeSig* timeSig = toTimeSig(timeSigSeg->element(track));
     709          125 :                 if (timeSig) {
     710          125 :                     libmei::StaffDef timeSigDef = Convert::meterToMEI(timeSig->sig(), timeSig->timeSigType());
     711          125 :                     meiStaffDef.SetMeterSym(timeSigDef.GetMeterSym());
     712          125 :                     meiStaffDef.SetMeterUnit(timeSigDef.GetMeterUnit());
     713          125 :                     meiStaffDef.SetMeterCount(timeSigDef.GetMeterCount());
     714          125 :                     break;
     715          125 :                 }
     716              :             }
     717              :         }
     718              :         // key signature
     719          125 :         Segment* keySigSeg = measure->findSegment(SegmentType::KeySig, tick);
     720          125 :         if (keySigSeg) {
     721          565 :             for (track_idx_t track = startTrack; track < endTrack; ++track) {
     722          455 :                 KeySig* keySig = toKeySig(keySigSeg->element(track));
     723              :                 // For the initial staffDef we do not write @key.sig="0"
     724          455 :                 if (keySig && keySig->key() != Key::C) {
     725           15 :                     meiStaffDef.SetKeysig(Convert::keyToMEI(keySig->key()));
     726           15 :                     break;
     727              :                 }
     728              :             }
     729              :         }
     730              :     }
     731              : 
     732          125 :     meiStaffDef.Write(staffDefNode);
     733              : 
     734          125 :     return true;
     735          125 : }
     736              : 
     737              : /**
     738              :  * Write label and label abbreviations.
     739              :  */
     740              : 
     741          103 : bool MeiExporter::writeLabel(pugi::xml_node node, const Part* part)
     742              : {
     743          103 :     IF_ASSERT_FAILED(part) {
     744            0 :         return false;
     745              :     }
     746              : 
     747          103 :     StringList lines;
     748          103 :     const Instrument* instrument = part->instrument();
     749          103 :     if (instrument && !instrument->longName().empty()) {
     750           97 :         libmei::Label meiLabel;
     751           97 :         pugi::xml_node labelNode = node.append_child();
     752           97 :         meiLabel.Write(labelNode);
     753           97 :         lines = instrument->nameAsPlainText().split(u"\n");
     754           97 :         this->writeLines(labelNode, lines);
     755           97 :     }
     756          103 :     if (instrument && !instrument->shortName().empty()) {
     757           96 :         libmei::LabelAbbr meiLabelAbbr;
     758           96 :         pugi::xml_node labelAbbrNode = node.append_child();
     759           96 :         meiLabelAbbr.Write(labelAbbrNode);
     760           96 :         lines = instrument->abbreviatureAsPlainText().split(u"\n");
     761           96 :         this->writeLines(labelAbbrNode, lines);
     762           96 :     }
     763              : 
     764          103 :     return true;
     765          103 : }
     766              : 
     767              : /**
     768              :  * Write instrument definition for MIDI information.
     769              :  */
     770              : 
     771          103 : bool MeiExporter::writeInstrDef(pugi::xml_node node, const Part* part)
     772              : {
     773          103 :     IF_ASSERT_FAILED(part) {
     774            0 :         return false;
     775              :     }
     776              : 
     777          103 :     const int midiProgram = part->midiProgram();
     778              :     // const int midiChannel = part->midiChannel();
     779              :     // const int midiPort = part->midiPort();
     780              : 
     781          103 :     if (midiProgram < 0) {
     782           69 :         return false;
     783              :     }
     784              : 
     785           34 :     libmei::InstrDef meiInstrDef;
     786           34 :     pugi::xml_node instrDefNode = node.append_child();
     787           34 :     if (midiProgram >= 0 && midiProgram < 128) {
     788           34 :         meiInstrDef.SetMidiInstrnum(midiProgram);
     789              :     }
     790           34 :     meiInstrDef.Write(instrDefNode);
     791              : 
     792           34 :     return true;
     793           34 : }
     794              : 
     795              : /**
     796              :  * Write an ending (opening).
     797              :  * Performs a lookup of voltas spanning the measure with MeiExporter::findVoltasInMeasure
     798              :  */
     799              : 
     800          404 : bool MeiExporter::writeEnding(const Measure* measure)
     801              : {
     802          404 :     std::vector<const Volta*> voltas = this->findVoltasInMeasure(measure);
     803          417 :     auto voltaIter = std::find_if(voltas.begin(), voltas.end(), [measure](const Volta* volta) { return volta->startMeasure() == measure; });
     804              : 
     805          404 :     if (voltaIter != voltas.end()) {
     806            4 :         libmei::Ending meiEnding = Convert::endingToMEI(*voltaIter);
     807            4 :         m_currentNode = m_currentNode.append_child();
     808            4 :         meiEnding.Write(m_currentNode, this->getXmlIdFor(*voltaIter, 'e'));
     809            4 :     }
     810          404 :     return true;
     811          404 : }
     812              : 
     813              : /**
     814              :  * Write an ending (closing).
     815              :  * Performs a lookup of voltas spanning the measure with MeiExporter::findVoltasInMeasure
     816              :  */
     817              : 
     818          404 : bool MeiExporter::writeEndingEnd(const Measure* measure)
     819              : {
     820          404 :     std::vector<const Volta*> voltas = this->findVoltasInMeasure(measure);
     821              : 
     822          404 :     if (muse::contains_if(voltas, [measure](const Volta* volta) {
     823           12 :         return volta->endMeasure() == measure;
     824              :     })) {
     825              :         // non critical assert
     826            4 :         assert(this->isCurrentNode(libmei::Ending()));
     827            4 :         m_currentNode = m_currentNode.parent();
     828              :     }
     829          404 :     return true;
     830          404 : }
     831              : 
     832              : /**
     833              :  * Write a measure and its content.
     834              :  * Write the staves and the control events.
     835              :  * Prepends a scoreDef change if a changing key signature or time signature is encountered in the content.
     836              :  */
     837              : 
     838          404 : bool MeiExporter::writeMeasure(const Measure* measure, int& measureN, bool& isFirst, bool& wasPreviousIrregular)
     839              : {
     840          404 :     IF_ASSERT_FAILED(measure) {
     841            0 :         return false;
     842              :     }
     843              : 
     844          404 :     bool success = true;
     845              : 
     846          404 :     libmei::Measure meiMeasure = Convert::measureToMEI(measure, measureN, wasPreviousIrregular);
     847          404 :     m_currentNode = m_currentNode.append_child();
     848          404 :     meiMeasure.Write(m_currentNode, this->getMeasureXmlId(measure));
     849              : 
     850              :     // Reset keySig and timeSig change
     851          404 :     m_keySig = nullptr;
     852          404 :     m_timeSig = nullptr;
     853              : 
     854         1054 :     for (Staff* staff : m_score->staves()) {
     855          650 :         this->writeStaff(staff, measure);
     856              :     }
     857              : 
     858          427 :     for (EngravingItem* item : measure->el()) {
     859           23 :         switch (item->type()) {
     860           14 :         case ElementType::JUMP: success = success && this->writeRepeatMark(toJump(item), measure);
     861           14 :             break;
     862            9 :         case ElementType::MARKER: success = success && this->writeRepeatMark(toMarker(item), measure);
     863            9 :             break;
     864            0 :         default: break;
     865              :         }
     866              :     }
     867              : 
     868          725 :     for (auto controlEvent : m_startingControlEventList) {
     869          321 :         if (controlEvent.first->isArpeggio()) {
     870           11 :             success = success && this->writeArpeg(toArpeggio(controlEvent.first), controlEvent.second);
     871          310 :         } else if (controlEvent.first->isBreath()) {
     872           17 :             success = success && this->writeBreath(toBreath(controlEvent.first), controlEvent.second);
     873          293 :         } else if (controlEvent.first->isDynamic()) {
     874           10 :             success = success && this->writeDynam(toDynamic(controlEvent.first), controlEvent.second);
     875          283 :         } else if (controlEvent.first->isExpression() || controlEvent.first->isPlayTechAnnotation()) {
     876            3 :             success = success && this->writeDir(toTextBase(controlEvent.first), controlEvent.second);
     877          280 :         } else if (controlEvent.first->isFermata()) {
     878           32 :             success = success && this->writeFermata(toFermata(controlEvent.first), controlEvent.second);
     879          248 :         } else if (controlEvent.first->isFiguredBass()) {
     880            7 :             success = success && this->writeFb(toFiguredBass(controlEvent.first), controlEvent.second);
     881          241 :         } else if (controlEvent.first->isFingering()) {
     882           50 :             success = success && this->writeFing(toFingering(controlEvent.first), controlEvent.second);
     883          191 :         } else if (controlEvent.first->isGlissando()) {
     884           19 :             success = success && this->writeGliss(toGlissando(controlEvent.first), controlEvent.second);
     885          172 :         } else if (controlEvent.first->isHairpin()) {
     886            9 :             success = success && this->writeHairpin(toHairpin(controlEvent.first), controlEvent.second);
     887          163 :         } else if (controlEvent.first->isHarmony()) {
     888           26 :             success = success && this->writeHarm(toHarmony(controlEvent.first), controlEvent.second);
     889          137 :         } else if (controlEvent.first->isHarpPedalDiagram()) {
     890            3 :             success = success && this->writeHarpPedal(toHarpPedalDiagram(controlEvent.first), controlEvent.second);
     891          134 :         } else if (controlEvent.first->isOrnament()) {
     892           33 :             success = success && this->writeOrnament(toOrnament(controlEvent.first), controlEvent.second);
     893          101 :         } else if (controlEvent.first->isOttava()) {
     894            7 :             success = success && this->writeOctave(toOttava(controlEvent.first), controlEvent.second);
     895           94 :         } else if (controlEvent.first->isPedal()) {
     896            5 :             success = success && this->writePedal(toPedal(controlEvent.first), controlEvent.second);
     897           89 :         } else if (controlEvent.first->isRehearsalMark()) {
     898            3 :             success = success && this->writeRehearsalMark(toRehearsalMark(controlEvent.first), controlEvent.second);
     899           86 :         } else if (controlEvent.first->isSlur()) {
     900           33 :             success = success && this->writeSlur(toSlur(controlEvent.first), controlEvent.second);
     901           53 :         } else if (controlEvent.first->isStaffText()) {
     902            1 :             success = success && this->writeDir(toTextBase(controlEvent.first), controlEvent.second);
     903           52 :         } else if (controlEvent.first->isSystemText()) {
     904            1 :             success = success && this->writeDir(toTextBase(controlEvent.first), controlEvent.second);
     905           51 :         } else if (controlEvent.first->isTempoText()) {
     906           11 :             success = success && this->writeTempo(toTempoText(controlEvent.first), controlEvent.second);
     907           40 :         } else if (controlEvent.first->isTie()) {
     908           36 :             success = success && this->writeTie(toTie(controlEvent.first), controlEvent.second);
     909            4 :         } else if (controlEvent.first->isTrill()) {
     910            4 :             success = success && this->writeTrill(toTrill(controlEvent.first), controlEvent.second);
     911              :         }
     912          321 :     }
     913          404 :     m_startingControlEventList.clear();
     914              : 
     915          410 :     for (auto controlEvent : m_tstampControlEventMap) {
     916            6 :         if (controlEvent.first->isFermata()) {
     917            6 :             success = success && this->writeFermata(toFermata(controlEvent.first), controlEvent.second.first,
     918              :                                                     controlEvent.second.second);
     919              :         }
     920            6 :     }
     921          404 :     m_tstampControlEventMap.clear();
     922              : 
     923          404 :     this->addEndidToControlEvents();
     924              : 
     925              :     // This will prepend the scoreDef
     926          404 :     if (!isFirst) {
     927          340 :         this->writeScoreDefChange();
     928              :     }
     929          404 :     isFirst = false;
     930              : 
     931          404 :     m_currentNode = m_currentNode.parent();
     932              : 
     933          404 :     return success;
     934          404 : }
     935              : 
     936              : /**
     937              :  * Write a staff and its content.
     938              :  * Checks if each voice has some content.
     939              :  */
     940              : 
     941          650 : bool MeiExporter::writeStaff(const Staff* staff, const Measure* measure)
     942              : {
     943          650 :     IF_ASSERT_FAILED(staff && measure) {
     944            0 :         return false;
     945              :     }
     946              : 
     947          650 :     m_currentNode = m_currentNode.append_child();
     948              : 
     949          650 :     libmei::Staff meiStaff;
     950          650 :     meiStaff.SetN(static_cast<int>(staff->idx() + 1));
     951          650 :     meiStaff.Write(m_currentNode, this->getStaffXmlId());
     952              : 
     953          650 :     track_idx_t startTrack = staff2track(staff->idx());
     954          650 :     track_idx_t endTrack = startTrack + VOICES;
     955         3250 :     for (track_idx_t track = startTrack; track < endTrack; ++track) {
     956         2600 :         writeLayer(track, staff, measure);
     957              :     }
     958              : 
     959          650 :     m_currentNode = m_currentNode.parent();
     960              : 
     961          650 :     return true;
     962          650 : }
     963              : 
     964              : /**
     965              :  * Write a layer (i.e., voice) and its content.
     966              :  */
     967              : 
     968         2600 : bool MeiExporter::writeLayer(track_idx_t track, const Staff* staff, const Measure* measure)
     969              : {
     970         2600 :     IF_ASSERT_FAILED(staff) {
     971            0 :         return false;
     972              :     }
     973              : 
     974              :     // If there is no voice, only increase the layer@n
     975         2600 :     if (!measure->hasVoice(track)) {
     976         1924 :         this->getLayerXmlId();
     977         1924 :         return true;
     978              :     }
     979              : 
     980          676 :     m_currentNode = m_currentNode.append_child();
     981          676 :     libmei::Layer meiLayer;
     982          676 :     meiLayer.SetN(static_cast<int>(track2voice(track) + 1));
     983          676 :     meiLayer.Write(m_currentNode, this->getLayerXmlId());
     984              : 
     985          676 :     if (measure->measureRepeatNumMeasures(track2staff(track)) == 1) {
     986            2 :         MeasureRepeat* measureRepeat = measure->measureRepeatElement(track2staff(track));
     987            2 :         this->writeMRpt(measureRepeat);
     988            2 :         return true;
     989              :     }
     990              : 
     991         4172 :     for (Segment* seg = measure->first(); seg; seg = seg->next()) {
     992         3498 :         if (seg->segmentType() == SegmentType::EndBarLine) {
     993          674 :             this->addFermataToMap(track, seg, measure);
     994              :         }
     995              : 
     996              :         // Do not go any further than the measure tick (ignore EndBarLine, KeySigAnnounce, TimeSigAnnounce)
     997         3498 :         const EngravingItem* item = seg->element(track);
     998         3498 :         if (!item || item->generated()) {
     999         1214 :             continue;
    1000              :         }
    1001              : 
    1002         2284 :         if (item->isClef()) {
    1003          156 :             this->writeClef(dynamic_cast<const Clef*>(item));
    1004         2128 :         } else if (item->isChord()) {
    1005         1531 :             this->writeChord(toChord(item), staff);
    1006          597 :         } else if (item->isRest()) {
    1007          260 :             this->writeRest(toRest(item), staff);
    1008          337 :         } else if (item->isBarLine()) {
    1009              :             //
    1010          187 :         } else if (item->isBreath()) {
    1011              :             //
    1012          170 :         } else if (item->isKeySig()) {
    1013           21 :             if (m_keySig && (seg != m_keySig)) {
    1014            0 :                 LOGD() << "MeiExporter::writeLayer unexpected KeySig segment";
    1015              :             }
    1016           21 :             m_keySig = seg;
    1017          149 :         } else if (item->isTimeSig()) {
    1018          149 :             if (m_timeSig && (seg != m_timeSig)) {
    1019            0 :                 LOGD() << "MeiExporter::writeLayer unexpected TimeSig segment";
    1020              :             }
    1021          149 :             m_timeSig = seg;
    1022              :         } else {
    1023            0 :             LOGD() << "MeiExporter::writeLayer unknown segment type " << item->typeName();
    1024              :         }
    1025              :     }
    1026              : 
    1027          674 :     m_currentNode = m_currentNode.parent();
    1028              : 
    1029          674 :     return true;
    1030          676 : }
    1031              : 
    1032              : //---------------------------------------------------------
    1033              : // write MEI layer elements
    1034              : //---------------------------------------------------------
    1035              : 
    1036              : /**
    1037              :  * Write the artics attached to a Chord
    1038              :  */
    1039              : 
    1040         1578 : bool MeiExporter::writeArtics(const Chord* chord)
    1041              : {
    1042         1578 :     IF_ASSERT_FAILED(chord) {
    1043            0 :         return false;
    1044              :     }
    1045              : 
    1046         1664 :     for (const Articulation* articulation : chord->articulations()) {
    1047           86 :         if (articulation->isArticulation() && !this->isLaissezVibrer(articulation->symId())) {
    1048           53 :             this->writeArtic(articulation);
    1049              :         }
    1050              :     }
    1051              : 
    1052         1578 :     return true;
    1053              : }
    1054              : 
    1055              : /**
    1056              :  * Write an artic (articulation).
    1057              :  */
    1058              : 
    1059           53 : bool MeiExporter::writeArtic(const Articulation* articulation)
    1060              : {
    1061           53 :     IF_ASSERT_FAILED(articulation) {
    1062            0 :         return false;
    1063              :     }
    1064              : 
    1065           53 :     pugi::xml_node articNode = m_currentNode.append_child();
    1066           53 :     libmei::Artic meiArtic = Convert::articToMEI(articulation);
    1067           53 :     meiArtic.Write(articNode, this->getXmlIdFor(articulation, 'a'));
    1068              : 
    1069           53 :     return true;
    1070           53 : }
    1071              : 
    1072           12 : bool MeiExporter::writeArtic(const ChordLine* chordline)
    1073              : {
    1074           12 :     IF_ASSERT_FAILED(chordline) {
    1075            0 :         return false;
    1076              :     }
    1077              : 
    1078           12 :     pugi::xml_node articNode = m_currentNode.append_child();
    1079           12 :     libmei::Artic meiArtic = Convert::articToMEI(chordline);
    1080           12 :     meiArtic.Write(articNode, this->getXmlIdFor(chordline, 'a'));
    1081              : 
    1082           12 :     return true;
    1083           12 : }
    1084              : 
    1085              : /**
    1086              :  * Open and close beam and tuplet elements for a ChordRest.
    1087              :  * When both a beam and a tuplet is opening and closing, check which is the appropriate nesting order.
    1088              :  * By default, nest the tuplet within the beam.
    1089              :  * Closing beam and tuplet only change the bool parameters and actually closing the elements is happening in MeiExporter::writeBeamAndTupletEnd
    1090              :  */
    1091              : 
    1092         1643 : bool MeiExporter::writeBeamAndTuplet(const ChordRest* chordRest, bool& closingBeam, bool& closingTuplet, bool& closingBeamInTuplet)
    1093              : {
    1094         1643 :     IF_ASSERT_FAILED(chordRest) {
    1095            0 :         return false;
    1096              :     }
    1097              : 
    1098         1643 :     const Beam* beam = (chordRest->beam()) ? toBeam(chordRest->beam()) : nullptr;
    1099         1643 :     const Tuplet* tuplet = (chordRest->tuplet()) ? toTuplet(chordRest->tuplet()) : nullptr;
    1100         1643 :     const Beam* beamInTuplet = nullptr;
    1101              : 
    1102         1643 :     if (beam && tuplet) {
    1103          165 :         if ((beam->elements().front() == chordRest) && ((tuplet->elements().front() == chordRest))) {
    1104           25 :             if (beam->elements().size() < tuplet->elements().size()) {
    1105            5 :                 beamInTuplet = beam;
    1106            5 :                 beam = nullptr;
    1107              :             }
    1108              :         }
    1109              :     }
    1110         1643 :     if (beam) {
    1111          596 :         this->writeBeam(beam, chordRest, closingBeam);
    1112              :     }
    1113         1643 :     if (tuplet) {
    1114          180 :         this->writeTuplet(tuplet, chordRest, closingTuplet);
    1115              :     }
    1116         1643 :     if (beamInTuplet) {
    1117            5 :         this->writeBeam(beamInTuplet, chordRest, closingBeamInTuplet);
    1118              :     }
    1119              : 
    1120              :     // Case when the beam was open within the tuplet but not at the beginning (fewer elements)
    1121              :     // We need to close it first since it is nested in the tuplet
    1122         1643 :     if (closingTuplet && closingBeam && beam->elements().size() < tuplet->elements().size()) {
    1123            7 :         closingBeam = false;
    1124            7 :         closingBeamInTuplet = true;
    1125              :     }
    1126              : 
    1127         1643 :     return true;
    1128              : }
    1129              : 
    1130              : /**
    1131              :  * Close beam and tuplet elements according to the parameters calculated in MeiExporter::writeBeamAndTuplet
    1132              :  */
    1133              : 
    1134         1643 : bool MeiExporter::writeBeamAndTupletEnd(bool closingBeam, bool closingTuplet, bool closingBeamInTuplet)
    1135              : {
    1136              :     // non critical asserts
    1137         1643 :     if (closingBeamInTuplet) {
    1138            7 :         assert(isCurrentNode(libmei::Beam()));
    1139            7 :         m_currentNode = m_currentNode.parent();
    1140              :     }
    1141              : 
    1142         1643 :     if (closingTuplet) {
    1143           41 :         assert(isCurrentNode(libmei::Tuplet()));
    1144           41 :         m_currentNode = m_currentNode.parent();
    1145              :     }
    1146              : 
    1147         1643 :     if (closingBeam) {
    1148          143 :         assert(isCurrentNode(libmei::Beam()));
    1149          143 :         m_currentNode = m_currentNode.parent();
    1150              :     }
    1151              : 
    1152         1643 :     return true;
    1153              : }
    1154              : 
    1155              : /**
    1156              :  * Write a beam if the ChordRest is the first element of the beam.
    1157              :  * If the ChordRest is the last, sets the closing flag to true.
    1158              :  * Also set the MEI `@breaksec` on the previous element when the BeamMode is BEGIN16 or BEGIN32
    1159              :  */
    1160              : 
    1161          601 : bool MeiExporter::writeBeam(const Beam* beam, const ChordRest* chordRest, bool& closing)
    1162              : {
    1163          601 :     IF_ASSERT_FAILED(beam && chordRest) {
    1164            0 :         return false;
    1165              :     }
    1166              : 
    1167              :     // Cross-measure beams are not supported in the export to MEI Basic
    1168          601 :     if (beam->elements().front()->measure() != beam->elements().back()->measure()) {
    1169            0 :         return true;
    1170              :     }
    1171              : 
    1172          601 :     if (beam->elements().front() == chordRest) {
    1173          150 :         libmei::Beam meiBeam;
    1174          150 :         m_currentNode = m_currentNode.append_child();
    1175          150 :         meiBeam.Write(m_currentNode, this->getLayerXmlIdFor(BEAM_L));
    1176          601 :     } else if ((chordRest->beamMode() == BeamMode::BEGIN16) || (chordRest->beamMode() == BeamMode::BEGIN32)) {
    1177           29 :         pugi::xml_node lastInBeam = this->getLastChordRest(m_currentNode);
    1178              :         // We have already written one chord/rest element in the beam, the last one is the one we need to modify
    1179           29 :         if (lastInBeam) {
    1180              :             // Create the attribute class to modify the already created XML element
    1181           29 :             libmei::InstBeamSecondary beamSecondary;
    1182           29 :             beamSecondary.SetBreaksec(Convert::breaksecToMEI(chordRest->beamMode()));
    1183           29 :             beamSecondary.WriteBeamSecondary(lastInBeam);
    1184           29 :         }
    1185              :     }
    1186              : 
    1187          601 :     if (beam->elements().back() == chordRest) {
    1188          150 :         closing = true;
    1189              :     }
    1190              : 
    1191          601 :     return true;
    1192              : }
    1193              : 
    1194              : /**
    1195              :  * Write a bTrem.
    1196              :  */
    1197              : 
    1198           22 : bool MeiExporter::writeBTrem(const TremoloSingleChord* tremolo)
    1199              : {
    1200           22 :     IF_ASSERT_FAILED(tremolo) {
    1201            0 :         return false;
    1202              :     }
    1203              : 
    1204           22 :     m_currentNode = m_currentNode.append_child();
    1205           22 :     libmei::BTrem meiBTrem;
    1206           22 :     std::string xmlId = this->getXmlIdFor(tremolo, 'b');
    1207           22 :     meiBTrem.Write(m_currentNode, xmlId);
    1208              : 
    1209           22 :     return true;
    1210           22 : }
    1211              : 
    1212              : /**
    1213              :  * Write a clef.
    1214              :  */
    1215              : 
    1216          156 : bool MeiExporter::writeClef(const Clef* clef)
    1217              : {
    1218          156 :     IF_ASSERT_FAILED(clef) {
    1219            0 :         return false;
    1220              :     }
    1221              : 
    1222          156 :     if (clef->isHeader()) {
    1223          125 :         return true;
    1224              :     }
    1225              : 
    1226           31 :     pugi::xml_node clefNode = m_currentNode.append_child();
    1227           31 :     libmei::Clef meiClef = Convert::clefToMEI(clef->clefType());
    1228           31 :     Convert::colorToMEI(clef, meiClef);
    1229           31 :     std::string xmlId = this->getXmlIdFor(clef, 'c');
    1230           31 :     meiClef.Write(clefNode, xmlId);
    1231              : 
    1232           31 :     return true;
    1233           31 : }
    1234              : 
    1235              : /**
    1236              :  * Write a chord (chord or note).
    1237              :  * If the Chord is a chord, write its notes with MeiExporter::writeNote.
    1238              :  * Also write the beam and tuplet (opening and closing) and fill the control event list pointing to it.
    1239              :  */
    1240              : 
    1241         1578 : bool MeiExporter::writeChord(const Chord* chord, const Staff* staff)
    1242              : {
    1243         1578 :     IF_ASSERT_FAILED(chord && staff) {
    1244            0 :         return false;
    1245              :     }
    1246              : 
    1247         1578 :     if (chord->graceNotes().size() > 0) {
    1248           24 :         this->writeGraceGrp(chord, staff);
    1249              :     }
    1250              : 
    1251         1578 :     bool closingBeam = false;
    1252         1578 :     bool closingTuplet = false;
    1253         1578 :     bool closingBeamInTuplet = false;
    1254         1578 :     this->writeBeamAndTuplet(chord, closingBeam, closingTuplet, closingBeamInTuplet);
    1255              : 
    1256         1578 :     if (chord->tremoloChordType() == TremoloChordType::TremoloFirstChord) {
    1257           12 :         this->writeFTrem(chord->tremoloTwoChord());
    1258              :     }
    1259              : 
    1260         1578 :     bool isBTrem = (chord->tremoloChordType() == TremoloChordType::TremoloSingle);
    1261         1578 :     if (isBTrem) {
    1262           22 :         this->writeBTrem(chord->tremoloSingleChord());
    1263              :     }
    1264              : 
    1265         1578 :     bool isChord = (chord->notes().size() > 1);
    1266         1578 :     if (isChord) {
    1267              :         // We need to create a <chord> before writing the notes
    1268          134 :         m_currentNode = m_currentNode.append_child();
    1269          134 :         libmei::Chord meiChord;
    1270          134 :         meiChord.SetDur(Convert::durToMEI(chord->durationType().type()));
    1271          134 :         if (chord->dots()) {
    1272            1 :             meiChord.SetDots(chord->dots());
    1273              :         }
    1274          134 :         this->writeBeamTypeAtt(chord, meiChord);
    1275          134 :         this->writeStaffIdentAtt(chord, staff, meiChord);
    1276          134 :         this->writeStemAtt(chord, meiChord);
    1277          134 :         this->writeArtics(chord);
    1278          134 :         this->writeVerses(chord);
    1279          134 :         std::string xmlId = this->getXmlIdFor(chord, 'c');
    1280          134 :         meiChord.Write(m_currentNode, xmlId);
    1281          134 :         this->fillControlEventMap(xmlId, chord);
    1282          134 :     }
    1283              : 
    1284         3393 :     for (const Note* note : chord->notes()) {
    1285         1815 :         this->writeNote(note, chord, staff, isChord);
    1286              :     }
    1287              : 
    1288         1578 :     if (isChord) {
    1289              :         // This is the end of the <chord> - non critical assert
    1290          134 :         assert(isCurrentNode(libmei::Chord()));
    1291          134 :         m_currentNode = m_currentNode.parent();
    1292              :     }
    1293              : 
    1294         1578 :     if (isBTrem) {
    1295              :         // This is the end of the <bTrem> - non critical assert
    1296           22 :         assert(isCurrentNode(libmei::BTrem()));
    1297           22 :         m_currentNode = m_currentNode.parent();
    1298              :     }
    1299              : 
    1300         1578 :     if (chord->tremoloChordType() == TremoloChordType::TremoloSecondChord) {
    1301              :         // This is the end of the <fTrem> - non critical assert
    1302           12 :         assert(isCurrentNode(libmei::FTrem()));
    1303           12 :         m_currentNode = m_currentNode.parent();
    1304              :     }
    1305              : 
    1306         1578 :     this->writeBeamAndTupletEnd(closingBeam, closingTuplet, closingBeamInTuplet);
    1307              : 
    1308         1578 :     if (chord->graceNotes().size() > 0) {
    1309           24 :         this->writeGraceGrp(chord, staff, true);
    1310              :     }
    1311              : 
    1312         1578 :     return true;
    1313              : }
    1314              : 
    1315              : /**
    1316              :  * Write a fTrem.
    1317              :  */
    1318              : 
    1319           12 : bool MeiExporter::writeFTrem(const TremoloTwoChord* tremolo)
    1320              : {
    1321           12 :     IF_ASSERT_FAILED(tremolo) {
    1322            0 :         return false;
    1323              :     }
    1324              : 
    1325           12 :     m_currentNode = m_currentNode.append_child();
    1326           12 :     libmei::FTrem meiFTrem;
    1327           12 :     meiFTrem.SetUnitdur(Convert::unitdurToMEI(tremolo));
    1328           12 :     meiFTrem.Write(m_currentNode, this->getXmlIdFor(tremolo, 'f'));
    1329              : 
    1330           12 :     return true;
    1331           12 : }
    1332              : 
    1333              : /**
    1334              :  * Write a graceGrp placed before or after the chord / note.
    1335              :  * Loop through the notes of the group and write them.
    1336              :  */
    1337              : 
    1338           48 : bool MeiExporter::writeGraceGrp(const Chord* chord, const Staff* staff, bool isAfter)
    1339              : {
    1340           48 :     IF_ASSERT_FAILED(chord) {
    1341            0 :         return false;
    1342              :     }
    1343              : 
    1344           48 :     GraceNotesGroup& graceNotes = (isAfter) ? chord->graceNotesAfter() : chord->graceNotesBefore();
    1345              : 
    1346           48 :     if (graceNotes.empty()) {
    1347           23 :         return true;
    1348              :     }
    1349              : 
    1350           25 :     m_currentNode = m_currentNode.append_child();
    1351              : 
    1352           25 :     libmei::GraceGrp meiGraceGrp;
    1353           25 :     auto [meiAttach, meiGrace] = Convert::gracegrpToMEI(isAfter, graceNotes.front()->noteType());
    1354           25 :     meiGraceGrp.SetAttach(meiAttach);
    1355           25 :     meiGraceGrp.SetGrace(meiGrace);
    1356           25 :     meiGraceGrp.Write(m_currentNode, this->getLayerXmlIdFor(GRACEGRP_L));
    1357              : 
    1358           72 :     for (auto graceChord : graceNotes) {
    1359           47 :         this->writeChord(graceChord, staff);
    1360              :     }
    1361              : 
    1362              :     // non critical assert
    1363           25 :     assert(isCurrentNode(meiGraceGrp));
    1364           25 :     m_currentNode = m_currentNode.parent();
    1365              : 
    1366           25 :     return true;
    1367           25 : }
    1368              : 
    1369              : /**
    1370              :  * Write a note (single note or chord note).
    1371              :  * For single notes, also writes the duration and the beam and stem attributes.
    1372              :  */
    1373              : 
    1374         1815 : bool MeiExporter::writeNote(const Note* note, const Chord* chord, const Staff* staff, bool isChord)
    1375              : {
    1376         1815 :     IF_ASSERT_FAILED(note && chord && staff) {
    1377            0 :         return false;
    1378              :     }
    1379              : 
    1380         1815 :     Interval interval = staff->part()->instrument()->transpose();
    1381         1815 :     auto [meiNote, meiAccid] = Convert::pitchToMEI(note, note->accidental(), interval);
    1382         1815 :     m_currentNode = m_currentNode.append_child();
    1383         1815 :     if (!isChord) {
    1384         1444 :         meiNote.SetDur(Convert::durToMEI(chord->durationType().type()));
    1385         1444 :         if (chord->dots()) {
    1386           56 :             meiNote.SetDots(chord->dots());
    1387              :         }
    1388         1444 :         this->writeBeamTypeAtt(chord, meiNote);
    1389         1444 :         this->writeStaffIdentAtt(chord, staff, meiNote);
    1390         1444 :         this->writeStemAtt(chord, meiNote);
    1391         1444 :         this->writeArtics(chord);
    1392         1444 :         this->writeVerses(chord);
    1393              :     }
    1394         1827 :     for (const EngravingItem* item : chord->el()) {
    1395              :         // add chordlines always to notes
    1396           12 :         if (item->isChordLine() && toChordLine(item)->note() && toChordLine(item)->note() == note) {
    1397           12 :             const ChordLine* chordline = toChordLine(item);
    1398           12 :             this->writeArtic(chordline);
    1399              :         }
    1400              :     }
    1401         1815 :     const int velocity = note->userVelocity();
    1402         1815 :     if (velocity != 0) {
    1403            0 :         meiNote.SetVel(velocity);
    1404              :     }
    1405         1815 :     Convert::colorToMEI(note, meiNote);
    1406         1815 :     std::string xmlId = this->getXmlIdFor(note, 'n');
    1407         1815 :     meiNote.Write(m_currentNode, xmlId);
    1408         1815 :     if (!isChord) {
    1409         1444 :         this->fillControlEventMap(xmlId, chord);
    1410              :     }
    1411              : 
    1412         1815 :     if (note->tieFor()) {
    1413           36 :         m_startingControlEventList.push_back(std::make_pair(note->tieFor(), "#" + xmlId));
    1414              :     }
    1415         1815 :     if (note->tieBack()) {
    1416           30 :         m_endingControlEventMap[note->tieBack()] = "#" + xmlId;
    1417              :     }
    1418              : 
    1419         1834 :     for (Spanner* spanner : note->spannerFor()) {
    1420           19 :         if (spanner->isGlissando()) {
    1421           19 :             m_startingControlEventList.push_back(std::make_pair(spanner, "#" + xmlId));
    1422              :         }
    1423              :     }
    1424         1834 :     for (Spanner* spanner : note->spannerBack()) {
    1425           19 :         if (spanner->isGlissando()) {
    1426           19 :             m_endingControlEventMap[spanner] = "#" + xmlId;
    1427              :         }
    1428              :     }
    1429              : 
    1430         1865 :     for (const EngravingItem* element : note->el()) {
    1431           50 :         if (element->isFingering()) {
    1432           50 :             m_startingControlEventList.push_back(std::make_pair(element, "#" + xmlId));
    1433              :         }
    1434              :     }
    1435              : 
    1436         1815 :     if (meiAccid.HasAccid() || meiAccid.HasAccidGes()) {
    1437          310 :         pugi::xml_node accidNode = m_currentNode.append_child();
    1438          310 :         Accidental* acc = note->accidental();
    1439          310 :         if (acc) {
    1440          198 :             Convert::colorToMEI(acc, meiAccid);
    1441          198 :             std::string xmlIdAcc = this->getXmlIdFor(acc, 'a');
    1442          198 :             meiAccid.Write(accidNode, xmlIdAcc);
    1443          198 :         } else {
    1444          112 :             meiAccid.Write(accidNode, this->getLayerXmlIdFor(ACCID_L));
    1445              :         }
    1446              :     }
    1447              : 
    1448              :     // non critical assert
    1449         1815 :     assert(isCurrentNode(meiNote));
    1450         1815 :     m_currentNode = m_currentNode.parent();
    1451              : 
    1452         1815 :     return true;
    1453         1815 : }
    1454              : 
    1455              : /**
    1456              :  * Write a rest (rest, mRest, and space).
    1457              :  */
    1458              : 
    1459          260 : bool MeiExporter::writeRest(const Rest* rest, const Staff* staff)
    1460              : {
    1461          260 :     IF_ASSERT_FAILED(rest) {
    1462            0 :         return false;
    1463              :     }
    1464              : 
    1465              :     // measure rest
    1466          260 :     if (rest->durationType() == DurationType::V_MEASURE) {
    1467          195 :         pugi::xml_node mRestNode = m_currentNode.append_child();
    1468          195 :         libmei::MRest meiMRest;
    1469          195 :         Convert::colorToMEI(rest, meiMRest);
    1470          195 :         std::string xmlId = this->getXmlIdFor(rest, 'm');
    1471          195 :         meiMRest.Write(mRestNode, xmlId);
    1472          195 :         this->fillControlEventMap(xmlId, rest);
    1473          195 :     } else {
    1474           65 :         bool closingBeam = false;
    1475           65 :         bool closingTuplet = false;
    1476           65 :         bool closingBeamInTuplet = false;
    1477           65 :         this->writeBeamAndTuplet(rest, closingBeam, closingTuplet, closingBeamInTuplet);
    1478              : 
    1479           65 :         pugi::xml_node restNode = m_currentNode.append_child();
    1480           65 :         libmei::Rest meiRest;
    1481           65 :         meiRest.SetDur(Convert::durToMEI(rest->durationType().type()));
    1482           65 :         if (rest->dots()) {
    1483            0 :             meiRest.SetDots(rest->dots());
    1484              :         }
    1485           65 :         if (rest->visible()) {
    1486           65 :             Convert::colorToMEI(rest, meiRest);
    1487              :         }
    1488           65 :         this->writeBeamTypeAtt(rest, meiRest);
    1489           65 :         this->writeStaffIdentAtt(rest, staff, meiRest);
    1490              :         // this->writeVerses(rest);
    1491           65 :         const char prefix = (rest->visible()) ? 'r' : 's';
    1492           65 :         std::string xmlId = this->getXmlIdFor(rest, prefix);
    1493           65 :         meiRest.Write(restNode, xmlId);
    1494           65 :         this->fillControlEventMap(xmlId, rest);
    1495              : 
    1496           65 :         this->writeBeamAndTupletEnd(closingBeam, closingTuplet, closingBeamInTuplet);
    1497              : 
    1498              :         // Change invisible rests to space by simply adjusting the element name
    1499           65 :         if (!rest->visible() || rest->isGap()) {
    1500            0 :             restNode.set_name("space");
    1501              :         }
    1502           65 :     }
    1503              : 
    1504          260 :     return true;
    1505              : }
    1506              : 
    1507              : /**
    1508              :  * Write a measure repeat
    1509              :  */
    1510              : 
    1511            2 : bool MeiExporter::writeMRpt(const MeasureRepeat* measureRepeat)
    1512              : {
    1513            2 :     IF_ASSERT_FAILED(measureRepeat) {
    1514            0 :         return false;
    1515              :     }
    1516              : 
    1517            2 :     libmei::MRpt meiMRpt;
    1518            2 :     Convert::colorToMEI(measureRepeat, meiMRpt);
    1519            2 :     meiMRpt.SetExpand(libmei::BOOLEAN_false);
    1520            2 :     pugi::xml_node mRptNode = m_currentNode.append_child();
    1521            2 :     meiMRpt.Write(mRptNode, this->getXmlIdFor(measureRepeat, 'm'));
    1522              : 
    1523            2 :     m_currentNode = m_currentNode.parent();
    1524              : 
    1525            2 :     return true;
    1526            2 : }
    1527              : 
    1528              : /**
    1529              :  * Write a syl with the corresponding text syllable and the elision type.
    1530              :  * The elision type is passed to the Convert methods that deals with the adjustment of @con and @wordpos.
    1531              :  */
    1532              : 
    1533          211 : bool MeiExporter::writeSyl(const Lyrics* lyrics, const String& text, ElisionType elision)
    1534              : {
    1535          211 :     libmei::Syl meiSyl = Convert::sylToMEI(lyrics, elision);
    1536          211 :     pugi::xml_node sylNode = m_currentNode.append_child();
    1537          211 :     meiSyl.Write(sylNode, this->getLayerXmlIdFor(SYL_L));
    1538          211 :     sylNode.text().set(text.toStdString().c_str());
    1539              : 
    1540          211 :     return true;
    1541          211 : }
    1542              : 
    1543              : /**
    1544              :  * Write a tuplet if the ChordRest is the first element of the tuplet.
    1545              :  * If the ChordRest is the last, sets the closing flag to true.
    1546              :  */
    1547              : 
    1548          180 : bool MeiExporter::writeTuplet(const Tuplet* tuplet, const EngravingItem* item, bool& closing)
    1549              : {
    1550          180 :     IF_ASSERT_FAILED(tuplet && item) {
    1551            0 :         return false;
    1552              :     }
    1553              : 
    1554          180 :     if (tuplet->elements().front() == item) {
    1555              :         // recursive call for handling nested tuplets
    1556              :         // nearly works except for closing which is happening to early (after the first note)
    1557              :         // when a nested tuplet is ending at the same time as its parent
    1558              :         /**
    1559              :         if (tuplet->tuplet()) {
    1560              :             writeTuplet(toTuplet(tuplet->tuplet()), tuplet, closing);
    1561              :         }
    1562              :         if (item->isTuplet()) {
    1563              :             LOGD() << "MeiExporter::writeTuplet nested tuplet export not fully supported";
    1564              :         }
    1565              :         */
    1566           41 :         libmei::Tuplet meiTuplet = Convert::tupletToMEI(tuplet);
    1567           41 :         m_currentNode = m_currentNode.append_child();
    1568           41 :         std::string xmlId = this->getXmlIdFor(tuplet, 't');
    1569           41 :         meiTuplet.Write(m_currentNode, xmlId);
    1570           41 :     }
    1571              : 
    1572          180 :     if (tuplet->elements().back() == item) {
    1573           41 :         closing = true;
    1574              :     }
    1575              : 
    1576          180 :     return true;
    1577              : }
    1578              : 
    1579              : /**
    1580              :  * Write the verses attached to a ChordRest
    1581              :  */
    1582              : 
    1583         1578 : bool MeiExporter::writeVerses(const ChordRest* chordRest)
    1584              : {
    1585         1578 :     IF_ASSERT_FAILED(chordRest) {
    1586            0 :         return false;
    1587              :     }
    1588              : 
    1589         1777 :     for (const Lyrics* lyrics : chordRest->lyrics()) {
    1590          199 :         this->writeVerse(lyrics);
    1591              :     }
    1592              : 
    1593         1578 :     return true;
    1594              : }
    1595              : 
    1596              : /**
    1597              :  * Write a verse for a Lyrics and the syl - one or more with elisions.
    1598              :  * If the Lyrics text has elision(s), then splits them into distinct MEI syl.
    1599              :  */
    1600              : 
    1601          199 : bool MeiExporter::writeVerse(const Lyrics* lyrics)
    1602              : {
    1603          199 :     IF_ASSERT_FAILED(lyrics) {
    1604            0 :         return false;
    1605              :     }
    1606              : 
    1607          199 :     libmei::Verse meiVerse;
    1608          199 :     meiVerse.SetN(String::number(lyrics->verse() + 1).toStdString());
    1609          199 :     if (lyrics->propertyFlags(engraving::Pid::PLACEMENT) == engraving::PropertyFlags::UNSTYLED) {
    1610            0 :         meiVerse.SetPlace(Convert::placeToMEI(lyrics->placement()));
    1611              :     }
    1612          199 :     Convert::colorToMEI(lyrics, meiVerse);
    1613          199 :     m_currentNode = m_currentNode.append_child();
    1614          199 :     std::string xmlId = this->getXmlIdFor(lyrics, 'v');
    1615          199 :     meiVerse.Write(m_currentNode, xmlId);
    1616              : 
    1617              :     // Split the syllable into line blocks
    1618          199 :     Convert::textWithSmufl lineBlocks;
    1619          199 :     Convert::textToMEI(lineBlocks, String(lyrics->plainText()));
    1620              : 
    1621              :     // If we have more than one line block we assume to have elision
    1622              :     // Ideally we should check that SMuFL line block do contain only an elision character
    1623              :     // It also means that any SMuFL special character in the lyrics will be considered to be an elision connector
    1624          199 :     ElisionType elision = (lineBlocks.size() > 1) ? ElisionFirst : ElisionNone;
    1625              : 
    1626          422 :     for (auto& lineBlock : lineBlocks) {
    1627              :         // For now assume any SMuFL line block to be an elision connector.
    1628              :         // That means we simply skip them.
    1629          223 :         if (lineBlock.first) {
    1630           12 :             continue;
    1631              :         }
    1632              :         // If the line block in the last one with an elision, mark it as such
    1633          211 :         if ((elision == ElisionMiddle) && (&lineBlock == &lineBlocks.back())) {
    1634            9 :             elision = ElisionLast;
    1635              :         }
    1636              :         // Create a /syl for each text line block
    1637          211 :         this->writeSyl(lyrics, lineBlock.second, elision);
    1638              :         // Next one will be a middle (or last) elision line block
    1639          211 :         elision = ElisionMiddle;
    1640              :     }
    1641              : 
    1642              :     // This is the end of the <verse> - non critical assert
    1643          199 :     assert(isCurrentNode(libmei::Verse()));
    1644          199 :     m_currentNode = m_currentNode.parent();
    1645              : 
    1646          199 :     return true;
    1647          199 : }
    1648              : 
    1649              : //---------------------------------------------------------
    1650              : // write MEI control events
    1651              : //---------------------------------------------------------
    1652              : 
    1653              : /**
    1654              :  * Write a arpeg.
    1655              :  */
    1656              : 
    1657           11 : bool MeiExporter::writeArpeg(const Arpeggio* arpeggio, const std::string& startid)
    1658              : {
    1659           11 :     IF_ASSERT_FAILED(arpeggio) {
    1660            0 :         return false;
    1661              :     }
    1662              : 
    1663           11 :     pugi::xml_node arpegNode = m_currentNode.append_child();
    1664           11 :     libmei::Arpeg meiArpeg = Convert::arpegToMEI(arpeggio);
    1665           11 :     meiArpeg.SetStartid(startid);
    1666              : 
    1667           11 :     meiArpeg.Write(arpegNode, this->getXmlIdFor(arpeggio, 'a'));
    1668              : 
    1669              :     // If the arpeggio is spanning to a lower staff, keep it as open control event
    1670           11 :     if (arpeggio->span() > 1) {
    1671            4 :         m_openControlEventMap[arpeggio] = arpegNode;
    1672              :     }
    1673              : 
    1674           11 :     return true;
    1675           11 : }
    1676              : 
    1677              : /**
    1678              :  * Write a breath (i.e., breath or caesura).
    1679              :  */
    1680              : 
    1681           17 : bool MeiExporter::writeBreath(const Breath* breath, const std::string& startid)
    1682              : {
    1683           17 :     IF_ASSERT_FAILED(breath) {
    1684            0 :         return false;
    1685              :     }
    1686              : 
    1687           17 :     pugi::xml_node breathNode = m_currentNode.append_child();
    1688           17 :     if (breath->isCaesura()) {
    1689           13 :         libmei::Caesura meiCaesura = Convert::caesuraToMEI(breath);
    1690           13 :         meiCaesura.SetStartid(startid);
    1691           13 :         meiCaesura.Write(breathNode, this->getXmlIdFor(breath, 'c'));
    1692           13 :     } else {
    1693            4 :         libmei::Breath meiBreath = Convert::breathToMEI(breath);
    1694            4 :         meiBreath.SetStartid(startid);
    1695            4 :         meiBreath.Write(breathNode, this->getXmlIdFor(breath, 'b'));
    1696            4 :     }
    1697              : 
    1698           17 :     return true;
    1699              : }
    1700              : 
    1701              : /**
    1702              :  * Write a dir and its text content.
    1703              :  */
    1704              : 
    1705            5 : bool MeiExporter::writeDir(const TextBase* dir, const std::string& startid)
    1706              : {
    1707            5 :     IF_ASSERT_FAILED(dir) {
    1708            0 :         return false;
    1709              :     }
    1710              : 
    1711            5 :     StringList meiLines;
    1712              : 
    1713            5 :     pugi::xml_node dirNode = m_currentNode.append_child();
    1714            5 :     libmei::Dir meiDir = Convert::dirToMEI(dir, meiLines);
    1715            5 :     meiDir.SetStartid(startid);
    1716            5 :     meiDir.Write(dirNode, this->getXmlIdFor(dir, 'd'));
    1717              : 
    1718            5 :     this->writeLines(dirNode, meiLines);
    1719              : 
    1720            5 :     return true;
    1721            5 : }
    1722              : 
    1723              : /**
    1724              :  * Write a dir (with extender) and its text content.
    1725              :  */
    1726              : 
    1727            3 : bool MeiExporter::writeDir(const TextLineBase* dir, const std::string& startid)
    1728              : {
    1729            3 :     IF_ASSERT_FAILED(dir) {
    1730            0 :         return false;
    1731              :     }
    1732              : 
    1733            3 :     StringList meiLines;
    1734              : 
    1735            3 :     pugi::xml_node dirNode = m_currentNode.append_child();
    1736            3 :     libmei::Dir meiDir = Convert::dirToMEI(dir, meiLines);
    1737            3 :     meiDir.SetStartid(startid);
    1738            3 :     meiDir.Write(dirNode, this->getXmlIdFor(dir, 'd'));
    1739              : 
    1740            3 :     this->writeLines(dirNode, meiLines);
    1741              : 
    1742              :     // Add the node to the map of open control events
    1743            3 :     this->addNodeToOpenControlEvents(dirNode, dir, startid);
    1744              : 
    1745            3 :     return true;
    1746            3 : }
    1747              : 
    1748              : /**
    1749              :  * Write a dynam and its text content.
    1750              :  */
    1751              : 
    1752           10 : bool MeiExporter::writeDynam(const Dynamic* dynamic, const std::string& startid)
    1753              : {
    1754           10 :     IF_ASSERT_FAILED(dynamic) {
    1755            0 :         return false;
    1756              :     }
    1757              : 
    1758           10 :     StringList meiLines;
    1759              : 
    1760           10 :     pugi::xml_node dynamNode = m_currentNode.append_child();
    1761           10 :     libmei::Dynam meiDynam = Convert::dynamToMEI(dynamic, meiLines);
    1762           10 :     meiDynam.SetStartid(startid);
    1763           10 :     meiDynam.Write(dynamNode, this->getXmlIdFor(dynamic, 'd'));
    1764              : 
    1765           10 :     this->writeLines(dynamNode, meiLines);
    1766              : 
    1767           10 :     return true;
    1768           10 : }
    1769              : 
    1770              : /**
    1771              :  * Write a f (FigureBassItem).
    1772              :  */
    1773              : 
    1774            8 : bool MeiExporter::writeF(const FiguredBassItem* figuredBassItem)
    1775              : {
    1776            8 :     IF_ASSERT_FAILED(figuredBassItem) {
    1777            0 :         return false;
    1778              :     }
    1779              : 
    1780            8 :     StringList meiLines;
    1781              : 
    1782            8 :     pugi::xml_node fNode = m_currentNode.append_child();
    1783            8 :     libmei::F meiF = Convert::fToMEI(figuredBassItem, meiLines);
    1784            8 :     meiF.Write(fNode, this->getXmlIdFor(figuredBassItem, 'f'));
    1785              : 
    1786            8 :     this->writeLines(fNode, meiLines);
    1787              : 
    1788            8 :     return true;
    1789            8 : }
    1790              : 
    1791              : /**
    1792              :  * Write a fb (figured bass).
    1793              :  */
    1794              : 
    1795            7 : bool MeiExporter::writeFb(const FiguredBass* figuredBass, const std::string& startid)
    1796              : {
    1797            7 :     IF_ASSERT_FAILED(figuredBass) {
    1798            0 :         return false;
    1799              :     }
    1800              : 
    1801            7 :     m_currentNode = m_currentNode.append_child();
    1802              : 
    1803            7 :     auto [meiHarm, meiFb] = Convert::fbToMEI(figuredBass);
    1804            7 :     meiHarm.SetStartid(startid);
    1805            7 :     meiHarm.Write(m_currentNode, this->getLayerXmlIdFor(HARM_L));
    1806              : 
    1807            7 :     m_currentNode = m_currentNode.append_child();
    1808            7 :     meiFb.Write(m_currentNode, this->getXmlIdFor(figuredBass, 'f'));
    1809              : 
    1810           15 :     for (const FiguredBassItem* f : figuredBass->items()) {
    1811            8 :         this->writeF(f);
    1812              :     }
    1813              : 
    1814              :     // This is the end of the <fb> - non critical assert
    1815            7 :     assert(isCurrentNode(libmei::Fb()));
    1816            7 :     m_currentNode = m_currentNode.parent();
    1817              : 
    1818              :     // This is the end of the <harm> - non critical assert
    1819            7 :     assert(isCurrentNode(libmei::Harm()));
    1820            7 :     m_currentNode = m_currentNode.parent();
    1821              : 
    1822            7 :     return true;
    1823            7 : }
    1824              : 
    1825              : /**
    1826              :  * Write a fermata.
    1827              :  */
    1828              : 
    1829           32 : bool MeiExporter::writeFermata(const Fermata* fermata, const std::string& startid)
    1830              : {
    1831           32 :     IF_ASSERT_FAILED(fermata) {
    1832            0 :         return false;
    1833              :     }
    1834              : 
    1835           32 :     pugi::xml_node fermataNode = m_currentNode.append_child();
    1836           32 :     libmei::Fermata meiFermata = Convert::fermataToMEI(fermata);
    1837           32 :     meiFermata.SetStartid(startid);
    1838              : 
    1839           32 :     meiFermata.Write(fermataNode, this->getXmlIdFor(fermata, 'f'));
    1840              : 
    1841           32 :     return true;
    1842           32 : }
    1843              : 
    1844              : /**
    1845              :  * Write a fermata with a staffNs and tstamp
    1846              :  */
    1847              : 
    1848            6 : bool MeiExporter::writeFermata(const Fermata* fermata, const libmei::xsdPositiveInteger_List& staffNs, double tstamp)
    1849              : {
    1850            6 :     IF_ASSERT_FAILED(fermata) {
    1851            0 :         return false;
    1852              :     }
    1853              : 
    1854            6 :     pugi::xml_node fermataNode = m_currentNode.append_child();
    1855            6 :     libmei::Fermata meiFermata = Convert::fermataToMEI(fermata);
    1856            6 :     meiFermata.SetStaff(staffNs);
    1857            6 :     meiFermata.SetTstamp(tstamp);
    1858              : 
    1859            6 :     meiFermata.Write(fermataNode, this->getXmlIdFor(fermata, 'f'));
    1860              : 
    1861            6 :     return true;
    1862            6 : }
    1863              : 
    1864              : /**
    1865              :  * Write a fing and its text content.
    1866              :  */
    1867              : 
    1868           50 : bool MeiExporter::writeFing(const Fingering* fing, const std::string& startid)
    1869              : {
    1870           50 :     IF_ASSERT_FAILED(fing) {
    1871            0 :         return false;
    1872              :     }
    1873              : 
    1874           50 :     StringList meiLines;
    1875              : 
    1876           50 :     pugi::xml_node fingNode = m_currentNode.append_child();
    1877           50 :     libmei::Fing meiFing = Convert::fingToMEI(fing, meiLines);
    1878           50 :     meiFing.SetStartid(startid);
    1879           50 :     meiFing.Write(fingNode, this->getXmlIdFor(fing, 'f'));
    1880              : 
    1881           50 :     this->writeLines(fingNode, meiLines);
    1882              : 
    1883           50 :     return true;
    1884           50 : }
    1885              : 
    1886              : /**
    1887              :  * Write a gliss and its text content.
    1888              :  */
    1889              : 
    1890           19 : bool MeiExporter::writeGliss(const Glissando* gliss, const std::string& startid)
    1891              : {
    1892           19 :     IF_ASSERT_FAILED(gliss) {
    1893            0 :         return false;
    1894              :     }
    1895              : 
    1896           19 :     pugi::xml_node glissNode = m_currentNode.append_child();
    1897           19 :     String text = gliss->text();
    1898           19 :     libmei::Gliss meiGliss = Convert::glissToMEI(gliss);
    1899           19 :     meiGliss.SetStartid(startid);
    1900           19 :     meiGliss.Write(glissNode, this->getXmlIdFor(gliss, 'g'));
    1901              : 
    1902           19 :     if (text.size() > 0) {
    1903            3 :         glissNode.text().set(text.toStdString().c_str());
    1904              :     }
    1905              : 
    1906              :     // Add the node to the map of open control events
    1907           19 :     this->addNodeToOpenControlEvents(glissNode, gliss, startid);
    1908              : 
    1909           19 :     return true;
    1910           19 : }
    1911              : 
    1912              : /**
    1913              :  * Write a hairpin.
    1914              :  */
    1915              : 
    1916            9 : bool MeiExporter::writeHairpin(const Hairpin* hairpin, const std::string& startid)
    1917              : {
    1918            9 :     IF_ASSERT_FAILED(hairpin) {
    1919            0 :         return false;
    1920              :     }
    1921              : 
    1922            9 :     if (hairpin->isLineType()) {
    1923            3 :         return this->writeDir(dynamic_cast<const TextLineBase*>(hairpin), startid);
    1924              :     }
    1925              : 
    1926            6 :     pugi::xml_node hairpinNode = m_currentNode.append_child();
    1927            6 :     libmei::Hairpin meiHairpin = Convert::hairpinToMEI(hairpin);
    1928            6 :     meiHairpin.SetStartid(startid);
    1929            6 :     meiHairpin.Write(hairpinNode, this->getXmlIdFor(hairpin, 'h'));
    1930              : 
    1931              :     // Add the node to the map of open control events
    1932            6 :     this->addNodeToOpenControlEvents(hairpinNode, hairpin, startid);
    1933              : 
    1934            6 :     return true;
    1935            6 : }
    1936              : 
    1937              : /**
    1938              :  * Write a harm and its text content.
    1939              :  */
    1940              : 
    1941           26 : bool MeiExporter::writeHarm(const Harmony* harmony, const std::string& startid)
    1942              : {
    1943           26 :     IF_ASSERT_FAILED(harmony) {
    1944            0 :         return false;
    1945              :     }
    1946              : 
    1947           26 :     StringList meiLines;
    1948              : 
    1949           26 :     pugi::xml_node harmNode = m_currentNode.append_child();
    1950           26 :     libmei::Harm meiHarm = Convert::harmToMEI(harmony, meiLines);
    1951           26 :     meiHarm.SetStartid(startid);
    1952           26 :     meiHarm.Write(harmNode, this->getXmlIdFor(harmony, 'h'));
    1953              : 
    1954           26 :     this->writeLines(harmNode, meiLines);
    1955              : 
    1956           26 :     return true;
    1957           26 : }
    1958              : 
    1959              : /**
    1960              :  * Write a harpPedal.
    1961              :  */
    1962              : 
    1963            3 : bool MeiExporter::writeHarpPedal(const HarpPedalDiagram* harpPedalDiagram, const std::string& startid)
    1964              : {
    1965            3 :     IF_ASSERT_FAILED(harpPedalDiagram) {
    1966            0 :         return false;
    1967              :     }
    1968            3 :     if (!harpPedalDiagram->isDiagram()) {
    1969            0 :         return true;
    1970              :     }
    1971              : 
    1972            3 :     pugi::xml_node harpPedalNode = m_currentNode.append_child();
    1973            3 :     libmei::HarpPedal meiHarpPedal = Convert::harpPedalToMEI(harpPedalDiagram);
    1974            3 :     meiHarpPedal.SetStartid(startid);
    1975            3 :     meiHarpPedal.Write(harpPedalNode, this->getXmlIdFor(harpPedalDiagram, 'h'));
    1976              : 
    1977            3 :     return true;
    1978            3 : }
    1979              : 
    1980              : /**
    1981              :  * Write a octave (ottava).
    1982              :  */
    1983              : 
    1984            7 : bool MeiExporter::writeOctave(const Ottava* ottava, const std::string& startid)
    1985              : {
    1986            7 :     IF_ASSERT_FAILED(ottava) {
    1987            0 :         return false;
    1988              :     }
    1989              : 
    1990            7 :     pugi::xml_node octaveNode = m_currentNode.append_child();
    1991            7 :     libmei::Octave meiOctave = Convert::octaveToMEI(ottava);
    1992            7 :     meiOctave.SetStartid(startid);
    1993              : 
    1994            7 :     meiOctave.Write(octaveNode, this->getXmlIdFor(ottava, 'o'));
    1995              : 
    1996              :     // Add the node to the map of open control events
    1997            7 :     this->addNodeToOpenControlEvents(octaveNode, ottava, startid);
    1998              : 
    1999            7 :     return true;
    2000            7 : }
    2001              : 
    2002              : /**
    2003              :  * Write a ornament.
    2004              :  * Select the appropriate corresponding MEI element for it.
    2005              :  */
    2006              : 
    2007           33 : bool MeiExporter::writeOrnament(const Ornament* ornament, const std::string& startid)
    2008              : {
    2009           33 :     IF_ASSERT_FAILED(ornament) {
    2010            0 :         return false;
    2011              :     }
    2012              : 
    2013           33 :     pugi::xml_node ornamentNode = m_currentNode.append_child();
    2014           33 :     if (Convert::isMordent(ornament)) {
    2015           22 :         libmei::Mordent meiMordent = Convert::mordentToMEI(ornament);
    2016           22 :         meiMordent.SetStartid(startid);
    2017           22 :         meiMordent.Write(ornamentNode, this->getXmlIdFor(ornament, 'm'));
    2018           33 :     } else if (Convert::isTrill(ornament)) {
    2019            5 :         libmei::Trill meiTrill = Convert::trillToMEI(ornament);
    2020            5 :         meiTrill.SetStartid(startid);
    2021            5 :         meiTrill.Write(ornamentNode, this->getXmlIdFor(ornament, 't'));
    2022           11 :     } else if (Convert::isTurn(ornament)) {
    2023            6 :         libmei::Turn meiTurn = Convert::turnToMEI(ornament);
    2024            6 :         meiTurn.SetStartid(startid);
    2025            6 :         meiTurn.Write(ornamentNode, this->getXmlIdFor(ornament, 't'));
    2026            6 :     } else {
    2027            0 :         libmei::Ornam meiOrnam = Convert::ornamToMEI(ornament);
    2028            0 :         meiOrnam.SetStartid(startid);
    2029            0 :         meiOrnam.Write(ornamentNode, this->getXmlIdFor(ornament, 'o'));
    2030            0 :     }
    2031              : 
    2032           33 :     return true;
    2033              : }
    2034              : 
    2035              : /**
    2036              :  * Write a pedal.
    2037              :  */
    2038              : 
    2039            5 : bool MeiExporter::writePedal(const Pedal* pedal, const std::string& startid)
    2040              : {
    2041            5 :     IF_ASSERT_FAILED(pedal) {
    2042            0 :         return false;
    2043              :     }
    2044              : 
    2045            5 :     pugi::xml_node pedalNode = m_currentNode.append_child();
    2046            5 :     libmei::Pedal meiPedal = Convert::pedalToMEI(pedal);
    2047            5 :     meiPedal.SetStartid(startid);
    2048              : 
    2049            5 :     meiPedal.Write(pedalNode, this->getXmlIdFor(pedal, 'p'));
    2050              : 
    2051              :     // Add the node to the map of open control events
    2052            5 :     this->addNodeToOpenControlEvents(pedalNode, pedal, startid);
    2053              : 
    2054            5 :     return true;
    2055            5 : }
    2056              : 
    2057              : /**
    2058              :  * Write a repeatMark from a Jump.
    2059              :  */
    2060              : 
    2061           14 : bool MeiExporter::writeRepeatMark(const Jump* jump, const Measure* measure)
    2062              : {
    2063           14 :     IF_ASSERT_FAILED(jump && measure) {
    2064            0 :         return false;
    2065              :     }
    2066              : 
    2067           14 :     pugi::xml_node repeatMarkNode = m_currentNode.append_child();
    2068           14 :     String text;
    2069           14 :     libmei::RepeatMark meiRepeatMark = Convert::jumpToMEI(jump, text);
    2070              : 
    2071           14 :     if (text.size() > 0) {
    2072           12 :         repeatMarkNode.text().set(text.toStdString().c_str());
    2073              :     }
    2074              : 
    2075           14 :     meiRepeatMark.SetTstamp(0.0);
    2076           14 :     std::string xmlId = this->getXmlIdFor(jump, 'r');
    2077           14 :     meiRepeatMark.Write(repeatMarkNode, xmlId);
    2078              : 
    2079              :     // Currently not used - builds a post-processing list to be processing in MeiExporter::addJumpToRepeatMarks
    2080              :     // this->addToRepeatMarkList(toTextBase(jump), repeatMarkNode, xmlId);
    2081              : 
    2082           14 :     return true;
    2083           14 : }
    2084              : 
    2085              : /**
    2086              :  * Write a reh from a RehearsalMark.
    2087              :  */
    2088              : 
    2089            3 : bool MeiExporter::writeRehearsalMark(const RehearsalMark* mark, const std::string& startid)
    2090              : {
    2091            3 :     IF_ASSERT_FAILED(mark) {
    2092            0 :         return false;
    2093              :     }
    2094              : 
    2095            3 :     pugi::xml_node rehNode = m_currentNode.append_child();
    2096            3 :     String text = mark->plainText();
    2097            3 :     libmei::Reh meiReh;
    2098            3 :     Convert::colorToMEI(mark, meiReh);
    2099              : 
    2100            3 :     if (text.size() > 0) {
    2101            3 :         rehNode.text().set(text.toStdString().c_str());
    2102              :     }
    2103              : 
    2104            3 :     meiReh.SetStartid(startid);
    2105              : 
    2106            3 :     std::string xmlId = this->getXmlIdFor(mark, 'r');
    2107            3 :     meiReh.Write(rehNode, xmlId);
    2108              : 
    2109            3 :     return true;
    2110            3 : }
    2111              : 
    2112              : /**
    2113              :  * Write a repeatMark from a Marker.
    2114              :  */
    2115              : 
    2116            9 : bool MeiExporter::writeRepeatMark(const Marker* marker, const Measure* measure)
    2117              : {
    2118            9 :     IF_ASSERT_FAILED(marker && measure) {
    2119            0 :         return false;
    2120              :     }
    2121              : 
    2122            9 :     pugi::xml_node repeatMarkNode = m_currentNode.append_child();
    2123            9 :     String text;
    2124            9 :     libmei::RepeatMark meiRepeatMark = Convert::markerToMEI(marker, text);
    2125              : 
    2126            9 :     if (text.size() > 0) {
    2127            2 :         repeatMarkNode.text().set(text.toStdString().c_str());
    2128              :     }
    2129              : 
    2130            9 :     meiRepeatMark.SetTstamp(0.0);
    2131            9 :     std::string xmlId = this->getXmlIdFor(marker, 'r');
    2132            9 :     meiRepeatMark.Write(repeatMarkNode, xmlId);
    2133              : 
    2134              :     // Currently not used.
    2135              :     // this->addToRepeatMarkList(toTextBase(marker), repeatMarkNode, xmlId);
    2136              : 
    2137            9 :     return true;
    2138            9 : }
    2139              : 
    2140              : /**
    2141              :  * Write a slur.
    2142              :  */
    2143              : 
    2144           33 : bool MeiExporter::writeSlur(const Slur* slur, const std::string& startid)
    2145              : {
    2146           33 :     IF_ASSERT_FAILED(slur) {
    2147            0 :         return false;
    2148              :     }
    2149              : 
    2150           33 :     pugi::xml_node slurNode = m_currentNode.append_child();
    2151           33 :     libmei::Slur meiSlur = Convert::slurToMEI(slur);
    2152           33 :     meiSlur.SetStartid(startid);
    2153              : 
    2154           33 :     meiSlur.Write(slurNode, this->getXmlIdFor(slur, 's'));
    2155              : 
    2156              :     // Add the node to the map of open control events
    2157           33 :     this->addNodeToOpenControlEvents(slurNode, slur, startid);
    2158              : 
    2159           33 :     return true;
    2160           33 : }
    2161              : 
    2162              : /**
    2163              :  * Write a tempo and its text content.
    2164              :  */
    2165              : 
    2166           11 : bool MeiExporter::writeTempo(const TempoText* tempoText, const std::string& startid)
    2167              : {
    2168           11 :     IF_ASSERT_FAILED(tempoText) {
    2169            0 :         return false;
    2170              :     }
    2171              : 
    2172           11 :     StringList meiLines;
    2173              : 
    2174           11 :     pugi::xml_node tempoNode = m_currentNode.append_child();
    2175           11 :     libmei::Tempo meiTempo = Convert::tempoToMEI(tempoText, meiLines);
    2176           11 :     if (tempoText->tick() == tempoText->measure()->tick()) {
    2177           10 :         double tstamp = Convert::tstampFromFraction(tempoText->tick() - tempoText->measure()->tick(), tempoText->measure()->timesig());
    2178           10 :         meiTempo.SetTstamp(tstamp);
    2179              :     } else {
    2180            1 :         meiTempo.SetStartid(startid);
    2181              :     }
    2182           11 :     meiTempo.Write(tempoNode, this->getXmlIdFor(tempoText, 't'));
    2183              : 
    2184           11 :     this->writeLinesWithSMuFL(tempoNode, meiLines);
    2185              : 
    2186           11 :     return true;
    2187           11 : }
    2188              : 
    2189              : /**
    2190              :  * Write a tie.
    2191              :  */
    2192              : 
    2193           36 : bool MeiExporter::writeTie(const Tie* tie, const std::string& startid)
    2194              : {
    2195           36 :     IF_ASSERT_FAILED(tie) {
    2196            0 :         return false;
    2197              :     }
    2198              : 
    2199           36 :     pugi::xml_node tieNode = m_currentNode.append_child();
    2200           36 :     libmei::Tie meiTie = Convert::tieToMEI(tie);
    2201           36 :     meiTie.SetStartid(startid);
    2202              : 
    2203           36 :     meiTie.Write(tieNode, this->getXmlIdFor(tie, tie->isLaissezVib() ? 'l' : 't'));
    2204              : 
    2205              :     // Change open ties by simply adjusting the element name
    2206           36 :     if (tie->isLaissezVib()) {
    2207            6 :         tieNode.set_name("lv");
    2208              :     }
    2209              : 
    2210              :     // Add the node to the map of open control events
    2211           36 :     this->addNodeToOpenControlEvents(tieNode, tie, startid);
    2212              : 
    2213           36 :     return true;
    2214           36 : }
    2215              : 
    2216              : /**
    2217              :  * Write a trill.
    2218              :  */
    2219              : 
    2220            4 : bool MeiExporter::writeTrill(const Trill* trill, const std::string& startid)
    2221              : {
    2222            4 :     IF_ASSERT_FAILED(trill) {
    2223            0 :         return false;
    2224              :     }
    2225              : 
    2226            4 :     pugi::xml_node trillNode = m_currentNode.append_child();
    2227            4 :     libmei::Trill meiTrill = Convert::trillToMEI(trill->ornament());
    2228            4 :     Convert::colorlineToMEI(trill, meiTrill);
    2229            4 :     meiTrill.SetExtender(libmei::BOOLEAN_true);
    2230            4 :     meiTrill.SetStartid(startid);
    2231              : 
    2232            4 :     meiTrill.Write(trillNode, this->getXmlIdFor(trill, 't'));
    2233              : 
    2234              :     // Add the node to the map of open control events
    2235            4 :     this->addNodeToOpenControlEvents(trillNode, trill, startid);
    2236              : 
    2237            4 :     return true;
    2238            4 : }
    2239              : 
    2240              : //---------------------------------------------------------
    2241              : // write MEI attribute classes
    2242              : //---------------------------------------------------------
    2243              : 
    2244              : /**
    2245              :  * Write the beam attributes for a ChordRest (i.e., chord, note, rest or space).
    2246              :  * Uses the `@type` attribute for storing MuseScore beaming added flags.
    2247              :  */
    2248              : 
    2249         1643 : bool MeiExporter::writeBeamTypeAtt(const ChordRest* chordRest, libmei::AttTyped& typeAtt)
    2250              : {
    2251         1643 :     IF_ASSERT_FAILED(chordRest) {
    2252            0 :         return false;
    2253              :     }
    2254              : 
    2255              :     // Make sure we do not add a @type for notes / rests longer the 8th with a hanging beam flag
    2256         1643 :     if (int(chordRest->durationType().type()) < int(DurationType::V_EIGHTH)) {
    2257          964 :         return true;
    2258              :     }
    2259              : 
    2260          679 :     switch (chordRest->beamMode()) {
    2261              :     // BeamMode::BEGIN16 and BEGIN32 is handled in MeiExporter::writeBeam, which will add MEI a @breaksec to the previous element
    2262              :     // This is BeamMode in MuseScore is on the first note _after_ the break, whereas it is on the last note _before_ it in MEI.
    2263          116 :     case (BeamMode::BEGIN):
    2264              :     case (BeamMode::MID):
    2265              :     case (BeamMode::NONE):
    2266          116 :         typeAtt.SetType(Convert::beamToMEI(chordRest->beamMode(), BEAM_ELEMENT_TYPE));
    2267          116 :         break;
    2268          563 :     default:
    2269          563 :         break;
    2270              :     }
    2271              : 
    2272          679 :     return true;
    2273              : }
    2274              : 
    2275              : /**
    2276              :  * Write the cross-staff attribute (@staff) for a ChordRest (i.e., chord, note, rest or space).
    2277              :  */
    2278              : 
    2279         1643 : bool MeiExporter::writeStaffIdentAtt(const ChordRest* chordRest, const Staff* staff, libmei::AttStaffIdent& staffIdentAtt)
    2280              : {
    2281         1643 :     if (chordRest->staffMove() != 0) {
    2282          102 :         staff_idx_t staffN = staff->idx() + chordRest->staffMove() + 1;
    2283          102 :         libmei::xsdPositiveInteger_List staffNs;
    2284          102 :         staffNs.push_back(static_cast<int>(staffN));
    2285          102 :         staffIdentAtt.SetStaff(staffNs);
    2286          102 :     }
    2287              : 
    2288         1643 :     return true;
    2289              : }
    2290              : 
    2291              : /**
    2292              :  * Write the stem attributes for a Chord (i.e., chord or note).
    2293              :  */
    2294              : 
    2295         1578 : bool MeiExporter::writeStemAtt(const Chord* chord, libmei::AttStems& stemsAtt)
    2296              : {
    2297         1578 :     IF_ASSERT_FAILED(chord) {
    2298            0 :         return false;
    2299              :     }
    2300              : 
    2301         1578 :     auto [meiStemDir, meiStemLen] = Convert::stemToMEI(chord->stemDirection(), chord->noStem());
    2302         1578 :     stemsAtt.SetStemDir(meiStemDir);
    2303         1578 :     stemsAtt.SetStemLen(meiStemLen);
    2304              : 
    2305         1578 :     if (chord->tremoloChordType() == TremoloChordType::TremoloSingle) {
    2306           22 :         stemsAtt.SetStemMod(Convert::stemModToMEI(chord->tremoloSingleChord()));
    2307              :     }
    2308              : 
    2309         1578 :     return true;
    2310              : }
    2311              : 
    2312         2480 : bool MeiExporter::isCurrentNode(const libmei::Element& element)
    2313              : {
    2314         4960 :     return element.m_name == std::string(m_currentNode.name());
    2315              : }
    2316              : 
    2317              : /**
    2318              :  * When writing a measure, find voltas (endings) spanning over it.
    2319              :  * This will then be use to check if the measure is the beginning or the end or a volta.
    2320              :  */
    2321              : 
    2322          808 : std::vector<const Volta*> MeiExporter::findVoltasInMeasure(const Measure* measure)
    2323              : {
    2324          808 :     std::vector<const Volta*> voltas;
    2325          808 :     auto spanners = m_score->spannerMap().findOverlapping(measure->tick().ticks(), measure->endTick().ticks());
    2326         1068 :     for (auto interval : spanners) {
    2327          260 :         Spanner* spanner = interval.value;
    2328          260 :         if (spanner && spanner->isVolta()) {
    2329           28 :             voltas.push_back(toVolta(spanner));
    2330              :         }
    2331              :     }
    2332          808 :     return voltas;
    2333          808 : }
    2334              : 
    2335              : /**
    2336              :  * Go through the list of annotations pointing to the ChordRest and map their element to the xmlId.
    2337              :  * When writing the annotation (i.e., control events) the map is used to generate the `@startid` of the control event.
    2338              :  * The value in the map already contains the `#`
    2339              :  * Also add Breath attached to a ChordRest, which are not represented as annotations but di
    2340              :  */
    2341              : 
    2342         1838 : void MeiExporter::fillControlEventMap(const std::string& xmlId, const ChordRest* chordRest)
    2343              : {
    2344         1838 :     IF_ASSERT_FAILED(chordRest) {
    2345            0 :         return;
    2346              :     }
    2347              : 
    2348         1838 :     track_idx_t trackIdx = chordRest->track();
    2349              : 
    2350         1838 :     if (!chordRest->isGrace()) {
    2351         1960 :         for (const EngravingItem* element : chordRest->segment()->annotations()) {
    2352          169 :             if (element->track() == trackIdx) {
    2353           97 :                 m_startingControlEventList.push_back(std::make_pair(element, "#" + xmlId));
    2354              :             }
    2355              :         }
    2356              :     }
    2357              :     // Breath is handled differently
    2358         1838 :     const Breath* breath = chordRest->hasBreathMark();
    2359         1838 :     if (breath) {
    2360           17 :         m_startingControlEventList.push_back(std::make_pair(breath, "#" + xmlId));
    2361              :     }
    2362              :     // Slurs
    2363         1838 :     SpannerMap& smap = m_score->spannerMap();
    2364         1838 :     auto spanners = smap.findOverlapping(chordRest->tick().ticks(), chordRest->tick().ticks());
    2365         2257 :     for (auto interval : spanners) {
    2366          419 :         Spanner* spanner = interval.value;
    2367          419 :         if (spanner && (spanner->isHairpin() || spanner->isOttava() || spanner->isPedal() || spanner->isSlur() || spanner->isTrill())) {
    2368          399 :             if (spanner->startCR() == chordRest) {
    2369           58 :                 m_startingControlEventList.push_back(std::make_pair(spanner, "#" + xmlId));
    2370          341 :             } else if (spanner->endCR() == chordRest) {
    2371           58 :                 m_endingControlEventMap[spanner] = "#" + xmlId;
    2372              :             }
    2373              :         }
    2374              :     }
    2375              :     // For chords only
    2376         1838 :     if (chordRest->isChord()) {
    2377         1578 :         const Chord* chord = toChord(chordRest);
    2378              :         // Ornaments and laissez vibrer
    2379         1664 :         for (const Articulation* articulation : chord->articulations()) {
    2380           86 :             if (this->isLaissezVibrer(articulation->symId())) {
    2381            0 :                 m_startingControlEventList.push_back(std::make_pair(articulation, "#" + xmlId));
    2382           86 :             } else if (articulation->isOrnament()) {
    2383           33 :                 m_startingControlEventList.push_back(std::make_pair(articulation, "#" + xmlId));
    2384              :             }
    2385              :         }
    2386              :         // Arpeggio
    2387         1578 :         const Arpeggio* arpeggio = chord->arpeggio();
    2388         1578 :         if (arpeggio) {
    2389           11 :             m_startingControlEventList.push_back(std::make_pair(arpeggio, "#" + xmlId));
    2390              :             // The arpeggio is spanning to a lower staff
    2391           11 :             if (arpeggio->span() > 1) {
    2392              :                 // We need to retrieve the chord it is spanning to
    2393            4 :                 track_idx_t bottomTrack = arpeggio->track() + (arpeggio->span() - 1);
    2394            4 :                 const EngravingItem* element = chord->segment()->element(bottomTrack);
    2395              :                 // We do not know the xml:id of the chord yet, keep it in a map
    2396            4 :                 if (element && element->isChord()) {
    2397            4 :                     m_arpegPlistMap[toChord(element)] = arpeggio;
    2398              :                 }
    2399              :             }
    2400              :         }
    2401              :         // This is the lower chord of a spanning arpeggio - we can now move it to the map with the chord xml:id
    2402         1578 :         if (m_arpegPlistMap.count(chord)) {
    2403            4 :             m_plistMap[m_arpegPlistMap.at(chord)] = "#" + xmlId;
    2404            4 :             m_arpegPlistMap.erase(chord);
    2405              :         }
    2406              :     }
    2407         1838 : }
    2408              : 
    2409              : /**
    2410              :  * Retrieve the `@startid` for a control event.
    2411              :  * The map has been filled previously by MeiExporter::addToStartIdMap.
    2412              :  */
    2413              : 
    2414            0 : std::string MeiExporter::findStartIdFor(const EngravingItem* item)
    2415              : {
    2416            0 :     std::string xmlId;
    2417              : 
    2418            0 :     auto result = std::find_if(m_startingControlEventList.begin(), m_startingControlEventList.end(),
    2419            0 :                                [item](const auto& entry) { return entry.first == item; });
    2420              : 
    2421            0 :     if (result != m_startingControlEventList.end()) {
    2422            0 :         xmlId = result->second;
    2423              :     }
    2424            0 :     return xmlId;
    2425            0 : }
    2426              : 
    2427              : /**
    2428              :  * Keep a list of Mei::Exporter::RepeatMark for post-processing jumps.
    2429              :  * This code is currently not used.
    2430              :  * During the post-processing, if a m_jumpToXmlId can be determined, it will be added to the XML m_node.
    2431              :  * See MeiExporter::addJumpToRepeatMarks
    2432              :  */
    2433              : 
    2434            0 : void MeiExporter::addToRepeatMarkList(const EngravingItem* repeatMark, pugi::xml_node node, const std::string& xmlId)
    2435              : {
    2436            0 :     IF_ASSERT_FAILED(repeatMark) {
    2437            0 :         return;
    2438              :     }
    2439              : 
    2440            0 :     m_repeatMarks.push_back(MeiExporter::RepeatMark());
    2441            0 :     RepeatMark& repeatMarkItem = m_repeatMarks.back();
    2442            0 :     repeatMarkItem.m_repeatMark = repeatMark;
    2443            0 :     repeatMarkItem.m_node = node;
    2444            0 :     repeatMarkItem.m_xmlId = xmlId;
    2445              : }
    2446              : 
    2447              : /**
    2448              :  * Add `@jumpto` to repeatMark MEI elements by unfolding MuseScore jumps.
    2449              :  * This code is currently unused because `@jumpto` is not available in MEI.
    2450              :  * The principle is to post-process the list of m_repeatMarks.
    2451              :  * For each mark, we lookup the `@jumpto` xmlId, which can then be written to the repeatMark node
    2452              :  */
    2453              : 
    2454            0 : void MeiExporter::addJumpToRepeatMarks()
    2455              : {
    2456            0 :     if (m_repeatMarks.size() < 1) {
    2457            0 :         return;
    2458              :     }
    2459              : 
    2460            0 :     for (RepeatMark& item : m_repeatMarks) {
    2461            0 :         if (item.m_repeatMark->isJump()) {
    2462              :             // For each jump, lookup the Marker that has the label matching the jumpTo
    2463            0 :             const Jump* jump = toJump(item.m_repeatMark);
    2464            0 :             item.m_jumptToLabel = jump->jumpTo().toStdString();
    2465            0 :             auto jumpTo = std::find_if(m_repeatMarks.begin(), m_repeatMarks.end(), [jump](RepeatMark& item) {
    2466            0 :                 if (!item.m_repeatMark->isMarker()) {
    2467            0 :                     return false;
    2468              :                 }
    2469            0 :                 const Marker* marker = toMarker(item.m_repeatMark);
    2470              :                 // Found it
    2471            0 :                 return marker->label() == jump->jumpTo();
    2472              :             });
    2473            0 :             if (jumpTo != m_repeatMarks.end()) {
    2474              :                 // This is the xmlId we need to add to the (jump) repeatMark
    2475            0 :                 item.m_jumpToXmlId = jumpTo->m_xmlId;
    2476              :             }
    2477              : 
    2478              :             // Try to see if we have a playUntil Marker and a continueAt Marker
    2479            0 :             auto playUntil = std::find_if(m_repeatMarks.begin(), m_repeatMarks.end(), [jump](RepeatMark& item) {
    2480            0 :                 if (!item.m_repeatMark->isMarker()) {
    2481            0 :                     return false;
    2482              :                 }
    2483            0 :                 const Marker* marker = toMarker(item.m_repeatMark);
    2484            0 :                 return marker->label() == jump->playUntil();
    2485              :             });
    2486            0 :             auto continueAt = std::find_if(m_repeatMarks.begin(), m_repeatMarks.end(), [jump](RepeatMark& item) {
    2487            0 :                 if (!item.m_repeatMark->isMarker()) {
    2488            0 :                     return false;
    2489              :                 }
    2490            0 :                 const Marker* marker = toMarker(item.m_repeatMark);
    2491            0 :                 return marker->label() == jump->continueAt();
    2492              :             });
    2493              :             // If yes, make the playUntil repeatMark jump to the continueAt
    2494            0 :             if (playUntil != m_repeatMarks.end() && continueAt != m_repeatMarks.end()) {
    2495            0 :                 playUntil->m_jumpToXmlId = continueAt->m_xmlId;
    2496              :             }
    2497              :         }
    2498              :     }
    2499              :     // Add the jumpto attribute for all the repeatMarks for which we filled a jumpToXmlId
    2500            0 :     for (RepeatMark& item : m_repeatMarks) {
    2501            0 :         if (item.m_jumpToXmlId.size() > 0) {
    2502            0 :             item.m_node.append_attribute("jumpto") = item.m_jumpToXmlId.c_str();
    2503              :         }
    2504              :     }
    2505              : }
    2506              : 
    2507              : /**
    2508              :  * Check if the Segment (barLineEnd type) has a Fermata annotation for the given track.
    2509              :  * Add the Fermata to the m_tstampControlEventMap with the appropriate @staff and @tstamp values.
    2510              :  */
    2511              : 
    2512          674 : bool MeiExporter::addFermataToMap(const track_idx_t track, const Segment* segment, const Measure* measure)
    2513              : {
    2514          674 :     IF_ASSERT_FAILED(segment) {
    2515            0 :         return false;
    2516              :     }
    2517              : 
    2518          686 :     for (const auto& annotation : segment->annotations()) {
    2519           12 :         if (annotation->isFermata() && track == annotation->track()) {
    2520            6 :             staff_idx_t staffN = track2staff(track) + 1;
    2521            6 :             libmei::xsdPositiveInteger_List staffNs;
    2522            6 :             staffNs.push_back(static_cast<int>(staffN));
    2523            6 :             double tstamp = Convert::tstampFromFraction(measure->ticks(), measure->timesig());
    2524            6 :             m_tstampControlEventMap.push_back(std::make_pair(toFermata(annotation), std::make_pair(staffNs, tstamp)));
    2525            6 :         }
    2526              :     }
    2527              : 
    2528          674 :     return true;
    2529              : }
    2530              : 
    2531              : /**
    2532              :  * Return true if the node name matches the name parameter.
    2533              :  */
    2534              : 
    2535          220 : bool MeiExporter::isNode(pugi::xml_node node, const String& name)
    2536              : {
    2537          220 :     if (!node) {
    2538            0 :         return false;
    2539              :     }
    2540              : 
    2541          220 :     String nodeName = String(node.name());
    2542          220 :     return nodeName == name;
    2543          220 : }
    2544              : 
    2545              : /**
    2546              :  * Return the last element in node that is a ChordRest (chord, note, or rest)
    2547              :  */
    2548              : 
    2549           29 : pugi::xml_node MeiExporter::getLastChordRest(pugi::xml_node node)
    2550              : {
    2551           29 :     pugi::xml_node chordRest;
    2552              : 
    2553          253 :     for (pugi::xml_node child : node.children()) {
    2554          112 :         if (this->isNode(child, u"chord") || this->isNode(child, u"note") || this->isNode(child, u"rest")) {
    2555          106 :             chordRest = child;
    2556              :         }
    2557              :     }
    2558           29 :     return chordRest;
    2559              : }
    2560              : 
    2561              : /**
    2562              :  * Add a spanner to the map of open control events to which a @endid needs to be added.
    2563              :  * For spanners starting and ending on the same element (hairpin, ottava), the @endid is added directly together with a @dur
    2564              :  */
    2565              : 
    2566          113 : void MeiExporter::addNodeToOpenControlEvents(pugi::xml_node node, const Spanner* spanner, const std::string& startid)
    2567              : {
    2568          113 :     if (spanner->startElement() && (spanner->startElement() == spanner->endElement())) {
    2569              :         // Add a @endid
    2570            0 :         libmei::InstStartEndId startEndId;
    2571            0 :         startEndId.SetEndid(startid);
    2572            0 :         startEndId.WriteStartEndId(node);
    2573              :         // Add a @dur
    2574            0 :         if (spanner->startElement()->isChordRest()) {
    2575            0 :             const ChordRest* startCR = toChordRest(spanner->startElement());
    2576            0 :             libmei::InstDurationLog durationLog;
    2577            0 :             durationLog.SetDur(Convert::durToMEI(startCR->durationType().type()));
    2578            0 :             durationLog.WriteDurationLog(node);
    2579            0 :         }
    2580            0 :     } else {
    2581          113 :         m_openControlEventMap[spanner] = node;
    2582              :     }
    2583          113 : }
    2584              : 
    2585              : /**
    2586              :  * Go trough the list of control event maps and add @endid when the end element has be written.
    2587              :  */
    2588              : 
    2589          404 : void MeiExporter::addEndidToControlEvents()
    2590              : {
    2591          404 :     std::vector<const EngravingItem*> closedEvents;
    2592          404 :     std::vector<const EngravingItem*> closedPlists;
    2593              : 
    2594              :     // Go through the list of open control events and see if the end element has been written
    2595          593 :     for (auto controlEvent : m_openControlEventMap) {
    2596              :         // Convenience variable
    2597          189 :         const EngravingItem* item = controlEvent.first;
    2598              :         // Check if we have both:
    2599              :         // * the end @xml:id (in m_endingControlEventMap)
    2600              :         // * the control event node (in m_openControlEventMap)
    2601          189 :         if (m_endingControlEventMap.count(item) && m_openControlEventMap.count(item)) {
    2602              :             // Create an attribute class instance to add the @endid
    2603          107 :             libmei::InstStartEndId startEndId;
    2604          107 :             startEndId.SetEndid(m_endingControlEventMap.at(item));
    2605          107 :             startEndId.WriteStartEndId(m_openControlEventMap.at(item));
    2606              :             // Add it to the list of closed events we can remove from the maps (below)
    2607          107 :             closedEvents.push_back(item);
    2608          107 :         }
    2609          189 :         if (m_plistMap.count(item) && m_openControlEventMap.count(item)) {
    2610              :             // Create an attribute class instance to add the @plist
    2611            4 :             libmei::InstPlist plist;
    2612           12 :             plist.SetPlist({ m_plistMap.at(item) });
    2613            4 :             plist.WritePlist(m_openControlEventMap.at(item));
    2614              :             // Add it to the list of closed plists we can remove from the maps (below)
    2615            4 :             closedPlists.push_back(item);
    2616            4 :         }
    2617              :     }
    2618              : 
    2619          511 :     for (auto item : closedEvents) {
    2620          107 :         m_openControlEventMap.erase(item);
    2621              :     }
    2622          408 :     for (auto item : closedPlists) {
    2623            4 :         m_plistMap.erase(item);
    2624              :     }
    2625          408 : }
    2626              : 
    2627              : //---------------------------------------------------------
    2628              : // generate XML:IDs
    2629              : //---------------------------------------------------------
    2630              : 
    2631              : /**
    2632              :  * Integer hash methods used for ID generation
    2633              :  */
    2634              : 
    2635            0 : uint32_t MeiExporter::hash(uint32_t number, bool reverse)
    2636              : {
    2637            0 :     const uint32_t magicNumber = reverse ? 0x119de1f3 : 0x45d9f3b;
    2638            0 :     number = ((number >> 16) ^ number) * magicNumber;
    2639            0 :     number = ((number >> 16) ^ number) * magicNumber;
    2640            0 :     number = (number >> 16) ^ number;
    2641            0 :     return number;
    2642              : }
    2643              : 
    2644              : /**
    2645              :  * Base encode a value into a std::string
    2646              :  */
    2647              : 
    2648            0 : std::string MeiExporter::baseEncodeInt(uint32_t value, uint8_t base)
    2649              : {
    2650            0 :     if ((base < 11) || (base > 62)) {
    2651            0 :         return "";
    2652              :     }
    2653              : 
    2654            0 :     static const std::string base62Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    2655              : 
    2656            0 :     std::string base62;
    2657            0 :     if (value < base) {
    2658            0 :         return std::string(1, base62Chars[value]);
    2659              :     }
    2660              : 
    2661            0 :     while (value) {
    2662            0 :         base62 += base62Chars[value % base];
    2663            0 :         value /= base;
    2664              :     }
    2665              : 
    2666            0 :     reverse(base62.begin(), base62.end());
    2667            0 :     return base62;
    2668            0 : }
    2669              : 
    2670              : /**
    2671              :  * Generate an xml:id using the hash method and the m_xmlIdCounter.
    2672              :  */
    2673              : 
    2674            0 : std::string MeiExporter::generateHashID()
    2675              : {
    2676            0 :     uint32_t nr = hash(++m_xmlIDCounter, false);
    2677              : 
    2678            0 :     return this->baseEncodeInt(nr, 36);
    2679              : }
    2680              : 
    2681              : /**
    2682              :  * Return the @xml:id for an element.
    2683              :  * First look in the UIDRegister if xml:id of the element has been registered and can be preserved.
    2684              :  * Otherwise generate a new hash xml:id.
    2685              :  * Init the m_xmlIdCounter when the method is called for the first time.
    2686              :  */
    2687              : 
    2688         3545 : std::string MeiExporter::getXmlIdFor(const EngravingItem* item, const char c)
    2689              : {
    2690         3545 :     const bool useMuseScoreIds = configuration()->meiUseMuseScoreIds();
    2691              : 
    2692         3545 :     if (useMuseScoreIds) {
    2693            0 :         EID eid = item->eid();
    2694            0 :         if (!eid.isValid()) {
    2695            0 :             eid = item->assignNewEID();
    2696              :         }
    2697            0 :         String eidStr = String::fromStdString(eid.toStdString().c_str());
    2698            0 :         return "mscore-" + eidStr.replace('/', '.').replace('+', '-').toStdString();
    2699         3545 :     } else if (m_uids->hasUid(item)) {
    2700         3545 :         return m_uids->uid(item);
    2701              :     }
    2702              : 
    2703              :     // First ID to be generated
    2704            0 :     if (m_xmlIDCounter == 0) {
    2705            0 :         std::random_device rd;
    2706            0 :         std::mt19937 randomGenerator(rd());
    2707              :         // Use xml:id metaTag's hash to initialize IDs
    2708            0 :         String xmlId = m_score->metaTag(u"xml:id");
    2709            0 :         if (!xmlId.isEmpty()) {
    2710            0 :             m_xmlIDCounter = static_cast<int>(xmlId.hash());
    2711              :         } else {
    2712            0 :             m_xmlIDCounter = randomGenerator();
    2713              :         }
    2714            0 :     }
    2715              : 
    2716            0 :     return c + this->generateHashID();
    2717              : }
    2718              : 
    2719              : /**
    2720              :  * Reset all the sub-counters for layer elements (e.g., accid, chord, note, etc.).
    2721              :  */
    2722              : 
    2723         2664 : void MeiExporter::resetLayerIDs()
    2724              : {
    2725         2664 :     std::fill(m_layerCounterFor.begin(), m_layerCounterFor.end(), 0);
    2726         2664 : }
    2727              : 
    2728              : /**
    2729              :  * Return the current @xml:id for a section.
    2730              :  */
    2731              : 
    2732           64 : std::string MeiExporter::getSectionXmlId()
    2733              : {
    2734           64 :     return String("s%1").arg(++m_sectionCounter).toStdString();
    2735              : }
    2736              : 
    2737              : /**
    2738              :  * Return the current @xml:id for a measure.
    2739              :  * Reset the staff counter and the measure sub-counters
    2740              :  */
    2741              : 
    2742          404 : std::string MeiExporter::getMeasureXmlId(const Measure* measure)
    2743              : {
    2744              :     // Reset the staff counter when a new measure starts
    2745          404 :     m_staffCounter = 0;
    2746          404 :     m_measureCounter++;
    2747              :     // Get the map IDs for the measure
    2748          404 :     return this->getXmlIdFor(measure, 'm');
    2749              : }
    2750              : 
    2751              : /**
    2752              :  * Return the current @xml:id for a staff.
    2753              :  * Reset the layer counters
    2754              :  */
    2755              : 
    2756          650 : std::string MeiExporter::getStaffXmlId()
    2757              : {
    2758              :     // Reset the layer counter when a new staff starts
    2759          650 :     m_layerCounter = 0;
    2760          650 :     return String("m%1s%2").arg(m_measureCounter).arg(++m_staffCounter).toStdString();
    2761              : }
    2762              : 
    2763              : /**
    2764              :  * Return the current @xml:id for a layer.
    2765              :  * Reset the layer sub-counters
    2766              :  */
    2767              : 
    2768         2600 : std::string MeiExporter::getLayerXmlId()
    2769              : {
    2770              :     // Reset the layer sub-counters when a new layer starts
    2771         2600 :     this->resetLayerIDs();
    2772         2600 :     return String("m%1s%2l%3").arg(m_measureCounter).arg(m_staffCounter).arg(++m_layerCounter).toStdString();
    2773              : }
    2774              : 
    2775              : /**
    2776              :  * Return the current counter-based @xml:id for a layer element.
    2777              :  */
    2778              : 
    2779          505 : std::string MeiExporter::getLayerXmlIdFor(layerElementCounter elementType)
    2780              : {
    2781          505 :     String id;
    2782              :     if (MEI_COUNTER_BASED_IDS) {
    2783              :         // m (Measure) / s (Staff) / l (Layer) / ? Layer element type
    2784              :         // The layer element abbreviation is given in the MeiExporter::s_layerXmlIdMap
    2785              :         id = String("m%1s%2l%3%4%5").arg(m_measureCounter).arg(m_staffCounter).arg(m_layerCounter).arg(MeiExporter::s_layerXmlIdMap.at(
    2786              :                                                                                                            elementType)).arg(++(
    2787              :                                                                                                                                  m_layerCounterFor
    2788              :                                                                                                                                  .at(
    2789              :                                                                                                                                      elementType)));
    2790              :     }
    2791         1010 :     return id.toStdString();
    2792          505 : }
    2793              : 
    2794              : /**
    2795              :  * Return true if the used symbol is a laissez vibrer
    2796              :  */
    2797              : 
    2798          139 : bool MeiExporter::isLaissezVibrer(const SymId id)
    2799              : {
    2800          139 :     return id == SymId::articLaissezVibrerAbove || id == SymId::articLaissezVibrerBelow;
    2801              : }
        

Generated by: LCOV version 2.0-1