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 : }
|