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