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 "read114.h"
24 :
25 : #include <cmath>
26 :
27 : #include "global/defer.h"
28 :
29 : #include "compat/pageformat.h"
30 :
31 : #include "engravingerrors.h"
32 :
33 : #include "infrastructure/htmlparser.h"
34 :
35 : #include "rw/compat/compatutils.h"
36 : #include "rw/compat/readchordlisthook.h"
37 :
38 : #include "style/defaultstyle.h"
39 : #include "style/style.h"
40 :
41 : #include "types/bps.h"
42 : #include "types/constants.h"
43 : #include "types/typesconv.h"
44 :
45 : #include "dom/accidental.h"
46 : #include "dom/barline.h"
47 : #include "dom/beam.h"
48 : #include "dom/box.h"
49 : #include "dom/bracketitem.h"
50 : #include "dom/breath.h"
51 : #include "dom/chord.h"
52 : #include "dom/clef.h"
53 : #include "dom/drumset.h"
54 : #include "dom/dynamic.h"
55 : #include "dom/excerpt.h"
56 : #include "dom/factory.h"
57 : #include "dom/fingering.h"
58 : #include "dom/harmony.h"
59 : #include "dom/image.h"
60 : #include "dom/jump.h"
61 : #include "dom/keysig.h"
62 : #include "dom/lyrics.h"
63 : #include "dom/marker.h"
64 : #include "dom/masterscore.h"
65 : #include "dom/measure.h"
66 : #include "dom/measurenumber.h"
67 : #include "dom/measurerepeat.h"
68 : #include "dom/mmrest.h"
69 : #include "dom/note.h"
70 : #include "dom/ottava.h"
71 : #include "dom/part.h"
72 : #include "dom/pedal.h"
73 : #include "dom/rest.h"
74 : #include "dom/segment.h"
75 : #include "dom/sig.h"
76 : #include "dom/slur.h"
77 : #include "dom/spacer.h"
78 : #include "dom/staff.h"
79 : #include "dom/stafftext.h"
80 : #include "dom/stafftype.h"
81 : #include "dom/stringdata.h"
82 : #include "dom/tempotext.h"
83 : #include "dom/text.h"
84 : #include "dom/textline.h"
85 : #include "dom/timesig.h"
86 : #include "dom/tremolotwochord.h"
87 : #include "dom/tremolosinglechord.h"
88 : #include "dom/trill.h"
89 : #include "dom/tuplet.h"
90 : #include "dom/utils.h"
91 : #include "dom/volta.h"
92 :
93 : #include "../../editing/editstaffbrackets.h"
94 : #include "../../editing/transpose.h"
95 :
96 : #include "../compat/readchordlisthook.h"
97 : #include "../compat/readstyle.h"
98 : #include "../compat/tremolocompat.h"
99 : #include "../read206/read206.h"
100 : #include "../read400/tread.h"
101 :
102 : #include "log.h"
103 :
104 : using namespace mu;
105 : using namespace mu::engraving;
106 : using namespace mu::engraving::rw;
107 : using namespace mu::engraving::read400;
108 : using namespace mu::engraving::read114;
109 : using namespace mu::engraving::read206;
110 : using namespace mu::engraving::compat;
111 :
112 : using mu::engraving::compat::TremoloCompat;
113 :
114 : static int g_guitarStrings[] = { 40, 45, 50, 55, 59, 64 };
115 : static int g_bassStrings[] = { 28, 33, 38, 43 };
116 : static int g_violinStrings[] = { 55, 62, 69, 76 };
117 : static int g_violaStrings[] = { 48, 55, 62, 69 };
118 : static int g_celloStrings[] = { 36, 43, 50, 57 };
119 :
120 : #define MM(x) ((x) / INCH)
121 :
122 : //---------------------------------------------------------
123 : // PaperSize
124 : //---------------------------------------------------------
125 :
126 : struct PaperSize {
127 : const char* name;
128 : double w, h; // size in inch
129 608 : PaperSize(const char* n, double wi, double hi)
130 608 : : name(n), w(wi), h(hi) {}
131 : };
132 :
133 : static const PaperSize paperSizes114[] = {
134 : PaperSize("Custom", MM(1), MM(1)),
135 : PaperSize("A4", MM(210), MM(297)),
136 : PaperSize("B5", MM(176), MM(250)),
137 : PaperSize("Letter", 8.5, 11),
138 : PaperSize("Legal", 8.5, 14),
139 : PaperSize("Executive", 7.5, 10),
140 : PaperSize("A0", MM(841), MM(1189)),
141 : PaperSize("A1", MM(594), MM(841)),
142 : PaperSize("A2", MM(420), MM(594)),
143 : PaperSize("A3", MM(297), MM(420)),
144 : PaperSize("A5", MM(148), MM(210)),
145 : PaperSize("A6", MM(105), MM(148)),
146 : PaperSize("A7", MM(74), MM(105)),
147 : PaperSize("A8", MM(52), MM(74)),
148 : PaperSize("A9", MM(37), MM(52)),
149 : PaperSize("A10", MM(26), MM(37)),
150 : PaperSize("B0", MM(1000), MM(1414)),
151 : PaperSize("B1", MM(707), MM(1000)),
152 : PaperSize("B2", MM(500), MM(707)),
153 : PaperSize("B3", MM(353), MM(500)),
154 : PaperSize("B4", MM(250), MM(353)),
155 : PaperSize("B6", MM(125), MM(176)),
156 : PaperSize("B7", MM(88), MM(125)),
157 : PaperSize("B8", MM(62), MM(88)),
158 : PaperSize("B9", MM(44), MM(62)),
159 : PaperSize("B10", MM(31), MM(44)),
160 : PaperSize("Comm10E", MM(105), MM(241)),
161 : PaperSize("DLE", MM(110), MM(220)),
162 : PaperSize("Folio", MM(210), MM(330)),
163 : PaperSize("Ledger", MM(432), MM(279)),
164 : PaperSize("Tabloid", MM(279), MM(432)),
165 : PaperSize(0, MM(1), MM(1)) // mark end of list
166 : };
167 :
168 : //---------------------------------------------------------
169 : // getPaperSize
170 : //---------------------------------------------------------
171 :
172 26 : static const PaperSize* getPaperSize114(const String& name)
173 : {
174 52 : for (int i = 0; paperSizes114[i].name; ++i) {
175 52 : if (name == paperSizes114[i].name) {
176 26 : return &paperSizes114[i];
177 : }
178 : }
179 0 : LOGD("unknown paper size");
180 0 : return &paperSizes114[0];
181 : }
182 :
183 : //---------------------------------------------------------
184 : // readTextProperties
185 : //---------------------------------------------------------
186 :
187 241 : static bool readTextProperties(XmlReader& e, ReadContext& ctx, TextBase* t, EngravingItem*)
188 : {
189 241 : const AsciiStringView tag(e.name());
190 241 : if (tag == "style") {
191 73 : int i = e.readInt();
192 73 : TextStyleType ss = TextStyleType::DEFAULT;
193 73 : switch (i) {
194 10 : case 2: ss = TextStyleType::TITLE;
195 10 : break;
196 2 : case 3: ss = TextStyleType::SUBTITLE;
197 2 : break;
198 2 : case 4: ss = TextStyleType::COMPOSER;
199 2 : break;
200 2 : case 5: ss = TextStyleType::LYRICIST;
201 2 : break;
202 :
203 0 : case 6: ss = TextStyleType::LYRICS_ODD;
204 0 : break;
205 0 : case 7: ss = TextStyleType::LYRICS_EVEN;
206 0 : break;
207 :
208 0 : case 8: ss = TextStyleType::FINGERING;
209 0 : break;
210 12 : case 9: ss = TextStyleType::INSTRUMENT_LONG;
211 12 : break;
212 12 : case 10: ss = TextStyleType::INSTRUMENT_SHORT;
213 12 : break;
214 0 : case 11: ss = TextStyleType::INSTRUMENT_EXCERPT;
215 0 : break;
216 :
217 0 : case 12: ss = TextStyleType::DYNAMICS;
218 0 : break;
219 0 : case 13: ss = TextStyleType::EXPRESSION;
220 0 : break;
221 0 : case 14: ss = TextStyleType::TEMPO;
222 0 : break;
223 0 : case 15: ss = TextStyleType::METRONOME;
224 0 : break;
225 1 : case 16: ss = TextStyleType::FOOTER;
226 1 : break; // TextStyleType::COPYRIGHT
227 26 : case 17: ss = TextStyleType::MEASURE_NUMBER;
228 26 : break;
229 0 : case 18: ss = TextStyleType::FOOTER;
230 0 : break; // TextStyleType::PAGE_NUMBER_ODD
231 0 : case 19: ss = TextStyleType::FOOTER;
232 0 : break; // TextStyleType::PAGE_NUMBER_EVEN
233 0 : case 20: ss = TextStyleType::TRANSLATOR;
234 0 : break;
235 0 : case 21: ss = TextStyleType::TUPLET;
236 0 : break;
237 :
238 0 : case 22: ss = TextStyleType::SYSTEM;
239 0 : break;
240 2 : case 23: ss = TextStyleType::STAFF;
241 2 : break;
242 0 : case 24: ss = TextStyleType::HARMONY_A;
243 0 : break;
244 0 : case 25: ss = TextStyleType::REHEARSAL_MARK;
245 0 : break;
246 0 : case 26: ss = TextStyleType::REPEAT_LEFT;
247 0 : break;
248 0 : case 27: ss = TextStyleType::VOLTA;
249 0 : break;
250 0 : case 28: ss = TextStyleType::FRAME;
251 0 : break;
252 2 : case 29: ss = TextStyleType::TEXTLINE;
253 2 : break;
254 0 : case 30: ss = TextStyleType::GLISSANDO;
255 0 : break;
256 0 : case 31: ss = TextStyleType::STRING_NUMBER;
257 0 : break;
258 :
259 2 : case 32: ss = TextStyleType::OTTAVA;
260 2 : break;
261 : //?? case 33: ss = TextStyleName::BENCH; break;
262 0 : case 34: ss = TextStyleType::HEADER;
263 0 : break;
264 0 : case 35: ss = TextStyleType::FOOTER;
265 0 : break;
266 0 : case 0:
267 : default:
268 0 : LOGD("style %d invalid", i);
269 0 : ss = TextStyleType::DEFAULT;
270 0 : break;
271 : }
272 73 : t->initTextStyleType(ss);
273 168 : } else if (tag == "subtype") {
274 73 : e.skipCurrentElement();
275 95 : } else if (tag == "html-data") {
276 47 : String ss = e.readXml();
277 47 : String s = HtmlParser::parse(ss);
278 : // LOGD("html-data <%s>", muPrintable(s));
279 47 : t->setXmlText(s);
280 95 : } else if (tag == "foregroundColor") { // same as "color" ?
281 0 : e.skipCurrentElement();
282 48 : } else if (tag == "frame") {
283 47 : t->setFrameType(e.readBool() ? FrameType::RECTANGLE : FrameType::NO_FRAME);
284 47 : t->setPropertyFlags(Pid::FRAME_TYPE, PropertyFlags::UNSTYLED);
285 1 : } else if (tag == "halign") {
286 0 : Align align = t->align();
287 0 : align.horizontal = TConv::fromXml(e.readAsciiText(), AlignH::LEFT);
288 0 : t->setAlign(align);
289 0 : t->setPropertyFlags(Pid::ALIGN, PropertyFlags::UNSTYLED);
290 0 : t->setPosition(align.horizontal);
291 0 : t->setPropertyFlags(Pid::POSITION, PropertyFlags::UNSTYLED);
292 1 : } else if (tag == "valign") {
293 0 : Align align = t->align();
294 0 : align.vertical = TConv::fromXml(e.readAsciiText(), AlignV::TOP);
295 0 : t->setAlign(align);
296 0 : t->setPropertyFlags(Pid::ALIGN, PropertyFlags::UNSTYLED);
297 1 : } else if (tag == "rxoffset") {
298 0 : e.readText();
299 1 : } else if (tag == "ryoffset") {
300 0 : e.readText();
301 1 : } else if (tag == "yoffset") {
302 0 : e.readText();
303 1 : } else if (tag == "systemFlag") {
304 0 : e.readText();
305 1 : } else if (!read400::TRead::readTextProperties(t, e, ctx)) {
306 0 : return false;
307 : }
308 241 : t->setOffset(PointF()); // ignore user offsets
309 241 : t->setAutoplace(true);
310 :
311 241 : t->setPosition(t->align().horizontal);
312 241 : if (t->position() != t->propertyDefault(Pid::POSITION).value<AlignH>()) {
313 0 : t->setPropertyFlags(Pid::POSITION, PropertyFlags::UNSTYLED);
314 : }
315 :
316 241 : return true;
317 : }
318 :
319 : //---------------------------------------------------------
320 : // readText114
321 : //---------------------------------------------------------
322 :
323 45 : static void readText114(XmlReader& e, ReadContext& ctx, TextBase* t, EngravingItem* be)
324 : {
325 271 : while (e.readNextStartElement()) {
326 181 : if (!readTextProperties(e, ctx, t, be)) {
327 0 : e.unknown();
328 : }
329 : }
330 45 : }
331 :
332 : //---------------------------------------------------------
333 : // readAccidental
334 : //---------------------------------------------------------
335 :
336 26 : static void readAccidental(Accidental* a, XmlReader& e, ReadContext& ctx)
337 : {
338 78 : while (e.readNextStartElement()) {
339 52 : const AsciiStringView tag(e.name());
340 52 : if (tag == "bracket") {
341 26 : int i = e.readInt();
342 26 : if (i == 0 || i == 1) {
343 26 : a->setBracket(AccidentalBracket(i));
344 : }
345 26 : } else if (tag == "subtype") {
346 26 : String text(e.readText());
347 : bool isInt;
348 26 : int i = text.toInt(&isInt);
349 26 : if (isInt) {
350 0 : a->setBracket(i & 0x8000 ? AccidentalBracket::PARENTHESIS : AccidentalBracket::BRACKET);
351 0 : i &= ~0x8000;
352 : AccidentalType at;
353 0 : switch (i) {
354 0 : case 0:
355 0 : at = AccidentalType::NONE;
356 0 : break;
357 0 : case 6:
358 0 : a->setBracket(AccidentalBracket::PARENTHESIS);
359 : // fall through
360 0 : case 1:
361 : case 11:
362 0 : at = AccidentalType::SHARP;
363 0 : break;
364 0 : case 7:
365 0 : a->setBracket(AccidentalBracket::PARENTHESIS);
366 : // fall through
367 0 : case 2:
368 : case 12:
369 0 : at = AccidentalType::FLAT;
370 0 : break;
371 0 : case 8:
372 0 : a->setBracket(AccidentalBracket::PARENTHESIS);
373 : // fall through
374 0 : case 3:
375 : case 13:
376 0 : at = AccidentalType::SHARP2;
377 0 : break;
378 0 : case 9:
379 0 : a->setBracket(AccidentalBracket::PARENTHESIS);
380 : // fall through
381 0 : case 4:
382 : case 14:
383 0 : at = AccidentalType::FLAT2;
384 0 : break;
385 0 : case 10:
386 0 : a->setBracket(AccidentalBracket::PARENTHESIS);
387 : // fall through
388 0 : case 5:
389 : case 15:
390 0 : at = AccidentalType::NATURAL;
391 0 : break;
392 0 : case 16:
393 0 : at = AccidentalType::FLAT_SLASH;
394 0 : break;
395 0 : case 17:
396 0 : at = AccidentalType::FLAT_SLASH2;
397 0 : break;
398 0 : case 18:
399 0 : at = AccidentalType::MIRRORED_FLAT2;
400 0 : break;
401 0 : case 19:
402 0 : at = AccidentalType::MIRRORED_FLAT;
403 0 : break;
404 0 : case 20:
405 0 : at = AccidentalType::NONE; //AccidentalType::MIRRORED_FLAT_SLASH;
406 0 : break;
407 0 : case 21:
408 0 : at = AccidentalType::NONE; //AccidentalType::FLAT_FLAT_SLASH;
409 0 : break;
410 0 : case 22:
411 0 : at = AccidentalType::SHARP_SLASH;
412 0 : break;
413 0 : case 23:
414 0 : at = AccidentalType::SHARP_SLASH2;
415 0 : break;
416 0 : case 24:
417 0 : at = AccidentalType::SHARP_SLASH3;
418 0 : break;
419 0 : case 25:
420 0 : at = AccidentalType::SHARP_SLASH4;
421 0 : break;
422 0 : case 26:
423 0 : at = AccidentalType::SHARP_ARROW_UP;
424 0 : break;
425 0 : case 27:
426 0 : at = AccidentalType::SHARP_ARROW_DOWN;
427 0 : break;
428 0 : case 28:
429 0 : at = AccidentalType::NONE; //AccidentalType::SHARP_ARROW_BOTH;
430 0 : break;
431 0 : case 29:
432 0 : at = AccidentalType::FLAT_ARROW_UP;
433 0 : break;
434 0 : case 30:
435 0 : at = AccidentalType::FLAT_ARROW_DOWN;
436 0 : break;
437 0 : case 31:
438 0 : at = AccidentalType::NONE; //AccidentalType::FLAT_ARROW_BOTH;
439 0 : break;
440 0 : case 32:
441 0 : at = AccidentalType::NATURAL_ARROW_UP;
442 0 : break;
443 0 : case 33:
444 0 : at = AccidentalType::NATURAL_ARROW_DOWN;
445 0 : break;
446 0 : case 34:
447 0 : at = AccidentalType::NONE; //AccidentalType::NATURAL_ARROW_BOTH;
448 0 : break;
449 0 : default:
450 0 : at = AccidentalType::NONE;
451 0 : break;
452 : }
453 0 : a->setAccidentalType(at);
454 : } else {
455 : static const std::map<String, AccidentalType> accMap = {
456 0 : { u"none", AccidentalType::NONE }, { u"sharp", AccidentalType::SHARP },
457 0 : { u"flat", AccidentalType::FLAT }, { u"natural", AccidentalType::NATURAL },
458 0 : { u"double sharp", AccidentalType::SHARP2 }, { u"double flat", AccidentalType::FLAT2 },
459 0 : { u"flat-slash", AccidentalType::FLAT_SLASH }, { u"flat-slash2", AccidentalType::FLAT_SLASH2 },
460 0 : { u"mirrored-flat2", AccidentalType::MIRRORED_FLAT2 }, { u"mirrored-flat", AccidentalType::MIRRORED_FLAT },
461 0 : { u"sharp-slash", AccidentalType::SHARP_SLASH }, { u"sharp-slash2", AccidentalType::SHARP_SLASH2 },
462 0 : { u"sharp-slash3", AccidentalType::SHARP_SLASH3 }, { u"sharp-slash4", AccidentalType::SHARP_SLASH4 },
463 0 : { u"sharp arrow up", AccidentalType::SHARP_ARROW_UP }, { u"sharp arrow down", AccidentalType::SHARP_ARROW_DOWN },
464 0 : { u"flat arrow up", AccidentalType::FLAT_ARROW_UP }, { u"flat arrow down", AccidentalType::FLAT_ARROW_DOWN },
465 0 : { u"natural arrow up", AccidentalType::NATURAL_ARROW_UP },
466 0 : { u"natural arrow down", AccidentalType::NATURAL_ARROW_DOWN },
467 0 : { u"sori", AccidentalType::SORI }, { u"koron", AccidentalType::KORON }
468 50 : };
469 26 : auto it = accMap.find(text);
470 26 : if (it == accMap.end()) {
471 5 : LOGD("invalid type %s", muPrintable(text));
472 5 : a->setAccidentalType(AccidentalType::NONE);
473 : } else {
474 21 : a->setAccidentalType(it->second);
475 : }
476 : }
477 26 : } else if (tag == "role") {
478 0 : AccidentalRole r = AccidentalRole(e.readInt());
479 0 : if (r == AccidentalRole::AUTO || r == AccidentalRole::USER) {
480 0 : a->setRole(r);
481 : }
482 0 : } else if (tag == "small") {
483 0 : a->setSmall(e.readInt());
484 0 : } else if (tag == "offset") {
485 0 : e.skipCurrentElement(); // ignore manual layout in older scores
486 0 : } else if (TRead::readItemProperties(a, e, ctx)) {
487 : } else {
488 0 : e.unknown();
489 : }
490 : }
491 27 : }
492 :
493 : //---------------------------------------------------------
494 : // readFingering114
495 : //---------------------------------------------------------
496 :
497 2 : static void readFingering114(XmlReader& e, Fingering* fing)
498 : {
499 2 : bool isStringNumber = false;
500 15 : while (e.readNextStartElement()) {
501 13 : const AsciiStringView tag(e.name());
502 :
503 13 : if (tag == "html-data") {
504 2 : auto htmlDdata = HtmlParser::parse(e.readXml());
505 2 : fing->setXmlText(htmlDdata);
506 13 : } else if (tag == "subtype") {
507 2 : auto subtype = e.readText();
508 2 : if (subtype == "StringNumber") {
509 1 : isStringNumber = true;
510 1 : fing->setProperty(Pid::TEXT_STYLE, TextStyleType::STRING_NUMBER);
511 1 : fing->setPropertyFlags(Pid::TEXT_STYLE, PropertyFlags::UNSTYLED);
512 : }
513 11 : } else if (tag == "frame") {
514 2 : auto frame = e.readInt();
515 2 : if (frame) {
516 1 : if (isStringNumber) {
517 : // default value is circle for string number, rectangle is set in <circle> tag
518 1 : fing->setFrameType(FrameType::CIRCLE);
519 : } else {
520 : // default value is rectangle for string number, circle is set in <circle> tag
521 0 : fing->setFrameType(FrameType::RECTANGLE);
522 : }
523 : } else {
524 1 : fing->setFrameType(FrameType::NO_FRAME);
525 : }
526 7 : } else if (tag == "circle") {
527 0 : auto circle = e.readInt();
528 0 : if (circle) {
529 0 : fing->setFrameType(FrameType::CIRCLE);
530 : } else {
531 0 : fing->setFrameType(FrameType::RECTANGLE);
532 : }
533 : } else {
534 7 : e.skipCurrentElement();
535 : }
536 : }
537 2 : }
538 :
539 : //---------------------------------------------------------
540 : // readNote
541 : //---------------------------------------------------------
542 :
543 439 : static void readNote(Note* note, XmlReader& e, ReadContext& ctx)
544 : {
545 439 : bool hasAccidental = false; // used for userAccidental backward compatibility
546 :
547 439 : note->setTpc1(Tpc::TPC_INVALID);
548 439 : note->setTpc2(Tpc::TPC_INVALID);
549 :
550 439 : if (e.hasAttribute("pitch")) { // obsolete
551 0 : note->setPitch(e.intAttribute("pitch"));
552 : }
553 439 : if (e.hasAttribute("tpc")) { // obsolete
554 0 : note->setTpc1(e.intAttribute("tpc"));
555 : }
556 :
557 1529 : while (e.readNextStartElement()) {
558 1090 : const AsciiStringView tag(e.name());
559 1090 : if (tag == "Accidental") {
560 : // on older scores, a note could have both a <userAccidental> tag and an <Accidental> tag
561 : // if a userAccidental has some other property set (like for instance offset)
562 : Accidental* a;
563 26 : if (hasAccidental) { // if the other tag has already been read,
564 22 : a = note->accidental(); // re-use the accidental it constructed
565 : } else {
566 4 : a = Factory::createAccidental(note);
567 : }
568 : // the accidental needs to know the properties of the
569 : // track it belongs to (??)
570 26 : a->setTrack(note->track());
571 26 : readAccidental(a, e, ctx);
572 26 : if (!hasAccidental) { // only the new accidental, if it has been added previously
573 4 : note->add(a);
574 : }
575 26 : hasAccidental = true; // we now have an accidental
576 1064 : } else if (tag == "Text") {
577 2 : Fingering* f = Factory::createFingering(note);
578 2 : readFingering114(e, f);
579 2 : note->add(f);
580 1062 : } else if (tag == "onTimeType") {
581 0 : if (e.readText() == "offset") {
582 0 : note->setOnTimeType(2);
583 : } else {
584 0 : note->setOnTimeType(1);
585 : }
586 1062 : } else if (tag == "offTimeType") {
587 0 : if (e.readText() == "offset") {
588 0 : note->setOffTimeType(2);
589 : } else {
590 0 : note->setOffTimeType(1);
591 : }
592 1062 : } else if (tag == "onTimeOffset") {
593 0 : if (note->onTimeType() == 1) {
594 0 : note->setOnTimeOffset(e.readInt() * 1000 / note->chord()->actualTicks().ticks());
595 : } else {
596 0 : note->setOnTimeOffset(e.readInt() * 10);
597 : }
598 1062 : } else if (tag == "offTimeOffset") {
599 0 : if (note->offTimeType() == 1) {
600 0 : note->setOffTimeOffset(1000 + (e.readInt() * 1000 / note->chord()->actualTicks().ticks()));
601 : } else {
602 0 : note->setOffTimeOffset(1000 + (e.readInt() * 10));
603 : }
604 1062 : } else if (tag == "userAccidental") {
605 44 : String val(e.readText());
606 : bool ok;
607 44 : int k = val.toInt(&ok);
608 44 : if (ok) {
609 : // on older scores, a note could have both a <userAccidental> tag and an <Accidental> tag
610 : // if a userAccidental has some other property set (like for instance offset)
611 : // only construct a new accidental, if the other tag has not been read yet
612 : // (<userAccidental> tag is only used in older scores: no need to check the score mscVersion)
613 44 : if (!hasAccidental) {
614 44 : Accidental* a = Factory::createAccidental(note);
615 44 : note->add(a);
616 : }
617 : // TODO: for backward compatibility
618 44 : bool bracket = k & 0x8000;
619 44 : k &= 0xfff;
620 44 : AccidentalType at = AccidentalType::NONE;
621 44 : switch (k) {
622 0 : case 0: at = AccidentalType::NONE;
623 0 : break;
624 0 : case 1: at = AccidentalType::SHARP;
625 0 : break;
626 0 : case 2: at = AccidentalType::FLAT;
627 0 : break;
628 0 : case 3: at = AccidentalType::SHARP2;
629 0 : break;
630 0 : case 4: at = AccidentalType::FLAT2;
631 0 : break;
632 2 : case 5: at = AccidentalType::NATURAL;
633 2 : break;
634 :
635 2 : case 6: at = AccidentalType::FLAT_SLASH;
636 2 : break;
637 2 : case 7: at = AccidentalType::FLAT_SLASH2;
638 2 : break;
639 2 : case 8: at = AccidentalType::MIRRORED_FLAT2;
640 2 : break;
641 2 : case 9: at = AccidentalType::MIRRORED_FLAT;
642 2 : break;
643 2 : case 10: at = AccidentalType::NONE; // AccidentalType::MIRRORED_FLAT_SLASH
644 2 : break;
645 2 : case 11: at = AccidentalType::NONE; // AccidentalType::FLAT_FLAT_SLASH
646 2 : break;
647 :
648 2 : case 12: at = AccidentalType::SHARP_SLASH;
649 2 : break;
650 2 : case 13: at = AccidentalType::SHARP_SLASH2;
651 2 : break;
652 2 : case 14: at = AccidentalType::SHARP_SLASH3;
653 2 : break;
654 2 : case 15: at = AccidentalType::SHARP_SLASH4;
655 2 : break;
656 :
657 2 : case 16: at = AccidentalType::SHARP_ARROW_UP;
658 2 : break;
659 2 : case 17: at = AccidentalType::SHARP_ARROW_DOWN;
660 2 : break;
661 2 : case 18: at = AccidentalType::NONE; // AccidentalType::SHARP_ARROW_BOTH
662 2 : break;
663 2 : case 19: at = AccidentalType::FLAT_ARROW_UP;
664 2 : break;
665 2 : case 20: at = AccidentalType::FLAT_ARROW_DOWN;
666 2 : break;
667 2 : case 21: at = AccidentalType::NONE; // AccidentalType::FLAT_ARROW_BOTH
668 2 : break;
669 2 : case 22: at = AccidentalType::NATURAL_ARROW_UP;
670 2 : break;
671 2 : case 23: at = AccidentalType::NATURAL_ARROW_DOWN;
672 2 : break;
673 2 : case 24: at = AccidentalType::NONE; // AccidentalType::NATURAL_ARROW_BOTH
674 2 : break;
675 2 : case 25: at = AccidentalType::SORI;
676 2 : break;
677 2 : case 26: at = AccidentalType::KORON;
678 2 : break;
679 : }
680 44 : note->accidental()->setAccidentalType(at);
681 :
682 44 : note->accidental()->setBracket(AccidentalBracket(bracket));
683 :
684 44 : note->accidental()->setRole(AccidentalRole::USER);
685 44 : hasAccidental = true; // we now have an accidental
686 : }
687 1062 : } else if (tag == "offset") {
688 6 : e.skipCurrentElement(); // ignore manual layout in older scores
689 1012 : } else if (tag == "move") {
690 0 : note->chord()->setStaffMove(e.readInt());
691 1012 : } else if (tag == "head") {
692 59 : int i = e.readInt();
693 59 : NoteHeadGroup val = Read206::convertHeadGroup(i);
694 59 : note->setHeadGroup(val);
695 953 : } else if (tag == "headType") {
696 42 : int i = e.readInt();
697 : NoteHeadType val;
698 42 : switch (i) {
699 14 : case 1:
700 14 : val = NoteHeadType::HEAD_WHOLE;
701 14 : break;
702 14 : case 2:
703 14 : val = NoteHeadType::HEAD_HALF;
704 14 : break;
705 0 : case 3:
706 0 : val = NoteHeadType::HEAD_QUARTER;
707 0 : break;
708 14 : case 4:
709 14 : val = NoteHeadType::HEAD_BREVIS;
710 14 : break;
711 0 : default:
712 0 : val = NoteHeadType::HEAD_AUTO;
713 : }
714 42 : note->setHeadType(val);
715 911 : } else if (Read206::readNoteProperties206(note, e, ctx)) {
716 : } else {
717 0 : e.unknown();
718 : }
719 : }
720 : // ensure sane values:
721 439 : note->setPitch(clampPitch(note->pitch()));
722 :
723 439 : if (note->concertPitch()) {
724 0 : note->setTpc2(Tpc::TPC_INVALID);
725 : } else {
726 439 : note->setPitch(note->pitch() + note->transposition());
727 439 : note->setTpc2(note->tpc1());
728 439 : note->setTpc1(Tpc::TPC_INVALID);
729 : }
730 439 : if (!tpcIsValid(note->tpc1()) && !tpcIsValid(note->tpc2())) {
731 0 : Key key = (note->staff() && note->chord()) ? note->staff()->key(note->chord()->tick()) : Key::C;
732 0 : int tpc = pitch2tpc(note->pitch(), key, Prefer::NEAREST);
733 0 : if (note->concertPitch()) {
734 0 : note->setTpc1(tpc);
735 : } else {
736 0 : note->setTpc2(tpc);
737 : }
738 : }
739 439 : if (!(tpcIsValid(note->tpc1()) && tpcIsValid(note->tpc2()))) {
740 439 : Fraction tick = note->chord() ? note->chord()->tick() : Fraction(-1, 1);
741 439 : Interval v = note->staff() ? note->staff()->transpose(tick) : Interval();
742 439 : if (tpcIsValid(note->tpc1())) {
743 0 : v.flip();
744 0 : if (v.isZero()) {
745 0 : note->setTpc2(note->tpc1());
746 : } else {
747 0 : note->setTpc2(Transpose::transposeTpc(note->tpc1(), v, true));
748 : }
749 : } else {
750 439 : if (v.isZero()) {
751 439 : note->setTpc1(note->tpc2());
752 : } else {
753 0 : note->setTpc1(Transpose::transposeTpc(note->tpc2(), v, true));
754 : }
755 : }
756 : }
757 :
758 : // check consistency of pitch, tpc1, tpc2, and transposition
759 : // see note in InstrumentChange::read() about a known case of tpc corruption produced in 2.0.x
760 : // but since there are other causes of tpc corruption (eg, https://musescore.org/en/node/74746)
761 : // including perhaps some we don't know about yet,
762 : // we will attempt to fix some problems here regardless of version
763 :
764 439 : if (!ctx.pasteMode() && !MScore::testMode) {
765 0 : int tpc1Pitch = (tpc2pitch(note->tpc1()) + 12) % 12;
766 0 : int tpc2Pitch = (tpc2pitch(note->tpc2()) + 12) % 12;
767 0 : int concertPitch = note->pitch() % 12;
768 0 : if (tpc1Pitch != concertPitch) {
769 0 : LOGD("bad tpc1 - concertPitch = %d, tpc1 = %d", concertPitch, tpc1Pitch);
770 0 : note->setPitch(note->pitch() + tpc1Pitch - concertPitch);
771 : }
772 0 : Interval v = note->staff()->transpose(ctx.tick());
773 0 : int transposedPitch = (note->pitch() - v.chromatic) % 12;
774 0 : if (tpc2Pitch != transposedPitch) {
775 0 : LOGD("bad tpc2 - transposedPitch = %d, tpc2 = %d", transposedPitch, tpc2Pitch);
776 : // just in case the staff transposition info is not reliable here,
777 0 : v.flip();
778 0 : note->setTpc2(Transpose::transposeTpc(note->tpc1(), v, true));
779 : }
780 : }
781 439 : }
782 :
783 : //---------------------------------------------------------
784 : // readClefType
785 : //---------------------------------------------------------
786 :
787 72 : static ClefType readClefType(int i)
788 : {
789 72 : ClefType ct = ClefType::G;
790 : // convert obsolete old coding
791 72 : switch (i) {
792 23 : default:
793 23 : case 0: ct = ClefType::G;
794 23 : break;
795 4 : case 1: ct = ClefType::G8_VA;
796 4 : break;
797 2 : case 2: ct = ClefType::G15_MA;
798 2 : break;
799 3 : case 3: ct = ClefType::G8_VB;
800 3 : break;
801 10 : case 4: ct = ClefType::F;
802 10 : break;
803 2 : case 5: ct = ClefType::F8_VB;
804 2 : break;
805 2 : case 6: ct = ClefType::F15_MB;
806 2 : break;
807 2 : case 7: ct = ClefType::F_B;
808 2 : break;
809 2 : case 8: ct = ClefType::F_C;
810 2 : break;
811 2 : case 9: ct = ClefType::C1;
812 2 : break;
813 2 : case 10: ct = ClefType::C2;
814 2 : break;
815 2 : case 11: ct = ClefType::C3;
816 2 : break;
817 2 : case 12: ct = ClefType::C4;
818 2 : break;
819 2 : case 13: ct = ClefType::TAB;
820 2 : break;
821 3 : case 14: ct = ClefType::PERC;
822 3 : break;
823 2 : case 15: ct = ClefType::C5;
824 2 : break;
825 2 : case 16: ct = ClefType::G_1;
826 2 : break;
827 2 : case 17: ct = ClefType::F_8VA;
828 2 : break;
829 2 : case 18: ct = ClefType::F_15MA;
830 2 : break;
831 1 : case 19: ct = ClefType::PERC;
832 1 : break; // PERC2 no longer supported
833 0 : case 20: ct = ClefType::TAB_SERIF;
834 0 : break;
835 : }
836 :
837 72 : return ct;
838 : }
839 :
840 : //---------------------------------------------------------
841 : // readClef
842 : //---------------------------------------------------------
843 :
844 21 : static void readClef(Clef* clef, XmlReader& e, ReadContext& ctx)
845 : {
846 41 : while (e.readNextStartElement()) {
847 20 : const AsciiStringView tag(e.name());
848 20 : if (tag == "subtype") {
849 20 : clef->setClefType(readClefType(e.readInt()));
850 0 : } else if (!read400::TRead::readProperties(clef, e, ctx)) {
851 0 : e.unknown();
852 : }
853 : }
854 21 : if (clef->clefType() == ClefType::INVALID) {
855 1 : clef->setClefType(ClefType::G);
856 : }
857 21 : }
858 :
859 : //---------------------------------------------------------
860 : // readTuplet
861 : //---------------------------------------------------------
862 :
863 16 : static void readTuplet(Tuplet* tuplet, XmlReader& e, ReadContext& ctx)
864 : {
865 16 : int bl = -1;
866 :
867 94 : while (e.readNextStartElement()) {
868 78 : const AsciiStringView tag(e.name());
869 78 : if (tag == "subtype") { // obsolete
870 0 : e.skipCurrentElement();
871 78 : } else if (tag == "hasNumber") { // obsolete even in 1.3
872 0 : tuplet->setNumberType(e.readInt() ? TupletNumberType::SHOW_NUMBER : TupletNumberType::NO_TEXT);
873 78 : } else if (tag == "hasLine") { // obsolete even in 1.3
874 0 : tuplet->setHasBracket(e.readInt());
875 0 : tuplet->setBracketType(TupletBracketType::AUTO_BRACKET);
876 78 : } else if (tag == "baseLen") { // obsolete even in 1.3
877 0 : bl = e.readInt();
878 78 : } else if (tag == "tick") {
879 4 : tuplet->setTick(Fraction::fromTicks(e.readInt()));
880 74 : } else if (!Read206::readTupletProperties206(e, ctx, tuplet)) {
881 0 : e.unknown();
882 : }
883 : }
884 16 : Fraction r = (tuplet->ratio() == Fraction(1, 1)) ? tuplet->ratio() : tuplet->ratio().reduced();
885 : // this may be wrong, but at this stage it is kept for compatibility. It will be corrected afterwards
886 : // during "sanitize" step
887 16 : Fraction f(r.denominator(), tuplet->baseLen().fraction().denominator());
888 16 : tuplet->setTicks(f.reduced());
889 16 : if (bl != -1) { // obsolete, even in 1.3
890 0 : TDuration d;
891 0 : d.setVal(bl);
892 0 : tuplet->setBaseLen(d);
893 0 : d.setVal(bl * tuplet->ratio().denominator());
894 0 : tuplet->setTicks(d.fraction());
895 : }
896 16 : }
897 :
898 : //---------------------------------------------------------
899 : // readTremolo
900 : //---------------------------------------------------------
901 :
902 6 : static void readTremolo(TremoloCompat* t, XmlReader& e, ReadContext& ctx)
903 : {
904 0 : auto createDefaultTremolo = [](TremoloCompat* t) {
905 0 : t->single = Factory::createTremoloSingleChord(t->parent);
906 0 : t->single->setTrack(t->parent.as<Chord>()->track());
907 0 : t->single->setTremoloType(TremoloType::R8);
908 0 : };
909 :
910 0 : auto item = [createDefaultTremolo](TremoloCompat* t) -> EngravingItem* {
911 0 : if (t->two) {
912 0 : return t->two;
913 : }
914 :
915 0 : if (!t->single) {
916 : // If no item been created yet at this point,
917 : // that means no "subtype" tag was present in the XML file.
918 : // In this case, we create a single eighth-note tremolo,
919 : // since that was the default.
920 0 : createDefaultTremolo(t);
921 : }
922 :
923 0 : return t->single;
924 : };
925 :
926 : enum class OldTremoloType : char {
927 : OLD_R8 = 0,
928 : OLD_R16,
929 : OLD_R32,
930 : OLD_C8,
931 : OLD_C16,
932 : OLD_C32
933 : };
934 :
935 12 : while (e.readNextStartElement()) {
936 6 : if (e.name() == "subtype") {
937 6 : OldTremoloType sti = OldTremoloType(e.readText().toInt());
938 6 : TremoloType type = TremoloType::INVALID_TREMOLO;
939 6 : switch (sti) {
940 0 : default:
941 0 : case OldTremoloType::OLD_R8: type = TremoloType::R8;
942 0 : break;
943 0 : case OldTremoloType::OLD_R16: type = TremoloType::R16;
944 0 : break;
945 0 : case OldTremoloType::OLD_R32: type = TremoloType::R32;
946 0 : break;
947 2 : case OldTremoloType::OLD_C8: type = TremoloType::C8;
948 2 : break;
949 2 : case OldTremoloType::OLD_C16: type = TremoloType::C16;
950 2 : break;
951 2 : case OldTremoloType::OLD_C32: type = TremoloType::C32;
952 2 : break;
953 : }
954 :
955 6 : if (isTremoloTwoChord(type)) {
956 6 : t->two = Factory::createTremoloTwoChord(t->parent);
957 6 : t->two->setTrack(t->parent.as<Chord>()->track());
958 6 : t->two->setTremoloType(type);
959 : } else {
960 0 : t->single = Factory::createTremoloSingleChord(t->parent);
961 0 : t->single->setTrack(t->parent.as<Chord>()->track());
962 0 : t->single->setTremoloType(type);
963 : }
964 0 : } else if (!TRead::readItemProperties(item(t), e, ctx)) {
965 0 : e.unknown();
966 : }
967 : }
968 :
969 6 : if (!t->two && !t->single) {
970 : // If no item been created yet at this point,
971 : // that means no "subtype" tag was present in the XML file.
972 : // In this case, we create a single eighth-note tremolo,
973 : // since that was the default.
974 0 : createDefaultTremolo(t);
975 : }
976 6 : }
977 :
978 : //---------------------------------------------------------
979 : // readChord
980 : //---------------------------------------------------------
981 :
982 427 : static void readChord(Measure* m, Chord* chord, XmlReader& e, ReadContext& ctx)
983 : {
984 1602 : while (e.readNextStartElement()) {
985 1175 : const AsciiStringView tag(e.name());
986 1175 : if (tag == "Note") {
987 439 : Note* note = Factory::createNote(chord);
988 : // the note needs to know the properties of the track it belongs to
989 439 : note->setTrack(chord->track());
990 439 : readNote(note, e, ctx);
991 439 : chord->add(note);
992 736 : } else if (tag == "Attribute" || tag == "Articulation") {
993 35 : EngravingItem* el = Read206::readArticulation(chord, e, ctx);
994 35 : if (el->isFermata()) {
995 8 : if (!chord->segment()) {
996 8 : chord->setOwnershipParent(m->getSegment(SegmentType::ChordRest, ctx.tick()));
997 : }
998 8 : chord->segment()->add(el);
999 : } else {
1000 27 : chord->add(el);
1001 : }
1002 701 : } else if (tag == "Tremolo") {
1003 6 : TremoloCompat tcompat { chord };
1004 6 : readTremolo(&tcompat, e, ctx);
1005 6 : if (tcompat.two) {
1006 6 : tcompat.two->setOwnershipParent(chord);
1007 6 : tcompat.two->setDurationType(chord->durationType());
1008 6 : chord->setTremoloTwoChord(tcompat.two, false);
1009 0 : } else if (tcompat.single) {
1010 0 : tcompat.single->setOwnershipParent(chord);
1011 0 : tcompat.single->setDurationType(chord->durationType());
1012 0 : chord->setTremoloSingleChord(tcompat.single);
1013 : } else {
1014 0 : UNREACHABLE;
1015 : }
1016 695 : } else if (Read206::readChordProperties206(e, ctx, chord)) {
1017 : } else {
1018 0 : e.unknown();
1019 : }
1020 : }
1021 427 : }
1022 :
1023 : //---------------------------------------------------------
1024 : // readRest
1025 : //---------------------------------------------------------
1026 :
1027 373 : static void readRest(Measure* m, Rest* rest, XmlReader& e, ReadContext& ctx)
1028 : {
1029 764 : while (e.readNextStartElement()) {
1030 391 : const AsciiStringView tag(e.name());
1031 391 : if (tag == "Attribute" || tag == "Articulation") {
1032 2 : EngravingItem* el = Read206::readArticulation(rest, e, ctx);
1033 2 : if (el->isFermata()) {
1034 2 : if (!rest->segment()) {
1035 2 : rest->setOwnershipParent(m->getSegment(SegmentType::ChordRest, ctx.tick()));
1036 : }
1037 2 : rest->segment()->add(el);
1038 : } else {
1039 0 : rest->add(el);
1040 : }
1041 389 : } else if (Read206::readChordRestProperties206(e, ctx, rest)) {
1042 : } else {
1043 0 : e.unknown();
1044 : }
1045 : }
1046 373 : }
1047 :
1048 : //---------------------------------------------------------
1049 : // readTempoText
1050 : //---------------------------------------------------------
1051 :
1052 0 : void readTempoText(TempoText* t, XmlReader& e, ReadContext& ctx)
1053 : {
1054 0 : while (e.readNextStartElement()) {
1055 0 : const AsciiStringView tag(e.name());
1056 0 : if (tag == "tempo") {
1057 0 : t->setTempo(e.readDouble());
1058 0 : } else if (!readTextProperties(e, ctx, t, t)) {
1059 0 : e.unknown();
1060 : }
1061 : }
1062 :
1063 0 : t->resetProperty(Pid::MUSIC_SYMBOL_SIZE);
1064 0 : }
1065 :
1066 : //---------------------------------------------------------
1067 : // readStaffText
1068 : //---------------------------------------------------------
1069 :
1070 28 : void readStaffText(StaffText* t, XmlReader& e, ReadContext& ctx)
1071 : {
1072 116 : while (e.readNextStartElement()) {
1073 60 : if (!readTextProperties(e, ctx, t, t)) {
1074 0 : e.unknown();
1075 : }
1076 : }
1077 28 : }
1078 :
1079 : //---------------------------------------------------------
1080 : // readLineSegment114
1081 : //---------------------------------------------------------
1082 :
1083 3 : static void readLineSegment114(XmlReader& e, ReadContext& ctx, LineSegment* ls)
1084 : {
1085 17 : while (e.readNextStartElement()) {
1086 14 : const AsciiStringView tag(e.name());
1087 14 : if (tag == "off1") {
1088 3 : ls->setOffset(e.readPoint() * ls->spatium());
1089 : } else {
1090 11 : read400::TRead::readProperties(ls, e, ctx);
1091 : }
1092 : }
1093 3 : }
1094 :
1095 : //---------------------------------------------------------
1096 : // readTextLineProperties114
1097 : //---------------------------------------------------------
1098 :
1099 21 : static bool readTextLineProperties114(XmlReader& e, ReadContext& ctx, TextLineBase* tl)
1100 : {
1101 21 : const AsciiStringView tag(e.name());
1102 :
1103 21 : if (tag == "beginText") {
1104 2 : Text* text = Factory::createText(tl, TextStyleType::DEFAULT, false);
1105 2 : readText114(e, ctx, text, tl);
1106 2 : tl->setBeginText(text->xmlText());
1107 2 : tl->setPropertyFlags(Pid::BEGIN_TEXT, PropertyFlags::UNSTYLED);
1108 2 : delete text;
1109 19 : } else if (tag == "continueText") {
1110 2 : Text* text = Factory::createText(tl, TextStyleType::DEFAULT, false);
1111 2 : readText114(e, ctx, text, tl);
1112 2 : tl->setContinueText(text->xmlText());
1113 2 : tl->setPropertyFlags(Pid::CONTINUE_TEXT, PropertyFlags::UNSTYLED);
1114 2 : delete text;
1115 17 : } else if (tag == "endText") {
1116 0 : Text* text = Factory::createText(tl, TextStyleType::DEFAULT, false);
1117 0 : readText114(e, ctx, text, tl);
1118 0 : tl->setEndText(text->xmlText());
1119 0 : tl->setPropertyFlags(Pid::END_TEXT, PropertyFlags::UNSTYLED);
1120 0 : delete text;
1121 17 : } else if (tag == "beginHook") {
1122 0 : tl->setBeginHookType(e.readBool() ? HookType::HOOK_90 : HookType::NONE);
1123 17 : } else if (tag == "endHook") {
1124 0 : tl->setEndHookType(e.readBool() ? HookType::HOOK_90 : HookType::NONE);
1125 17 : } else if (tag == "beginHookType") {
1126 0 : tl->setBeginHookType(e.readInt() == 0 ? HookType::HOOK_90 : HookType::HOOK_45);
1127 17 : } else if (tag == "endHookType") {
1128 0 : tl->setEndHookType(e.readInt() == 0 ? HookType::HOOK_90 : HookType::HOOK_45);
1129 17 : } else if (tag == "Segment") {
1130 3 : LineSegment* ls = tl->createLineSegment();
1131 3 : ls->setTrack(tl->track()); // needed in read to get the right staff mag
1132 3 : tl->add(ls);
1133 3 : readLineSegment114(e, ctx, ls);
1134 : // in v1.x "visible" is a property of the segment only;
1135 : // we must ensure that it propagates also to the parent element.
1136 : // That's why the visibility is set after adding the segment
1137 : // to the corresponding spanner
1138 3 : ls->setVisible(ls->visible());
1139 3 : ls->setOffset(PointF()); // ignore offsets
1140 3 : ls->setAutoplace(true);
1141 14 : } else if (!read400::TRead::readProperties(tl, e, ctx)) {
1142 0 : return false;
1143 : }
1144 21 : return true;
1145 : }
1146 :
1147 : //---------------------------------------------------------
1148 : // readVolta114
1149 : //---------------------------------------------------------
1150 :
1151 0 : static void readVolta114(XmlReader& e, ReadContext& ctx, Volta* volta)
1152 : {
1153 0 : while (e.readNextStartElement()) {
1154 0 : const AsciiStringView tag(e.name());
1155 0 : if (tag == "endings") {
1156 0 : String s = e.readText();
1157 0 : StringList sl = s.split(u',', muse::SkipEmptyParts);
1158 0 : volta->endings().clear();
1159 0 : for (const String& l : sl) {
1160 0 : int i = l.simplified().toInt();
1161 0 : volta->endings().push_back(i);
1162 : }
1163 0 : } else if (tag == "subtype") {
1164 0 : volta->setVoltaType(e.readInt() == 1 ? Volta::Type::CLOSED : Volta::Type::OPEN);
1165 0 : } else if (tag == "lineWidth") {
1166 0 : volta->setLineWidth(Spatium(e.readDouble()));
1167 0 : volta->setPropertyFlags(Pid::LINE_WIDTH, PropertyFlags::UNSTYLED);
1168 0 : } else if (!readTextLineProperties114(e, ctx, volta)) {
1169 0 : e.unknown();
1170 : }
1171 : }
1172 0 : volta->setOffset(PointF()); // ignore offsets
1173 0 : volta->setAutoplace(true);
1174 0 : CompatUtils::resetHookHeightSign(volta);
1175 0 : }
1176 :
1177 : //---------------------------------------------------------
1178 : // readOttava114
1179 : //---------------------------------------------------------
1180 :
1181 1 : static void readOttava114(XmlReader& e, ReadContext& ctx, Ottava* ottava)
1182 : {
1183 12 : while (e.readNextStartElement()) {
1184 11 : const AsciiStringView tag(e.name());
1185 11 : if (tag == "subtype") {
1186 0 : String s = e.readText();
1187 : bool ok;
1188 0 : int idx = s.toInt(&ok);
1189 0 : if (!ok) {
1190 0 : idx = int(OttavaType::OTTAVA_8VA);
1191 0 : for (unsigned i = 0; i < sizeof(ottavaDefault) / sizeof(*ottavaDefault); ++i) {
1192 0 : if (s == ottavaDefault[i].name) {
1193 0 : idx = i;
1194 0 : break;
1195 : }
1196 : }
1197 : }
1198 : //subtype are now in a different order...
1199 0 : if (idx == 1) {
1200 0 : idx = 2;
1201 0 : } else if (idx == 2) {
1202 0 : idx = 1;
1203 : }
1204 0 : ottava->setOttavaType(OttavaType(idx));
1205 11 : } else if (tag == "numbersOnly") {
1206 0 : ottava->setNumbersOnly(e.readInt());
1207 11 : } else if (tag == "lineWidth") {
1208 1 : read400::TRead::readProperty(ottava, e, ctx, Pid::LINE_WIDTH);
1209 10 : } else if (tag == "lineStyle") {
1210 1 : read400::TRead::readProperty(ottava, e, ctx, Pid::LINE_STYLE);
1211 9 : } else if (tag == "beginSymbol") { // obsolete
1212 9 : } else if (tag == "continueSymbol") { // obsolete
1213 9 : } else if (!readTextLineProperties114(e, ctx, ottava)) {
1214 0 : e.unknown();
1215 : }
1216 : }
1217 :
1218 1 : CompatUtils::resetHookHeightSign(ottava);
1219 1 : }
1220 :
1221 : //---------------------------------------------------------
1222 : // resolveSymCompatibility
1223 : //---------------------------------------------------------
1224 :
1225 1 : static String resolveSymCompatibility(SymId i, String programVersion)
1226 : {
1227 1 : if (!programVersion.isEmpty() && programVersion < u"1.1") {
1228 0 : i = SymId(int(i) + 5);
1229 : }
1230 1 : switch (int(i)) {
1231 1 : case 197:
1232 1 : return u"keyboardPedalPed";
1233 0 : case 191:
1234 0 : return u"keyboardPedalUp";
1235 0 : case 193:
1236 0 : return u"noSym"; //SymId(pedaldotSym);
1237 0 : case 192:
1238 0 : return u"noSym"; //SymId(pedaldashSym);
1239 0 : case 139:
1240 0 : return u"ornamentTrill";
1241 0 : default:
1242 0 : return u"noSym";
1243 : }
1244 : }
1245 :
1246 : //---------------------------------------------------------
1247 : // readTextLine114
1248 : //---------------------------------------------------------
1249 :
1250 1 : static void readTextLine114(XmlReader& e, ReadContext& ctx, TextLine* textLine)
1251 : {
1252 10 : while (e.readNextStartElement()) {
1253 9 : const AsciiStringView tag(e.name());
1254 :
1255 9 : if (tag == "lineVisible") {
1256 0 : textLine->setLineVisible(e.readBool());
1257 9 : } else if (tag == "beginHookHeight") {
1258 0 : textLine->setBeginHookHeight(Spatium(e.readDouble()));
1259 9 : } else if (tag == "endHookHeight" || tag == "hookHeight") { // hookHeight is obsolete
1260 1 : textLine->setEndHookHeight(Spatium(e.readDouble()));
1261 1 : textLine->setPropertyFlags(Pid::END_HOOK_HEIGHT, PropertyFlags::UNSTYLED);
1262 8 : } else if (tag == "hookUp") { // obsolete
1263 0 : textLine->setEndHookHeight(-1.0_sp);
1264 8 : } else if (tag == "beginSymbol" || tag == "symbol") { // "symbol" is obsolete
1265 0 : String text(e.readText());
1266 0 : textLine->setBeginText(String(u"<sym>%1</sym>").arg(
1267 0 : text.at(0).isDigit()
1268 0 : ? resolveSymCompatibility(SymId(text.toInt()), ctx.mscoreVersion())
1269 : : text));
1270 8 : } else if (tag == "continueSymbol") {
1271 0 : String text(e.readText());
1272 0 : textLine->setContinueText(String(u"<sym>%1</sym>").arg(
1273 0 : text.at(0).isDigit()
1274 0 : ? resolveSymCompatibility(SymId(text.toInt()), ctx.mscoreVersion())
1275 : : text));
1276 8 : } else if (tag == "endSymbol") {
1277 0 : String text(e.readText());
1278 0 : textLine->setEndText(String(u"<sym>%1</sym>").arg(
1279 0 : text.at(0).isDigit()
1280 0 : ? resolveSymCompatibility(SymId(text.toInt()), ctx.mscoreVersion())
1281 : : text));
1282 8 : } else if (tag == "beginSymbolOffset") { // obsolete
1283 0 : e.readPoint();
1284 8 : } else if (tag == "continueSymbolOffset") { // obsolete
1285 0 : e.readPoint();
1286 8 : } else if (tag == "endSymbolOffset") { // obsolete
1287 0 : e.readPoint();
1288 8 : } else if (tag == "beginTextPlace") {
1289 1 : textLine->setBeginTextPlace(TConv::fromXml(e.readAsciiText(), TextPlace::AUTO));
1290 7 : } else if (tag == "continueTextPlace") {
1291 1 : textLine->setContinueTextPlace(TConv::fromXml(e.readAsciiText(), TextPlace::AUTO));
1292 6 : } else if (tag == "endTextPlace") {
1293 0 : textLine->setEndTextPlace(TConv::fromXml(e.readAsciiText(), TextPlace::AUTO));
1294 6 : } else if (!readTextLineProperties114(e, ctx, textLine)) {
1295 0 : e.unknown();
1296 : }
1297 : }
1298 :
1299 1 : CompatUtils::resetHookHeightSign(textLine);
1300 1 : }
1301 :
1302 : //---------------------------------------------------------
1303 : // readPedal114
1304 : //---------------------------------------------------------
1305 :
1306 1 : static void readPedal114(XmlReader& e, ReadContext& ctx, Pedal* pedal)
1307 : {
1308 1 : bool beginTextTag = false;
1309 1 : bool continueTextTag = false;
1310 1 : bool endTextTag = false;
1311 :
1312 12 : while (e.readNextStartElement()) {
1313 11 : const AsciiStringView tag(e.name());
1314 11 : if (tag == "subtype") {
1315 0 : e.skipCurrentElement();
1316 11 : } else if (tag == "endHookHeight" || tag == "hookHeight") { // hookHeight is obsolete
1317 1 : pedal->setEndHookHeight(Spatium(e.readDouble()));
1318 1 : pedal->setPropertyFlags(Pid::END_HOOK_HEIGHT, PropertyFlags::UNSTYLED);
1319 10 : } else if (tag == "lineWidth") {
1320 1 : pedal->setLineWidth(Spatium(e.readDouble()));
1321 1 : pedal->setPropertyFlags(Pid::LINE_WIDTH, PropertyFlags::UNSTYLED);
1322 9 : } else if (tag == "lineStyle") {
1323 1 : read400::TRead::readProperty(pedal, e, ctx, Pid::LINE_STYLE);
1324 1 : pedal->setPropertyFlags(Pid::LINE_STYLE, PropertyFlags::UNSTYLED);
1325 8 : } else if (tag == "beginSymbol" || tag == "symbol") { // "symbol" is obsolete
1326 1 : beginTextTag = true;
1327 1 : String text(e.readText());
1328 1 : String symbol = String(u"<sym>%1</sym>").arg(
1329 1 : text.at(0).isDigit()
1330 2 : ? resolveSymCompatibility(SymId(text.toInt()), ctx.mscoreVersion())
1331 1 : : text);
1332 1 : if (symbol != pedal->propertyDefault(Pid::BEGIN_TEXT).value<String>()) {
1333 0 : pedal->setBeginText(symbol);
1334 0 : pedal->setPropertyFlags(Pid::BEGIN_TEXT, PropertyFlags::UNSTYLED);
1335 : }
1336 8 : } else if (tag == "continueSymbol") {
1337 0 : continueTextTag = true;
1338 0 : String text(e.readText());
1339 0 : String symbol = String(u"<sym>%1</sym>").arg(
1340 0 : text.at(0).isDigit()
1341 0 : ? resolveSymCompatibility(SymId(text.toInt()), ctx.mscoreVersion())
1342 0 : : text);
1343 0 : if (symbol != pedal->propertyDefault(Pid::CONTINUE_TEXT).value<String>()) {
1344 0 : pedal->setContinueText(symbol);
1345 0 : pedal->setPropertyFlags(Pid::CONTINUE_TEXT, PropertyFlags::UNSTYLED);
1346 : }
1347 7 : } else if (tag == "endSymbol") {
1348 0 : endTextTag = true;
1349 0 : String text(e.readText());
1350 0 : String symbol = String(u"<sym>%1</sym>").arg(
1351 0 : text.at(0).isDigit()
1352 0 : ? resolveSymCompatibility(SymId(text.toInt()), ctx.mscoreVersion())
1353 0 : : text);
1354 0 : if (symbol != pedal->propertyDefault(Pid::END_TEXT).value<String>()) {
1355 0 : pedal->setEndText(symbol);
1356 0 : pedal->setPropertyFlags(Pid::END_TEXT, PropertyFlags::UNSTYLED);
1357 : }
1358 7 : } else if (tag == "beginSymbolOffset") { // obsolete
1359 1 : e.readPoint();
1360 6 : } else if (tag == "continueSymbolOffset") { // obsolete
1361 0 : e.readPoint();
1362 6 : } else if (tag == "endSymbolOffset") { // obsolete
1363 0 : e.readPoint();
1364 6 : } else if (readTextLineProperties114(e, ctx, pedal)) {
1365 6 : beginTextTag = beginTextTag || tag == "beginText";
1366 6 : continueTextTag = continueTextTag || tag == "continueText";
1367 6 : endTextTag = endTextTag || tag == "endText";
1368 : } else {
1369 0 : e.unknown();
1370 : }
1371 : }
1372 :
1373 : // Set to the 114 defaults if no value was specified;
1374 : // or follow the new style setting if the specified value matches it
1375 1 : if (!beginTextTag) {
1376 0 : pedal->setBeginText(String());
1377 0 : pedal->setPropertyFlags(Pid::BEGIN_TEXT, PropertyFlags::UNSTYLED);
1378 1 : } else if (pedal->beginText() == pedal->propertyDefault(Pid::BEGIN_TEXT).value<String>()) {
1379 1 : pedal->setPropertyFlags(Pid::BEGIN_TEXT, PropertyFlags::STYLED);
1380 : }
1381 1 : if (!continueTextTag) {
1382 0 : pedal->setContinueText(String());
1383 0 : pedal->setPropertyFlags(Pid::CONTINUE_TEXT, PropertyFlags::UNSTYLED);
1384 1 : } else if (pedal->continueText() == pedal->propertyDefault(Pid::CONTINUE_TEXT).value<String>()) {
1385 0 : pedal->setPropertyFlags(Pid::CONTINUE_TEXT, PropertyFlags::STYLED);
1386 : }
1387 1 : if (!endTextTag) {
1388 1 : pedal->setEndText(String());
1389 1 : pedal->setPropertyFlags(Pid::END_TEXT, PropertyFlags::UNSTYLED);
1390 0 : } else if (pedal->endText() == pedal->propertyDefault(Pid::END_TEXT).value<String>()) {
1391 0 : pedal->setPropertyFlags(Pid::END_TEXT, PropertyFlags::STYLED);
1392 : }
1393 :
1394 1 : CompatUtils::resetHookHeightSign(pedal);
1395 1 : }
1396 :
1397 : //---------------------------------------------------------
1398 : // readHarmony114
1399 : //---------------------------------------------------------
1400 :
1401 0 : static void readHarmony114(XmlReader& e, ReadContext& ctx, Harmony* h)
1402 : {
1403 : // convert table to tpc values
1404 : static const int table[] = {
1405 : 14, 9, 16, 11, 18, 13, 8, 15, 10, 17, 12, 19
1406 : };
1407 :
1408 0 : HarmonyInfo* info = new HarmonyInfo(ctx.score());
1409 :
1410 0 : while (e.readNextStartElement()) {
1411 0 : const AsciiStringView tag(e.name());
1412 0 : if (tag == "base") {
1413 0 : if (ctx.mscVersion() >= 106) {
1414 0 : info->setBassTpc(e.readInt());
1415 : } else {
1416 0 : info->setBassTpc(table[e.readInt() - 1]);
1417 : }
1418 0 : } else if (tag == "baseCase") {
1419 0 : h->setBassCase(static_cast<NoteCaseType>(e.readInt()));
1420 0 : } else if (tag == "extension") {
1421 0 : info->setId(e.readInt());
1422 0 : } else if (tag == "name") {
1423 0 : info->setTextName(e.readText());
1424 0 : } else if (tag == "root") {
1425 0 : if (ctx.mscVersion() >= 106) {
1426 0 : info->setRootTpc(e.readInt());
1427 : } else {
1428 0 : info->setRootTpc(table[e.readInt() - 1]);
1429 : }
1430 0 : } else if (tag == "rootCase") {
1431 0 : h->setRootCase(static_cast<NoteCaseType>(e.readInt()));
1432 0 : } else if (tag == "degree") {
1433 0 : int degreeValue = 0;
1434 0 : int degreeAlter = 0;
1435 0 : String degreeType;
1436 0 : while (e.readNextStartElement()) {
1437 0 : const AsciiStringView t(e.name());
1438 0 : if (t == "degree-value") {
1439 0 : degreeValue = e.readInt();
1440 0 : } else if (t == "degree-alter") {
1441 0 : degreeAlter = e.readInt();
1442 0 : } else if (t == "degree-type") {
1443 0 : degreeType = e.readText();
1444 : } else {
1445 0 : e.unknown();
1446 : }
1447 : }
1448 0 : if (degreeValue <= 0 || degreeValue > 13
1449 0 : || degreeAlter < -2 || degreeAlter > 2
1450 0 : || (degreeType != "add" && degreeType != "alter" && degreeType != "subtract")) {
1451 0 : LOGD("incorrect degree: degreeValue=%d degreeAlter=%d degreeType=%s",
1452 0 : degreeValue, degreeAlter, muPrintable(degreeType));
1453 : } else {
1454 0 : if (degreeType == "add") {
1455 0 : h->addDegree(HDegree(degreeValue, degreeAlter, HDegreeType::ADD));
1456 0 : } else if (degreeType == "alter") {
1457 0 : h->addDegree(HDegree(degreeValue, degreeAlter, HDegreeType::ALTER));
1458 0 : } else if (degreeType == "subtract") {
1459 0 : h->addDegree(HDegree(degreeValue, degreeAlter, HDegreeType::SUBTRACT));
1460 : }
1461 : }
1462 0 : } else if (tag == "leftParen") {
1463 0 : h->setParenthesesMode(h->rightParen() ? ParenthesesMode::BOTH : ParenthesesMode::LEFT, true, false);
1464 0 : e.readNext();
1465 0 : } else if (tag == "rightParen") {
1466 0 : h->setParenthesesMode(h->leftParen() ? ParenthesesMode::BOTH : ParenthesesMode::RIGHT, true, false);
1467 0 : e.readNext();
1468 0 : } else if (!readTextProperties(e, ctx, h, h)) {
1469 0 : e.unknown();
1470 : }
1471 : }
1472 :
1473 0 : h->addChord(info);
1474 :
1475 0 : h->afterRead();
1476 0 : }
1477 :
1478 : //---------------------------------------------------------
1479 : // readMeasure
1480 : //---------------------------------------------------------
1481 :
1482 446 : static void readMeasure(Measure* m, int staffIdx, XmlReader& e, ReadContext& ctx)
1483 : {
1484 446 : Segment* segment = 0;
1485 :
1486 446 : std::vector<Chord*> graceNotes;
1487 :
1488 : //sort tuplet elements. needed for nested tuplets #22537
1489 457 : for (auto& p : ctx.tuplets()) {
1490 11 : Tuplet* t = p.second;
1491 11 : t->sortElements();
1492 : }
1493 446 : ctx.tuplets().clear();
1494 446 : ctx.setTrack(staffIdx * VOICES);
1495 :
1496 446 : m->createStaves(staffIdx);
1497 :
1498 : // tick is obsolete
1499 446 : if (e.hasAttribute("tick")) {
1500 0 : ctx.setTick(Fraction::fromTicks(ctx.fileDivision(e.intAttribute("tick"))));
1501 : }
1502 :
1503 446 : if (e.hasAttribute("len")) {
1504 0 : StringList sl = e.attribute("len").split('/');
1505 0 : if (sl.size() == 2) {
1506 0 : m->setTicks(Fraction(sl[0].toInt(), sl[1].toInt()));
1507 : } else {
1508 0 : LOGD("illegal measure size <%s>", muPrintable(e.attribute("len")));
1509 : }
1510 0 : ctx.compatTimeSigMap()->add(m->tick().ticks(), SigEvent(m->ticks(), m->timesig()));
1511 0 : ctx.compatTimeSigMap()->add(m->endTick().ticks(), SigEvent(m->timesig()));
1512 0 : }
1513 :
1514 446 : Staff* staff = ctx.staff(staffIdx);
1515 446 : Fraction timeStretch(staff->timeStretch(m->tick()));
1516 :
1517 : // keep track of tick of previous element
1518 : // this allows markings that need to apply to previous element to do so
1519 : // even though we may have already advanced to next tick position
1520 446 : Fraction lastTick = ctx.tick();
1521 :
1522 1430 : while (e.readNextStartElement()) {
1523 984 : const AsciiStringView tag(e.name());
1524 :
1525 984 : if (tag == "move") {
1526 0 : ctx.setTick(e.readFraction() + m->tick());
1527 984 : } else if (tag == "tick") {
1528 0 : ctx.setTick(Fraction::fromTicks(ctx.fileDivision(e.readInt())));
1529 0 : lastTick = ctx.tick();
1530 984 : } else if (tag == "BarLine") {
1531 30 : BarLine* barLine = Factory::createBarLine(ctx.dummy());
1532 30 : barLine->setTrack(ctx.track());
1533 : // initialize span properties with values from staff
1534 30 : barLine->resetProperty(Pid::BARLINE_SPAN_FROM);
1535 30 : barLine->resetProperty(Pid::BARLINE_SPAN_TO);
1536 :
1537 61 : while (e.readNextStartElement()) {
1538 31 : const AsciiStringView tg(e.name());
1539 31 : if (tg == "subtype") {
1540 25 : BarLineType t = BarLineType::NORMAL;
1541 25 : switch (e.readInt()) {
1542 0 : default:
1543 : case 0:
1544 0 : t = BarLineType::NORMAL;
1545 0 : break;
1546 0 : case 1:
1547 0 : t = BarLineType::DOUBLE;
1548 0 : break;
1549 0 : case 2:
1550 0 : t = BarLineType::START_REPEAT;
1551 0 : break;
1552 0 : case 3:
1553 0 : t = BarLineType::END_REPEAT;
1554 0 : break;
1555 0 : case 4:
1556 0 : t = BarLineType::BROKEN;
1557 0 : break;
1558 25 : case 5:
1559 25 : t = BarLineType::END;
1560 25 : break;
1561 0 : case 6:
1562 0 : t = BarLineType::END_START_REPEAT;
1563 0 : break;
1564 : }
1565 25 : barLine->setBarLineType(t);
1566 6 : } else if (!TRead::readItemProperties(barLine, e, ctx)) {
1567 0 : e.unknown();
1568 : }
1569 : }
1570 :
1571 : //
1572 : // StartRepeatBarLine: always at the beginning tick of a measure, always BarLineType::START_REPEAT
1573 : // BarLine: in the middle of a measure, has no semantic
1574 : // EndBarLine: at the end tick of a measure
1575 : // BeginBarLine: first segment of a measure
1576 :
1577 : SegmentType st;
1578 30 : if ((ctx.tick() != m->tick()) && (ctx.tick() != m->endTick())) {
1579 0 : st = SegmentType::BarLine;
1580 30 : } else if (barLine->barLineType() == BarLineType::START_REPEAT && ctx.tick() == m->tick()) {
1581 0 : st = SegmentType::StartRepeatBarLine;
1582 30 : } else if (ctx.tick() == m->tick() && segment == 0) {
1583 0 : st = SegmentType::BeginBarLine;
1584 : } else {
1585 30 : st = SegmentType::EndBarLine;
1586 : }
1587 30 : segment = m->getSegment(st, ctx.tick());
1588 30 : segment->add(barLine);
1589 954 : } else if (tag == "Chord") {
1590 : // where the chord belongs is only known once it has been read: a grace note
1591 : // goes on the chord it decorates rather than on a segment of its own, and
1592 : // reading moves the tick the segment is looked up by
1593 427 : Chord* chord = Factory::createChord(ctx.score()->dummy());
1594 427 : chord->setTrack(ctx.track());
1595 427 : readChord(m, chord, e, ctx);
1596 427 : if (!chord->segment()) {
1597 419 : chord->setOwnershipParent(m->getSegment(SegmentType::ChordRest, ctx.tick()));
1598 : }
1599 427 : segment = chord->segment();
1600 427 : if (chord->noteType() != NoteType::NORMAL) {
1601 0 : graceNotes.push_back(chord);
1602 : } else {
1603 427 : segment->add(chord);
1604 427 : assert(segment->segmentType() == SegmentType::ChordRest);
1605 :
1606 427 : for (size_t i = 0; i < graceNotes.size(); ++i) {
1607 0 : Chord* gc = graceNotes[i];
1608 0 : gc->setGraceIndex(static_cast<int>(i));
1609 0 : chord->add(gc);
1610 : }
1611 427 : graceNotes.clear();
1612 427 : Fraction crticks = chord->actualTicks();
1613 :
1614 427 : if (chord->tremoloSingleChord()) {
1615 0 : chord->tremoloSingleChord()->setOwnershipParent(chord);
1616 427 : } else if (chord->tremoloTwoChord()) {
1617 6 : TremoloTwoChord* tremolo = chord->tremoloTwoChord();
1618 6 : track_idx_t track = chord->track();
1619 6 : Segment* ss = 0;
1620 12 : for (Segment* ps = m->first(SegmentType::ChordRest); ps; ps = ps->next(SegmentType::ChordRest)) {
1621 12 : if (ps->tick() >= ctx.tick()) {
1622 6 : break;
1623 : }
1624 6 : if (ps->element(track)) {
1625 6 : ss = ps;
1626 : }
1627 : }
1628 6 : Chord* pch = 0; // previous chord
1629 6 : if (ss) {
1630 6 : ChordRest* cr = toChordRest(ss->element(track));
1631 6 : if (cr && cr->isChord()) {
1632 6 : pch = toChord(cr);
1633 : }
1634 : }
1635 6 : if (pch) {
1636 6 : tremolo->setOwnershipParent(pch);
1637 6 : pch->setTremoloTwoChord(tremolo);
1638 6 : chord->setTremoloTwoChord(nullptr);
1639 : // force duration to half
1640 6 : Fraction pts(timeStretch * pch->globalTicks());
1641 6 : pch->setTicks(pts * Fraction(1, 2));
1642 6 : chord->setTicks(crticks * Fraction(1, 2));
1643 : } else {
1644 0 : LOGD("tremolo: first note not found");
1645 : }
1646 6 : crticks = crticks * Fraction(1, 2);
1647 : }
1648 427 : lastTick = ctx.tick();
1649 427 : ctx.incTick(crticks);
1650 : }
1651 527 : } else if (tag == "Rest") {
1652 373 : if (m->isMMRest()) {
1653 0 : segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
1654 0 : MMRest* mmr = Factory::createMMRest(segment);
1655 0 : mmr->setTrack(ctx.track());
1656 0 : read400::TRead::read(mmr, e, ctx);
1657 0 : mmr->setTicks(m->ticks());
1658 0 : segment->add(mmr);
1659 0 : lastTick = ctx.tick();
1660 0 : ctx.incTick(mmr->actualTicks());
1661 : } else {
1662 373 : Rest* rest = Factory::createRest(ctx.score()->dummy());
1663 373 : rest->setDurationType(DurationType::V_MEASURE);
1664 373 : rest->setTicks(m->timesig() / timeStretch);
1665 373 : rest->setTrack(ctx.track());
1666 373 : readRest(m, rest, e, ctx);
1667 :
1668 373 : Segment* segment2 = m->getSegment(SegmentType::ChordRest, ctx.tick());
1669 373 : rest->setOwnershipParent(segment2);
1670 373 : segment2->add(rest);
1671 :
1672 373 : if (!rest->ticks().isValid()) { // hack
1673 0 : rest->setTicks(m->timesig() / timeStretch);
1674 : }
1675 :
1676 373 : lastTick = ctx.tick();
1677 373 : ctx.incTick(rest->actualTicks());
1678 : }
1679 154 : } else if (tag == "Breath") {
1680 0 : segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
1681 0 : Breath* breath = Factory::createBreath(segment);
1682 0 : breath->setTrack(ctx.track());
1683 0 : Fraction tick = ctx.tick();
1684 0 : breath->setPlacement(breath->track() & 1 ? PlacementV::BELOW : PlacementV::ABOVE);
1685 0 : read400::TRead::read(breath, e, ctx);
1686 : // older scores placed the breath segment right after the chord to which it applies
1687 : // rather than before the next chordrest segment with an element for the staff
1688 : // result would be layout too far left if there are other segments due to notes in other staves
1689 : // we need to find tick of chord to which this applies, and add its duration
1690 0 : Fraction prevTick;
1691 0 : if (ctx.tick() < tick) {
1692 0 : prevTick = ctx.tick(); // use our own tick if we explicitly reset to earlier position
1693 : } else {
1694 0 : prevTick = lastTick; // otherwise use tick of previous tick/chord/rest tag
1695 : }
1696 : // find segment
1697 0 : Segment* prev = m->findSegment(SegmentType::ChordRest, prevTick);
1698 0 : if (prev) {
1699 : // find chordrest
1700 0 : ChordRest* lastCR = toChordRest(prev->element(ctx.track()));
1701 0 : if (lastCR) {
1702 0 : tick = prevTick + lastCR->actualTicks();
1703 : }
1704 : }
1705 0 : segment = m->getSegment(SegmentType::Breath, tick);
1706 0 : segment->add(breath);
1707 154 : } else if (tag == "endSpanner") {
1708 0 : int id = e.attribute("id").toInt();
1709 0 : Spanner* spanner = ctx.findSpanner(id);
1710 0 : if (spanner) {
1711 0 : spanner->setTicks(ctx.tick() - spanner->tick());
1712 : // if (spanner->track2() == -1)
1713 : // the absence of a track tag [?] means the
1714 : // track is the same as the beginning of the slur
1715 0 : if (spanner->track2() == muse::nidx) {
1716 0 : spanner->setTrack2(spanner->track() ? spanner->track() : ctx.track());
1717 : }
1718 : } else {
1719 : // remember "endSpanner" values
1720 0 : SpannerValues sv;
1721 0 : sv.spannerId = id;
1722 0 : sv.track2 = ctx.track();
1723 0 : sv.tick2 = ctx.tick();
1724 0 : ctx.addSpannerValues(sv);
1725 : }
1726 0 : e.readNext();
1727 154 : } else if (tag == "Slur") {
1728 0 : Slur* sl = Factory::createSlur(ctx.score()->dummy());
1729 0 : sl->setTick(ctx.tick());
1730 0 : Read206::readSlur206(e, ctx, sl);
1731 : //
1732 : // check if we already saw "endSpanner"
1733 : //
1734 0 : int id = ctx.spannerId(sl);
1735 0 : const SpannerValues* sv = ctx.spannerValues(id);
1736 0 : if (sv) {
1737 0 : sl->setTick2(sv->tick2);
1738 0 : sl->setTrack2(sv->track2);
1739 : }
1740 0 : ctx.addSpanner(sl);
1741 154 : } else if (tag == "HairPin"
1742 154 : || tag == "Pedal"
1743 154 : || tag == "Ottava"
1744 154 : || tag == "Trill"
1745 154 : || tag == "TextLine"
1746 308 : || tag == "Volta") {
1747 0 : Spanner* sp = toSpanner(Factory::createItemByName(tag, m));
1748 0 : sp->setTrack(ctx.track());
1749 0 : sp->setTick(ctx.tick());
1750 : // ?? sp->setAnchor(Spanner::Anchor::SEGMENT);
1751 0 : if (tag == "Volta") {
1752 0 : readVolta114(e, ctx, toVolta(sp));
1753 : } else {
1754 0 : Read206::readTextLine206(e, ctx, toTextLineBase(sp));
1755 : }
1756 0 : ctx.addSpanner(sp);
1757 : //
1758 : // check if we already saw "endSpanner"
1759 : //
1760 0 : int id = ctx.spannerId(sp);
1761 0 : const SpannerValues* sv = ctx.spannerValues(id);
1762 0 : if (sv) {
1763 0 : sp->setTicks(sv->tick2 - sp->tick());
1764 0 : sp->setTrack2(sv->track2);
1765 : }
1766 154 : } else if (tag == "RepeatMeasure") {
1767 0 : segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
1768 0 : MeasureRepeat* rm = Factory::createMeasureRepeat(segment);
1769 0 : rm->setTrack(ctx.track());
1770 0 : readRest(m, rm, e, ctx);
1771 0 : rm->setNumMeasures(1);
1772 0 : m->setMeasureRepeatCount(1, staffIdx);
1773 0 : segment->add(rm);
1774 0 : if (rm->actualTicks().isZero()) { // might happen with 1.3 scores
1775 0 : rm->setTicks(m->ticks());
1776 : }
1777 0 : lastTick = ctx.tick();
1778 0 : ctx.incTick(m->ticks());
1779 154 : } else if (tag == "Clef") {
1780 : // there may be more than one clef segment for same tick position
1781 : // the first clef may be missing and is added later in layout
1782 :
1783 : bool header;
1784 21 : if (ctx.tick() != m->tick()) {
1785 20 : header = false;
1786 1 : } else if (!segment) {
1787 1 : header = true;
1788 : } else {
1789 0 : header = true;
1790 0 : for (Segment* s = m->segments().first(); s && s->rtick().isZero(); s = s->next()) {
1791 0 : if (s->isKeySigType() || s->isTimeSigType()) {
1792 : // hack: there may be other segment types which should
1793 : // generate a clef at current position
1794 0 : header = false;
1795 0 : break;
1796 : }
1797 : }
1798 : }
1799 21 : segment = m->getSegment(header ? SegmentType::HeaderClef : SegmentType::Clef, ctx.tick());
1800 :
1801 21 : Clef* clef = Factory::createClef(segment);
1802 21 : clef->setTrack(ctx.track());
1803 21 : readClef(clef, e, ctx);
1804 21 : if (ctx.mscVersion() < 113) {
1805 0 : clef->setOffset(PointF());
1806 : }
1807 21 : clef->setGenerated(false);
1808 : // MS3 doesn't support wrong clef for staff type: Default to G
1809 21 : bool isDrumStaff = staff->isDrumStaff(ctx.tick());
1810 21 : if (clef->clefType() == ClefType::TAB
1811 20 : || (clef->clefType() == ClefType::PERC && !isDrumStaff)
1812 41 : || (clef->clefType() != ClefType::PERC && isDrumStaff)) {
1813 2 : clef->setClefType(ClefType::G);
1814 2 : staff->clefList().erase(ctx.tick().ticks());
1815 2 : staff->clefList().insert(std::pair<int, ClefType>(ctx.tick().ticks(), ClefType::G));
1816 : }
1817 :
1818 : // Clef segments are sorted on layout now. Previously, clef barline position could be out of sync with segment placement.
1819 41 : if (ctx.tick() != Fraction(0, 1) && ctx.tick() == m->tick()
1820 41 : && !(m->prevMeasure() && m->prevMeasure()->repeatEnd()) && !header) {
1821 0 : clef->setClefToBarlinePosition(ClefToBarlinePosition::AFTER);
1822 : }
1823 :
1824 21 : segment->add(clef);
1825 133 : } else if (tag == "TimeSig") {
1826 : // if time sig not at beginning of measure => courtesy time sig
1827 33 : Fraction currTick = ctx.tick();
1828 33 : bool courtesySig = (currTick > m->tick());
1829 33 : if (courtesySig) {
1830 : // if courtesy sig., just add it without map processing
1831 0 : segment = m->getSegment(SegmentType::TimeSigAnnounce, currTick);
1832 : } else {
1833 : // if 'real' time sig., do full process
1834 33 : segment = m->getSegment(SegmentType::TimeSig, currTick);
1835 : }
1836 :
1837 33 : TimeSig* ts = Factory::createTimeSig(segment);
1838 33 : ts->setTrack(ctx.track());
1839 33 : read400::TRead::read(ts, e, ctx);
1840 :
1841 33 : segment->add(ts);
1842 33 : if (!courtesySig) {
1843 33 : timeStretch = ts->stretch().reduced();
1844 33 : m->setTimesig(ts->sig() / timeStretch);
1845 : }
1846 100 : } else if (tag == "KeySig") {
1847 4 : KeySig* ks = Factory::createKeySig(ctx.dummy());
1848 4 : ks->setTrack(ctx.track());
1849 4 : read400::TRead::read(ks, e, ctx);
1850 4 : Fraction curTick = ctx.tick();
1851 : // if key sig not at beginning of measure => courtesy key sig
1852 4 : bool courtesySig = (curTick == m->endTick());
1853 4 : segment = m->getSegment(courtesySig ? SegmentType::KeySigAnnounce : SegmentType::KeySig, curTick);
1854 4 : segment->add(ks);
1855 4 : staff->setKey(curTick, ks->keySigEvent());
1856 96 : } else if (tag == "Lyrics") {
1857 0 : Lyrics* l = Factory::createLyrics(ctx.dummy());
1858 0 : l->setTrack(ctx.track());
1859 :
1860 0 : int iEndTick = 0; // used for backward compatibility
1861 0 : Text* _verseNumber = 0;
1862 :
1863 0 : while (e.readNextStartElement()) {
1864 0 : const AsciiStringView t(e.name());
1865 0 : if (t == "no") {
1866 0 : l->setVerse(e.readInt());
1867 0 : if (l->isEven()) {
1868 0 : l->initTextStyleType(TextStyleType::LYRICS_EVEN);
1869 : }
1870 0 : } else if (t == "syllabic") {
1871 0 : String val(e.readText());
1872 0 : if (val == "single") {
1873 0 : l->setSyllabic(LyricsSyllabic::SINGLE);
1874 0 : } else if (val == "begin") {
1875 0 : l->setSyllabic(LyricsSyllabic::BEGIN);
1876 0 : } else if (val == "end") {
1877 0 : l->setSyllabic(LyricsSyllabic::END);
1878 0 : } else if (val == "middle") {
1879 0 : l->setSyllabic(LyricsSyllabic::MIDDLE);
1880 : } else {
1881 0 : LOGD("bad syllabic property");
1882 : }
1883 0 : } else if (t == "endTick") { // obsolete
1884 : // store <endTick> tag value until a <ticks> tag has been read
1885 : // which positions this lyrics element in the score
1886 0 : iEndTick = e.readInt();
1887 0 : } else if (t == "ticks") {
1888 0 : l->setTicks(Fraction::fromTicks(e.readInt()));
1889 0 : } else if (t == "Number") { // obsolete
1890 0 : _verseNumber = Factory::createText(l);
1891 0 : readText114(e, ctx, _verseNumber, l);
1892 0 : } else if (!readTextProperties(e, ctx, l, l)) {
1893 0 : e.unknown();
1894 : }
1895 : }
1896 : // if any endTick, make it relative to current tick
1897 0 : if (iEndTick) {
1898 0 : l->setTicks(Fraction::fromTicks(iEndTick - ctx.tick().ticks()));
1899 : // LOGD("Lyrics::endTick: %d ticks %d", iEndTick, _ticks);
1900 : }
1901 : if (_verseNumber) {
1902 : // TODO: add text to main text
1903 : }
1904 :
1905 0 : delete _verseNumber;
1906 :
1907 0 : segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
1908 0 : ChordRest* cr = toChordRest(segment->element(l->track()));
1909 0 : if (!cr) {
1910 0 : cr = toChordRest(segment->element(ctx.track())); // in case lyric itself has bad track info
1911 : }
1912 0 : if (!cr) {
1913 0 : LOGD("Internal error: no chord/rest for lyrics");
1914 : } else {
1915 0 : cr->add(l);
1916 : }
1917 96 : } else if (tag == "Text") {
1918 26 : segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
1919 26 : StaffText* t = Factory::createStaffText(segment);
1920 26 : t->setTrack(ctx.track());
1921 26 : readStaffText(t, e, ctx);
1922 26 : if (t->empty()) {
1923 26 : LOGD("reading empty text: deleted");
1924 26 : delete t;
1925 : } else {
1926 0 : segment->add(t);
1927 : }
1928 70 : } else if (tag == "Dynamic") {
1929 0 : segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
1930 0 : Dynamic* dyn = Factory::createDynamic(segment);
1931 0 : dyn->setTrack(ctx.track());
1932 0 : read400::TRead::read(dyn, e, ctx); // for 114 scores, dynamics are frontloaded in the measure with <tick> attributes.
1933 : // so we need to reset its parent to the correct one after that element is read.
1934 0 : segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
1935 0 : dyn->setOwnershipParent(segment);
1936 0 : if (dyn->dynamicType() == DynamicType::OTHER && dyn->xmlText().isEmpty()) {
1937 : // if we add this dynamic, it will be an unselectable invisible object that
1938 : // messes with collision detection.
1939 0 : delete dyn;
1940 : } else {
1941 0 : dyn->setDynamicType(dyn->xmlText());
1942 0 : segment->add(dyn);
1943 : }
1944 70 : } else if (tag == "Tempo") {
1945 0 : segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
1946 0 : TempoText* t = Factory::createTempoText(segment);
1947 0 : t->setTrack(ctx.track());
1948 0 : readTempoText(t, e, ctx);
1949 0 : segment->add(t);
1950 70 : } else if (tag == "StaffText") {
1951 2 : segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
1952 2 : StaffText* t = Factory::createStaffText(segment);
1953 2 : t->setTrack(ctx.track());
1954 2 : readStaffText(t, e, ctx);
1955 2 : segment->add(t);
1956 68 : } else if (tag == "Harmony") {
1957 0 : segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
1958 0 : Harmony* h = Factory::createHarmony(segment);
1959 0 : h->setTrack(ctx.track());
1960 0 : readHarmony114(e, ctx, h);
1961 0 : segment->add(h);
1962 68 : } else if (tag == "Harmony"
1963 68 : || tag == "FretDiagram"
1964 68 : || tag == "TremoloBar"
1965 68 : || tag == "Symbol"
1966 68 : || tag == "StaffText"
1967 68 : || tag == "RehearsalMark"
1968 68 : || tag == "InstrumentChange"
1969 136 : || tag == "StaffState"
1970 : ) {
1971 0 : EngravingItem* el = Factory::createItemByName(tag, ctx.dummy());
1972 : // hack - needed because tick tags are unreliable in 1.3 scores
1973 : // for symbols attached to anything but a measure
1974 0 : if (el->isSymbol()) {
1975 0 : el->setOwnershipParent(m); // this will get reset when adding to segment
1976 : }
1977 0 : el->setTrack(ctx.track());
1978 0 : read400::TRead::readItem(el, e, ctx);
1979 0 : segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
1980 0 : segment->add(el);
1981 68 : } else if (tag == "Jump") {
1982 6 : Jump* j = Factory::createJump(m);
1983 6 : j->setTrack(ctx.track());
1984 54 : while (e.readNextStartElement()) {
1985 48 : const AsciiStringView t(e.name());
1986 48 : if (t == "jumpTo") {
1987 6 : j->setJumpTo(e.readText());
1988 42 : } else if (t == "playUntil") {
1989 6 : j->setPlayUntil(e.readText());
1990 36 : } else if (t == "continueAt") {
1991 6 : j->setContinueAt(e.readText());
1992 30 : } else if (t == "subtype") {
1993 6 : e.skipCurrentElement(); // obsolete, always "Repeat"
1994 24 : } else if (!TRead::readTextProperties(j, e, ctx)) {
1995 18 : e.unknown();
1996 : }
1997 : }
1998 :
1999 : // infer jump type
2000 6 : String jumpTo = j->jumpTo();
2001 6 : String playUntil = j->playUntil();
2002 6 : if (jumpTo == "start") {
2003 3 : if (playUntil == "end") {
2004 1 : j->setJumpType(JumpType::DC);
2005 2 : } else if (playUntil == "fine") {
2006 1 : j->setJumpType(JumpType::DC_AL_FINE);
2007 : } else {
2008 1 : j->setJumpType(JumpType::DC_AL_CODA);
2009 : }
2010 : } else {
2011 3 : if (playUntil == "end") {
2012 1 : j->setJumpType(JumpType::DS);
2013 2 : } else if (playUntil == "fine") {
2014 1 : j->setJumpType(JumpType::DS_AL_FINE);
2015 : } else {
2016 1 : j->setJumpType(JumpType::DS_AL_CODA);
2017 : }
2018 : }
2019 :
2020 6 : m->add(j);
2021 68 : } else if (tag == "Marker") {
2022 6 : Marker* a = Factory::createMarker(m);
2023 6 : a->setTrack(ctx.track());
2024 :
2025 6 : MarkerType mt = MarkerType::SEGNO;
2026 46 : while (e.readNextStartElement()) {
2027 40 : const AsciiStringView t(e.name());
2028 40 : if (t == "subtype" || t == "label") {
2029 12 : AsciiStringView s(e.readAsciiText());
2030 12 : a->setLabel(String::fromAscii(s.ascii()));
2031 12 : mt = TConv::fromXml(s, MarkerType::USER);
2032 28 : } else if (!TRead::readTextProperties(a, e, ctx)) {
2033 22 : e.unknown();
2034 : }
2035 : }
2036 6 : a->setMarkerType(mt);
2037 :
2038 11 : if (a->markerType() == MarkerType::SEGNO || a->markerType() == MarkerType::CODA
2039 11 : || a->markerType() == MarkerType::VARCODA || a->markerType() == MarkerType::CODETTA) {
2040 : // force the marker type for correct display
2041 4 : a->setXmlText(u"");
2042 4 : a->setMarkerType(a->markerType());
2043 4 : a->setTextStyleType(TextStyleType::REPEAT_LEFT);
2044 : }
2045 6 : m->add(a);
2046 56 : } else if (tag == "Image") {
2047 0 : if (MScore::noImages) {
2048 0 : e.skipCurrentElement();
2049 : } else {
2050 0 : segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
2051 0 : EngravingItem* el = Factory::createItemByName(tag, segment);
2052 0 : el->setTrack(ctx.track());
2053 0 : read400::TRead::readItem(el, e, ctx);
2054 0 : segment->add(el);
2055 : }
2056 56 : } else if (tag == "stretch") {
2057 : // Ignore measure stretch pre 4.0
2058 0 : e.skipCurrentElement();
2059 56 : } else if (tag == "noOffset") {
2060 0 : m->setMeasureNumberOffset(e.readInt());
2061 56 : } else if (tag == "measureNumberMode") {
2062 0 : m->setMeasureNumberMode(MeasureNumberMode(e.readInt()));
2063 56 : } else if (tag == "irregular") {
2064 0 : m->setExcludeFromNumbering(true);
2065 0 : e.skipCurrentElement();
2066 56 : } else if (tag == "breakMultiMeasureRest") {
2067 0 : m->setBreakMultiMeasureRest(e.readBool());
2068 56 : } else if (tag == "sysInitBarLineType") {
2069 0 : segment = m->getSegment(SegmentType::BeginBarLine, m->tick());
2070 0 : BarLine* barLine = Factory::createBarLine(segment);
2071 0 : barLine->setTrack(ctx.track());
2072 0 : barLine->setBarLineType(TConv::fromXml(e.readAsciiText(), BarLineType::NORMAL));
2073 0 : segment->add(barLine);
2074 56 : } else if (tag == "Tuplet") {
2075 16 : int tupletId = e.intAttribute("id");
2076 16 : Tuplet* tuplet = Factory::createTuplet(m);
2077 16 : tuplet->setTrack(ctx.track());
2078 16 : tuplet->setTick(ctx.tick());
2079 16 : readTuplet(tuplet, e, ctx);
2080 16 : ctx.addTuplet(tupletId, tuplet);
2081 40 : } else if (tag == "startRepeat") {
2082 0 : m->setRepeatStart(true);
2083 0 : e.readNext();
2084 40 : } else if (tag == "endRepeat") {
2085 0 : m->setRepeatCount(e.readInt());
2086 0 : m->setRepeatEnd(true);
2087 40 : } else if (tag == "vspacer" || tag == "vspacerDown") {
2088 0 : if (!m->vspacerDown(staffIdx)) {
2089 0 : Spacer* spacer = Factory::createSpacer(m);
2090 0 : spacer->setSpacerType(SpacerType::DOWN);
2091 0 : spacer->setTrack(staffIdx * VOICES);
2092 0 : m->add(spacer);
2093 : }
2094 0 : m->vspacerDown(staffIdx)->setGap(Spatium(e.readDouble()));
2095 40 : } else if (tag == "vspacer" || tag == "vspacerUp") {
2096 0 : if (!m->vspacerUp(staffIdx)) {
2097 0 : Spacer* spacer = Factory::createSpacer(m);
2098 0 : spacer->setSpacerType(SpacerType::UP);
2099 0 : spacer->setTrack(staffIdx * VOICES);
2100 0 : m->add(spacer);
2101 : }
2102 0 : m->vspacerUp(staffIdx)->setGap(Spatium(e.readDouble()));
2103 40 : } else if (tag == "visible") {
2104 0 : m->setStaffVisible(staffIdx, e.readInt());
2105 40 : } else if (tag == "slashStyle") {
2106 0 : m->setStaffStemless(staffIdx, e.readInt());
2107 40 : } else if (tag == "Beam") {
2108 20 : int beamId = e.intAttribute("id");
2109 20 : Beam* beam = Factory::createBeam(ctx.score());
2110 20 : beam->setTrack(ctx.track());
2111 20 : read400::TRead::read(beam, e, ctx);
2112 20 : ctx.addBeam(beamId, beam);
2113 20 : } else if (tag == "Segment") {
2114 0 : if (segment) {
2115 0 : read400::TRead::read(segment, e, ctx);
2116 : }
2117 0 : while (e.readNextStartElement()) {
2118 0 : const AsciiStringView t(e.name());
2119 0 : if (t == "off1") {
2120 0 : double o = e.readDouble();
2121 0 : LOGD("TODO: off1 %f", o);
2122 : } else {
2123 0 : e.unknown();
2124 : }
2125 : }
2126 20 : } else if (tag == "MeasureNumber") {
2127 0 : MeasureNumber* noText = new MeasureNumber(m);
2128 0 : readText114(e, ctx, noText, m);
2129 0 : noText->setTrack(ctx.track());
2130 0 : m->setMeasureNumber(noText->staffIdx(), noText);
2131 20 : } else if (tag == "multiMeasureRest") {
2132 0 : m->setMMRestCount(e.readInt());
2133 : // set tick to previous measure
2134 0 : m->setTick(ctx.lastMeasure()->tick());
2135 0 : ctx.setTick(ctx.lastMeasure()->tick());
2136 20 : } else if (TRead::readProperties(static_cast<MeasureBase*>(m), e, ctx)) {
2137 : } else {
2138 0 : e.unknown();
2139 : }
2140 : }
2141 : // For nested tuplets created with MuseScore 1.3 tuplet dialog (i.e. "Other..." dialog),
2142 : // the parent tuplet was not set. Try to infer if the tuplet was actually a nested tuplet
2143 462 : for (auto& p : ctx.tuplets()) {
2144 16 : Tuplet* tuplet = p.second;
2145 16 : Fraction tupletTick = tuplet->tick();
2146 16 : Fraction tupletDuration = tuplet->actualTicks() - Fraction::eps();
2147 16 : std::vector<DurationElement*> tElements = tuplet->elements();
2148 48 : for (auto& p2 : ctx.tuplets()) {
2149 32 : Tuplet* tuplet2 = p2.second;
2150 32 : if ((tuplet2->tuplet()) || (tuplet2->voice() != tuplet->voice())) { // already a nested tuplet or in a different voice
2151 2 : continue;
2152 : }
2153 : // int possibleDuration = tuplet2->duration().ticks() * tuplet->ratio().denominator() / tuplet->ratio().numerator() - 1;
2154 30 : Fraction possibleDuration = tuplet2->ticks() * Fraction(tuplet->ratio().denominator(), (tuplet->ratio().numerator() - 1));
2155 :
2156 15 : if ((tuplet2 != tuplet) && (tuplet2->tick() >= tupletTick) && (tuplet2->tick() < tupletTick + tupletDuration)
2157 45 : && (tuplet2->tick() + possibleDuration < tupletTick + tupletDuration)) {
2158 2 : bool found = false;
2159 4 : for (DurationElement* de : tElements) {
2160 2 : if (de == tuplet2) {
2161 0 : found = true;
2162 : }
2163 : }
2164 2 : if (!found) {
2165 2 : LOGD("Adding tuplet %p as nested tuplet to tuplet %p", tuplet2, tuplet);
2166 2 : tuplet2->setTuplet(tuplet);
2167 2 : tuplet->add(tuplet2);
2168 : }
2169 : }
2170 : }
2171 16 : }
2172 446 : ctx.checkTuplets();
2173 446 : m->connectTremolo();
2174 446 : }
2175 :
2176 : //---------------------------------------------------------
2177 : // readBoxProperties
2178 : //---------------------------------------------------------
2179 :
2180 : static void readBox(XmlReader& e, ReadContext& ctx, Box* b);
2181 :
2182 71 : static bool readBoxProperties(XmlReader& e, ReadContext& ctx, Box* b)
2183 : {
2184 71 : const AsciiStringView tag(e.name());
2185 71 : if (tag == "height") {
2186 10 : b->setBoxHeight(Spatium(e.readDouble()));
2187 61 : } else if (tag == "width") {
2188 1 : b->setBoxWidth(Spatium(e.readDouble()));
2189 60 : } else if (tag == "topGap") {
2190 0 : read400::TRead::readProperty(b, e, ctx, Pid::TOP_GAP);
2191 60 : } else if (tag == "bottomGap") {
2192 0 : read400::TRead::readProperty(b, e, ctx, Pid::BOTTOM_GAP);
2193 60 : } else if (tag == "leftMargin") {
2194 11 : b->setLeftMargin(e.readDouble());
2195 49 : } else if (tag == "rightMargin") {
2196 11 : b->setRightMargin(e.readDouble());
2197 38 : } else if (tag == "topMargin") {
2198 11 : b->setTopMargin(e.readDouble());
2199 27 : } else if (tag == "bottomMargin") {
2200 11 : b->setBottomMargin(e.readDouble());
2201 16 : } else if (tag == "offset") {
2202 0 : b->setOffset(e.readPoint() * b->spatium());
2203 16 : } else if (tag == "pos") { // ignore
2204 0 : e.readPoint();
2205 16 : } else if (tag == "Text") {
2206 : Text* t;
2207 16 : if (b->isTBox()) {
2208 0 : t = toTBox(b)->text();
2209 0 : readText114(e, ctx, t, t);
2210 : } else {
2211 16 : t = Factory::createText(b);
2212 16 : readText114(e, ctx, t, t);
2213 16 : if (t->empty()) {
2214 0 : LOGD("read empty text");
2215 : } else {
2216 16 : b->add(t);
2217 : }
2218 : }
2219 0 : } else if (tag == "Symbol") {
2220 0 : Symbol* s = new Symbol(b);
2221 0 : read400::TRead::read(s, e, ctx);
2222 0 : b->add(s);
2223 0 : } else if (tag == "Image") {
2224 0 : if (MScore::noImages) {
2225 0 : e.skipCurrentElement();
2226 : } else {
2227 0 : Image* image = new Image(b);
2228 0 : image->setTrack(ctx.track());
2229 0 : read400::TRead::read(image, e, ctx);
2230 0 : b->add(image);
2231 : }
2232 0 : } else if (tag == "HBox") {
2233 0 : HBox* hb = Factory::createHBox(b->score());
2234 0 : readBox(e, ctx, hb);
2235 0 : b->add(hb);
2236 0 : } else if (tag == "VBox") {
2237 0 : VBox* vb = Factory::createVBox(b->score());
2238 0 : readBox(e, ctx, vb);
2239 0 : b->add(vb);
2240 : }
2241 : // else if (MeasureBase::readProperties(e))
2242 : // ;
2243 : else {
2244 0 : return false;
2245 : }
2246 71 : return true;
2247 : }
2248 :
2249 : //---------------------------------------------------------
2250 : // readBox
2251 : //---------------------------------------------------------
2252 :
2253 11 : static void readBox(XmlReader& e, ReadContext& ctx, Box* b)
2254 : {
2255 11 : b->setAutoSizeEnabled(false); // didn't exist in Mu1
2256 :
2257 11 : b->setBoxHeight(0_sp); // override default set in constructor
2258 11 : b->setBoxWidth(0_sp);
2259 11 : bool keepMargins = false; // whether original margins have to be kept when reading old file
2260 :
2261 82 : while (e.readNextStartElement()) {
2262 71 : const AsciiStringView tag(e.name());
2263 71 : if (tag == "HBox") {
2264 0 : HBox* hb = Factory::createHBox(b->score());
2265 0 : readBox(e, ctx, hb);
2266 0 : b->add(hb);
2267 0 : keepMargins = true; // in old file, box nesting used outer box margins
2268 71 : } else if (tag == "VBox") {
2269 0 : VBox* vb = Factory::createVBox(b->score());
2270 0 : readBox(e, ctx, vb);
2271 0 : b->add(vb);
2272 0 : keepMargins = true; // in old file, box nesting used outer box margins
2273 71 : } else if (!readBoxProperties(e, ctx, b)) {
2274 0 : e.unknown();
2275 : }
2276 : }
2277 :
2278 : // with .msc versions prior to 1.17, box margins were only used when nesting another box inside this box:
2279 : // for backward compatibility set them to 0.0 in all other cases, the Mu1 defaults of 5.0 just look horrible in Mu3 and Mu4
2280 :
2281 11 : if (ctx.mscVersion() <= 114 && (b->isHBox() || b->isVBox()) && !keepMargins) {
2282 11 : b->setLeftMargin(0.0);
2283 11 : b->setRightMargin(0.0);
2284 11 : b->setTopMargin(0.0); // 2.0 would look closest to Mu1 and Mu2, but 0.0 is the default since Mu2
2285 11 : b->setBottomMargin(0.0); // 1.0 would look closest to Mu1 and Mu2, but 0.0 is the default since Mu2
2286 : }
2287 11 : }
2288 :
2289 : //---------------------------------------------------------
2290 : // readStaffContent
2291 : //---------------------------------------------------------
2292 :
2293 33 : static void readStaffContent(Score* score, XmlReader& e, ReadContext& ctx)
2294 : {
2295 33 : int staff = e.intAttribute("id", 1) - 1;
2296 33 : ctx.setTick(Fraction(0, 1));
2297 33 : ctx.setTrack(staff * VOICES);
2298 :
2299 33 : Measure* measure = score->firstMeasure();
2300 490 : while (e.readNextStartElement()) {
2301 457 : const AsciiStringView tag(e.name());
2302 :
2303 457 : if (tag == "Measure") {
2304 446 : if (staff == 0) {
2305 359 : measure = Factory::createMeasure(score);
2306 359 : measure->setTick(ctx.tick());
2307 359 : const SigEvent& ev = score->sigmap()->timesig(measure->tick());
2308 359 : measure->setTicks(ev.timesig());
2309 359 : measure->setTimesig(ev.nominal());
2310 :
2311 359 : readMeasure(measure, staff, e, ctx);
2312 359 : measure->checkMeasure(staff);
2313 :
2314 359 : if (!measure->isMMRest()) {
2315 359 : score->measures()->append(measure);
2316 359 : ctx.setLastMeasure(measure);
2317 359 : ctx.setTick(measure->endTick());
2318 : } else {
2319 : // this is a multi measure rest
2320 : // always preceded by the first measure it replaces
2321 0 : Measure* m = ctx.lastMeasure();
2322 :
2323 0 : if (m) {
2324 0 : m->setMMRest(measure);
2325 0 : measure->setTick(m->tick());
2326 : }
2327 : }
2328 : } else {
2329 87 : if (measure == 0) {
2330 0 : LOGD("Score::readStaff(): missing measure!");
2331 0 : measure = Factory::createMeasure(score);
2332 0 : measure->setTick(ctx.tick());
2333 0 : score->measures()->append(measure);
2334 : }
2335 87 : ctx.setTick(measure->tick());
2336 :
2337 87 : readMeasure(measure, staff, e, ctx);
2338 87 : measure->checkMeasure(staff);
2339 :
2340 87 : if (measure->isMMRest()) {
2341 0 : measure = ctx.lastMeasure()->nextMeasure();
2342 : } else {
2343 87 : ctx.setLastMeasure(measure);
2344 87 : if (measure->mmRest()) {
2345 0 : measure = measure->mmRest();
2346 : } else {
2347 87 : measure = measure->nextMeasure();
2348 : }
2349 : }
2350 : }
2351 11 : } else if (tag == "HBox" || tag == "VBox" || tag == "TBox" || tag == "FBox") {
2352 11 : Box* mb = toBox(Factory::createItemByName(tag, score->dummy()));
2353 11 : readBox(e, ctx, mb);
2354 11 : mb->setTick(ctx.tick());
2355 11 : score->measures()->append(mb);
2356 0 : } else if (tag == "tick") {
2357 0 : ctx.setTick(Fraction::fromTicks(score->fileDivision(e.readInt())));
2358 : } else {
2359 0 : e.unknown();
2360 : }
2361 : }
2362 33 : }
2363 :
2364 : //---------------------------------------------------------
2365 : // readStaff
2366 : //---------------------------------------------------------
2367 :
2368 33 : static void readStaff(Staff* staff, XmlReader& e, ReadContext& ctx)
2369 : {
2370 133 : while (e.readNextStartElement()) {
2371 100 : const AsciiStringView tag(e.name());
2372 100 : if (tag == "lines") {
2373 1 : int lines = e.readInt();
2374 1 : staff->setLines(Fraction(0, 1), lines);
2375 1 : if (lines != 5) {
2376 1 : staff->setBarLineFrom(lines == 1 ? BARLINE_SPAN_1LINESTAFF_FROM : 0);
2377 1 : staff->setBarLineTo(lines == 1 ? BARLINE_SPAN_1LINESTAFF_TO : (lines - 1) * 2);
2378 : }
2379 99 : } else if (tag == "small") {
2380 0 : staff->staffType(Fraction(0, 1))->setSmall(e.readInt());
2381 99 : } else if (tag == "invisible") {
2382 0 : staff->setIsLinesInvisible(Fraction(0, 1), e.readInt());
2383 99 : } else if (tag == "slashStyle") {
2384 0 : e.skipCurrentElement();
2385 99 : } else if (tag == "cleflist") {
2386 33 : staff->clefList().clear();
2387 85 : while (e.readNextStartElement()) {
2388 52 : if (e.name() == "clef") {
2389 52 : int tick = e.intAttribute("tick", 0);
2390 52 : ClefType ct = readClefType(e.intAttribute("idx", 0));
2391 52 : staff->clefList().insert(std::pair<int, ClefType>(ctx.fileDivision(tick), ct));
2392 52 : e.readNext();
2393 : } else {
2394 0 : e.unknown();
2395 : }
2396 : }
2397 33 : if (staff->clefList().empty()) {
2398 0 : staff->clefList().insert(std::pair<int, ClefType>(0, ClefType::G));
2399 : }
2400 66 : } else if (tag == "keylist") {
2401 33 : read400::TRead::read(staff->keyList(), e, ctx);
2402 33 : } else if (tag == "bracket") {
2403 26 : size_t col = ctx.score()->brackets(staff->idx()).size();
2404 26 : EditStaffBrackets::setBracketType(ctx.score(), staff->idx(), col, BracketType(e.intAttribute("type", -1)));
2405 26 : EditStaffBrackets::setBracketSpan(ctx.score(), staff->idx(), col, e.intAttribute("span", 0));
2406 26 : e.readNext();
2407 7 : } else if (tag == "barLineSpan") {
2408 7 : const int barLineSpan = e.readInt();
2409 7 : if (barLineSpan > 0) {
2410 7 : ctx.setStaffBarLineSpan(staff->idx(), static_cast<size_t>(barLineSpan - 1));
2411 : } else {
2412 0 : if (barLineSpan < 0) {
2413 0 : LOGW() << "barLineSpan is negative: " << barLineSpan;
2414 : }
2415 0 : ctx.setStaffBarLineSpan(staff->idx(), 0);
2416 : }
2417 : } else {
2418 0 : e.unknown();
2419 : }
2420 : }
2421 33 : }
2422 :
2423 : //---------------------------------------------------------
2424 : // readDrumset
2425 : //---------------------------------------------------------
2426 :
2427 26 : static void readDrumset(Drumset* ds, XmlReader& e)
2428 : {
2429 26 : int pitch = e.intAttribute("pitch", -1);
2430 26 : if (!pitchIsValid(pitch)) {
2431 0 : LOGD("load drumset: invalid pitch %d", pitch);
2432 0 : return;
2433 : }
2434 158 : while (e.readNextStartElement()) {
2435 132 : const AsciiStringView tag(e.name());
2436 132 : if (tag == "head") {
2437 26 : ds->drum(pitch).notehead = Read206::convertHeadGroup(e.readInt());
2438 106 : } else if (ds->readDrumProperties(e, pitch)) {
2439 : } else {
2440 0 : e.unknown();
2441 : }
2442 : }
2443 : }
2444 :
2445 : //---------------------------------------------------------
2446 : // readInstrument
2447 : //---------------------------------------------------------
2448 :
2449 26 : static void readInstrument(Instrument* i, Part* p, XmlReader& e, ReadContext& ctx)
2450 : {
2451 26 : int program = -1;
2452 26 : int bank = 0;
2453 26 : int chorus = 30;
2454 26 : int reverb = 30;
2455 26 : int volume = 100;
2456 26 : int pan = 60;
2457 26 : bool customDrumset = false;
2458 26 : i->clearChannels(); // remove default channel
2459 198 : while (e.readNextStartElement()) {
2460 172 : const AsciiStringView tag(e.name());
2461 172 : if (tag == "chorus") {
2462 0 : chorus = e.readInt();
2463 172 : } else if (tag == "reverb") {
2464 0 : reverb = e.readInt();
2465 172 : } else if (tag == "midiProgram") {
2466 0 : program = e.readInt();
2467 172 : } else if (tag == "volume") {
2468 0 : volume = e.readInt();
2469 172 : } else if (tag == "pan") {
2470 0 : pan = e.readInt();
2471 172 : } else if (tag == "midiChannel") {
2472 0 : e.skipCurrentElement();
2473 172 : } else if (tag == "Drum") {
2474 : // if we see one of this tags, a custom drumset will
2475 : // be created
2476 26 : if (!i->drumset()) {
2477 0 : i->setDrumset(new Drumset(*smDrumset));
2478 : }
2479 26 : if (!customDrumset) {
2480 2 : i->drumset()->clear();
2481 2 : customDrumset = true;
2482 : }
2483 26 : readDrumset(i->drumset(), e);
2484 146 : } else if (read400::TRead::readProperties(i, e, ctx, p, &customDrumset)) {
2485 : } else {
2486 0 : e.unknown();
2487 : }
2488 : }
2489 :
2490 26 : i->updateInstrumentId();
2491 :
2492 26 : if (program == -1) {
2493 26 : program = i->recognizeMidiProgram();
2494 : }
2495 :
2496 26 : if (i->channel().empty()) { // for backward compatibility
2497 0 : InstrChannel* a = new InstrChannel;
2498 0 : a->setName(String::fromUtf8(InstrChannel::DEFAULT_NAME));
2499 0 : a->setProgram(program);
2500 0 : a->setBank(bank);
2501 0 : a->setVolume(volume);
2502 0 : a->setPan(pan);
2503 0 : a->setReverb(reverb);
2504 0 : a->setChorus(chorus);
2505 0 : i->appendChannel(a);
2506 26 : } else if (i->channel(0)->program() < 0) {
2507 0 : i->channel(0)->setProgram(program);
2508 : }
2509 26 : if (i->useDrumset()) {
2510 2 : if (i->channel()[0]->bank() == 0) {
2511 0 : i->channel()[0]->setBank(128);
2512 : }
2513 : }
2514 :
2515 : // Fix user bank controller read
2516 52 : for (InstrChannel* c : i->channel()) {
2517 26 : if (c->bank() == 0) {
2518 24 : c->setUserBankController(false);
2519 : }
2520 : }
2521 :
2522 : // Read single-note dynamics from template
2523 26 : i->setSingleNoteDynamicsFromTemplate();
2524 26 : }
2525 :
2526 : //---------------------------------------------------------
2527 : // readPart
2528 : //---------------------------------------------------------
2529 :
2530 26 : static void readPart(Part* part, XmlReader& e, ReadContext& ctx)
2531 : {
2532 109 : while (e.readNextStartElement()) {
2533 83 : const AsciiStringView tag(e.name());
2534 83 : if (tag == "Staff") {
2535 33 : Staff* staff = Factory::createStaff(part);
2536 33 : staff->setStaffType(Fraction(0, 1), StaffType()); // will reset later if needed
2537 33 : ctx.appendStaff(staff);
2538 33 : readStaff(staff, e, ctx);
2539 50 : } else if (tag == "Instrument") {
2540 26 : Instrument* i = part->instrument();
2541 26 : readInstrument(i, part, e, ctx);
2542 : // add string data from MIDI program number, if possible
2543 26 : if (i->stringData()->strings() == 0
2544 26 : && i->channel().size() > 0
2545 52 : && i->drumset() == nullptr) {
2546 24 : int program = i->channel(0)->program();
2547 24 : if (program >= 24 && program <= 30) { // guitars
2548 1 : i->setStringData(StringData(19, 6, g_guitarStrings));
2549 23 : } else if ((program >= 32 && program <= 39) || program == 43) { // bass / double-bass
2550 0 : i->setStringData(StringData(24, 4, g_bassStrings));
2551 23 : } else if (program == 40) { // violin and other treble string instr.
2552 0 : i->setStringData(StringData(24, 4, g_violinStrings));
2553 23 : } else if (program == 41) { // viola and other alto string instr.
2554 0 : i->setStringData(StringData(24, 4, g_violaStrings));
2555 23 : } else if (program == 42) { // cello and other bass string instr.
2556 0 : i->setStringData(StringData(24, 4, g_celloStrings));
2557 : }
2558 : }
2559 26 : Drumset* d = i->drumset();
2560 26 : Staff* st = part->staff(0);
2561 26 : if (d && st && st->lines(Fraction(0, 1)) != 5) {
2562 1 : int n = 0;
2563 1 : if (st->lines(Fraction(0, 1)) == 1) {
2564 1 : n = 4;
2565 : }
2566 129 : for (int j = 0; j < DRUM_INSTRUMENTS; ++j) {
2567 128 : d->drum(j).line -= n;
2568 : }
2569 : }
2570 24 : } else if (tag == "name") {
2571 12 : Text* t = Factory::createText(ctx.dummy(), TextStyleType::DEFAULT, false);
2572 12 : readText114(e, ctx, t, t);
2573 12 : part->instrument()->setLongName(t->xmlText());
2574 12 : delete t;
2575 12 : } else if (tag == "shortName") {
2576 12 : Text* t = Factory::createText(ctx.dummy(), TextStyleType::DEFAULT, false);
2577 12 : readText114(e, ctx, t, t);
2578 12 : part->instrument()->setShortName(t->xmlText());
2579 12 : delete t;
2580 0 : } else if (tag == "show") {
2581 0 : part->setShow(e.readInt());
2582 : } else {
2583 0 : e.unknown();
2584 : }
2585 : }
2586 :
2587 26 : if (part->instrument()->useDrumset()) {
2588 4 : for (Staff* staff : part->staves()) {
2589 2 : int lines = staff->lines(Fraction(0, 1));
2590 2 : int bf = staff->barLineFrom();
2591 2 : int bt = staff->barLineTo();
2592 2 : staff->setStaffType(Fraction(0, 1), *StaffType::getDefaultPreset(StaffGroup::PERCUSSION));
2593 :
2594 : // this allows 2/3-line percussion staves to keep the double spacing they had in 1.3
2595 :
2596 2 : if (lines == 2 || lines == 3) {
2597 0 : ((StaffType*)(staff->staffType(Fraction(0, 1))))->setLineDistance(2.0_sp);
2598 : }
2599 :
2600 2 : staff->setLines(Fraction(0, 1), lines); // this also sets stepOffset
2601 2 : staff->setBarLineFrom(bf);
2602 2 : staff->setBarLineTo(bt);
2603 : }
2604 : }
2605 : //set default articulations
2606 26 : std::vector<MidiArticulation> articulations;
2607 26 : articulations.push_back(MidiArticulation(u"", u"", 100, 100));
2608 26 : articulations.push_back(MidiArticulation(u"staccato", u"", 100, 50));
2609 26 : articulations.push_back(MidiArticulation(u"tenuto", u"", 100, 100));
2610 26 : articulations.push_back(MidiArticulation(u"sforzato", u"", 120, 100));
2611 26 : part->instrument()->setArticulation(articulations);
2612 26 : }
2613 :
2614 : //---------------------------------------------------------
2615 : // readPageFormat
2616 : //---------------------------------------------------------
2617 :
2618 26 : static void readPageFormat(PageFormat* pf, int& pageNumberOffset, XmlReader& e)
2619 : {
2620 26 : double _oddRightMargin = 0.0;
2621 26 : double _evenRightMargin = 0.0;
2622 26 : bool landscape = false;
2623 26 : AsciiStringView type;
2624 :
2625 128 : while (e.readNextStartElement()) {
2626 102 : const AsciiStringView tag(e.name());
2627 102 : if (tag == "landscape") {
2628 0 : landscape = e.readInt();
2629 102 : } else if (tag == "page-margins") {
2630 50 : type = e.asciiAttribute("type", "both");
2631 50 : double lm = 0.0, rm = 0.0, tm = 0.0, bm = 0.0;
2632 250 : while (e.readNextStartElement()) {
2633 200 : const AsciiStringView t(e.name());
2634 200 : double val = e.readDouble() * 0.5 / PPI;
2635 200 : if (t == "left-margin") {
2636 50 : lm = val;
2637 150 : } else if (t == "right-margin") {
2638 50 : rm = val;
2639 100 : } else if (t == "top-margin") {
2640 50 : tm = val;
2641 50 : } else if (t == "bottom-margin") {
2642 50 : bm = val;
2643 : } else {
2644 0 : e.unknown();
2645 : }
2646 : }
2647 50 : pf->setTwosided(type == "odd" || type == "even");
2648 50 : if (type == "odd" || type == "both") {
2649 26 : pf->setOddLeftMargin(lm);
2650 26 : _oddRightMargin = rm;
2651 26 : pf->setOddTopMargin(tm);
2652 26 : pf->setOddBottomMargin(bm);
2653 : }
2654 50 : if (type == "even" || type == "both") {
2655 26 : pf->setEvenLeftMargin(lm);
2656 26 : _evenRightMargin = rm;
2657 26 : pf->setEvenTopMargin(tm);
2658 26 : pf->setEvenBottomMargin(bm);
2659 : }
2660 52 : } else if (tag == "page-height") {
2661 0 : pf->setSize(SizeF(pf->size().width(), e.readDouble() * 0.5 / PPI));
2662 52 : } else if (tag == "page-width") {
2663 0 : pf->setSize(SizeF(e.readDouble() * 0.5 / PPI, pf->size().height()));
2664 52 : } else if (tag == "pageFormat") {
2665 26 : const PaperSize* s = getPaperSize114(e.readText());
2666 26 : pf->setSize(SizeF(s->w, s->h));
2667 26 : } else if (tag == "page-offset") {
2668 26 : pageNumberOffset = e.readInt();
2669 : } else {
2670 0 : e.unknown();
2671 : }
2672 : }
2673 26 : if (landscape) {
2674 0 : pf->setSize(pf->size().transposed());
2675 : }
2676 26 : double w1 = pf->size().width() - pf->oddLeftMargin() - _oddRightMargin;
2677 26 : double w2 = pf->size().width() - pf->evenLeftMargin() - _evenRightMargin;
2678 26 : pf->setPrintableWidth(std::min(w1, w2)); // silently adjust right margins
2679 26 : }
2680 :
2681 : //---------------------------------------------------------
2682 : // readStyle
2683 : //---------------------------------------------------------
2684 :
2685 25 : static void readStyle(MStyle* style, XmlReader& e, ReadChordListHook& readChordListHook)
2686 : {
2687 173 : while (e.readNextStartElement()) {
2688 148 : String tag = String::fromAscii(e.name().ascii());
2689 :
2690 148 : if (tag == "lyricsDistance") { // was renamed
2691 1 : tag = u"lyricsPosBelow";
2692 : }
2693 :
2694 148 : if (tag == "TextStyle") {
2695 0 : e.skipCurrentElement();
2696 148 : } else if (tag == "lyricsMinBottomDistance") {
2697 : // no longer meaningful since it is now measured from skyline rather than staff
2698 : //style->set(Sid::lyricsMinBottomDistance, PointF(0.0, y));
2699 1 : e.skipCurrentElement();
2700 147 : } else if (tag == "Spatium") {
2701 0 : style->set(Sid::spatium, e.readDouble() * DPMM);
2702 147 : } else if (tag == "page-layout") {
2703 0 : readPageFormat206(style, e);
2704 147 : } else if (tag == "displayInConcertPitch") {
2705 0 : style->set(Sid::concertPitch, bool(e.readInt()));
2706 147 : } else if (tag == "ChordList") {
2707 0 : readChordListHook.read(e);
2708 147 : } else if (tag == "pageFillLimit" || tag == "genTimesig" || tag == "FixMeasureNumbers" || tag == "FixMeasureWidth") { // obsolete
2709 8 : e.skipCurrentElement();
2710 139 : } else if (tag == "systemDistance") { // obsolete
2711 6 : style->set(Sid::minSystemDistance, e.readDouble());
2712 133 : } else if (tag == "stemDir") {
2713 0 : int voice = e.intAttribute("voice", 1) - 1;
2714 0 : switch (voice) {
2715 0 : case 0: tag = u"StemDir1";
2716 0 : break;
2717 0 : case 1: tag = u"StemDir2";
2718 0 : break;
2719 0 : case 2: tag = u"StemDir3";
2720 0 : break;
2721 0 : case 3: tag = u"StemDir4";
2722 0 : break;
2723 : }
2724 : }
2725 : // for compatibility:
2726 133 : else if (tag == "oddHeader" || tag == "evenHeader" || tag == "oddFooter" || tag == "evenFooter") {
2727 0 : tag += u"C";
2728 : } else {
2729 133 : if (!ReadStyleHook::readStyleProperties(style, e)) {
2730 48 : e.skipCurrentElement();
2731 : }
2732 : }
2733 148 : }
2734 :
2735 25 : readChordListHook.validate();
2736 25 : }
2737 :
2738 : //---------------------------------------------------------
2739 : // read114
2740 : // import old version <= 1.3 files
2741 : //---------------------------------------------------------
2742 :
2743 26 : muse::Ret Read114::readScoreFile(Score* score, XmlReader& e, ReadInOutData* out)
2744 : {
2745 26 : IF_ASSERT_FAILED(score->isMaster()) {
2746 0 : return make_ret(Err::FileUnknownError);
2747 : }
2748 :
2749 26 : ReadContext ctx(score);
2750 26 : if (out) {
2751 26 : if (out->overriddenSpatium.has_value()) {
2752 0 : ctx.setSpatium(out->overriddenSpatium.value());
2753 0 : ctx.setOverrideSpatium(true);
2754 : }
2755 :
2756 26 : ctx.setPropertiesToSkip(out->propertiesToSkip);
2757 26 : ctx.setForcePageMode(out->forcePageMode);
2758 : }
2759 :
2760 26 : DEFER {
2761 26 : if (out) {
2762 26 : out->settingsCompat = std::move(ctx.settingCompat());
2763 : }
2764 26 : };
2765 :
2766 26 : MasterScore* masterScore = static_cast<MasterScore*>(score);
2767 :
2768 26 : std::map<int, double> tm;
2769 414 : while (e.readNextStartElement()) {
2770 388 : ctx.setTrack(muse::nidx);
2771 388 : const AsciiStringView tag(e.name());
2772 388 : if (tag == "Staff") {
2773 33 : readStaffContent(masterScore, e, ctx);
2774 355 : } else if (tag == "KeySig") { // not supported
2775 0 : KeySig* ks = Factory::createKeySig(masterScore->dummy());
2776 0 : read400::TRead::read(ks, e, ctx);
2777 0 : delete ks;
2778 355 : } else if (tag == "siglist") {
2779 26 : Read206::readTimeSigMap(masterScore->m_sigmap, e, ctx);
2780 329 : } else if (tag == "programVersion") {
2781 24 : masterScore->setMscoreVersion(e.readText());
2782 305 : } else if (tag == "programRevision") {
2783 24 : masterScore->setMscoreRevision(e.readInt());
2784 281 : } else if (tag == "Mag"
2785 281 : || tag == "MagIdx"
2786 281 : || tag == "xoff"
2787 281 : || tag == "Symbols"
2788 281 : || tag == "cursorTrack"
2789 562 : || tag == "yoff") {
2790 23 : e.skipCurrentElement(); // obsolete
2791 258 : } else if (tag == "tempolist") {
2792 : // store the tempo list to create invisible tempo text later
2793 26 : masterScore->setTempoMultiplier(e.doubleAttribute("fix", 2.0));
2794 26 : while (e.readNextStartElement()) {
2795 0 : if (e.name() == "tempo") {
2796 0 : int tick = e.attribute("tick").toInt();
2797 0 : double tmp = e.readText().toDouble();
2798 0 : tick = ctx.fileDivision(tick);
2799 0 : tm[tick] = tmp;
2800 0 : } else if (e.name() == "relTempo") {
2801 0 : e.readText();
2802 : } else {
2803 0 : e.unknown();
2804 : }
2805 : }
2806 232 : } else if (tag == "playMode") {
2807 0 : e.skipCurrentElement();
2808 232 : } else if (tag == "SyntiSettings") {
2809 0 : masterScore->m_synthesizerState.read(e);
2810 232 : } else if (tag == "Spatium") {
2811 26 : if (ctx.overrideSpatium()) {
2812 0 : masterScore->style().setSpatium(ctx.spatium());
2813 0 : if (out) {
2814 0 : out->originalSpatium = e.readDouble() * DPMM;
2815 : } else {
2816 0 : e.skipCurrentElement();
2817 : }
2818 : } else {
2819 26 : masterScore->style().setSpatium(e.readDouble() * DPMM);
2820 : }
2821 206 : } else if (tag == "Division") {
2822 26 : masterScore->m_fileDivision = e.readInt();
2823 180 : } else if (tag == "showInvisible") {
2824 26 : masterScore->setShowInvisible(e.readInt());
2825 154 : } else if (tag == "showFrames") {
2826 26 : masterScore->setShowFrames(e.readInt());
2827 128 : } else if (tag == "showMargins") {
2828 0 : masterScore->setShowPageborders(e.readInt());
2829 128 : } else if (tag == "Style") {
2830 25 : double sp = masterScore->style().spatium();
2831 25 : compat::ReadChordListHook clhook(masterScore);
2832 25 : readStyle(&masterScore->style(), e, clhook);
2833 25 : if (ctx.overrideSpatium()) {
2834 0 : masterScore->style().setSpatium(sp);
2835 : }
2836 128 : } else if (tag == "TextStyle") {
2837 30 : e.skipCurrentElement();
2838 73 : } else if (tag == "page-layout") {
2839 26 : compat::PageFormat pf;
2840 26 : int pageNumberOffset = 0;
2841 26 : initPageFormat(&masterScore->style(), &pf);
2842 26 : readPageFormat(&pf, pageNumberOffset, e);
2843 26 : setPageFormat(&masterScore->style(), pf);
2844 26 : masterScore->setPageNumberOffset(pageNumberOffset);
2845 47 : } else if (tag == "copyright" || tag == "rights") {
2846 1 : Text* text = Factory::createText(masterScore->dummy(), TextStyleType::DEFAULT, false);
2847 1 : readText114(e, ctx, text, text);
2848 1 : masterScore->setMetaTag(u"copyright", text->plainText());
2849 1 : delete text;
2850 46 : } else if (tag == "movement-number") {
2851 0 : masterScore->setMetaTag(u"movementNumber", e.readText());
2852 46 : } else if (tag == "movement-title") {
2853 0 : masterScore->setMetaTag(u"movementTitle", e.readText());
2854 46 : } else if (tag == "work-number") {
2855 0 : masterScore->setMetaTag(u"workNumber", e.readText());
2856 46 : } else if (tag == "work-title") {
2857 0 : masterScore->setMetaTag(u"workTitle", e.readText());
2858 46 : } else if (tag == "source") {
2859 0 : masterScore->setMetaTag(u"source", e.readText());
2860 46 : } else if (tag == "metaTag") {
2861 0 : String name = e.attribute("name");
2862 0 : masterScore->setMetaTag(name, e.readText());
2863 46 : } else if (tag == "Part") {
2864 26 : Part* part = new Part(masterScore);
2865 26 : readPart(part, e, ctx);
2866 26 : masterScore->appendPart(part);
2867 20 : } else if (tag == "Slur") {
2868 16 : Slur* slur = Factory::createSlur(ctx.dummy());
2869 16 : Read206::readSlur206(e, ctx, slur);
2870 16 : ctx.addSpanner(slur);
2871 4 : } else if ((tag == "HairPin")
2872 3 : || (tag == "Ottava")
2873 2 : || (tag == "TextLine")
2874 1 : || (tag == "Volta")
2875 1 : || (tag == "Trill")
2876 7 : || (tag == "Pedal")) {
2877 4 : Spanner* s = toSpanner(Factory::createItemByName(tag, masterScore->dummy()));
2878 4 : if (tag == "Volta") {
2879 0 : readVolta114(e, ctx, toVolta(s));
2880 4 : } else if (tag == "Ottava") {
2881 1 : readOttava114(e, ctx, toOttava(s));
2882 3 : } else if (tag == "TextLine") {
2883 1 : readTextLine114(e, ctx, toTextLine(s));
2884 2 : } else if (tag == "Pedal") {
2885 1 : readPedal114(e, ctx, toPedal(s));
2886 1 : } else if (tag == "Trill") {
2887 0 : Ornament* ornament = Factory::createOrnament(score->dummy());
2888 0 : toTrill(s)->setOrnament(ornament);
2889 0 : Read206::readTrill206(e, ctx, toTrill(s));
2890 : } else {
2891 1 : assert(tag == "HairPin");
2892 1 : Read206::readHairpin206(e, ctx, toHairpin(s));
2893 : }
2894 4 : if (s->track() == muse::nidx) {
2895 0 : s->setTrack(ctx.track());
2896 : } else {
2897 4 : ctx.setTrack(s->track()); // update current track
2898 : }
2899 4 : if (s->tick() == Fraction(-1, 1)) {
2900 0 : s->setTick(ctx.tick());
2901 : } else {
2902 4 : ctx.setTick(s->tick()); // update current tick
2903 : }
2904 4 : if (s->track2() == muse::nidx) {
2905 0 : s->setTrack2(s->track());
2906 : }
2907 4 : if (s->ticks().isZero()) {
2908 0 : LOGD("zero spanner %s ticks: %d", s->typeName(), s->ticks().ticks());
2909 0 : delete s;
2910 : } else {
2911 4 : masterScore->addSpanner(s);
2912 : }
2913 0 : } else if (tag == "Excerpt") {
2914 0 : Excerpt* ex = new Excerpt(masterScore);
2915 0 : readExcerpt(ex, e, ctx);
2916 0 : masterScore->m_excerpts.push_back(ex);
2917 0 : } else if (tag == "Beam") {
2918 0 : Beam* beam = Factory::createBeam(masterScore);
2919 0 : read400::TRead::read(beam, e, ctx);
2920 : // _beams.append(beam);
2921 0 : delete beam;
2922 : } else {
2923 0 : e.unknown();
2924 : }
2925 : }
2926 :
2927 26 : if (e.error() != muse::XmlStreamReader::NoError) {
2928 0 : LOGD() << "XML read error at byte offset " << e.byteOffset() << ": " << e.errorString();
2929 0 : return make_ret(Err::FileBadFormat, e.errorString());
2930 : }
2931 :
2932 26 : setBarLineSpanToStaves(masterScore, ctx);
2933 :
2934 59 : for (Staff* s : masterScore->staves()) {
2935 33 : size_t idx = s->idx();
2936 33 : track_idx_t track = idx * VOICES;
2937 :
2938 85 : for (auto i : s->clefList()) {
2939 52 : Fraction tick = Fraction::fromTicks(i.first);
2940 52 : ClefType clefId = i.second.concertClef;
2941 52 : Measure* m = masterScore->tick2measure(tick);
2942 52 : if (!m) {
2943 0 : continue;
2944 : }
2945 52 : SegmentType st = SegmentType::Clef;
2946 52 : if (tick == m->tick()) {
2947 52 : if (m->prevMeasure()) {
2948 20 : m = m->prevMeasure();
2949 : } else {
2950 32 : st = SegmentType::HeaderClef;
2951 : }
2952 : }
2953 52 : Segment* seg = m->getSegment(st, tick);
2954 52 : if (seg->element(track)) {
2955 21 : seg->element(track)->setGenerated(false);
2956 : } else {
2957 31 : Clef* clef = Factory::createClef(seg);
2958 31 : clef->setClefType(clefId);
2959 31 : clef->setTrack(track);
2960 31 : clef->setGenerated(false);
2961 31 : seg->add(clef);
2962 : }
2963 : }
2964 :
2965 : // create missing KeySig
2966 33 : KeyList* km = s->keyList();
2967 69 : for (auto i = km->begin(); i != km->end(); ++i) {
2968 36 : Fraction tick = Fraction::fromTicks(i->first);
2969 36 : if (tick < Fraction(0, 1)) {
2970 0 : LOGD("read114: Key tick %d", tick.ticks());
2971 0 : continue;
2972 : }
2973 36 : Measure* m = masterScore->tick2measure(tick);
2974 36 : if (!m) { //empty score
2975 0 : break;
2976 : }
2977 36 : Segment* seg = m->getSegment(SegmentType::KeySig, tick);
2978 36 : if (seg->element(track)) {
2979 4 : toKeySig(seg->element(track))->setGenerated(false);
2980 : } else {
2981 32 : KeySigEvent ke = i->second;
2982 32 : KeySig* ks = Factory::createKeySig(seg);
2983 32 : ks->setKeySigEvent(ke);
2984 32 : ks->setTrack(track);
2985 32 : ks->setGenerated(false);
2986 32 : seg->add(ks);
2987 32 : }
2988 : }
2989 : }
2990 :
2991 46 : for (std::pair<int, Spanner*> p : masterScore->spanner()) {
2992 20 : Spanner* s = p.second;
2993 20 : if (s->isOttava() || s->isPedal() || s->isTrill() || s->isTextLine()) {
2994 3 : double yo = 0;
2995 3 : if (s->isOttava()) {
2996 : // fix ottava position
2997 1 : yo = masterScore->styleValue(Pid::OFFSET, Sid::ottavaPosAbove).value<PointF>().y();
2998 1 : if (s->placeBelow()) {
2999 0 : yo = -yo + s->staff()->staffHeight();
3000 : }
3001 2 : } else if (s->isPedal()) {
3002 1 : yo = masterScore->styleValue(Pid::OFFSET, Sid::pedalPosBelow).value<PointF>().y();
3003 1 : } else if (s->isTrill()) {
3004 0 : yo = masterScore->styleValue(Pid::OFFSET, Sid::trillPosAbove).value<PointF>().y();
3005 1 : } else if (s->isTextLine()) {
3006 1 : yo = -5.0 * masterScore->style().spatium();
3007 : }
3008 3 : if (!s->spannerSegments().empty()) {
3009 6 : for (SpannerSegment* seg : s->spannerSegments()) {
3010 3 : if (!seg->offset().isNull()) {
3011 0 : seg->ryoffset() = seg->offset().y() - yo;
3012 : }
3013 : }
3014 : } else {
3015 0 : s->ryoffset() = -yo;
3016 : }
3017 : }
3018 : }
3019 :
3020 : //
3021 : // remove "middle beam" flags from first ChordRest in
3022 : // measure
3023 : //
3024 385 : for (Measure* m = masterScore->firstMeasure(); m; m = m->nextMeasure()) {
3025 359 : size_t tracks = masterScore->nstaves() * VOICES;
3026 359 : bool first = true;
3027 2143 : for (size_t track = 0; track < tracks; ++track) {
3028 7328 : for (Segment* s = m->first(); s; s = s->next()) {
3029 5544 : if (s->segmentType() != SegmentType::ChordRest) {
3030 2272 : continue;
3031 : }
3032 3272 : ChordRest* cr = toChordRest(s->element(static_cast<int>(track)));
3033 3272 : if (cr) {
3034 800 : if (!first) {
3035 0 : switch (cr->beamMode()) {
3036 0 : case BeamMode::AUTO:
3037 : case BeamMode::BEGIN:
3038 : case BeamMode::END:
3039 : case BeamMode::NONE:
3040 0 : break;
3041 0 : case BeamMode::MID:
3042 : case BeamMode::BEGIN16:
3043 : case BeamMode::BEGIN32:
3044 0 : cr->setBeamMode(BeamMode::BEGIN);
3045 0 : break;
3046 0 : case BeamMode::INVALID:
3047 0 : if (cr->isChord()) {
3048 0 : cr->setBeamMode(BeamMode::AUTO);
3049 : } else {
3050 0 : cr->setBeamMode(BeamMode::NONE);
3051 : }
3052 0 : break;
3053 : }
3054 0 : first = false;
3055 : }
3056 : }
3057 : }
3058 : }
3059 : }
3060 396 : for (MeasureBase* mb = masterScore->first(); mb; mb = mb->next()) {
3061 370 : if (mb->isVBox()) {
3062 10 : VBox* b = toVBox(mb);
3063 10 : Spatium y = masterScore->style().styleS(Sid::staffUpperBorder);
3064 10 : b->setBottomGap(y);
3065 : }
3066 : }
3067 :
3068 26 : masterScore->m_fileDivision = Constants::DIVISION;
3069 :
3070 59 : for (Staff* staff : masterScore->staves()) {
3071 33 : staff->updateOttava();
3072 : }
3073 :
3074 : // adjust some styles
3075 26 : if (masterScore->style().styleB(Sid::hideEmptyStaves)) { // http://musescore.org/en/node/16228
3076 1 : masterScore->style().set(Sid::dontHideStavesInFirstSystem, false);
3077 : }
3078 26 : if (masterScore->style().styleB(Sid::showPageNumberOne)) { // http://musescore.org/en/node/21207
3079 1 : masterScore->style().set(Sid::evenFooterL, String(u"$P"));
3080 1 : masterScore->style().set(Sid::oddFooterR, String(u"$P"));
3081 : }
3082 26 : if (masterScore->style().styleI(Sid::minEmptyMeasures) == 0) {
3083 0 : masterScore->style().set(Sid::minEmptyMeasures, 1);
3084 : }
3085 26 : masterScore->style().set(Sid::frameSystemDistance, masterScore->style().styleS(Sid::frameSystemDistance) + 6.0_sp);
3086 26 : masterScore->resetStyleValue(Sid::measureSpacing);
3087 :
3088 : // add invisible tempo text if necessary
3089 : // some 1.3 scores have tempolist but no tempo text
3090 26 : masterScore->updateTicksAndTimeSigMap();
3091 26 : for (const auto& i : tm) {
3092 0 : Fraction tick = Fraction::fromTicks(i.first);
3093 0 : BeatsPerSecond tempo = i.second;
3094 :
3095 0 : if (i.first == tm.begin()->first && tempo == Constants::DEFAULT_TEMPO) {
3096 0 : continue;
3097 : }
3098 :
3099 0 : Measure* m = masterScore->tick2measure(tick);
3100 0 : if (!m) {
3101 0 : continue;
3102 : }
3103 :
3104 0 : Segment* seg = m->findSegment(SegmentType::ChordRest, tick);
3105 0 : bool hasMatchingTempo = false;
3106 0 : if (seg) {
3107 0 : for (const EngravingItem* segAnnotation : seg->annotations()) {
3108 0 : if (segAnnotation->isTempoText() && toTempoText(segAnnotation)->tempo() == tempo) {
3109 0 : hasMatchingTempo = true;
3110 0 : break;
3111 : }
3112 : }
3113 : }
3114 0 : if (hasMatchingTempo) {
3115 0 : continue;
3116 : }
3117 :
3118 0 : TempoText* tt = Factory::createTempoText(masterScore->dummy());
3119 0 : tt->setXmlText(String(u"<sym>metNoteQuarterUp</sym> = %1").arg(std::round(tempo.toBPM().val)));
3120 0 : tt->setTempo(tempo);
3121 0 : tt->setTrack(0);
3122 0 : tt->setVisible(false);
3123 :
3124 0 : if (!seg) {
3125 0 : seg = m->getSegment(SegmentType::ChordRest, tick);
3126 : }
3127 0 : seg->add(tt);
3128 : }
3129 :
3130 : // While reading the score, some elements might use `score->repeatList()` (which is incorrect
3131 : // anyway, because the repeatList will be incomplete because the score is incomplete, but some
3132 : // elements still do it).
3133 : // `score->repeatList()` calls `_repeatList->update()`; the repeat list then thinks that it is
3134 : // up-to-date from that point. But we weren't finished reading the score, so the score will still
3135 : // change. We need to tell the repeat list about that, so that it will be updated next time
3136 : // someone uses it.
3137 26 : masterScore->invalidateRepeatList();
3138 26 : masterScore->connectTies();
3139 26 : masterScore->undoRemoveStaleTieJumpPoints(false);
3140 :
3141 : // create excerpts
3142 : {
3143 26 : std::vector<Excerpt*> readExcerpts;
3144 26 : readExcerpts.swap(masterScore->m_excerpts);
3145 26 : for (Excerpt* excerpt : readExcerpts) {
3146 0 : if (excerpt->parts().empty()) { // ignore empty parts
3147 0 : delete excerpt;
3148 0 : continue;
3149 : }
3150 0 : masterScore->m_excerpts.push_back(excerpt);
3151 0 : Score* nscore = masterScore->createScore();
3152 0 : ReadStyleHook::setupDefaultStyle(nscore);
3153 0 : excerpt->setExcerptScore(nscore);
3154 0 : nscore->style().set(Sid::createMultiMeasureRests, true);
3155 0 : Excerpt::createExcerpt(excerpt);
3156 : }
3157 26 : }
3158 : // volta offsets in older scores are hardcoded to be relative to a voltaY of -2.0sp
3159 : // we'll force this and live with it for the score
3160 : // but we wait until now to do it so parts don't have this issue
3161 :
3162 26 : if (masterScore->style().styleV(Sid::voltaPosAbove) == DefaultStyle::baseStyle().value(Sid::voltaPosAbove)) {
3163 26 : masterScore->style().set(Sid::voltaPosAbove, PointF(0.0, -2.0f));
3164 : }
3165 :
3166 52 : for (Part* p : masterScore->parts()) {
3167 26 : p->updateHarmonyChannels(false);
3168 : }
3169 :
3170 26 : masterScore->rebuildMidiMapping();
3171 26 : masterScore->updateChannel();
3172 :
3173 26 : CompatUtils::assignInitialPartToExcerpts(masterScore->excerpts());
3174 :
3175 : // Cleanup invalid spanners
3176 26 : std::vector<Spanner*> invalidSpanners;
3177 52 : auto spanners = score->spanner();
3178 46 : for (auto iter = spanners.begin(); iter != spanners.end(); ++iter) {
3179 20 : Spanner* spanner = (*iter).second;
3180 20 : bool invalid = spanner->tick().negative() || spanner->track() == muse::nidx;
3181 20 : if (invalid) {
3182 0 : invalidSpanners.push_back(spanner);
3183 : }
3184 : }
3185 26 : for (Spanner* invalidSpanner : invalidSpanners) {
3186 0 : score->removeElement(invalidSpanner);
3187 : }
3188 :
3189 26 : return muse::make_ok();
3190 26 : }
3191 :
3192 0 : void Read114::readExcerpt(Excerpt* item, XmlReader& e, ReadContext&)
3193 : {
3194 0 : const std::vector<Part*>& pl = item->masterScore()->parts();
3195 0 : std::vector<Part*> parts;
3196 0 : while (e.readNextStartElement()) {
3197 0 : const AsciiStringView tag = e.name();
3198 0 : if (tag == "name" || tag == "title") {
3199 0 : item->setName(e.readText().trimmed());
3200 0 : } else if (tag == "part") {
3201 0 : size_t partIdx = static_cast<size_t>(e.readInt());
3202 0 : if (partIdx >= pl.size()) {
3203 0 : LOGD("Excerpt::read: bad part index");
3204 : } else {
3205 0 : parts.push_back(pl.at(partIdx));
3206 : }
3207 : }
3208 : }
3209 0 : item->setParts(parts);
3210 0 : }
3211 :
3212 0 : bool Read114::pasteStaff(XmlReader&, Segment*, staff_idx_t, Fraction)
3213 : {
3214 0 : UNREACHABLE;
3215 : return false;
3216 : }
3217 :
3218 0 : void Read114::pasteSymbols(XmlReader&, ChordRest*)
3219 : {
3220 0 : UNREACHABLE;
3221 : }
3222 :
3223 0 : void Read114::readTremoloCompat(TremoloCompat*, XmlReader&)
3224 : {
3225 0 : UNREACHABLE;
3226 : }
3227 :
3228 : // also propagates to non-generated bar lines
3229 59 : void Read114::setBarLineSpanToStaves(Score* score, const read400::ReadContext& ctx)
3230 : {
3231 59 : const size_t numStaves = score->nstaves();
3232 59 : size_t barLineSpan = 0;
3233 146 : for (Staff* s : score->staves()) {
3234 87 : const staff_idx_t staffIdx = s->idx();
3235 87 : const size_t maxSpan = numStaves - staffIdx - 1;
3236 :
3237 87 : size_t staffBarLineSpan = ctx.getStaffBarLineSpan(staffIdx);
3238 87 : if (staffBarLineSpan > maxSpan) {
3239 0 : LOGW() << "invalid barline span " << staffBarLineSpan << " (max " << maxSpan << ")";
3240 0 : staffBarLineSpan = maxSpan;
3241 : }
3242 :
3243 87 : barLineSpan = std::max(barLineSpan, staffBarLineSpan);
3244 87 : if (barLineSpan == 0) {
3245 79 : s->setProperty(Pid::STAFF_BARLINE_SPAN, false);
3246 79 : continue;
3247 : }
3248 8 : --barLineSpan;
3249 :
3250 8 : s->setProperty(Pid::STAFF_BARLINE_SPAN, true);
3251 : }
3252 59 : }
3253 :
3254 0 : void Read114::doReadItem(EngravingItem*, XmlReader&)
3255 : {
3256 0 : UNREACHABLE;
3257 : }
|