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 "read206.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 "iengravingfont.h"
34 :
35 : #include "rw/compat/compatutils.h"
36 :
37 : #include "style/style.h"
38 : #include "style/textstyle.h"
39 :
40 : #include "types/typesconv.h"
41 : #include "types/symnames.h"
42 :
43 : #include "dom/accidental.h"
44 : #include "dom/ambitus.h"
45 : #include "dom/arpeggio.h"
46 : #include "dom/articulation.h"
47 : #include "dom/barline.h"
48 : #include "dom/beam.h"
49 : #include "dom/bend.h"
50 : #include "dom/box.h"
51 : #include "dom/breath.h"
52 : #include "dom/chord.h"
53 : #include "dom/chordline.h"
54 : #include "dom/drumset.h"
55 : #include "dom/dynamic.h"
56 : #include "dom/excerpt.h"
57 : #include "dom/factory.h"
58 : #include "dom/fermata.h"
59 : #include "dom/fingering.h"
60 : #include "dom/glissando.h"
61 : #include "dom/hairpin.h"
62 : #include "dom/hook.h"
63 : #include "dom/image.h"
64 : #include "dom/keysig.h"
65 : #include "dom/linkedobjects.h"
66 : #include "dom/lyrics.h"
67 : #include "dom/marker.h"
68 : #include "dom/masterscore.h"
69 : #include "dom/measure.h"
70 : #include "dom/measurenumber.h"
71 : #include "dom/measurerepeat.h"
72 : #include "dom/mmrest.h"
73 : #include "dom/note.h"
74 : #include "dom/noteline.h"
75 : #include "dom/ornament.h"
76 : #include "dom/ottava.h"
77 : #include "dom/page.h"
78 : #include "dom/part.h"
79 : #include "dom/pedal.h"
80 : #include "dom/rehearsalmark.h"
81 : #include "dom/rest.h"
82 : #include "dom/score.h"
83 : #include "dom/sig.h"
84 : #include "dom/slur.h"
85 : #include "dom/spacer.h"
86 : #include "dom/staff.h"
87 : #include "dom/staff.h"
88 : #include "dom/stafftext.h"
89 : #include "dom/stem.h"
90 : #include "dom/stemslash.h"
91 : #include "dom/system.h"
92 : #include "dom/systemdivider.h"
93 : #include "dom/systemtext.h"
94 : #include "dom/tempotext.h"
95 : #include "dom/textline.h"
96 : #include "dom/tie.h"
97 : #include "dom/timesig.h"
98 : #include "dom/trill.h"
99 : #include "dom/tremolotwochord.h"
100 : #include "dom/tremolosinglechord.h"
101 : #include "dom/tuplet.h"
102 : #include "dom/utils.h"
103 : #include "dom/volta.h"
104 :
105 : #include "../../editing/editstaffbrackets.h"
106 : #include "../../editing/transpose.h"
107 :
108 : #include "../compat/readchordlisthook.h"
109 : #include "../compat/readstyle.h"
110 :
111 : #include "engraving/rw/read114/read114.h"
112 : #include "engraving/rw/read400/tread.h"
113 :
114 : #include "log.h"
115 :
116 : using namespace mu;
117 : using namespace muse;
118 : using namespace mu::engraving;
119 : using namespace mu::engraving::rw;
120 : using namespace mu::engraving::read400;
121 : using namespace mu::engraving::read206;
122 : using namespace mu::engraving::compat;
123 :
124 : static void readText206(XmlReader& e, ReadContext& ctx, TextBase* t, EngravingItem* be);
125 :
126 26 : static int readSigEvent(SigEvent* sigEvent, XmlReader& e, int fileDivision)
127 : {
128 26 : int tick = e.intAttribute("tick", 0);
129 26 : tick = tick * Constants::DIVISION / fileDivision;
130 :
131 26 : int numerator = 1;
132 26 : int denominator = 1;
133 26 : int denominator2 = -1;
134 26 : int numerator2 = -1;
135 :
136 78 : while (e.readNextStartElement()) {
137 52 : const AsciiStringView tag(e.name());
138 52 : if (tag == "nom") {
139 26 : numerator = e.readInt();
140 26 : } else if (tag == "denom") {
141 26 : denominator = e.readInt();
142 0 : } else if (tag == "nom2") {
143 0 : numerator2 = e.readInt();
144 0 : } else if (tag == "denom2") {
145 0 : denominator2 = e.readInt();
146 : } else {
147 0 : e.unknown();
148 : }
149 : }
150 26 : if ((numerator2 == -1) || (denominator2 == -1)) {
151 26 : numerator2 = numerator;
152 26 : denominator2 = denominator;
153 : }
154 :
155 26 : sigEvent->setTimesig(TimeSigFrac(numerator, denominator));
156 26 : sigEvent->setNominal(TimeSigFrac(numerator2, denominator2));
157 26 : return tick;
158 : }
159 :
160 26 : void Read206::readTimeSigMap(TimeSigMap* map, XmlReader& e, read400::ReadContext& ctx)
161 : {
162 52 : while (e.readNextStartElement()) {
163 26 : const AsciiStringView tag(e.name());
164 26 : if (tag == "sig") {
165 26 : SigEvent t;
166 26 : int tick = readSigEvent(&t, e, ctx.fileDivision());
167 26 : (*map)[tick] = t;
168 : } else {
169 0 : e.unknown();
170 : }
171 : }
172 26 : map->normalize();
173 26 : }
174 :
175 : //---------------------------------------------------------
176 : // excessTextStyles206
177 : // The first map has the name of the style as the string
178 : // The second map has the mapping of each Sid that the style identifies
179 : // to the default value for that sid.
180 : //---------------------------------------------------------
181 :
182 : static std::map<String, std::map<Sid, PropertyValue> > excessTextStyles206;
183 :
184 107 : void Read206::readTextStyle206(MStyle* style, XmlReader& e, ReadContext& ctx, std::map<String, std::map<Sid, PropertyValue> >& excessStyles)
185 : {
186 107 : String family = u"FreeSerif";
187 107 : double size = 10;
188 107 : bool sizeIsSpatiumDependent = false;
189 107 : FontStyle fontStyle = FontStyle::Normal;
190 107 : Align align = { AlignH::LEFT, AlignV::TOP };
191 107 : PointF offset;
192 107 : OffsetType offsetType = OffsetType::SPATIUM;
193 :
194 107 : FrameType frameType = FrameType::NO_FRAME;
195 107 : Spatium paddingWidth(0.0);
196 107 : Spatium frameWidth(0.0);
197 107 : Color foregroundColor = Color::BLACK;
198 107 : Color backgroundColor = Color::transparent;
199 :
200 107 : PlacementV placement = PlacementV::ABOVE;
201 107 : bool placementValid = false;
202 :
203 107 : String name = e.attribute("name");
204 107 : Color frameColor = Color::BLACK;
205 :
206 107 : bool systemFlag = false;
207 107 : double lineWidth = -1.0;
208 :
209 942 : while (e.readNextStartElement()) {
210 835 : const AsciiStringView tag(e.name());
211 :
212 835 : if (tag == "name") {
213 106 : name = e.readText();
214 729 : } else if (tag == "family") {
215 107 : family = e.readText();
216 622 : } else if (tag == "size") {
217 107 : size = e.readDouble();
218 515 : } else if (tag == "bold") {
219 13 : if (e.readInt()) {
220 13 : fontStyle = fontStyle + FontStyle::Bold;
221 : }
222 502 : } else if (tag == "italic") {
223 16 : if (e.readInt()) {
224 16 : fontStyle = fontStyle + FontStyle::Italic;
225 : }
226 486 : } else if (tag == "underline") {
227 0 : if (e.readInt()) {
228 0 : fontStyle = fontStyle + FontStyle::Underline;
229 : }
230 : #if 0 // should not happen, but won't harm either
231 : } else if (tag == "strike") {
232 : if (e.readInt()) {
233 : fontStyle = fontStyle + FontStyle::Strike;
234 : }
235 : #endif
236 486 : } else if (tag == "align") { // obsolete
237 0 : e.skipCurrentElement();
238 486 : } else if (tag == "anchor") { // obsolete
239 0 : e.skipCurrentElement();
240 486 : } else if (tag == "halign") {
241 107 : align.horizontal = TConv::fromXml(e.readAsciiText(), AlignH::LEFT);
242 379 : } else if (tag == "valign") {
243 107 : align.vertical = TConv::fromXml(e.readAsciiText(), AlignV::TOP);
244 272 : } else if (tag == "xoffset") {
245 41 : double xo = e.readDouble();
246 41 : if (offsetType == OffsetType::ABS) {
247 0 : xo /= INCH;
248 : }
249 41 : offset.setX(xo);
250 231 : } else if (tag == "yoffset") {
251 41 : double yo = e.readDouble();
252 41 : if (offsetType == OffsetType::ABS) {
253 0 : yo /= INCH;
254 : }
255 41 : offset.setY(yo);
256 190 : } else if (tag == "rxoffset" || tag == "ryoffset") { // obsolete
257 0 : e.readDouble();
258 190 : } else if (tag == "offsetType") {
259 107 : const String& val(e.readText());
260 107 : OffsetType ot = OffsetType::ABS;
261 107 : if (val == "spatium" || val == "1") {
262 83 : ot = OffsetType::SPATIUM;
263 : }
264 107 : if (ot != offsetType) {
265 24 : offsetType = ot;
266 24 : if (ot == OffsetType::ABS) {
267 24 : offset /= INCH; // convert spatium -> inch
268 : } else {
269 0 : offset *= INCH; // convert inch -> spatium
270 : }
271 : }
272 190 : } else if (tag == "sizeIsSpatiumDependent" || tag == "spatiumSizeDependent") {
273 58 : sizeIsSpatiumDependent = e.readInt();
274 25 : } else if (tag == "frameWidth") { // obsolete
275 0 : frameType = FrameType::RECTANGLE;
276 0 : /*frameWidthMM =*/ e.readDouble();
277 25 : } else if (tag == "frameWidthS") {
278 3 : frameType = FrameType::RECTANGLE;
279 3 : frameWidth = Spatium(e.readDouble());
280 22 : } else if (tag == "frame") {
281 0 : frameType = e.readInt() ? FrameType::RECTANGLE : FrameType::NO_FRAME;
282 22 : } else if (tag == "paddingWidth") { // obsolete
283 : /*paddingWidthMM =*/
284 0 : e.readDouble();
285 22 : } else if (tag == "paddingWidthS") {
286 3 : paddingWidth = Spatium(e.readDouble());
287 19 : } else if (tag == "frameRound") {
288 3 : e.readDouble();
289 16 : } else if (tag == "frameColor") {
290 3 : frameColor = e.readColor();
291 13 : } else if (tag == "foregroundColor") {
292 0 : foregroundColor = e.readColor();
293 13 : } else if (tag == "backgroundColor") {
294 0 : backgroundColor = e.readColor();
295 13 : } else if (tag == "circle") {
296 1 : frameType = e.readInt() ? FrameType::CIRCLE : FrameType::NO_FRAME;
297 12 : } else if (tag == "systemFlag") {
298 12 : systemFlag = e.readInt();
299 0 : } else if (tag == "placement") {
300 0 : String value(e.readText());
301 0 : if (value == "above") {
302 0 : placement = PlacementV::ABOVE;
303 0 : } else if (value == "below") {
304 0 : placement = PlacementV::BELOW;
305 : }
306 0 : placementValid = true;
307 0 : } else if (tag == "lineWidth") {
308 0 : lineWidth = e.readDouble();
309 : } else {
310 0 : e.unknown();
311 : }
312 : }
313 107 : if (family == "MuseJazz") {
314 42 : family = u"MuseJazz Text";
315 : }
316 :
317 : struct StyleTable {
318 : const char* name;
319 : TextStyleType ss;
320 107 : } styleTable[] = {
321 : { "", TextStyleType::DEFAULT },
322 : { "Title", TextStyleType::TITLE },
323 : { "Subtitle", TextStyleType::SUBTITLE },
324 : { "Composer", TextStyleType::COMPOSER },
325 : { "Lyricist", TextStyleType::LYRICIST },
326 : { "Lyrics Odd Lines", TextStyleType::LYRICS_ODD },
327 : { "Lyrics Even Lines", TextStyleType::LYRICS_EVEN },
328 : { "Fingering", TextStyleType::FINGERING },
329 : { "LH Guitar Fingering", TextStyleType::LH_GUITAR_FINGERING },
330 : { "RH Guitar Fingering", TextStyleType::RH_GUITAR_FINGERING },
331 : { "String Number", TextStyleType::STRING_NUMBER },
332 : { "Instrument Name (Long)", TextStyleType::INSTRUMENT_LONG },
333 : { "Instrument Name (Short)", TextStyleType::INSTRUMENT_SHORT },
334 : { "Instrument Name (Part)", TextStyleType::INSTRUMENT_EXCERPT },
335 : { "Dynamics", TextStyleType::DYNAMICS },
336 : { "Technique", TextStyleType::IGNORED_TYPES },
337 : { "Tempo", TextStyleType::TEMPO },
338 : { "Metronome", TextStyleType::METRONOME },
339 : { "Measure Number", TextStyleType::MEASURE_NUMBER },
340 : { "Translator", TextStyleType::TRANSLATOR },
341 : { "Tuplet", TextStyleType::TUPLET },
342 : { "System", TextStyleType::SYSTEM },
343 : { "Staff", TextStyleType::STAFF },
344 : { "Chord Symbol", TextStyleType::HARMONY_A },
345 : { "Rehearsal Mark", TextStyleType::REHEARSAL_MARK },
346 : { "Repeat Text Left", TextStyleType::REPEAT_LEFT },
347 : { "Repeat Text Right", TextStyleType::REPEAT_RIGHT },
348 : { "Frame", TextStyleType::FRAME },
349 : { "Text Line", TextStyleType::TEXTLINE },
350 : { "Glissando", TextStyleType::GLISSANDO },
351 : { "Ottava", TextStyleType::OTTAVA },
352 : { "Pedal", TextStyleType::PEDAL },
353 : { "Hairpin", TextStyleType::HAIRPIN },
354 : { "Bend", TextStyleType::BEND },
355 : { "Header", TextStyleType::HEADER },
356 : { "Footer", TextStyleType::FOOTER },
357 : { "Instrument Change", TextStyleType::INSTRUMENT_CHANGE },
358 : { "Repeat Text", TextStyleType::IGNORED_TYPES, }, // Repeat Text style no longer exists
359 : { "Figured Bass", TextStyleType::IGNORED_TYPES, }, // F.B. data are in style properties
360 : { "Volta", TextStyleType::VOLTA },
361 : };
362 107 : TextStyleType ss = TextStyleType::TEXT_TYPES;
363 2792 : for (const auto& i : styleTable) {
364 2762 : if (name == i.name) {
365 77 : ss = i.ss;
366 77 : break;
367 : }
368 : }
369 :
370 107 : if (ss == TextStyleType::IGNORED_TYPES) {
371 4 : return;
372 : }
373 :
374 103 : if (ss == TextStyleType::DEFAULT) {
375 : // This could be false in older files but newer files must have this true
376 1 : sizeIsSpatiumDependent = true;
377 : }
378 :
379 103 : bool isExcessStyle = false;
380 103 : if (ss == TextStyleType::TEXT_TYPES) {
381 30 : ss = ctx.addUserTextStyle(name);
382 30 : if (ss == TextStyleType::TEXT_TYPES) {
383 0 : LOGD("unhandled substyle <%s>", muPrintable(name));
384 0 : isExcessStyle = true;
385 : } else {
386 30 : int idx = int(ss) - int(TextStyleType::USER1);
387 30 : if ((int(ss) < int(TextStyleType::USER1)) || (int(ss) > int(TextStyleType::USER12))) {
388 0 : LOGD("User style index %d outside of range.", idx);
389 0 : return;
390 : }
391 30 : Sid sid[] = { Sid::user1Name, Sid::user2Name, Sid::user3Name, Sid::user4Name, Sid::user5Name, Sid::user6Name,
392 : Sid::user7Name, Sid::user8Name, Sid::user9Name, Sid::user10Name, Sid::user11Name, Sid::user12Name };
393 30 : style->set(sid[idx], name);
394 : }
395 : }
396 :
397 103 : std::map<Sid, PropertyValue> excessPairs;
398 : const TextStyle* ts;
399 103 : if (isExcessStyle) {
400 0 : ts = textStyle(TextStyleType::USER1);
401 : } else {
402 103 : ts = textStyle(ss);
403 : }
404 1751 : for (const auto& i : *ts) {
405 1648 : if (ctx.shouldSkipProperty(i.pid)) {
406 0 : continue;
407 : }
408 :
409 1648 : PropertyValue value;
410 1648 : if (i.sid == Sid::NOSTYLE) {
411 0 : break;
412 : }
413 1648 : switch (i.pid) {
414 0 : case Pid::TEXT_STYLE:
415 0 : value = ss;
416 0 : break;
417 103 : case Pid::BEGIN_FONT_FACE:
418 : case Pid::CONTINUE_FONT_FACE:
419 : case Pid::END_FONT_FACE:
420 : case Pid::FONT_FACE:
421 103 : value = family;
422 103 : break;
423 103 : case Pid::BEGIN_FONT_SIZE:
424 : case Pid::CONTINUE_FONT_SIZE:
425 : case Pid::END_FONT_SIZE:
426 : case Pid::FONT_SIZE:
427 103 : value = size;
428 103 : break;
429 103 : case Pid::BEGIN_FONT_STYLE:
430 : case Pid::CONTINUE_FONT_STYLE:
431 : case Pid::END_FONT_STYLE:
432 : case Pid::FONT_STYLE:
433 103 : value = int(fontStyle);
434 103 : break;
435 103 : case Pid::FRAME_TYPE:
436 103 : value = int(frameType);
437 103 : break;
438 103 : case Pid::FRAME_WIDTH:
439 103 : value = frameWidth;
440 103 : break;
441 103 : case Pid::FRAME_PADDING:
442 103 : value = paddingWidth;
443 103 : break;
444 103 : case Pid::FRAME_FG_COLOR:
445 103 : value = PropertyValue::fromValue(frameColor);
446 103 : break;
447 103 : case Pid::FRAME_BG_COLOR:
448 103 : value = PropertyValue::fromValue(backgroundColor);
449 103 : break;
450 103 : case Pid::SIZE_SPATIUM_DEPENDENT:
451 103 : value = sizeIsSpatiumDependent;
452 103 : break;
453 103 : case Pid::BEGIN_TEXT_ALIGN:
454 : case Pid::CONTINUE_TEXT_ALIGN:
455 : case Pid::END_TEXT_ALIGN:
456 : case Pid::ALIGN:
457 103 : value = PropertyValue::fromValue(align);
458 103 : break;
459 91 : case Pid::POSITION:
460 91 : value = PropertyValue::fromValue(align.horizontal);
461 91 : break;
462 0 : case Pid::SYSTEM_FLAG:
463 0 : value = systemFlag;
464 0 : break;
465 0 : case Pid::BEGIN_HOOK_HEIGHT:
466 : case Pid::END_HOOK_HEIGHT:
467 0 : value = PropertyValue();
468 0 : break;
469 0 : case Pid::PLACEMENT:
470 0 : if (placementValid) {
471 0 : value = placement;
472 : }
473 0 : break;
474 0 : case Pid::LINE_WIDTH:
475 0 : if (lineWidth != -1.0) {
476 0 : value = lineWidth;
477 : }
478 0 : break;
479 527 : default:
480 : // LOGD("unhandled property <%s>%d", propertyName(i.pid), int (i.pid));
481 527 : break;
482 : }
483 1648 : if (value.isValid()) {
484 1121 : if (isExcessStyle) {
485 0 : excessPairs[i.sid] = value;
486 : } else {
487 1121 : style->set(i.sid, value);
488 : }
489 : }
490 : // else
491 : // LOGD("invalid style value <%s> pid<%s>", MStyle::valueName(i.sid), propertyName(i.pid));
492 1648 : }
493 :
494 103 : if (isExcessStyle && excessPairs.size() > 0) {
495 0 : excessStyles[name] = excessPairs;
496 : }
497 111 : }
498 :
499 142 : void Read206::readAccidental206(Accidental* a, XmlReader& e, ReadContext& ctx)
500 : {
501 360 : while (e.readNextStartElement()) {
502 218 : const AsciiStringView tag(e.name());
503 218 : if (tag == "bracket") {
504 21 : int i = e.readInt();
505 21 : if (i == 0 || i == 1) {
506 21 : a->setBracket(AccidentalBracket(i));
507 : }
508 197 : } else if (tag == "subtype") {
509 142 : String text = e.readText();
510 : static const std::map<String, AccidentalType> accMap = {
511 0 : { u"none", AccidentalType::NONE },
512 0 : { u"sharp", AccidentalType::SHARP },
513 0 : { u"flat", AccidentalType::FLAT },
514 0 : { u"natural", AccidentalType::NATURAL },
515 0 : { u"double sharp", AccidentalType::SHARP2 },
516 0 : { u"double flat", AccidentalType::FLAT2 },
517 0 : { u"flat-slash", AccidentalType::FLAT_SLASH },
518 0 : { u"flat-slash2", AccidentalType::FLAT_SLASH2 },
519 0 : { u"mirrored-flat2", AccidentalType::MIRRORED_FLAT2 },
520 0 : { u"mirrored-flat", AccidentalType::MIRRORED_FLAT },
521 0 : { u"sharp-slash", AccidentalType::SHARP_SLASH },
522 0 : { u"sharp-slash2", AccidentalType::SHARP_SLASH2 },
523 0 : { u"sharp-slash3", AccidentalType::SHARP_SLASH3 },
524 0 : { u"sharp-slash4", AccidentalType::SHARP_SLASH4 },
525 0 : { u"sharp arrow up", AccidentalType::SHARP_ARROW_UP },
526 0 : { u"sharp arrow down", AccidentalType::SHARP_ARROW_DOWN },
527 0 : { u"flat arrow up", AccidentalType::FLAT_ARROW_UP },
528 0 : { u"flat arrow down", AccidentalType::FLAT_ARROW_DOWN },
529 0 : { u"natural arrow up", AccidentalType::NATURAL_ARROW_UP },
530 0 : { u"natural arrow down", AccidentalType::NATURAL_ARROW_DOWN },
531 0 : { u"sori", AccidentalType::SORI },
532 0 : { u"koron", AccidentalType::KORON }
533 166 : };
534 142 : auto it = accMap.find(text);
535 142 : if (it == accMap.end()) {
536 0 : LOGD("invalid type %s", muPrintable(text));
537 0 : a->setAccidentalType(AccidentalType::NONE);
538 : } else {
539 142 : a->setAccidentalType(it->second);
540 : }
541 197 : } else if (tag == "role") {
542 43 : AccidentalRole r = AccidentalRole(e.readInt());
543 43 : if (r == AccidentalRole::AUTO || r == AccidentalRole::USER) {
544 43 : a->setRole(r);
545 : }
546 12 : } else if (tag == "small") {
547 0 : a->setSmall(e.readInt());
548 12 : } else if (TRead::readItemProperties(a, e, ctx)) {
549 : } else {
550 0 : e.unknown();
551 : }
552 : }
553 143 : }
554 :
555 0 : VoiceAssignment Read206::readDynamicRange(int i)
556 : {
557 0 : switch (i) {
558 0 : case 0:
559 0 : return VoiceAssignment::ALL_VOICE_IN_STAFF;
560 0 : case 1:
561 : case 2:
562 0 : break;
563 : }
564 :
565 0 : return VoiceAssignment::ALL_VOICE_IN_INSTRUMENT;
566 : }
567 :
568 253 : NoteHeadGroup Read206::convertHeadGroup(int i)
569 : {
570 : NoteHeadGroup val;
571 253 : switch (i) {
572 89 : case 1:
573 89 : val = NoteHeadGroup::HEAD_CROSS;
574 89 : break;
575 28 : case 2:
576 28 : val = NoteHeadGroup::HEAD_DIAMOND;
577 28 : break;
578 14 : case 3:
579 14 : val = NoteHeadGroup::HEAD_TRIANGLE_DOWN;
580 14 : break;
581 9 : case 4:
582 9 : val = NoteHeadGroup::HEAD_MI;
583 9 : break;
584 14 : case 5:
585 14 : val = NoteHeadGroup::HEAD_SLASH;
586 14 : break;
587 18 : case 6:
588 18 : val = NoteHeadGroup::HEAD_XCIRCLE;
589 18 : break;
590 7 : case 7:
591 7 : val = NoteHeadGroup::HEAD_DO;
592 7 : break;
593 7 : case 8:
594 7 : val = NoteHeadGroup::HEAD_RE;
595 7 : break;
596 7 : case 9:
597 7 : val = NoteHeadGroup::HEAD_FA;
598 7 : break;
599 7 : case 10:
600 7 : val = NoteHeadGroup::HEAD_LA;
601 7 : break;
602 7 : case 11:
603 7 : val = NoteHeadGroup::HEAD_TI;
604 7 : break;
605 7 : case 12:
606 7 : val = NoteHeadGroup::HEAD_SOL;
607 7 : break;
608 8 : case 13:
609 8 : val = NoteHeadGroup::HEAD_BREVIS_ALT;
610 8 : break;
611 31 : case 0:
612 : default:
613 31 : val = NoteHeadGroup::HEAD_NORMAL;
614 : }
615 253 : return val;
616 : }
617 :
618 43 : static NoteHeadType convertHeadType(int i)
619 : {
620 : NoteHeadType val;
621 43 : switch (i) {
622 14 : case 0:
623 14 : val = NoteHeadType::HEAD_WHOLE;
624 14 : break;
625 15 : case 1:
626 15 : val = NoteHeadType::HEAD_HALF;
627 15 : break;
628 0 : case 2:
629 0 : val = NoteHeadType::HEAD_QUARTER;
630 0 : break;
631 14 : case 3:
632 14 : val = NoteHeadType::HEAD_BREVIS;
633 14 : break;
634 0 : default:
635 0 : val = NoteHeadType::HEAD_AUTO;
636 : }
637 43 : return val;
638 : }
639 :
640 : //---------------------------------------------------------
641 : // ArticulationNames
642 : //---------------------------------------------------------
643 :
644 : static struct ArticulationNames {
645 : SymId id;
646 : AsciiStringView name;
647 : } articulationNames[] = {
648 : { SymId::fermataAbove, "fermata", },
649 : { SymId::fermataShortAbove, "shortfermata", },
650 : { SymId::fermataLongAbove, "longfermata", },
651 : { SymId::fermataVeryLongAbove, "verylongfermata", },
652 : { SymId::articAccentAbove, "sforzato", },
653 : { SymId::articStaccatoAbove, "staccato", },
654 : { SymId::articStaccatissimoAbove, "staccatissimo", },
655 : { SymId::articTenutoAbove, "tenuto", },
656 : { SymId::articTenutoStaccatoAbove, "portato", },
657 : { SymId::articMarcatoAbove, "marcato", },
658 : { SymId::guitarFadeIn, "fadein", },
659 : { SymId::guitarFadeOut, "fadeout", },
660 : { SymId::guitarVolumeSwell, "volumeswell", },
661 : { SymId::wiggleSawtooth, "wigglesawtooth", },
662 : { SymId::wiggleSawtoothWide, "wigglesawtoothwide", },
663 : { SymId::wiggleVibratoLargeFaster, "wigglevibratolargefaster", },
664 : { SymId::wiggleVibratoLargeSlowest, "wigglevibratolargeslowest", },
665 : { SymId::brassMuteOpen, "ouvert", },
666 : { SymId::brassMuteClosed, "plusstop", },
667 : { SymId::stringsUpBow, "upbow", },
668 : { SymId::stringsDownBow, "downbow", },
669 : { SymId::ornamentTurnInverted, "reverseturn", },
670 : { SymId::ornamentTurn, "turn", },
671 : { SymId::ornamentTrill, "trill", },
672 : { SymId::ornamentShortTrill, "prall", },
673 : { SymId::ornamentMordent, "mordent", },
674 : { SymId::ornamentTremblement, "prallprall", },
675 : { SymId::ornamentPrallMordent, "prallmordent", },
676 : { SymId::ornamentUpPrall, "upprall", },
677 : { SymId::ornamentUpMordent, "upmordent", },
678 : { SymId::ornamentDownMordent, "downmordent", },
679 : { SymId::ornamentPrallDown, "pralldown", },
680 : { SymId::ornamentPrallUp, "prallup", },
681 : { SymId::ornamentLinePrall, "lineprall", },
682 : { SymId::ornamentPrecompSlide, "schleifer", },
683 : { SymId::pluckedSnapPizzicatoAbove, "snappizzicato", },
684 : { SymId::stringsThumbPosition, "thumb", },
685 : { SymId::luteFingeringRHThumb, "lutefingeringthumb", },
686 : { SymId::luteFingeringRHFirst, "lutefingering1st", },
687 : { SymId::luteFingeringRHSecond, "lutefingering2nd", },
688 : { SymId::luteFingeringRHThird, "lutefingering3rd", },
689 :
690 : { SymId::ornamentPrecompMordentUpperPrefix, "downprall" },
691 : { SymId::ornamentPrecompMordentUpperPrefix, "ornamentDownPrall" },
692 : };
693 :
694 249 : SymId Read206::articulationNames2SymId206(const AsciiStringView& s)
695 : {
696 4300 : for (auto i : articulationNames) {
697 4286 : if (i.name == s) {
698 235 : return i.id;
699 : }
700 : }
701 14 : return SymId::noSym;
702 : }
703 :
704 : //---------------------------------------------------------
705 : // readDrumset
706 : //---------------------------------------------------------
707 :
708 68 : static void readDrumset206(Drumset* ds, XmlReader& e)
709 : {
710 68 : int pitch = e.intAttribute("pitch", -1);
711 68 : if (!pitchIsValid(pitch)) {
712 0 : LOGD("load drumset: invalid pitch %d", pitch);
713 0 : return;
714 : }
715 415 : while (e.readNextStartElement()) {
716 347 : const AsciiStringView tag(e.name());
717 347 : if (tag == "head") {
718 68 : ds->drum(pitch).notehead = Read206::convertHeadGroup(e.readInt());
719 279 : } else if (tag == "variants") {
720 0 : while (e.readNextStartElement()) {
721 0 : const AsciiStringView tagv(e.name());
722 0 : if (tagv == "variant") {
723 0 : DrumInstrumentVariant div;
724 0 : div.pitch = e.attribute("pitch").toInt();
725 0 : while (e.readNextStartElement()) {
726 0 : const AsciiStringView taga(e.name());
727 0 : if (taga == "articulation") {
728 0 : AsciiStringView oldArticulationName = e.readAsciiText();
729 0 : SymId oldId = Read206::articulationNames2SymId206(oldArticulationName);
730 0 : div.articulationName = Articulation::symId2ArticulationName(oldId);
731 0 : } else if (taga == "tremolo") {
732 0 : div.tremolo = TConv::fromXml(e.readAsciiText(), TremoloType::INVALID_TREMOLO);
733 : }
734 : }
735 0 : ds->drum(pitch).addVariant(div);
736 0 : }
737 : }
738 279 : } else if (ds->readDrumProperties(e, pitch)) {
739 : } else {
740 0 : e.unknown();
741 : }
742 : }
743 : }
744 :
745 : //---------------------------------------------------------
746 : // readInstrument
747 : //---------------------------------------------------------
748 :
749 55 : static void readInstrument206(Instrument* i, Part* p, XmlReader& e, ReadContext& ctx)
750 : {
751 55 : int bank = 0;
752 55 : char volume = 100;
753 55 : char pan = 60;
754 55 : char chorus = 30;
755 55 : char reverb = 30;
756 55 : bool customDrumset = false;
757 55 : i->clearChannels(); // remove default channel
758 957 : while (e.readNextStartElement()) {
759 902 : const AsciiStringView tag(e.name());
760 902 : if (tag == "Drum") {
761 : // if we see one of this tags, a custom drumset will
762 : // be created
763 68 : if (!i->drumset()) {
764 0 : i->setDrumset(new Drumset(*smDrumset));
765 : }
766 68 : if (!customDrumset) {
767 4 : i->drumset()->clear();
768 4 : customDrumset = true;
769 : }
770 68 : readDrumset206(i->drumset(), e);
771 834 : } else if (read400::TRead::readProperties(i, e, ctx, p, &customDrumset)) {
772 : } else {
773 0 : e.unknown();
774 : }
775 : }
776 :
777 55 : i->updateInstrumentId();
778 :
779 : // Read single-note dynamics from template
780 55 : i->setSingleNoteDynamicsFromTemplate();
781 :
782 55 : if (i->channel().empty()) { // for backward compatibility
783 0 : InstrChannel* a = new InstrChannel;
784 0 : a->setName(String::fromUtf8(InstrChannel::DEFAULT_NAME));
785 0 : a->setProgram(i->recognizeMidiProgram());
786 0 : a->setBank(bank);
787 0 : a->setVolume(volume);
788 0 : a->setPan(pan);
789 0 : a->setReverb(reverb);
790 0 : a->setChorus(chorus);
791 0 : i->appendChannel(a);
792 55 : } else if (i->channel(0)->program() < 0) {
793 0 : i->channel(0)->setProgram(i->recognizeMidiProgram());
794 : }
795 55 : if (i->useDrumset()) {
796 4 : if (i->channel()[0]->bank() == 0) {
797 0 : i->channel()[0]->setBank(128);
798 : }
799 : }
800 55 : }
801 :
802 : //---------------------------------------------------------
803 : // readStaff
804 : //---------------------------------------------------------
805 :
806 60 : static void readStaff(Staff* staff, XmlReader& e, ReadContext& ctx)
807 : {
808 161 : while (e.readNextStartElement()) {
809 101 : const AsciiStringView tag(e.name());
810 101 : if (tag == "type") { // obsolete
811 0 : int staffTypeIdx = e.readInt();
812 0 : LOGD("obsolete: Staff::read staffTypeIdx %d", staffTypeIdx);
813 101 : } else if (tag == "StaffType") {
814 60 : StaffType st;
815 60 : TRead::read(&st, e, ctx);
816 60 : staff->setStaffType(Fraction(0, 1), st);
817 101 : } else if (tag == "defaultClef") { // sets both default transposing and concert clef
818 9 : ClefType ct = TConv::fromXml(e.readAsciiText(), ClefType::G);
819 9 : staff->setDefaultClefType(ClefTypeList(ct, ct));
820 32 : } else if (tag == "defaultConcertClef") {
821 0 : staff->setDefaultClefType(ClefTypeList(TConv::fromXml(e.readAsciiText(), ClefType::G),
822 0 : staff->defaultClefType().transposingClef));
823 32 : } else if (tag == "defaultTransposingClef") {
824 0 : staff->setDefaultClefType(ClefTypeList(staff->defaultClefType().concertClef,
825 0 : TConv::fromXml(e.readAsciiText(), ClefType::G)));
826 32 : } else if (tag == "small") {
827 0 : staff->staffType(Fraction(0, 1))->setSmall(e.readInt());
828 32 : } else if (tag == "invisible") {
829 0 : staff->staffType(Fraction(0, 1))->setInvisible(e.readInt());
830 32 : } else if (tag == "neverHide") {
831 0 : bool v = e.readInt();
832 0 : if (v) {
833 0 : staff->setHideWhenEmpty(AutoOnOff::OFF);
834 : }
835 32 : } else if (tag == "showIfSystemEmpty") {
836 0 : staff->setShowIfEntireSystemEmpty(e.readInt());
837 32 : } else if (tag == "hideSystemBarLine") {
838 0 : staff->setHideSystemBarLine(e.readInt());
839 32 : } else if (tag == "keylist") {
840 0 : read400::TRead::read(staff->keyList(), e, ctx);
841 32 : } else if (tag == "bracket") {
842 24 : int col = e.intAttribute("col", -1);
843 24 : if (col == -1) {
844 24 : col = static_cast<int>(ctx.score()->brackets(staff->idx()).size());
845 : }
846 24 : EditStaffBrackets::setBracketType(ctx.score(), staff->idx(), col, BracketType(e.intAttribute("type", -1)));
847 24 : EditStaffBrackets::setBracketSpan(ctx.score(), staff->idx(), col, e.intAttribute("span", 0));
848 24 : EditStaffBrackets::setBracketVisible(ctx.score(), staff->idx(), col, static_cast<bool>(e.intAttribute("visible", 1)));
849 24 : e.readNext();
850 8 : } else if (tag == "barLineSpan") {
851 2 : staff->setBarLineFrom(e.intAttribute("from", 0));
852 2 : staff->setBarLineTo(e.intAttribute("to", 0));
853 2 : const int barLineSpan = e.readInt();
854 2 : if (barLineSpan > 0) {
855 1 : ctx.setStaffBarLineSpan(staff->idx(), static_cast<size_t>(barLineSpan - 1));
856 : } else {
857 1 : if (barLineSpan < 0) {
858 0 : LOGW() << "barLineSpan is negative: " << barLineSpan;
859 : }
860 1 : ctx.setStaffBarLineSpan(staff->idx(), 0);
861 : }
862 6 : } else if (tag == "distOffset") {
863 0 : staff->setUserDist(Spatium(e.readDouble()));
864 6 : } else if (tag == "mag") {
865 : /*_userMag =*/
866 0 : e.readDouble(0.1, 10.0);
867 6 : } else if (tag == "linkedTo") {
868 6 : int v = e.readInt() - 1;
869 6 : Staff* st = staff->score()->masterScore()->staff(v);
870 6 : if (staff->links()) {
871 0 : LOGD("Staff::readProperties: multiple <linkedTo> tags");
872 0 : if (!st || staff->isLinked(st)) { // maybe we don't need actually to relink...
873 0 : continue;
874 : }
875 : // not using unlink() here as it may delete _links
876 : // a pointer to which is stored also in XmlReader.
877 0 : staff->links()->remove(staff);
878 0 : staff->setLinks(nullptr);
879 : }
880 6 : if (st && st != staff) {
881 6 : staff->linkTo(st);
882 0 : } else if (!staff->score()->isMaster() && !st) {
883 : // if it is a master score it is OK not to find
884 : // a staff which is going after the current one.
885 0 : LOGD("staff %d not found in parent", v);
886 : }
887 0 : } else if (tag == "color") {
888 0 : staff->staffType(Fraction(0, 1))->setColor(e.readColor());
889 0 : } else if (tag == "transposeDiatonic") {
890 0 : ctx.setTransposeDiatonic(static_cast<int8_t>(e.readInt()));
891 0 : } else if (tag == "transposeChromatic") {
892 0 : ctx.setTransposeChromatic(static_cast<int8_t>(e.readInt()));
893 : } else {
894 0 : e.unknown();
895 : }
896 : }
897 60 : }
898 :
899 : //---------------------------------------------------------
900 : // readPart
901 : //---------------------------------------------------------
902 :
903 55 : void Read206::readPart206(Part* part, XmlReader& e, ReadContext& ctx)
904 : {
905 226 : while (e.readNextStartElement()) {
906 171 : const AsciiStringView tag(e.name());
907 171 : if (tag == "Instrument") {
908 55 : Instrument* i = part->instrument();
909 55 : readInstrument206(i, part, e, ctx);
910 55 : Drumset* ds = i->drumset();
911 55 : Staff* s = part->staff(0);
912 55 : int lld = s ? std::round(s->lineDistance(Fraction(0, 1))) : 1;
913 55 : if (ds && s && lld > 1) {
914 0 : for (int j = 0; j < DRUM_INSTRUMENTS; ++j) {
915 0 : ds->drum(j).line /= lld;
916 : }
917 : }
918 116 : } else if (tag == "Staff") {
919 60 : Staff* staff = Factory::createStaff(part);
920 60 : ctx.appendStaff(staff);
921 60 : readStaff(staff, e, ctx);
922 56 : } else if (tag == "name") {
923 0 : part->instrument()->setLongName(e.readText());
924 56 : } else if (tag == "shortName") {
925 0 : part->instrument()->setShortName(e.readText());
926 56 : } else if (tag == "show") {
927 1 : part->setShow(e.readInt());
928 : } else {
929 55 : e.unknown();
930 : }
931 : }
932 55 : }
933 :
934 : //---------------------------------------------------------
935 : // readAmbitus
936 : //---------------------------------------------------------
937 :
938 2 : static void readAmbitus(Ambitus* ambitus, XmlReader& e, ReadContext& ctx)
939 : {
940 15 : while (e.readNextStartElement()) {
941 13 : const AsciiStringView tag(e.name());
942 13 : if (tag == "head") {
943 1 : ambitus->setNoteHeadGroup(Read206::convertHeadGroup(e.readInt()));
944 12 : } else if (tag == "headType") {
945 1 : ambitus->setNoteHeadType(convertHeadType(e.readInt()));
946 11 : } else if (read400::TRead::readProperties(ambitus, e, ctx)) {
947 : } else {
948 0 : e.unknown();
949 : }
950 : }
951 2 : }
952 :
953 : //---------------------------------------------------------
954 : // readNote
955 : //---------------------------------------------------------
956 :
957 986 : static void readNote206(Note* note, XmlReader& e, ReadContext& ctx)
958 : {
959 986 : note->setTpc1(Tpc::TPC_INVALID);
960 986 : note->setTpc2(Tpc::TPC_INVALID);
961 :
962 3629 : while (e.readNextStartElement()) {
963 2643 : const AsciiStringView tag(e.name());
964 2643 : if (tag == "Accidental") {
965 141 : Accidental* a = Factory::createAccidental(note);
966 141 : a->setTrack(note->track());
967 141 : Read206::readAccidental206(a, e, ctx);
968 141 : note->add(a);
969 2502 : } else if (tag == "head") {
970 99 : int i = e.readInt();
971 99 : NoteHeadGroup val = Read206::convertHeadGroup(i);
972 99 : note->setHeadGroup(val);
973 2403 : } else if (tag == "headType") {
974 42 : int i = e.readInt();
975 42 : NoteHeadType val = convertHeadType(i);
976 42 : note->setHeadType(val);
977 2361 : } else if (Read206::readNoteProperties206(note, e, ctx)) {
978 : } else {
979 362 : e.unknown();
980 : }
981 : }
982 : // ensure sane values:
983 986 : note->setPitch(clampPitch(note->pitch()));
984 :
985 986 : if (!tpcIsValid(note->tpc1()) && !tpcIsValid(note->tpc2())) {
986 0 : Key key = (note->staff() && note->chord()) ? note->staff()->key(note->chord()->tick()) : Key::C;
987 0 : int tpc = pitch2tpc(note->pitch(), key, Prefer::NEAREST);
988 0 : if (note->concertPitch()) {
989 0 : note->setTpc1(tpc);
990 : } else {
991 0 : note->setTpc2(tpc);
992 : }
993 : }
994 986 : if (!(tpcIsValid(note->tpc1()) && tpcIsValid(note->tpc2()))) {
995 0 : Fraction tick = note->chord() ? note->chord()->tick() : Fraction(-1, 1);
996 0 : Interval v = note->staff() ? note->staff()->transpose(tick) : Interval();
997 0 : if (tpcIsValid(note->tpc1())) {
998 0 : v.flip();
999 0 : if (v.isZero()) {
1000 0 : note->setTpc2(note->tpc1());
1001 : } else {
1002 0 : note->setTpc2(Transpose::transposeTpc(note->tpc1(), v, true));
1003 : }
1004 : } else {
1005 0 : if (v.isZero()) {
1006 0 : note->setTpc1(note->tpc2());
1007 : } else {
1008 0 : note->setTpc1(Transpose::transposeTpc(note->tpc2(), v, true));
1009 : }
1010 : }
1011 : }
1012 986 : }
1013 :
1014 : //---------------------------------------------------------
1015 : // adjustPlacement
1016 : //---------------------------------------------------------
1017 :
1018 18 : static void adjustPlacement(EngravingItem* e)
1019 : {
1020 18 : if (!e || !e->staff()) {
1021 2 : return;
1022 : }
1023 :
1024 : // element to use to determine placement
1025 : // for spanners, choose first segment
1026 : EngravingItem* ee;
1027 : Spanner* spanner;
1028 16 : if (e->isSpanner()) {
1029 7 : spanner = toSpanner(e);
1030 7 : if (spanner->spannerSegments().empty()) {
1031 7 : return;
1032 : }
1033 0 : ee = spanner->spannerSegments().front();
1034 0 : if (!ee) {
1035 0 : return;
1036 : }
1037 : } else {
1038 9 : spanner = nullptr;
1039 9 : ee = e;
1040 : }
1041 :
1042 : // determine placement based on offset
1043 : // anything below staff will be set to below
1044 9 : double staffHeight = e->staff()->staffHeight();
1045 9 : double threshold = staffHeight;
1046 9 : double offsetAdjust = 0.0;
1047 9 : PlacementV defaultPlacement = e->propertyDefault(Pid::PLACEMENT).value<PlacementV>();
1048 : PlacementV newPlacement;
1049 : // most offsets will be recorded as relative to top staff line
1050 : // exceptions are styled offsets on elements with default placement below
1051 : double normalize;
1052 9 : if (defaultPlacement == PlacementV::BELOW && ee->offset().isNull()) {
1053 0 : normalize = staffHeight;
1054 : } else {
1055 9 : normalize = 0.0;
1056 : }
1057 9 : double ypos = ee->offset().y() + normalize;
1058 9 : if (ypos >= threshold) {
1059 0 : newPlacement = PlacementV::BELOW;
1060 0 : offsetAdjust -= staffHeight;
1061 : } else {
1062 9 : newPlacement = PlacementV::ABOVE;
1063 : }
1064 :
1065 : // set placement
1066 9 : e->setProperty(Pid::PLACEMENT, newPlacement);
1067 9 : if (newPlacement != defaultPlacement) {
1068 0 : e->setPropertyFlags(Pid::PLACEMENT, PropertyFlags::UNSTYLED);
1069 : }
1070 :
1071 : // adjust offset
1072 9 : if (spanner) {
1073 : // adjust segments individually
1074 0 : for (auto a : spanner->spannerSegments()) {
1075 : // spanner segments share the placement setting of the spanner
1076 : // just adjust offset
1077 0 : if (defaultPlacement == PlacementV::BELOW && a->offset().isNull()) {
1078 0 : normalize = staffHeight;
1079 : } else {
1080 0 : normalize = 0.0;
1081 : }
1082 0 : double yp = a->offset().y() + normalize;
1083 0 : a->ryoffset() += normalize + offsetAdjust;
1084 :
1085 : // if any segments are offset to opposite side of staff from placement,
1086 : // or if they are within staff,
1087 : // disable autoplace
1088 : bool disableAutoplace;
1089 0 : if (yp + a->height() <= 0.0) {
1090 0 : disableAutoplace = (newPlacement == PlacementV::BELOW);
1091 0 : } else if (yp > staffHeight) {
1092 0 : disableAutoplace = (newPlacement == PlacementV::ABOVE);
1093 : } else {
1094 0 : disableAutoplace = true;
1095 : }
1096 0 : if (disableAutoplace) {
1097 0 : a->setAutoplace(false);
1098 : }
1099 : // needed for https://musescore.org/en/node/281312
1100 : // ideally we would rebase and calculate new offset
1101 : // but this may not be possible
1102 : // since original offset is relative to system
1103 0 : a->rxoffset() = 0;
1104 : }
1105 : } else {
1106 9 : e->ryoffset() += normalize + offsetAdjust;
1107 : // if within staff, disable autoplace
1108 9 : if (ypos + e->height() > 0.0 && ypos <= staffHeight) {
1109 0 : e->setAutoplace(false);
1110 : }
1111 : }
1112 : }
1113 :
1114 3272 : bool Read206::readNoteProperties206(Note* note, XmlReader& e, ReadContext& ctx)
1115 : {
1116 3272 : const AsciiStringView tag(e.name());
1117 :
1118 3272 : if (tag == "pitch") {
1119 1425 : note->setPitch(e.readInt());
1120 1847 : } else if (tag == "tpc") {
1121 1425 : const int tpc = e.readInt();
1122 1425 : note->setTpc1(tpc);
1123 1425 : note->setTpc2(tpc);
1124 422 : } else if (tag == "track") { // for performance
1125 43 : note->setTrack(e.readInt());
1126 379 : } else if (tag == "Accidental") {
1127 0 : Accidental* a = Factory::createAccidental(note);
1128 0 : a->setTrack(note->track());
1129 0 : read400::TRead::read(a, e, ctx);
1130 0 : note->add(a);
1131 379 : } else if (tag == "Tie") {
1132 0 : Tie* tie = Factory::createTie(note);
1133 0 : tie->setTrack(note->track());
1134 0 : readTie206(e, ctx, tie);
1135 0 : tie->setStartNote(note);
1136 0 : note->setTieFor(tie);
1137 379 : } else if (tag == "tpc2") {
1138 0 : note->setTpc2(e.readInt());
1139 379 : } else if (tag == "small") {
1140 0 : note->setSmall(e.readInt());
1141 379 : } else if (tag == "mirror") {
1142 0 : read400::TRead::readProperty(note, e, ctx, Pid::MIRROR_HEAD);
1143 379 : } else if (tag == "dotPosition") {
1144 0 : read400::TRead::readProperty(note, e, ctx, Pid::DOT_POSITION);
1145 379 : } else if (tag == "fixed") {
1146 0 : note->setFixed(e.readBool());
1147 379 : } else if (tag == "fixedLine") {
1148 0 : note->setFixedLine(e.readInt());
1149 379 : } else if (tag == "head") {
1150 0 : read400::TRead::readProperty(note, e, ctx, Pid::HEAD_GROUP);
1151 379 : } else if (tag == "velocity") {
1152 : // TODO: convert to MU4
1153 0 : e.skipCurrentElement();
1154 379 : } else if (tag == "play") {
1155 0 : note->setPlay(e.readInt());
1156 379 : } else if (tag == "tuning") {
1157 0 : note->setTuning(e.readDouble());
1158 379 : } else if (tag == "fret") {
1159 0 : note->setFret(e.readInt());
1160 379 : } else if (tag == "string") {
1161 0 : note->setString(e.readInt());
1162 379 : } else if (tag == "ghost") {
1163 0 : note->setGhost(e.readInt());
1164 379 : } else if (tag == "headType") {
1165 0 : read400::TRead::readProperty(note, e, ctx, Pid::HEAD_TYPE);
1166 379 : } else if (tag == "veloType") {
1167 0 : read400::TRead::readProperty(note, e, ctx, Pid::VELO_TYPE);
1168 379 : } else if (tag == "line") {
1169 0 : note->setLine(e.readInt());
1170 379 : } else if (tag == "Fingering") {
1171 4 : Fingering* f = Factory::createFingering(note);
1172 4 : f->setTrack(note->track());
1173 4 : readText206(e, ctx, f, note);
1174 4 : note->add(f);
1175 375 : } else if (tag == "Symbol") {
1176 0 : Symbol* s = new Symbol(note);
1177 0 : s->setTrack(note->track());
1178 0 : read400::TRead::read(s, e, ctx);
1179 0 : if (s->sym() == SymId::noteheadParenthesisLeft) {
1180 0 : note->setParenthesesMode(note->rightParen() ? ParenthesesMode::BOTH : ParenthesesMode::LEFT);
1181 0 : s->deleteLater();
1182 0 : } else if (s->sym() == SymId::noteheadParenthesisRight) {
1183 0 : note->setParenthesesMode(note->leftParen() ? ParenthesesMode::BOTH : ParenthesesMode::RIGHT);
1184 0 : s->deleteLater();
1185 : } else {
1186 0 : note->add(s);
1187 : }
1188 375 : } else if (tag == "Image") {
1189 0 : if (MScore::noImages) {
1190 0 : e.skipCurrentElement();
1191 : } else {
1192 0 : Image* image = new Image(note);
1193 0 : image->setTrack(note->track());
1194 0 : read400::TRead::read(image, e, ctx);
1195 0 : note->add(image);
1196 : }
1197 375 : } else if (tag == "Bend") {
1198 4 : Bend* b = Factory::createBend(note);
1199 4 : b->setTrack(note->track());
1200 4 : read400::TRead::read(b, e, ctx);
1201 4 : note->add(b);
1202 371 : } else if (tag == "NoteDot") {
1203 0 : NoteDot* dot = Factory::createNoteDot(note);
1204 0 : read400::TRead::read(dot, e, ctx);
1205 0 : note->add(dot);
1206 371 : } else if (tag == "Events") {
1207 0 : note->playEvents().clear(); // remove default event
1208 0 : while (e.readNextStartElement()) {
1209 0 : const AsciiStringView etag(e.name());
1210 0 : if (etag == "Event") {
1211 0 : NoteEvent ne;
1212 0 : read400::TRead::read(&ne, e, ctx);
1213 0 : note->playEvents().push_back(ne);
1214 : } else {
1215 0 : e.unknown();
1216 : }
1217 : }
1218 0 : if (Chord* ch = note->chord()) {
1219 0 : ch->setPlayEventType(PlayEventType::User);
1220 : }
1221 371 : } else if (tag == "endSpanner") {
1222 0 : int id = e.intAttribute("id");
1223 0 : Spanner* sp = ctx.findSpanner(id);
1224 0 : if (sp) {
1225 0 : sp->setEndElement(note);
1226 0 : if (sp->isTie()) {
1227 0 : note->setTieBack(toTie(sp));
1228 : } else {
1229 0 : bool isNoteAnchoredTextLine = sp->isNoteLine() && toNoteLine(sp)->enforceMinLength();
1230 0 : if ((sp->isGlissando() || isNoteAnchoredTextLine) && note->ownershipParent() && note->ownershipParent()->isChord()) {
1231 0 : toChord(note->ownershipParent())->setEndsNoteAnchoredLine(true);
1232 : }
1233 0 : note->addSpannerBack(sp);
1234 : }
1235 0 : ctx.removeSpanner(sp);
1236 : } else {
1237 : // End of a spanner whose start element will appear later;
1238 : // may happen for cross-staff spanner from a lower to a higher staff
1239 : // (for instance a glissando from bass to treble staff of piano).
1240 : // Create a place-holder spanner with end data
1241 : // (a TextLine is used only because both Spanner or SLine are abstract,
1242 : // the actual class does not matter, as long as it is derived from Spanner)
1243 0 : int id1 = e.intAttribute("id", -1);
1244 0 : Staff* staff = note->staff();
1245 0 : if (id1 != -1
1246 0 : && // DISABLE if pasting into a staff with linked staves
1247 : // because the glissando is not properly cloned into the linked staves
1248 0 : staff && (!ctx.pasteMode() || !staff->links() || staff->links()->empty())) {
1249 0 : Spanner* placeholder = new TextLine(ctx.dummy());
1250 0 : placeholder->setEndElement(note);
1251 0 : placeholder->setTrack2(note->track());
1252 0 : placeholder->setTick(Fraction(0, 1));
1253 0 : placeholder->setTick2(ctx.tick());
1254 0 : ctx.addSpanner(id1, placeholder);
1255 : }
1256 : }
1257 0 : e.readNext();
1258 371 : } else if (tag == "TextLine"
1259 371 : || tag == "Glissando") {
1260 0 : Spanner* sp = toSpanner(Factory::createItemByName(tag, ctx.dummy()));
1261 : // check this is not a lower-to-higher cross-staff spanner we already got
1262 0 : int id = e.intAttribute("id");
1263 0 : Spanner* placeholder = ctx.findSpanner(id);
1264 0 : if (placeholder && placeholder->endElement()) {
1265 : // if it is, fill end data from place-holder
1266 0 : sp->setEndElement(placeholder->endElement());
1267 0 : sp->setTrack2(placeholder->track2());
1268 0 : sp->setTick(ctx.tick()); // make sure tick2 will be correct
1269 0 : sp->setTick2(placeholder->tick2());
1270 0 : toNote(placeholder->endElement())->addSpannerBack(sp);
1271 : // remove no longer needed place-holder before reading the new spanner,
1272 : // as reading it also adds it to XML reader list of spanners,
1273 : // which would overwrite the place-holder
1274 0 : ctx.removeSpanner(placeholder);
1275 0 : delete placeholder;
1276 : }
1277 0 : sp->setTrack(note->track());
1278 0 : read400::TRead::readItem(sp, e, ctx);
1279 0 : Staff* staff = note->staff();
1280 : // DISABLE pasting of glissandi into staves with other linked staves
1281 : // because the glissando is not properly cloned into the linked staves
1282 0 : if (ctx.pasteMode() && staff && staff->links() && !staff->links()->empty()) {
1283 0 : ctx.removeSpanner(sp); // read() added the element to the XMLReader: remove it
1284 0 : delete sp;
1285 : } else {
1286 0 : sp->setStartElement(note);
1287 0 : sp->setTick(ctx.tick());
1288 0 : note->addSpannerFor(sp);
1289 0 : sp->setOwnershipParent(note);
1290 0 : adjustPlacement(sp);
1291 : }
1292 371 : } else if (tag == "offset") {
1293 0 : TRead::readItemProperties(note, e, ctx);
1294 371 : } else if (TRead::readItemProperties(note, e, ctx)) {
1295 : } else {
1296 362 : return false;
1297 : }
1298 2910 : return true;
1299 : }
1300 :
1301 : //---------------------------------------------------------
1302 : // ReadStyleName206
1303 : // Retrieve the content of the "style" tag from the
1304 : // String with the content of the whole text tag
1305 : //---------------------------------------------------------
1306 :
1307 127 : static String ReadStyleName206(String xmlTag)
1308 : {
1309 127 : size_t beginIdx = xmlTag.indexOf(u"<style>");
1310 127 : if (beginIdx == muse::nidx) {
1311 20 : return String();
1312 : }
1313 107 : beginIdx += String(u"<style>").size();
1314 :
1315 107 : size_t endIdx = xmlTag.indexOf(u"</style>");
1316 107 : IF_ASSERT_FAILED(endIdx > 0) {
1317 0 : return String();
1318 : }
1319 :
1320 107 : String s = xmlTag.mid(beginIdx, (endIdx - beginIdx));
1321 107 : return s;
1322 107 : }
1323 :
1324 : //---------------------------------------------------------
1325 : // readTextPropertyStyle206
1326 : // This reads only the 'style' tag, so that it can be read
1327 : // before setting anything else.
1328 : //---------------------------------------------------------
1329 :
1330 116 : static bool readTextPropertyStyle206(String xmlTag, ReadContext& ctx, TextBase* t, EngravingItem* be)
1331 : {
1332 116 : String s = ReadStyleName206(xmlTag);
1333 :
1334 116 : if (s.isEmpty()) {
1335 16 : return false;
1336 : }
1337 :
1338 100 : if (!be->isTuplet()) { // Hack
1339 73 : if (excessTextStyles206.find(s) != excessTextStyles206.end()) {
1340 : // Init the text with a style that can't be stored as a user style
1341 : // due to the limit on the number of user styles possible.
1342 : // Use User-1, since it has all the possible user style pids
1343 0 : t->initTextStyleType(TextStyleType::DEFAULT);
1344 0 : std::map<Sid, PropertyValue> styleVals = excessTextStyles206[s];
1345 0 : for (const auto& p : *textStyle(TextStyleType::USER1)) {
1346 0 : if (t->getProperty(p.pid) == t->propertyDefault(p.pid) && styleVals.find(p.sid) != styleVals.end()) {
1347 0 : t->setProperty(p.pid, styleVals[p.sid]);
1348 : }
1349 : }
1350 0 : } else {
1351 : TextStyleType ss;
1352 73 : ss = ctx.lookupUserTextStyle(s);
1353 73 : if (ss == TextStyleType::TEXT_TYPES) {
1354 45 : muse::ByteArray ba = s.toAscii();
1355 45 : ss = TConv::fromXml(ba.constChar(), TextStyleType::DEFAULT);
1356 45 : }
1357 73 : if (ss != TextStyleType::TEXT_TYPES) {
1358 73 : t->initTextStyleType(ss);
1359 : }
1360 : }
1361 : }
1362 :
1363 100 : return true;
1364 116 : }
1365 :
1366 : //---------------------------------------------------------
1367 : // readTextProperties206
1368 : //---------------------------------------------------------
1369 :
1370 292 : static bool readTextProperties206(XmlReader& e, ReadContext& ctx, TextBase* t)
1371 : {
1372 292 : const AsciiStringView tag(e.name());
1373 292 : if (tag == "style") {
1374 100 : e.skipCurrentElement(); // read in readTextPropertyStyle206
1375 192 : } else if (tag == "foregroundColor") { // same as "color" ?
1376 0 : e.skipCurrentElement();
1377 192 : } else if (tag == "frame") {
1378 16 : t->setFrameType(e.readBool() ? FrameType::RECTANGLE : FrameType::NO_FRAME);
1379 16 : t->setPropertyFlags(Pid::FRAME_TYPE, PropertyFlags::UNSTYLED);
1380 176 : } else if (tag == "frameRound") {
1381 0 : read400::TRead::readProperty(t, e, ctx, Pid::FRAME_ROUND);
1382 176 : } else if (tag == "circle") {
1383 0 : if (e.readBool()) {
1384 0 : t->setFrameType(FrameType::CIRCLE);
1385 : } else {
1386 0 : if (t->circle()) {
1387 0 : t->setFrameType(FrameType::RECTANGLE);
1388 : }
1389 : }
1390 0 : t->setPropertyFlags(Pid::FRAME_TYPE, PropertyFlags::UNSTYLED);
1391 176 : } else if (tag == "paddingWidthS") {
1392 0 : read400::TRead::readProperty(t, e, ctx, Pid::FRAME_PADDING);
1393 176 : } else if (tag == "frameWidthS") {
1394 0 : read400::TRead::readProperty(t, e, ctx, Pid::FRAME_WIDTH);
1395 176 : } else if (tag == "frameColor") {
1396 0 : read400::TRead::readProperty(t, e, ctx, Pid::FRAME_FG_COLOR);
1397 176 : } else if (tag == "backgroundColor") {
1398 0 : read400::TRead::readProperty(t, e, ctx, Pid::FRAME_BG_COLOR);
1399 176 : } else if (tag == "halign") {
1400 0 : Align align = t->align();
1401 0 : align.horizontal = TConv::fromXml(e.readAsciiText(), AlignH::LEFT);
1402 0 : t->setAlign(align);
1403 0 : t->setPropertyFlags(Pid::ALIGN, PropertyFlags::UNSTYLED);
1404 0 : t->setPosition(align.horizontal);
1405 0 : t->setPropertyFlags(Pid::POSITION, PropertyFlags::UNSTYLED);
1406 176 : } else if (tag == "valign") {
1407 0 : Align align = t->align();
1408 0 : align.vertical = TConv::fromXml(e.readAsciiText(), AlignV::TOP);
1409 0 : t->setAlign(align);
1410 0 : t->setPropertyFlags(Pid::ALIGN, PropertyFlags::UNSTYLED);
1411 176 : } else if (tag == "pos") {
1412 7 : read400::TRead::readProperty(t, e, ctx, Pid::OFFSET);
1413 7 : if (t->align() == AlignV::TOP) {
1414 2 : t->ryoffset() += .5 * ctx.spatium(); // HACK: bbox is different in 2.x
1415 : }
1416 7 : adjustPlacement(t);
1417 169 : } else if (!read400::TRead::readTextProperties(t, e, ctx)) {
1418 52 : return false;
1419 : }
1420 240 : return true;
1421 : }
1422 :
1423 : //---------------------------------------------------------
1424 : // TextReaderContext206
1425 : // For 2.x files, the style tag could be in a different
1426 : // position with respect to 3.x files. Since seek
1427 : // position is not reliable for readline in QIODevices (for
1428 : // example because of non-single-byte characters in at least
1429 : // one of the fields; some two-byte characters are counted as
1430 : // two single-byte characters and thus the reading could
1431 : // start at the wrong position), a copy of the text tag
1432 : // is created and read in a separate XmlReader, while
1433 : // the text style is extracted from a String containing
1434 : // the whole text xml tag.
1435 : // TextReaderContext206 takes care of this process
1436 : //---------------------------------------------------------
1437 :
1438 : class TextReaderContext206
1439 : {
1440 : XmlReader& origReader;
1441 : XmlReader tagReader;
1442 : String xmlTag;
1443 :
1444 : public:
1445 116 : TextReaderContext206(XmlReader& e)
1446 116 : : origReader(e)
1447 : {
1448 : // Create a new xml document containing only the (text) xml chunk
1449 116 : String name = String::fromAscii(origReader.name().ascii());
1450 116 : String prefix = u"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<" + name + u">";
1451 116 : xmlTag = origReader.readXml();
1452 116 : xmlTag.prepend(prefix);
1453 116 : xmlTag.append(u"</" + name + u">\n");
1454 116 : muse::ByteArray data = xmlTag.toUtf8();
1455 116 : tagReader.setData(data); // Add the xml data to the XmlReader
1456 : // the additional lines are needed to output the correct line number
1457 : // of the original file in case of error
1458 116 : int64_t additionalOffset = origReader.byteOffset() - prefix.toStdString().size(); // Subtracting the 2 new lines that will be added
1459 116 : tagReader.setByteOffsetAdjustment(additionalOffset);
1460 116 : copyProperties(origReader, tagReader);
1461 116 : tagReader.readNextStartElement(); // read up to the first "name" tag
1462 116 : }
1463 :
1464 : // Disable copying the TextReaderContext206
1465 : TextReaderContext206(const TextReaderContext206&) = delete;
1466 : TextReaderContext206& operator=(const TextReaderContext206&) = delete;
1467 :
1468 116 : ~TextReaderContext206()
1469 : {
1470 : // Ensure to copy back the potentially changed properties
1471 : // to the original XmlReader before destruction
1472 116 : copyProperties(tagReader, origReader);
1473 116 : }
1474 :
1475 823 : XmlReader& reader() { return tagReader; }
1476 127 : const String& tag() { return xmlTag; }
1477 : private:
1478 : void copyProperties(XmlReader& original, XmlReader& derived);
1479 : };
1480 :
1481 : //---------------------------------------------------------
1482 : // copyProperties
1483 : // Copy some of the XmlReader properties of an original
1484 : // XmlReader object to a "derived" XmlReader object.
1485 : // This is used for example when using the derived XmlReader
1486 : // to read the element properties of a 2.x text, or to
1487 : // update the base XmlReader object (for example, its
1488 : // link list) after reading the properties of a 2.x text
1489 : //---------------------------------------------------------
1490 :
1491 232 : void TextReaderContext206::copyProperties(XmlReader& original, XmlReader& derived)
1492 : {
1493 232 : derived.setDocName(original.docName());
1494 232 : }
1495 :
1496 : //---------------------------------------------------------
1497 : // convertMScoreFontMarkerSymbols206
1498 : //---------------------------------------------------------
1499 :
1500 12 : static String convertMScoreFontMarkerSymbols206(const String& xmlText)
1501 : {
1502 12 : String s = xmlText;
1503 12 : s.replace(u"\ue177", u"<sym>segno</sym>");
1504 12 : s.replace(u"\ue178", u"<sym>coda</sym>");
1505 12 : s.replace(u"\ue179", u"<sym>codaSquare</sym>");
1506 12 : return s;
1507 0 : }
1508 :
1509 : //---------------------------------------------------------
1510 : // readText206
1511 : //---------------------------------------------------------
1512 :
1513 91 : static void readText206(XmlReader& e, ReadContext& ctx, TextBase* t, EngravingItem* be)
1514 : {
1515 91 : TextReaderContext206 tctx(e);
1516 91 : readTextPropertyStyle206(tctx.tag(), ctx, t, be);
1517 423 : while (tctx.reader().readNextStartElement()) {
1518 241 : if (!readTextProperties206(tctx.reader(), ctx, t)) {
1519 48 : tctx.reader().unknown();
1520 : }
1521 : }
1522 91 : CompatUtils::migrateOffsetPre302(t, ctx.mscVersion());
1523 91 : }
1524 :
1525 : //---------------------------------------------------------
1526 : // read
1527 : //---------------------------------------------------------
1528 :
1529 1 : static void readTempoText(TempoText* t, XmlReader& e, ReadContext& ctx)
1530 : {
1531 1 : TextReaderContext206 tctx(e);
1532 1 : readTextPropertyStyle206(tctx.tag(), ctx, t, t);
1533 4 : while (tctx.reader().readNextStartElement()) {
1534 3 : const AsciiStringView tag(tctx.reader().name());
1535 3 : if (tag == "tempo") {
1536 1 : t->setTempo(tctx.reader().readDouble());
1537 2 : } else if (tag == "followText") {
1538 1 : t->setFollowText(tctx.reader().readInt());
1539 1 : } else if (!readTextProperties206(tctx.reader(), ctx, t)) {
1540 0 : tctx.reader().unknown();
1541 : }
1542 : }
1543 : // check sanity
1544 1 : if (t->xmlText().isEmpty()) {
1545 0 : t->setXmlText(String(u"<sym>metNoteQuarterUp</sym> = %1").arg(int(lrint(t->tempo().toBPM().val))));
1546 0 : t->setVisible(false);
1547 : } else {
1548 1 : t->setXmlText(t->xmlText().replace(u"<sym>unicode", u"<sym>met"));
1549 : }
1550 :
1551 1 : CompatUtils::migrateOffsetPre302(t, ctx.mscVersion());
1552 :
1553 1 : t->resetProperty(Pid::MUSIC_SYMBOL_SIZE);
1554 1 : }
1555 :
1556 : //---------------------------------------------------------
1557 : // readMarker
1558 : //---------------------------------------------------------
1559 :
1560 12 : static void readMarker(Marker* m, XmlReader& e, ReadContext& ctx)
1561 : {
1562 12 : TextReaderContext206 tctx(e);
1563 12 : readTextPropertyStyle206(tctx.tag(), ctx, m, m);
1564 12 : MarkerType mt = MarkerType::SEGNO;
1565 :
1566 48 : while (tctx.reader().readNextStartElement()) {
1567 36 : const AsciiStringView tag(tctx.reader().name());
1568 36 : if (tag == "label") {
1569 12 : AsciiStringView s(tctx.reader().readAsciiText());
1570 12 : m->setLabel(String::fromAscii(s.ascii()));
1571 12 : mt = TConv::fromXml(s, MarkerType::USER);
1572 24 : } else if (!readTextProperties206(tctx.reader(), ctx, m)) {
1573 0 : tctx.reader().unknown();
1574 : }
1575 : }
1576 12 : m->setXmlText(convertMScoreFontMarkerSymbols206(m->xmlText()));
1577 12 : m->setMarkerType(mt);
1578 :
1579 12 : CompatUtils::migrateOffsetPre302(m, ctx.mscVersion());
1580 12 : }
1581 :
1582 : //---------------------------------------------------------
1583 : // readDynamic
1584 : //---------------------------------------------------------
1585 :
1586 1 : static void readDynamic(Dynamic* d, XmlReader& e, ReadContext& ctx)
1587 : {
1588 1 : TextReaderContext206 tctx(e);
1589 1 : readTextPropertyStyle206(tctx.tag(), ctx, d, d);
1590 3 : while (tctx.reader().readNextStartElement()) {
1591 2 : const AsciiStringView tag = tctx.reader().name();
1592 2 : if (tag == "subtype") {
1593 1 : d->setDynamicType(tctx.reader().readText());
1594 1 : } else if (tag == "velocity") {
1595 0 : d->setVelocity(tctx.reader().readInt());
1596 1 : } else if (tag == "dynType") {
1597 0 : d->setVoiceAssignment(Read206::readDynamicRange(tctx.reader().readInt()));
1598 1 : } else if (tag == "size") {
1599 0 : e.skipCurrentElement();
1600 1 : } else if (!readTextProperties206(tctx.reader(), ctx, d)) {
1601 0 : tctx.reader().unknown();
1602 : }
1603 : }
1604 1 : d->setSize(10.0);
1605 1 : if (d->xmlText().contains(u"<sym>") && !d->xmlText().contains(u"<font")) {
1606 1 : d->setAlign(Align(AlignH::HCENTER, AlignV::BASELINE));
1607 : }
1608 1 : CompatUtils::migrateOffsetPre302(d, ctx.mscVersion());
1609 1 : }
1610 :
1611 : //---------------------------------------------------------
1612 : // readTuplet
1613 : //---------------------------------------------------------
1614 :
1615 11 : static void readTuplet206(Tuplet* tuplet, XmlReader& e, ReadContext& ctx)
1616 : {
1617 64 : while (e.readNextStartElement()) {
1618 53 : const AsciiStringView tag(e.name());
1619 53 : if (tag == "Number") {
1620 11 : Text* _number = Factory::createText(tuplet);
1621 11 : _number->setComposition(true);
1622 11 : tuplet->setNumber(_number);
1623 : // _number reads property defaults from parent tuplet as "composition" is set:
1624 11 : tuplet->resetNumberProperty();
1625 11 : readText206(e, ctx, _number, tuplet);
1626 11 : _number->setVisible(tuplet->visible()); //?? override saved property
1627 11 : _number->setColor(tuplet->color());
1628 11 : _number->setTrack(tuplet->track());
1629 : // move property flags from _number
1630 66 : for (auto p : { Pid::FONT_FACE, Pid::FONT_SIZE, Pid::FONT_STYLE, Pid::ALIGN, Pid::SIZE_SPATIUM_DEPENDENT }) {
1631 55 : tuplet->setPropertyFlags(p, _number->propertyFlags(p));
1632 : }
1633 42 : } else if (!Read206::readTupletProperties206(e, ctx, tuplet)) {
1634 0 : e.unknown();
1635 : }
1636 : }
1637 11 : Fraction r = (tuplet->ratio() == Fraction(1, 1)) ? tuplet->ratio() : tuplet->ratio().reduced();
1638 11 : Fraction f(r.denominator(), tuplet->baseLen().fraction().denominator());
1639 11 : tuplet->setTicks(f.reduced());
1640 11 : }
1641 :
1642 : //---------------------------------------------------------
1643 : // readLyrics
1644 : //---------------------------------------------------------
1645 :
1646 1 : static void readLyrics(Lyrics* lyrics, XmlReader& e, ReadContext& ctx)
1647 : {
1648 1 : int iEndTick = 0; // used for backward compatibility
1649 1 : Text* _verseNumber = 0;
1650 :
1651 2 : while (e.readNextStartElement()) {
1652 1 : const AsciiStringView tag(e.name());
1653 1 : if (tag == "endTick") {
1654 : // store <endTick> tag value until a <ticks> tag has been read
1655 : // which positions this lyrics element in the score
1656 0 : iEndTick = e.readInt();
1657 1 : } else if (tag == "Number") {
1658 0 : _verseNumber = Factory::createText(lyrics);
1659 0 : readText206(e, ctx, _verseNumber, lyrics);
1660 0 : _verseNumber->setOwnershipParent(lyrics);
1661 1 : } else if (tag == "style") {
1662 0 : e.readText(); // ignore style
1663 1 : } else if (!read400::TRead::readProperties(lyrics, e, ctx)) {
1664 0 : e.unknown();
1665 : }
1666 : }
1667 :
1668 : // if any endTick, make it relative to current tick
1669 1 : if (iEndTick) {
1670 0 : lyrics->setTicks(Fraction::fromTicks(iEndTick) - ctx.tick());
1671 : }
1672 1 : if (_verseNumber) {
1673 : // TODO: add text to main text
1674 0 : delete _verseNumber;
1675 : }
1676 1 : lyrics->setAutoplace(true);
1677 :
1678 1 : if (!lyrics->offset().isNull() && !ctx.pasteMode()) {
1679 : // fix offset for pre-3.1 scores
1680 : // 2.x and earlier: y offset was relative to staff; x offset was relative to center of notehead
1681 0 : lyrics->rxoffset() -= lyrics->symWidth(SymId::noteheadBlack) * 0.5;
1682 : //lyrics->ryoffset() -= lyrics->placeBelow() && lyrics->staff() ? lyrics->staff()->height() : 0.0;
1683 : // temporarily set placement to above, since the original offset is relative to top of staff
1684 : // depend on adjustPlacement() to change the placement if appropriate
1685 0 : lyrics->setPlacement(PlacementV::ABOVE);
1686 0 : adjustPlacement(lyrics);
1687 : }
1688 1 : CompatUtils::migrateOffsetPre302(lyrics, ctx.mscVersion());
1689 1 : }
1690 :
1691 742 : bool Read206::readDurationProperties206(XmlReader& e, ReadContext& ctx, DurationElement* de)
1692 : {
1693 742 : if (e.name() == "Tuplet") {
1694 136 : int i = e.readInt();
1695 136 : Tuplet* t = ctx.findTuplet(i);
1696 136 : if (!t) {
1697 0 : LOGD("readDurationProperties206(): Tuplet id %d not found", i);
1698 : }
1699 136 : if (t) {
1700 136 : de->setTuplet(t);
1701 136 : if (!ctx.undoStackActive()) { // HACK, also added in Undo::AddElement()
1702 136 : t->add(de);
1703 : }
1704 : }
1705 136 : return true;
1706 606 : } else if (TRead::readItemProperties(de, e, ctx)) {
1707 73 : return true;
1708 : }
1709 533 : return false;
1710 : }
1711 :
1712 116 : bool Read206::readTupletProperties206(XmlReader& e, ReadContext& ctx, Tuplet* de)
1713 : {
1714 116 : const AsciiStringView tag(e.name());
1715 :
1716 116 : if (read400::TRead::readStyledProperty(de, tag, e, ctx)) {
1717 102 : } else if (tag == "normalNotes") {
1718 27 : de->setProperty(Pid::NORMAL_NOTES, e.readInt());
1719 75 : } else if (tag == "actualNotes") {
1720 27 : de->setProperty(Pid::ACTUAL_NOTES, e.readInt());
1721 48 : } else if (tag == "p1") {
1722 0 : de->setProperty(Pid::P1, PropertyValue::fromValue(e.readPoint() * ctx.spatium()));
1723 48 : } else if (tag == "p2") {
1724 0 : de->setProperty(Pid::P2, PropertyValue::fromValue(e.readPoint() * ctx.spatium()));
1725 48 : } else if (tag == "baseNote") {
1726 27 : de->setBaseLen(TDuration(TConv::fromXml(e.readAsciiText(), DurationType::V_INVALID)));
1727 21 : } else if (tag == "Number") {
1728 16 : Text* _number = Factory::createText(de);
1729 16 : de->setNumber(_number);
1730 16 : _number->setComposition(true);
1731 : // _number->setSubStyleId(SubStyleId::TUPLET);
1732 : // initSubStyle(SubStyleId::TUPLET); // hack: initialize number
1733 80 : for (auto p : { Pid::FONT_FACE, Pid::FONT_SIZE, Pid::FONT_STYLE, Pid::ALIGN }) {
1734 64 : _number->resetProperty(p);
1735 : }
1736 16 : readText206(e, ctx, _number, de);
1737 16 : _number->setVisible(de->visible()); //?? override saved property
1738 16 : _number->setTrack(de->track());
1739 : // move property flags from _number
1740 80 : for (auto p : { Pid::FONT_FACE, Pid::FONT_SIZE, Pid::FONT_STYLE, Pid::ALIGN }) {
1741 64 : de->setPropertyFlags(p, _number->propertyFlags(p));
1742 : }
1743 5 : } else if (!readDurationProperties206(e, ctx, de)) {
1744 0 : return false;
1745 : }
1746 116 : return true;
1747 : }
1748 :
1749 3220 : bool Read206::readChordRestProperties206(XmlReader& e, ReadContext& ctx, ChordRest* ch)
1750 : {
1751 3220 : const AsciiStringView tag(e.name());
1752 :
1753 3220 : if (tag == "durationType") {
1754 1945 : ch->setDurationType(TConv::fromXml(e.readAsciiText(), DurationType::V_QUARTER));
1755 1945 : if (ch->actualDurationType().type() != DurationType::V_MEASURE) {
1756 1436 : if (ctx.mscVersion() < 112 && (ch->isRest())
1757 0 : && // for backward compatibility, convert V_WHOLE rests to V_MEASURE
1758 : // if long enough to fill a measure.
1759 : // OTOH, freshly created (un-initialized) rests have numerator == 0 (< 4/4)
1760 : // (see Fraction() constructor in fraction.h; this happens for instance
1761 : // when pasting selection from clipboard): they should not be converted
1762 0 : ch->ticks().numerator() != 0
1763 1436 : && // rest durations are initialized to full measure duration when
1764 : // created upon reading the <Rest> tag (see Measure::read() )
1765 : // so a V_WHOLE rest in a measure of 4/4 or less => V_MEASURE
1766 1436 : (ch->actualDurationType() == DurationType::V_WHOLE && ch->ticks() <= Fraction(4, 4))) {
1767 : // old pre 2.0 scores: convert
1768 0 : ch->setDurationType(DurationType::V_MEASURE);
1769 : } else { // not from old score: set duration fraction from duration type
1770 1436 : ch->setTicks(ch->actualDurationType().fraction());
1771 : }
1772 : }
1773 1275 : } else if (tag == "BeamMode") {
1774 : // 206 used begin32/begin64 for beam mode
1775 : // 410 uses begin16/begin32
1776 2 : auto txt = e.readAsciiText();
1777 2 : if (txt == "begin64") {
1778 0 : txt = "begin32";
1779 2 : } else if (txt == "begin32") {
1780 0 : txt = "begin16";
1781 : }
1782 2 : ch->setBeamMode(TConv::fromXml(txt, BeamMode::AUTO));
1783 1273 : } else if (tag == "Articulation") {
1784 215 : EngravingItem* el = readArticulation(ch, e, ctx);
1785 215 : if (el->isFermata()) {
1786 31 : ch->segment()->add(el);
1787 : } else {
1788 184 : ch->add(el);
1789 : }
1790 1058 : } else if (tag == "leadingSpace" || tag == "trailingSpace") {
1791 0 : LOGD("ChordRest: %s obsolete", tag.ascii());
1792 0 : e.skipCurrentElement();
1793 1058 : } else if (tag == "Beam") {
1794 98 : int id = e.readInt();
1795 98 : Beam* beam = ctx.findBeam(id);
1796 98 : if (beam) {
1797 98 : beam->add(ch); // also calls ch->setBeam(beam)
1798 : } else {
1799 0 : LOGD("Beam id %d not found", id);
1800 : }
1801 960 : } else if (tag == "small") {
1802 0 : ch->setSmall(e.readInt());
1803 960 : } else if (tag == "duration") {
1804 183 : ch->setTicks(e.readFraction());
1805 777 : } else if (tag == "ticklen") { // obsolete (version < 1.12)
1806 0 : int mticks = ctx.compatTimeSigMap()->timesig(ctx.tick()).timesig().ticks();
1807 0 : int i = e.readInt();
1808 0 : if (i == 0) {
1809 0 : i = mticks;
1810 : }
1811 0 : if ((ch->isRest()) && (mticks == i)) {
1812 0 : ch->setDurationType(DurationType::V_MEASURE);
1813 0 : ch->setTicks(Fraction::fromTicks(i));
1814 : } else {
1815 0 : Fraction f = Fraction::fromTicks(i);
1816 0 : ch->setTicks(f);
1817 0 : ch->setDurationType(TDuration(f));
1818 : }
1819 777 : } else if (tag == "dots") {
1820 8 : ch->setDots(e.readInt());
1821 769 : } else if (tag == "move") {
1822 0 : ch->setStaffMove(e.readInt());
1823 769 : } else if (tag == "Slur") {
1824 32 : int id = e.intAttribute("id");
1825 32 : if (id == 0) {
1826 32 : id = e.intAttribute("number"); // obsolete
1827 : }
1828 32 : Spanner* spanner = ctx.findSpanner(id);
1829 32 : String atype(e.attribute("type"));
1830 :
1831 32 : if (!spanner) {
1832 0 : if (atype == "stop") {
1833 0 : SpannerValues sv;
1834 0 : sv.spannerId = id;
1835 0 : sv.track2 = ch->track();
1836 0 : sv.tick2 = ctx.tick();
1837 0 : ctx.addSpannerValues(sv);
1838 0 : } else if (atype == "start") {
1839 0 : LOGD("spanner: start without spanner");
1840 : }
1841 : } else {
1842 32 : if (atype == "start") {
1843 16 : if (spanner->ticks() > Fraction(0, 1) && spanner->tick() == Fraction(-1, 1)) { // stop has been read first
1844 3 : spanner->setTicks(spanner->ticks() - ctx.tick() - Fraction::eps());
1845 : }
1846 16 : spanner->setTick(ctx.tick());
1847 16 : spanner->setTrack(ch->track());
1848 16 : if (spanner->isSlur()) {
1849 16 : spanner->setStartElement(ch);
1850 : }
1851 16 : if (ctx.pasteMode()) {
1852 0 : for (EngravingObject* el : spanner->linkList()) {
1853 0 : if (el == spanner) {
1854 0 : continue;
1855 : }
1856 0 : Spanner* ls = toSpanner(el);
1857 0 : ls->setTick(spanner->tick());
1858 0 : for (EngravingObject* ee : ch->linkList()) {
1859 0 : ChordRest* cr = toChordRest(ee);
1860 0 : if (cr->staffIdx() == ls->staffIdx()) {
1861 0 : ls->setTrack(cr->track());
1862 0 : if (ls->isSlur()) {
1863 0 : ls->setStartElement(cr);
1864 : }
1865 0 : break;
1866 : }
1867 0 : }
1868 0 : }
1869 : }
1870 16 : } else if (atype == "stop") {
1871 16 : spanner->setTick2(ctx.tick());
1872 16 : spanner->setTrack2(ch->track());
1873 16 : if (spanner->isSlur()) {
1874 16 : spanner->setEndElement(ch);
1875 : }
1876 16 : ChordRest* start = toChordRest(spanner->startElement());
1877 16 : if (start) {
1878 13 : spanner->setTrack(start->track());
1879 : }
1880 16 : if (ctx.pasteMode()) {
1881 0 : for (EngravingObject* el : spanner->linkList()) {
1882 0 : if (el == spanner) {
1883 0 : continue;
1884 : }
1885 0 : Spanner* ls = toSpanner(el);
1886 0 : ls->setTick2(spanner->tick2());
1887 0 : for (EngravingObject* ee : ch->linkList()) {
1888 0 : ChordRest* cr = toChordRest(ee);
1889 0 : if (cr->staffIdx() == ls->staffIdx()) {
1890 0 : ls->setTrack2(cr->track());
1891 0 : if (ls->isSlur()) {
1892 0 : ls->setEndElement(cr);
1893 : }
1894 0 : break;
1895 : }
1896 0 : }
1897 0 : }
1898 : }
1899 : } else {
1900 0 : LOGD("readChordRestProperties206(): unknown Slur type <%s>", muPrintable(atype));
1901 : }
1902 : }
1903 32 : e.readNext();
1904 769 : } else if (tag == "Lyrics") {
1905 0 : Lyrics* l = Factory::createLyrics(ch);
1906 0 : l->setTrack(ctx.track());
1907 0 : readLyrics(l, e, ctx);
1908 0 : ch->add(l);
1909 737 : } else if (tag == "pos") {
1910 0 : PointF pt = e.readPoint();
1911 0 : ch->setOffset(pt * ch->spatium());
1912 737 : } else if (ch->isRest() && tag == "Image") {
1913 0 : if (MScore::noImages) {
1914 0 : e.skipCurrentElement();
1915 : } else {
1916 0 : Image* image = new Image(ch);
1917 0 : image->setTrack(ctx.track());
1918 0 : read400::TRead::read(image, e, ctx);
1919 0 : ch->add(image);
1920 : }
1921 737 : } else if (!readDurationProperties206(e, ctx, ch)) {
1922 533 : return false;
1923 : }
1924 2687 : return true;
1925 : }
1926 :
1927 2324 : bool Read206::readChordProperties206(XmlReader& e, ReadContext& ctx, Chord* ch)
1928 : {
1929 2324 : const AsciiStringView tag(e.name());
1930 :
1931 2324 : if (tag == "Note") {
1932 0 : Note* note = Factory::createNote(ch);
1933 : // the note needs to know the properties of the track it belongs to
1934 0 : note->setTrack(ch->track());
1935 0 : readNote206(note, e, ctx);
1936 0 : ch->add(note);
1937 2324 : } else if (readChordRestProperties206(e, ctx, ch)) {
1938 455 : } else if (tag == "Stem") {
1939 1 : Stem* s = Factory::createStem(ch);
1940 1 : read400::TRead::read(s, e, ctx);
1941 1 : ch->add(s);
1942 454 : } else if (tag == "Hook") {
1943 0 : Hook* hook = new Hook(ch);
1944 0 : read400::TRead::read(hook, e, ctx);
1945 0 : ch->add(hook);
1946 454 : } else if (tag == "appoggiatura") {
1947 0 : ch->setNoteType(NoteType::APPOGGIATURA);
1948 0 : e.readNext();
1949 454 : } else if (tag == "acciaccatura") {
1950 0 : ch->setNoteType(NoteType::ACCIACCATURA);
1951 0 : e.readNext();
1952 454 : } else if (tag == "grace4") {
1953 0 : ch->setNoteType(NoteType::GRACE4);
1954 0 : e.readNext();
1955 454 : } else if (tag == "grace16") {
1956 0 : ch->setNoteType(NoteType::GRACE16);
1957 0 : e.readNext();
1958 454 : } else if (tag == "grace32") {
1959 0 : ch->setNoteType(NoteType::GRACE32);
1960 0 : e.readNext();
1961 454 : } else if (tag == "grace8after") {
1962 0 : ch->setNoteType(NoteType::GRACE8_AFTER);
1963 0 : e.readNext();
1964 454 : } else if (tag == "grace16after") {
1965 0 : ch->setNoteType(NoteType::GRACE16_AFTER);
1966 0 : e.readNext();
1967 454 : } else if (tag == "grace32after") {
1968 0 : ch->setNoteType(NoteType::GRACE32_AFTER);
1969 0 : e.readNext();
1970 454 : } else if (tag == "StemSlash") {
1971 0 : StemSlash* ss = Factory::createStemSlash(ch);
1972 0 : read400::TRead::read(ss, e, ctx);
1973 0 : ch->add(ss);
1974 454 : } else if (read400::TRead::readProperty(ch, tag, e, ctx, Pid::STEM_DIRECTION)) {
1975 362 : } else if (tag == "noStem") {
1976 0 : ch->setNoStem(e.readInt());
1977 362 : } else if (tag == "Arpeggio") {
1978 0 : Arpeggio* arpeggio = Factory::createArpeggio(ch);
1979 0 : arpeggio->setTrack(ch->track());
1980 0 : read400::TRead::read(arpeggio, e, ctx);
1981 0 : ch->add(arpeggio);
1982 : }
1983 : // old glissando format, chord-to-chord, attached to its final chord
1984 362 : else if (tag == "Glissando") {
1985 : // the measure we are reading is not inserted in the score yet
1986 : // as well as, possibly, the glissando intended initial chord;
1987 : // then we cannot fully link the glissando right now;
1988 : // temporarily attach the glissando to its final note as a back spanner;
1989 : // after the whole score is read, Score::connectTies() will look for
1990 : // the suitable initial note
1991 0 : Note* finalNote = ch->upNote();
1992 0 : Glissando* gliss = Factory::createGlissando(ctx.dummy());
1993 0 : read400::TRead::read(gliss, e, ctx);
1994 0 : gliss->setStartElement(nullptr);
1995 0 : gliss->setEndElement(nullptr);
1996 : // in TAB, use straight line with no text
1997 0 : if (ctx.staff(static_cast<int>(ctx.track()) >> 2)->isTabStaff(ch->tick())) {
1998 0 : gliss->setGlissandoType(GlissandoType::STRAIGHT);
1999 0 : gliss->setShowText(false);
2000 : }
2001 0 : finalNote->addSpannerBack(gliss);
2002 362 : } else if (tag == "Tremolo") {
2003 0 : read400::TRead::TremoloCompat tcompat;
2004 0 : tcompat.parent = ch;
2005 0 : read400::TRead::read(tcompat, e, ctx);
2006 0 : if (tcompat.two) {
2007 0 : tcompat.two->setOwnershipParent(ch);
2008 0 : tcompat.two->setDurationType(ch->durationType());
2009 0 : ch->setTremoloTwoChord(tcompat.two, false);
2010 0 : } else if (tcompat.single) {
2011 0 : tcompat.single->setOwnershipParent(ch);
2012 0 : tcompat.single->setDurationType(ch->durationType());
2013 0 : ch->setTremoloSingleChord(tcompat.single);
2014 : } else {
2015 0 : UNREACHABLE;
2016 : }
2017 362 : } else if (tag == "tickOffset") { // obsolete
2018 362 : } else if (tag == "ChordLine") {
2019 0 : ChordLine* cl = Factory::createChordLine(ch);
2020 0 : read400::TRead::read(cl, e, ctx);
2021 0 : cl->setOffset(0.0, 0.0);
2022 0 : ch->add(cl);
2023 : } else {
2024 362 : return false;
2025 : }
2026 1962 : return true;
2027 : }
2028 :
2029 : //---------------------------------------------------------
2030 : // convertDoubleArticulations
2031 : // Replace double articulations with proper SMuFL
2032 : // symbols which were not available for use prior to 3.0
2033 : //---------------------------------------------------------
2034 :
2035 906 : static void convertDoubleArticulations(Chord* chord)
2036 : {
2037 906 : std::vector<Articulation*> pairableArticulations;
2038 1090 : for (Articulation* a : chord->articulations()) {
2039 342 : if (a->isStaccato() || a->isTenuto()
2040 342 : || a->isAccent() || a->isMarcato()) {
2041 76 : pairableArticulations.push_back(a);
2042 : }
2043 : }
2044 906 : if (pairableArticulations.size() != 2) {
2045 : // Do not replace triple articulation if this happens
2046 878 : return;
2047 : }
2048 :
2049 28 : SymId newSymId = SymId::noSym;
2050 56 : for (int i = 0; i < 2; ++i) {
2051 56 : if (newSymId != SymId::noSym) {
2052 28 : break;
2053 : }
2054 28 : Articulation* ai = pairableArticulations[i];
2055 28 : Articulation* aj = pairableArticulations[(i == 0) ? 1 : 0];
2056 28 : if (ai->isStaccato()) {
2057 18 : if (aj->isAccent()) {
2058 6 : newSymId = SymId::articAccentStaccatoAbove;
2059 12 : } else if (aj->isMarcato()) {
2060 6 : newSymId = SymId::articMarcatoStaccatoAbove;
2061 6 : } else if (aj->isTenuto()) {
2062 6 : newSymId = SymId::articTenutoStaccatoAbove;
2063 : }
2064 10 : } else if (ai->isTenuto()) {
2065 10 : if (aj->isAccent()) {
2066 4 : newSymId = SymId::articTenutoAccentAbove;
2067 6 : } else if (aj->isMarcato()) {
2068 6 : newSymId = SymId::articMarcatoTenutoAbove;
2069 : }
2070 : }
2071 : }
2072 :
2073 28 : if (newSymId != SymId::noSym) {
2074 : // We reuse old articulation and change symbol ID
2075 : // rather than constructing a new articulation
2076 : // in order to preserve its other properties.
2077 28 : Articulation* newArtic = pairableArticulations[0];
2078 84 : for (Articulation* a : pairableArticulations) {
2079 56 : chord->remove(a);
2080 56 : if (a != newArtic) {
2081 28 : delete a;
2082 : }
2083 : }
2084 :
2085 28 : ArticulationAnchor anchor = newArtic->anchor();
2086 28 : newArtic->setSymId(newSymId);
2087 28 : newArtic->setAnchor(anchor);
2088 28 : chord->add(newArtic);
2089 : }
2090 906 : }
2091 :
2092 : //---------------------------------------------------------
2093 : // fixTies
2094 : //---------------------------------------------------------
2095 :
2096 906 : static void fixTies(Chord* chord)
2097 : {
2098 906 : std::vector<Note*> notes;
2099 1892 : for (Note* note : chord->notes()) {
2100 986 : Tie* tie = note->tieBack();
2101 986 : if (tie && tie->startNote()->pitch() != note->pitch()) {
2102 0 : notes.push_back(tie->startNote());
2103 : }
2104 : }
2105 906 : for (Note* note : notes) {
2106 0 : Note* endNote = chord->findNote(note->pitch());
2107 0 : Note* oldNote = note->tieFor()->endNote();
2108 0 : if (oldNote) {
2109 0 : oldNote->setTieBack(nullptr);
2110 : }
2111 0 : note->tieFor()->setEndNote(endNote);
2112 : }
2113 906 : }
2114 :
2115 : //---------------------------------------------------------
2116 : // readChord
2117 : //---------------------------------------------------------
2118 :
2119 906 : static void readChord(Chord* chord, XmlReader& e, ReadContext& ctx)
2120 : {
2121 3522 : while (e.readNextStartElement()) {
2122 2616 : const AsciiStringView tag(e.name());
2123 2616 : if (tag == "Note") {
2124 986 : Note* note = Factory::createNote(chord);
2125 : // the note needs to know the properties of the track it belongs to
2126 986 : note->setTrack(chord->track());
2127 986 : readNote206(note, e, ctx);
2128 986 : chord->add(note);
2129 1630 : } else if (tag == "Stem") {
2130 0 : Stem* stem = Factory::createStem(chord);
2131 0 : while (e.readNextStartElement()) {
2132 0 : const AsciiStringView t(e.name());
2133 0 : if (t == "subtype") { // obsolete
2134 0 : e.skipCurrentElement();
2135 0 : } else if (!read400::TRead::readProperties(stem, e, ctx)) {
2136 0 : e.unknown();
2137 : }
2138 : }
2139 0 : chord->add(stem);
2140 1630 : } else if (tag == "Lyrics") {
2141 1 : Lyrics* lyrics = Factory::createLyrics(chord);
2142 1 : lyrics->setTrack(ctx.track());
2143 1 : readLyrics(lyrics, e, ctx);
2144 1 : chord->add(lyrics);
2145 1629 : } else if (Read206::readChordProperties206(e, ctx, chord)) {
2146 : } else {
2147 362 : e.unknown();
2148 : }
2149 : }
2150 906 : convertDoubleArticulations(chord);
2151 906 : fixTies(chord);
2152 906 : }
2153 :
2154 : //---------------------------------------------------------
2155 : // readRest
2156 : //---------------------------------------------------------
2157 :
2158 239 : static void readRest(Rest* rest, XmlReader& e, ReadContext& ctx)
2159 : {
2160 985 : while (e.readNextStartElement()) {
2161 507 : if (!Read206::readChordRestProperties206(e, ctx, rest)) {
2162 78 : e.unknown();
2163 : }
2164 : }
2165 239 : }
2166 :
2167 : //---------------------------------------------------------
2168 : // readTextLineProperties
2169 : //---------------------------------------------------------
2170 :
2171 18 : static bool readTextLineProperties(XmlReader& e, ReadContext& ctx, TextLineBase* tl)
2172 : {
2173 18 : const AsciiStringView tag(e.name());
2174 :
2175 18 : if (tag == "beginText") {
2176 5 : Text* text = Factory::createText(ctx.dummy(), TextStyleType::DEFAULT, false);
2177 5 : readText206(e, ctx, text, tl);
2178 5 : tl->setBeginText(text->xmlText());
2179 5 : tl->setPropertyFlags(Pid::BEGIN_TEXT, PropertyFlags::UNSTYLED);
2180 5 : delete text;
2181 13 : } else if (tag == "continueText") {
2182 3 : Text* text = Factory::createText(ctx.dummy(), TextStyleType::DEFAULT, false);
2183 3 : readText206(e, ctx, text, tl);
2184 3 : tl->setContinueText(text->xmlText());
2185 3 : tl->setPropertyFlags(Pid::CONTINUE_TEXT, PropertyFlags::UNSTYLED);
2186 3 : delete text;
2187 10 : } else if (tag == "endText") {
2188 0 : Text* text = Factory::createText(ctx.dummy(), TextStyleType::DEFAULT, false);
2189 0 : readText206(e, ctx, text, tl);
2190 0 : tl->setEndText(text->xmlText());
2191 0 : tl->setPropertyFlags(Pid::END_TEXT, PropertyFlags::UNSTYLED);
2192 0 : delete text;
2193 10 : } else if (tag == "beginHook") {
2194 0 : tl->setBeginHookType(e.readBool() ? HookType::HOOK_90 : HookType::NONE);
2195 10 : } else if (tag == "endHook") {
2196 3 : tl->setEndHookType(e.readBool() ? HookType::HOOK_90 : HookType::NONE);
2197 7 : } else if (tag == "beginHookType") {
2198 0 : tl->setBeginHookType(e.readInt() == 0 ? HookType::HOOK_90 : HookType::HOOK_45);
2199 7 : } else if (tag == "endHookType") {
2200 0 : tl->setEndHookType(e.readInt() == 0 ? HookType::HOOK_90 : HookType::HOOK_45);
2201 7 : } else if (!read400::TRead::readProperties(tl, e, ctx)) {
2202 2 : return false;
2203 : }
2204 :
2205 16 : return true;
2206 : }
2207 :
2208 : //---------------------------------------------------------
2209 : // readVolta206
2210 : //---------------------------------------------------------
2211 :
2212 1 : static void readVolta206(XmlReader& e, ReadContext& ctx, Volta* volta)
2213 : {
2214 4 : while (e.readNextStartElement()) {
2215 3 : const AsciiStringView tag(e.name());
2216 3 : if (tag == "endings") {
2217 1 : String s = e.readText();
2218 1 : StringList sl = s.split(u',', muse::SkipEmptyParts);
2219 1 : volta->endings().clear();
2220 2 : for (const String& l : sl) {
2221 1 : int i = l.simplified().toInt();
2222 1 : volta->endings().push_back(i);
2223 : }
2224 3 : } else if (tag == "lineWidth") {
2225 0 : volta->setLineWidth(Spatium(e.readDouble()));
2226 2 : } else if (!readTextLineProperties(e, ctx, volta)) {
2227 0 : e.unknown();
2228 : }
2229 : }
2230 1 : CompatUtils::resetHookHeightSign(volta);
2231 1 : adjustPlacement(volta);
2232 1 : CompatUtils::migrateOffsetPre302(volta, ctx.mscVersion());
2233 1 : }
2234 :
2235 : //---------------------------------------------------------
2236 : // readPedal
2237 : //---------------------------------------------------------
2238 :
2239 1 : static void readPedal(XmlReader& e, ReadContext& ctx, Pedal* pedal)
2240 : {
2241 1 : bool beginTextTag = false;
2242 1 : bool continueTextTag = false;
2243 1 : bool endTextTag = false;
2244 :
2245 6 : while (e.readNextStartElement()) {
2246 5 : const AsciiStringView tag(e.name());
2247 5 : if (readTextLineProperties(e, ctx, pedal)) {
2248 5 : beginTextTag = beginTextTag || tag == "beginText";
2249 5 : continueTextTag = continueTextTag || tag == "continueText";
2250 5 : endTextTag = endTextTag || tag == "endText";
2251 : } else {
2252 0 : e.unknown();
2253 : }
2254 : }
2255 :
2256 : // Set to the 206 defaults if no value was specified;
2257 : // or follow the new style setting if the specified value matches it
2258 1 : if (!beginTextTag) {
2259 0 : pedal->setBeginText(String());
2260 0 : pedal->setPropertyFlags(Pid::BEGIN_TEXT, PropertyFlags::UNSTYLED);
2261 1 : } else if (pedal->beginText() == pedal->propertyDefault(Pid::BEGIN_TEXT).value<String>()) {
2262 1 : pedal->setPropertyFlags(Pid::BEGIN_TEXT, PropertyFlags::STYLED);
2263 : }
2264 1 : if (!continueTextTag) {
2265 0 : pedal->setContinueText(String());
2266 0 : pedal->setPropertyFlags(Pid::CONTINUE_TEXT, PropertyFlags::UNSTYLED);
2267 1 : } else if (pedal->continueText() == pedal->propertyDefault(Pid::CONTINUE_TEXT).value<String>()) {
2268 1 : pedal->setPropertyFlags(Pid::CONTINUE_TEXT, PropertyFlags::STYLED);
2269 : }
2270 1 : if (!endTextTag) {
2271 1 : pedal->setEndText(String());
2272 1 : pedal->setPropertyFlags(Pid::END_TEXT, PropertyFlags::UNSTYLED);
2273 0 : } else if (pedal->endText() == pedal->propertyDefault(Pid::END_TEXT).value<String>()) {
2274 0 : pedal->setPropertyFlags(Pid::END_TEXT, PropertyFlags::STYLED);
2275 : }
2276 :
2277 1 : CompatUtils::resetHookHeightSign(pedal);
2278 1 : adjustPlacement(pedal);
2279 1 : CompatUtils::migrateOffsetPre302(pedal, ctx.mscVersion());
2280 1 : }
2281 :
2282 : //---------------------------------------------------------
2283 : // readOttava
2284 : //---------------------------------------------------------
2285 :
2286 1 : static void readOttava(XmlReader& e, ReadContext& ctx, Ottava* ottava)
2287 : {
2288 2 : while (e.readNextStartElement()) {
2289 1 : const AsciiStringView tag(e.name());
2290 1 : if (tag == "subtype") {
2291 1 : String s = e.readText();
2292 : bool ok;
2293 1 : int idx = s.toInt(&ok);
2294 1 : if (!ok) {
2295 1 : idx = 0; // OttavaType::OTTAVA_8VA;
2296 1 : int i = 0;
2297 2 : for (auto p : { u"8va", u"8vb", u"15ma", u"15mb", u"22ma", u"22mb" }) {
2298 2 : if (p == s) {
2299 1 : idx = i;
2300 1 : break;
2301 : }
2302 1 : ++i;
2303 : }
2304 : }
2305 1 : ottava->setOttavaType(OttavaType(idx));
2306 1 : } else if (tag == "numbersOnly") {
2307 0 : ottava->setNumbersOnly(e.readBool());
2308 0 : } else if (!readTextLineProperties(e, ctx, ottava)) {
2309 0 : e.unknown();
2310 : }
2311 : }
2312 1 : ottava->styleChanged();
2313 1 : CompatUtils::resetHookHeightSign(ottava);
2314 1 : adjustPlacement(ottava);
2315 1 : CompatUtils::migrateOffsetPre302(ottava, ctx.mscVersion());
2316 1 : }
2317 :
2318 3 : void Read206::readHairpin206(XmlReader& e, ReadContext& ctx, Hairpin* h)
2319 : {
2320 3 : bool useText = false;
2321 13 : while (e.readNextStartElement()) {
2322 10 : const AsciiStringView tag(e.name());
2323 10 : if (tag == "subtype") {
2324 2 : h->setHairpinType(HairpinType(e.readInt()));
2325 8 : } else if (tag == "lineWidth") {
2326 0 : h->setLineWidth(Spatium(e.readDouble()));
2327 : // lineWidthStyle = PropertyFlags::UNSTYLED;
2328 8 : } else if (tag == "hairpinHeight") {
2329 0 : h->setHairpinHeight(Spatium(e.readDouble()));
2330 : // hairpinHeightStyle = PropertyFlags::UNSTYLED;
2331 8 : } else if (tag == "hairpinContHeight") {
2332 0 : h->setHairpinContHeight(Spatium(e.readDouble()));
2333 : // hairpinContHeightStyle = PropertyFlags::UNSTYLED;
2334 8 : } else if (tag == "hairpinCircledTip") {
2335 0 : h->setHairpinCircledTip(e.readInt());
2336 8 : } else if (tag == "veloChange") {
2337 0 : h->setVeloChange(e.readInt());
2338 8 : } else if (tag == "dynType") {
2339 0 : h->setVoiceAssignment(readDynamicRange(e.readInt()));
2340 8 : } else if (tag == "useTextLine") { // < 206
2341 0 : e.readInt();
2342 0 : if (h->hairpinType() == HairpinType::CRESC_HAIRPIN) {
2343 0 : h->setHairpinType(HairpinType::CRESC_LINE);
2344 0 : } else if (h->hairpinType() == HairpinType::DIM_HAIRPIN) {
2345 0 : h->setHairpinType(HairpinType::DIM_LINE);
2346 : }
2347 0 : useText = true;
2348 8 : } else if (!readTextLineProperties(e, ctx, h)) {
2349 2 : e.unknown();
2350 : }
2351 : }
2352 3 : if (!useText) {
2353 3 : h->setBeginText(u"");
2354 3 : h->setContinueText(u"");
2355 3 : h->setEndText(u"");
2356 : }
2357 3 : CompatUtils::resetHookHeightSign(h);
2358 3 : adjustPlacement(h);
2359 3 : CompatUtils::migrateOffsetPre302(h, ctx.mscVersion());
2360 3 : }
2361 :
2362 0 : void Read206::readTrill206(XmlReader& e, ReadContext& ctx, Trill* t)
2363 : {
2364 0 : while (e.readNextStartElement()) {
2365 0 : const AsciiStringView tag(e.name());
2366 0 : if (tag == "subtype") {
2367 0 : t->setTrillType(TConv::fromXml(e.readAsciiText(), TrillType::TRILL_LINE));
2368 0 : } else if (tag == "Accidental") {
2369 0 : Accidental* _accidental = Factory::createAccidental(t);
2370 0 : readAccidental206(_accidental, e, ctx);
2371 0 : t->setAccidental(_accidental);
2372 0 : if (t->ornament()) {
2373 0 : t->ornament()->setTrillOldCompatAccidental(_accidental);
2374 : }
2375 0 : } else if (tag == "ornamentStyle") {
2376 0 : read400::TRead::readProperty(t, e, ctx, Pid::ORNAMENT_STYLE);
2377 0 : } else if (tag == "play") {
2378 0 : t->setPlaySpanner(e.readBool());
2379 0 : } else if (!TRead::readProperties(static_cast<SLine*>(t), e, ctx)) {
2380 0 : e.unknown();
2381 : }
2382 : }
2383 0 : adjustPlacement(t);
2384 0 : CompatUtils::migrateOffsetPre302(t, ctx.mscVersion());
2385 0 : }
2386 :
2387 1 : void Read206::readTextLine206(XmlReader& e, ReadContext& ctx, TextLineBase* tlb)
2388 : {
2389 5 : while (e.readNextStartElement()) {
2390 3 : if (!readTextLineProperties(e, ctx, tlb)) {
2391 0 : e.unknown();
2392 : }
2393 : }
2394 1 : CompatUtils::resetHookHeightSign(tlb);
2395 1 : adjustPlacement(tlb);
2396 1 : CompatUtils::migrateOffsetPre302(tlb, ctx.mscVersion());
2397 1 : }
2398 :
2399 : //---------------------------------------------------------
2400 : // setFermataPlacement
2401 : // set fermata placement from old ArticulationAnchor
2402 : // for backwards compatibility
2403 : //---------------------------------------------------------
2404 :
2405 18 : static void setFermataPlacement(EngravingItem* el, ArticulationAnchor anchor, DirectionV direction)
2406 : {
2407 18 : if (direction == DirectionV::UP) {
2408 6 : el->setPlacement(PlacementV::ABOVE);
2409 12 : } else if (direction == DirectionV::DOWN) {
2410 12 : el->setPlacement(PlacementV::BELOW);
2411 : } else {
2412 0 : switch (anchor) {
2413 0 : case ArticulationAnchor::TOP:
2414 0 : el->setPlacement(PlacementV::ABOVE);
2415 0 : break;
2416 :
2417 0 : case ArticulationAnchor::BOTTOM:
2418 0 : el->setPlacement(PlacementV::BELOW);
2419 0 : break;
2420 :
2421 0 : case ArticulationAnchor::AUTO:
2422 0 : break;
2423 0 : default:
2424 0 : break;
2425 : }
2426 : }
2427 18 : }
2428 :
2429 252 : EngravingItem* Read206::readArticulation(EngravingItem* parent, XmlReader& e, ReadContext& ctx)
2430 : {
2431 252 : EngravingItem* el = nullptr;
2432 252 : SymId sym = SymId::fermataAbove; // default -- backward compatibility (no type = ufermata in 1.2)
2433 252 : ArticulationAnchor anchor = ArticulationAnchor::TOP;
2434 252 : DirectionV direction = DirectionV::AUTO;
2435 252 : track_idx_t track = parent->track();
2436 252 : double timeStretch = 0.0;
2437 252 : bool useDefaultPlacement = true;
2438 :
2439 98 : auto readProperties = [](EngravingItem* el, XmlReader& e, ReadContext& ctx)
2440 : {
2441 98 : if (el->isFermata()) {
2442 4 : return read400::TRead::readProperties(toFermata(el), e, ctx);
2443 94 : } else if (el->isArticulationFamily()) {
2444 94 : return read400::TRead::readProperties(dynamic_cast<Articulation*>(el), e, ctx);
2445 : }
2446 0 : UNREACHABLE;
2447 : return false;
2448 : };
2449 :
2450 665 : while (e.readNextStartElement()) {
2451 413 : const AsciiStringView tag(e.name());
2452 413 : if (tag == "subtype") {
2453 249 : String s = e.readText();
2454 249 : if (s.at(0).isDigit()) {
2455 0 : int oldType = s.toInt();
2456 0 : sym = articulationNames[oldType].id;
2457 : } else {
2458 249 : muse::ByteArray ba = s.toAscii();
2459 249 : sym = articulationNames2SymId206(ba.constChar());
2460 249 : if (sym == SymId::noSym) {
2461 : struct {
2462 : const char* name;
2463 : bool up;
2464 : SymId id;
2465 14 : } al[] =
2466 : {
2467 : { "fadein", true, SymId::guitarFadeIn },
2468 : { "fadeout", true, SymId::guitarFadeOut },
2469 : { "volumeswell", true, SymId::guitarVolumeSwell },
2470 : { "wigglesawtooth", true, SymId::wiggleSawtooth },
2471 : { "wigglesawtoothwide", true, SymId::wiggleSawtoothWide },
2472 : { "wigglevibratolargefaster", true, SymId::wiggleVibratoLargeFaster },
2473 : { "wigglevibratolargeslowest", true, SymId::wiggleVibratoLargeSlowest },
2474 : { "umarcato", true, SymId::articMarcatoAbove },
2475 : { "dmarcato", false, SymId::articMarcatoBelow },
2476 : { "ufermata", true, SymId::fermataAbove },
2477 : { "dfermata", false, SymId::fermataBelow },
2478 : { "ushortfermata", true, SymId::fermataShortAbove },
2479 : { "dshortfermata", false, SymId::fermataShortBelow },
2480 : { "ulongfermata", true, SymId::fermataLongAbove },
2481 : { "dlongfermata", false, SymId::fermataLongBelow },
2482 : { "uverylongfermata", true, SymId::fermataVeryLongAbove },
2483 : { "dverylongfermata", false, SymId::fermataVeryLongBelow },
2484 :
2485 : // watch out, bug in 1.2 uportato and dportato are reversed
2486 : { "dportato", true, SymId::articTenutoStaccatoAbove },
2487 : { "uportato", false, SymId::articTenutoStaccatoBelow },
2488 : { "ustaccatissimo", true, SymId::articStaccatissimoAbove },
2489 : { "dstaccatissimo", false, SymId::articStaccatissimoBelow }
2490 : };
2491 : int i;
2492 14 : int n = sizeof(al) / sizeof(*al);
2493 215 : for (i = 0; i < n; ++i) {
2494 214 : if (s == al[i].name) {
2495 13 : sym = al[i].id;
2496 13 : bool up = al[i].up;
2497 13 : direction = up ? DirectionV::UP : DirectionV::DOWN;
2498 13 : if ((direction == DirectionV::DOWN) != (track & 1)) {
2499 7 : useDefaultPlacement = false;
2500 : }
2501 13 : break;
2502 : }
2503 : }
2504 14 : if (i == n) {
2505 1 : sym = SymNames::symIdByName(s);
2506 1 : if (sym == SymId::noSym) {
2507 1 : LOGD("Articulation: unknown type <%s>", muPrintable(s));
2508 : }
2509 : }
2510 : }
2511 249 : }
2512 249 : switch (sym) {
2513 38 : case SymId::fermataAbove:
2514 : case SymId::fermataBelow:
2515 : case SymId::fermataShortAbove:
2516 : case SymId::fermataShortBelow:
2517 : case SymId::fermataLongAbove:
2518 : case SymId::fermataLongBelow:
2519 : case SymId::fermataVeryLongAbove:
2520 : case SymId::fermataVeryLongBelow: {
2521 38 : Fermata* fe = Factory::createFermata(ctx.dummy());
2522 38 : fe->setSymIdAndTimeStretch(sym);
2523 38 : el = fe;
2524 38 : } break;
2525 211 : default:
2526 211 : Articulation* ar = Factory::createArticulation(ctx.dummy());
2527 211 : ar->setSymId(sym);
2528 211 : ar->setDirection(direction);
2529 211 : el = ar;
2530 211 : break;
2531 : }
2532 413 : } else if (tag == "anchor") {
2533 22 : useDefaultPlacement = false;
2534 22 : if (!el || el->isFermata()) {
2535 0 : anchor = CompatUtils::translateToNewArticulationAnchor(e.readInt());
2536 : } else {
2537 22 : readProperties(el, e, ctx);
2538 : }
2539 142 : } else if (tag == "direction") {
2540 64 : useDefaultPlacement = false;
2541 64 : if (!el || el->isFermata()) {
2542 64 : direction = TConv::fromXml(e.readAsciiText(), DirectionV::AUTO);
2543 : } else {
2544 0 : readProperties(el, e, ctx);
2545 : }
2546 78 : } else if (tag == "timeStretch") {
2547 2 : timeStretch = e.readDouble();
2548 : } else {
2549 76 : if (!el) {
2550 0 : LOGD("not handled <%s>", tag.ascii());
2551 : }
2552 76 : if (!el || !readProperties(el, e, ctx)) {
2553 56 : e.unknown();
2554 : }
2555 : }
2556 : }
2557 : // Special case for "no type" = ufermata, with missing subtype tag
2558 252 : if (!el) {
2559 3 : Fermata* f = Factory::createFermata(ctx.dummy());
2560 3 : f->setSymIdAndTimeStretch(sym);
2561 3 : el = f;
2562 : }
2563 252 : if (el->isFermata()) {
2564 41 : if (!RealIsNull(timeStretch)) {
2565 2 : el->setProperty(Pid::TIME_STRETCH, timeStretch);
2566 : }
2567 41 : if (useDefaultPlacement) {
2568 23 : el->setPlacement(track & 1 ? PlacementV::BELOW : PlacementV::ABOVE);
2569 : } else {
2570 18 : setFermataPlacement(el, anchor, direction);
2571 : }
2572 : }
2573 252 : el->setTrack(track);
2574 252 : return el;
2575 : }
2576 :
2577 : //---------------------------------------------------------
2578 : // readSlurTieProperties
2579 : //---------------------------------------------------------
2580 :
2581 0 : static bool readSlurTieProperties(XmlReader& e, ReadContext& ctx, SlurTie* st)
2582 : {
2583 0 : const AsciiStringView tag(e.name());
2584 :
2585 0 : if (read400::TRead::readProperty(st, tag, e, ctx, Pid::SLUR_DIRECTION)) {
2586 0 : } else if (tag == "lineType") {
2587 0 : st->setStyleType(static_cast<SlurStyleType>(e.readInt()));
2588 0 : } else if (tag == "SlurSegment") {
2589 0 : SlurTieSegment* s = st->newSlurTieSegment();
2590 0 : st->add(s);
2591 0 : read400::TRead::read(s, e, ctx);
2592 0 : } else if (!TRead::readItemProperties(st, e, ctx)) {
2593 0 : return false;
2594 : }
2595 0 : return true;
2596 : }
2597 :
2598 16 : void Read206::readSlur206(XmlReader& e, ReadContext& ctx, Slur* s)
2599 : {
2600 16 : s->setTrack(ctx.track()); // set staff
2601 16 : ctx.addSpanner(e.intAttribute("id"), s);
2602 16 : while (e.readNextStartElement()) {
2603 0 : const AsciiStringView tag(e.name());
2604 0 : if (tag == "track2") {
2605 0 : s->setTrack2(e.readInt());
2606 0 : } else if (tag == "startTrack") { // obsolete
2607 0 : s->setTrack(e.readInt());
2608 0 : } else if (tag == "endTrack") { // obsolete
2609 0 : e.readInt();
2610 0 : } else if (!readSlurTieProperties(e, ctx, s)) {
2611 0 : e.unknown();
2612 : }
2613 : }
2614 16 : if (s->track2() == muse::nidx) {
2615 16 : s->setTrack2(s->track());
2616 : }
2617 16 : }
2618 :
2619 0 : void Read206::readTie206(XmlReader& e, ReadContext& ctx, Tie* t)
2620 : {
2621 0 : ctx.addSpanner(e.intAttribute("id"), t);
2622 0 : while (e.readNextStartElement()) {
2623 0 : if (readSlurTieProperties(e, ctx, t)) {
2624 : } else {
2625 0 : e.unknown();
2626 : }
2627 : }
2628 0 : if (ctx.mscVersion() <= 114 && t->spannerSegments().size() == 1) {
2629 : // ignore manual adjustments to single-segment ties in older scores
2630 0 : TieSegment* ss = t->frontSegment();
2631 0 : PointF zeroP;
2632 0 : ss->ups(Grip::START).off = zeroP;
2633 0 : ss->ups(Grip::BEZIER1).off = zeroP;
2634 0 : ss->ups(Grip::BEZIER2).off = zeroP;
2635 0 : ss->ups(Grip::END).off = zeroP;
2636 0 : ss->setOffset(zeroP);
2637 0 : ss->setUserOff2(zeroP);
2638 : }
2639 0 : }
2640 :
2641 : //---------------------------------------------------------
2642 : // readMeasure
2643 : //---------------------------------------------------------
2644 :
2645 441 : static void readMeasure206(Measure* m, int staffIdx, XmlReader& e, ReadContext& ctx)
2646 : {
2647 441 : Segment* segment = 0;
2648 :
2649 441 : std::vector<Chord*> graceNotes;
2650 441 : ctx.tuplets().clear();
2651 441 : ctx.setTrack(staffIdx * VOICES);
2652 :
2653 441 : m->createStaves(staffIdx);
2654 :
2655 : // tick is obsolete
2656 441 : if (e.hasAttribute("tick")) {
2657 0 : ctx.setTick(Fraction::fromTicks(ctx.fileDivision(e.intAttribute("tick"))));
2658 : }
2659 :
2660 : bool irregular;
2661 441 : if (e.hasAttribute("len")) {
2662 10 : StringList sl = e.attribute("len").split(u'/');
2663 10 : if (sl.size() == 2) {
2664 10 : m->setTicks(Fraction(sl.at(0).toInt(), sl.at(1).toInt()));
2665 : } else {
2666 0 : LOGD("illegal measure size <%s>", muPrintable(e.attribute("len")));
2667 : }
2668 10 : irregular = true;
2669 10 : ctx.compatTimeSigMap()->add(m->tick().ticks(), SigEvent(m->ticks(), m->timesig()));
2670 10 : ctx.compatTimeSigMap()->add(m->endTick().ticks(), SigEvent(m->timesig()));
2671 10 : } else {
2672 431 : irregular = false;
2673 : }
2674 :
2675 441 : Staff* staff = ctx.staff(staffIdx);
2676 441 : Fraction timeStretch(staff->timeStretch(m->tick()));
2677 :
2678 : // keep track of tick of previous element
2679 : // this allows markings that need to apply to previous element to do so
2680 : // even though we may have already advanced to next tick position
2681 441 : Fraction lastTick = ctx.tick();
2682 :
2683 1906 : while (e.readNextStartElement()) {
2684 1465 : const AsciiStringView tag(e.name());
2685 :
2686 1465 : if (tag == "move") {
2687 0 : ctx.setTick(e.readFraction() + m->tick());
2688 1465 : } else if (tag == "tick") {
2689 8 : ctx.setTick(Fraction::fromTicks(ctx.fileDivision(e.readInt())));
2690 8 : lastTick = ctx.tick();
2691 1457 : } else if (tag == "BarLine") {
2692 64 : Fermata* fermataAbove = nullptr;
2693 64 : Fermata* fermataBelow = nullptr;
2694 64 : BarLine* bl = Factory::createBarLine(ctx.dummy());
2695 64 : bl->setTrack(ctx.track());
2696 202 : while (e.readNextStartElement()) {
2697 138 : const AsciiStringView t(e.name());
2698 138 : if (t == "subtype") {
2699 64 : bl->setBarLineType(TConv::fromXml(e.readAsciiText(), BarLineType::NORMAL));
2700 74 : } else if (t == "customSubtype") { // obsolete
2701 0 : e.readInt();
2702 74 : } else if (t == "span") {
2703 64 : const int span = e.readInt();
2704 64 : if (span > 0) {
2705 64 : ctx.setBarLineSpan(bl, static_cast<size_t>(span - 1));
2706 : } else {
2707 0 : if (span < 0) {
2708 0 : LOGW() << "bar line span is negative: " << span;
2709 : }
2710 :
2711 0 : ctx.setBarLineSpan(bl, 0);
2712 : }
2713 10 : } else if (t == "spanFromOffset") {
2714 0 : bl->setSpanFrom(e.readInt());
2715 10 : } else if (t == "spanToOffset") {
2716 0 : bl->setSpanTo(e.readInt());
2717 10 : } else if (t == "Articulation") {
2718 0 : EngravingItem* el = Read206::readArticulation(bl, e, ctx);
2719 0 : if (el->isFermata()) {
2720 0 : if (el->placement() == PlacementV::ABOVE) {
2721 0 : fermataAbove = toFermata(el);
2722 : } else {
2723 0 : fermataBelow = toFermata(el);
2724 0 : const std::optional<size_t> barLineSpan = ctx.getBarLineSpan(bl);
2725 0 : if (barLineSpan) {
2726 0 : fermataBelow->setTrack(staff2track(bl->staffIdx() + *barLineSpan));
2727 : } else {
2728 0 : fermataBelow->setTrack(staff2track(bl->staffIdx() + ctx.getStaffBarLineSpan(staffIdx)));
2729 : }
2730 : }
2731 : } else {
2732 0 : bl->add(el);
2733 : }
2734 10 : } else if (!TRead::readItemProperties(bl, e, ctx)) {
2735 10 : e.unknown();
2736 : }
2737 : }
2738 : //
2739 : // StartRepeatBarLine: always at the beginning tick of a measure, always BarLineType::START_REPEAT
2740 : // BarLine: in the middle of a measure, has no semantic
2741 : // EndBarLine: at the end tick of a measure
2742 : // BeginBarLine: first segment of a measure
2743 :
2744 : SegmentType st;
2745 64 : if ((ctx.tick() != m->tick()) && (ctx.tick() != m->endTick())) {
2746 0 : st = SegmentType::BarLine;
2747 64 : } else if (bl->barLineType() == BarLineType::START_REPEAT && ctx.tick() == m->tick()) {
2748 0 : st = SegmentType::StartRepeatBarLine;
2749 64 : } else if (ctx.tick() == m->tick() && segment == 0) {
2750 0 : st = SegmentType::BeginBarLine;
2751 : } else {
2752 64 : st = SegmentType::EndBarLine;
2753 : }
2754 64 : segment = m->getSegment(st, ctx.tick());
2755 64 : segment->add(bl);
2756 64 : if (fermataAbove) {
2757 0 : segment->add(fermataAbove);
2758 : }
2759 64 : if (fermataBelow) {
2760 0 : segment->add(fermataBelow);
2761 : }
2762 1393 : } else if (tag == "Chord") {
2763 906 : segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
2764 906 : Chord* chord = Factory::createChord(segment);
2765 906 : chord->setTrack(ctx.track());
2766 906 : readChord(chord, e, ctx);
2767 906 : if (chord->noteType() != NoteType::NORMAL) {
2768 0 : graceNotes.push_back(chord);
2769 : } else {
2770 906 : segment->add(chord);
2771 906 : for (size_t i = 0; i < graceNotes.size(); ++i) {
2772 0 : Chord* gc = graceNotes[i];
2773 0 : gc->setGraceIndex(i);
2774 0 : chord->add(gc);
2775 : }
2776 906 : graceNotes.clear();
2777 906 : Fraction crticks = chord->actualTicks();
2778 906 : lastTick = ctx.tick();
2779 906 : ctx.incTick(crticks);
2780 : }
2781 487 : } else if (tag == "Rest") {
2782 241 : if (m->isMMRest()) {
2783 4 : segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
2784 4 : MMRest* mmr = Factory::createMMRest(segment);
2785 4 : mmr->setTrack(ctx.track());
2786 4 : read400::TRead::read(mmr, e, ctx);
2787 4 : mmr->setTicks(m->ticks());
2788 4 : segment->add(mmr);
2789 4 : lastTick = ctx.tick();
2790 4 : ctx.incTick(mmr->actualTicks());
2791 : } else {
2792 237 : segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
2793 237 : Rest* rest = Factory::createRest(segment);
2794 237 : rest->setDurationType(DurationType::V_MEASURE);
2795 237 : rest->setTicks(m->timesig() / timeStretch);
2796 237 : rest->setTrack(ctx.track());
2797 237 : readRest(rest, e, ctx);
2798 237 : segment->add(rest);
2799 :
2800 237 : if (!rest->ticks().isValid()) { // hack
2801 0 : rest->setTicks(m->timesig() / timeStretch);
2802 : }
2803 :
2804 237 : lastTick = ctx.tick();
2805 237 : ctx.incTick(rest->actualTicks());
2806 : }
2807 246 : } else if (tag == "Breath") {
2808 3 : Breath* breath = Factory::createBreath(ctx.dummy());
2809 3 : breath->setTrack(ctx.track());
2810 3 : breath->setPlacement(PlacementV::ABOVE);
2811 3 : Fraction tick = ctx.tick();
2812 3 : read400::TRead::read(breath, e, ctx);
2813 : // older scores placed the breath segment right after the chord to which it applies
2814 : // rather than before the next chordrest segment with an element for the staff
2815 : // result would be layout too far left if there are other segments due to notes in other staves
2816 : // we need to find tick of chord to which this applies, and add its duration
2817 3 : Fraction prevTick;
2818 3 : if (ctx.tick() < tick) {
2819 0 : prevTick = ctx.tick(); // use our own tick if we explicitly reset to earlier position
2820 : } else {
2821 3 : prevTick = lastTick; // otherwise use tick of previous tick/chord/rest tag
2822 : }
2823 : // find segment
2824 3 : Segment* prev = m->findSegment(SegmentType::ChordRest, prevTick);
2825 3 : if (prev) {
2826 : // find chordrest
2827 3 : ChordRest* lastCR = toChordRest(prev->element(ctx.track()));
2828 3 : if (lastCR) {
2829 3 : tick = prevTick + lastCR->actualTicks();
2830 : }
2831 : }
2832 3 : segment = m->getSegment(SegmentType::Breath, tick);
2833 3 : segment->add(breath);
2834 243 : } else if (tag == "endSpanner") {
2835 6 : int id = e.attribute("id").toInt();
2836 6 : Spanner* spanner = ctx.findSpanner(id);
2837 6 : if (spanner) {
2838 6 : spanner->setTicks(ctx.tick() - spanner->tick());
2839 : // if (spanner->track2() == -1)
2840 : // the absence of a track tag [?] means the
2841 : // track is the same as the beginning of the slur
2842 6 : if (spanner->track2() == muse::nidx) {
2843 0 : spanner->setTrack2(spanner->track() ? spanner->track() : ctx.track());
2844 : }
2845 : } else {
2846 : // remember "endSpanner" values
2847 0 : SpannerValues sv;
2848 0 : sv.spannerId = id;
2849 0 : sv.track2 = ctx.track();
2850 0 : sv.tick2 = ctx.tick();
2851 0 : ctx.addSpannerValues(sv);
2852 : }
2853 6 : e.readNext();
2854 237 : } else if (tag == "Slur") {
2855 0 : Slur* sl = Factory::createSlur(ctx.dummy());
2856 0 : sl->setTick(ctx.tick());
2857 0 : Read206::readSlur206(e, ctx, sl);
2858 : //
2859 : // check if we already saw "endSpanner"
2860 : //
2861 0 : int id = ctx.spannerId(sl);
2862 0 : const SpannerValues* sv = ctx.spannerValues(id);
2863 0 : if (sv) {
2864 0 : sl->setTick2(sv->tick2);
2865 0 : sl->setTrack2(sv->track2);
2866 : }
2867 0 : ctx.addSpanner(sl);
2868 237 : } else if (tag == "HairPin"
2869 235 : || tag == "Pedal"
2870 234 : || tag == "Ottava"
2871 233 : || tag == "Trill"
2872 233 : || tag == "TextLine"
2873 472 : || tag == "Volta") {
2874 6 : Spanner* sp = toSpanner(Factory::createItemByName(tag, ctx.dummy()));
2875 6 : sp->setTrack(ctx.track());
2876 6 : sp->setTrack2(sp->track());
2877 6 : sp->setTick(ctx.tick());
2878 6 : sp->eraseSpannerSegments();
2879 6 : ctx.addSpanner(e.intAttribute("id", -1), sp);
2880 :
2881 6 : if (tag == "Volta") {
2882 1 : readVolta206(e, ctx, toVolta(sp));
2883 5 : } else if (tag == "Pedal") {
2884 1 : readPedal(e, ctx, toPedal(sp));
2885 4 : } else if (tag == "Ottava") {
2886 1 : readOttava(e, ctx, toOttava(sp));
2887 3 : } else if (tag == "HairPin") {
2888 2 : Read206::readHairpin206(e, ctx, toHairpin(sp));
2889 1 : } else if (tag == "Trill") {
2890 0 : Ornament* ornament = Factory::createOrnament(m->score()->dummy());
2891 0 : toTrill(sp)->setOrnament(ornament);
2892 0 : Read206::readTrill206(e, ctx, toTrill(sp));
2893 : } else {
2894 1 : Read206::readTextLine206(e, ctx, toTextLineBase(sp));
2895 : }
2896 6 : ctx.addSpanner(sp);
2897 : //
2898 : // check if we already saw "endSpanner"
2899 : //
2900 6 : int id = ctx.spannerId(sp);
2901 6 : const SpannerValues* sv = ctx.spannerValues(id);
2902 6 : if (sv) {
2903 0 : sp->setTicks(sv->tick2 - sp->tick());
2904 0 : sp->setTrack2(sv->track2);
2905 : }
2906 231 : } else if (tag == "RepeatMeasure") {
2907 2 : segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
2908 2 : MeasureRepeat* rm = Factory::createMeasureRepeat(segment);
2909 2 : rm->setTrack(ctx.track());
2910 2 : readRest(rm, e, ctx);
2911 2 : rm->setNumMeasures(1);
2912 2 : m->setMeasureRepeatCount(1, staffIdx);
2913 2 : segment->add(rm);
2914 2 : lastTick = ctx.tick();
2915 2 : ctx.incTick(m->ticks());
2916 229 : } else if (tag == "Clef") {
2917 29 : Clef* clef = Factory::createClef(ctx.dummy());
2918 29 : clef->setTrack(ctx.track());
2919 29 : read400::TRead::read(clef, e, ctx);
2920 29 : clef->setGenerated(false);
2921 29 : if (ctx.tick().isZero()) {
2922 11 : if (ctx.staff(staffIdx)->clef(Fraction(0, 1)) != clef->clefType()) {
2923 5 : ctx.staff(staffIdx)->setDefaultClefType(clef->clefType());
2924 : }
2925 11 : delete clef;
2926 11 : continue;
2927 : }
2928 : // there may be more than one clef segment for same tick position
2929 18 : if (!segment) {
2930 : // this is the first segment of measure
2931 1 : segment = m->getSegment(SegmentType::Clef, ctx.tick());
2932 : } else {
2933 17 : bool firstSegment = false;
2934 : // the first clef may be missing and is added later in layout
2935 17 : for (Segment* s = m->segments().first(); s && s->tick() == ctx.tick(); s = s->next()) {
2936 0 : if (s->segmentType() == SegmentType::Clef
2937 : // hack: there may be other segment types which should
2938 : // generate a clef at current position
2939 0 : || s->segmentType() == SegmentType::StartRepeatBarLine
2940 : ) {
2941 0 : firstSegment = true;
2942 0 : break;
2943 : }
2944 : }
2945 17 : if (firstSegment) {
2946 0 : Segment* ns = 0;
2947 0 : if (segment->next()) {
2948 0 : ns = segment->next();
2949 0 : while (ns && ns->tick() < ctx.tick()) {
2950 0 : ns = ns->next();
2951 : }
2952 : }
2953 0 : segment = 0;
2954 0 : for (Segment* s = ns; s && s->tick() == ctx.tick(); s = s->next()) {
2955 0 : if (s->segmentType() == SegmentType::Clef) {
2956 0 : segment = s;
2957 0 : break;
2958 : }
2959 : }
2960 0 : if (!segment) {
2961 0 : segment = Factory::createSegment(m, SegmentType::Clef, ctx.tick() - m->tick());
2962 0 : m->segments().insert(segment, ns);
2963 : }
2964 : } else {
2965 : // this is the first clef: move to left
2966 17 : segment = m->getSegment(SegmentType::Clef, ctx.tick());
2967 : }
2968 : }
2969 18 : if (ctx.tick() != m->tick()) {
2970 17 : clef->setSmall(true); // TODO: layout does this ?
2971 : }
2972 :
2973 : // Clef segments are sorted on layout now. Previously, clef barline position could be out of sync with segment placement.
2974 18 : if (ctx.tick() != Fraction(0, 1) && ctx.tick() == m->tick() && !(m->prevMeasure() && m->prevMeasure()->repeatEnd())) {
2975 1 : clef->setClefToBarlinePosition(ClefToBarlinePosition::AFTER);
2976 : }
2977 :
2978 18 : segment->add(clef);
2979 200 : } else if (tag == "TimeSig") {
2980 62 : TimeSig* ts = Factory::createTimeSig(ctx.dummy());
2981 62 : ts->setTrack(ctx.track());
2982 62 : read400::TRead::read(ts, e, ctx);
2983 : // if time sig not at beginning of measure => courtesy time sig
2984 62 : Fraction currTick = ctx.tick();
2985 62 : bool courtesySig = (currTick > m->tick());
2986 62 : if (courtesySig) {
2987 : // if courtesy sig., just add it without map processing
2988 0 : segment = m->getSegment(SegmentType::TimeSigAnnounce, currTick);
2989 0 : segment->add(ts);
2990 : } else {
2991 : // if 'real' time sig., do full process
2992 62 : segment = m->getSegment(SegmentType::TimeSig, currTick);
2993 62 : segment->add(ts);
2994 :
2995 62 : timeStretch = ts->stretch().reduced();
2996 62 : m->setTimesig(ts->sig() / timeStretch);
2997 :
2998 62 : if (irregular) {
2999 9 : ctx.compatTimeSigMap()->add(m->tick().ticks(), SigEvent(m->ticks(), m->timesig()));
3000 9 : ctx.compatTimeSigMap()->add(m->endTick().ticks(), SigEvent(m->timesig()));
3001 : } else {
3002 53 : m->setTicks(m->timesig());
3003 53 : ctx.compatTimeSigMap()->add(m->tick().ticks(), SigEvent(m->timesig()));
3004 : }
3005 : }
3006 138 : } else if (tag == "KeySig") {
3007 19 : KeySig* ks = Factory::createKeySig(ctx.dummy());
3008 19 : ks->setTrack(ctx.track());
3009 19 : read400::TRead::read(ks, e, ctx);
3010 19 : Fraction curTick = ctx.tick();
3011 : // if key sig not at beginning of measure => courtesy key sig
3012 19 : bool courtesySig = (curTick == m->endTick());
3013 19 : segment = m->getSegment(courtesySig ? SegmentType::KeySigAnnounce : SegmentType::KeySig, curTick);
3014 19 : segment->add(ks);
3015 19 : staff->setKey(curTick, ks->keySigEvent());
3016 119 : } else if (tag == "Text" || tag == "StaffText") {
3017 : // MuseScore 3 has different types for system text and
3018 : // staff text while MuseScore 2 didn't.
3019 : // We need to decide first which one we should create.
3020 11 : TextReaderContext206 tctx(e);
3021 11 : String styleName = ReadStyleName206(tctx.tag());
3022 : StaffTextBase* t;
3023 18 : if (styleName == "System" || styleName == "Tempo"
3024 6 : || styleName == "Marker" || styleName == "Jump"
3025 18 : || styleName == "Volta") { // TODO: is it possible to get it from style?
3026 5 : t = Factory::createSystemText(ctx.dummy());
3027 : } else {
3028 6 : t = Factory::createStaffText(ctx.dummy());
3029 : }
3030 11 : t->setTrack(ctx.track());
3031 11 : readTextPropertyStyle206(tctx.tag(), ctx, t, t);
3032 47 : while (tctx.reader().readNextStartElement()) {
3033 25 : if (!readTextProperties206(tctx.reader(), ctx, t)) {
3034 4 : tctx.reader().unknown();
3035 : }
3036 : }
3037 11 : CompatUtils::migrateOffsetPre302(t, ctx.mscVersion());
3038 11 : if (t->empty()) {
3039 4 : if (t->links()) {
3040 0 : if (t->links()->size() == 1) {
3041 0 : delete t;
3042 : }
3043 : }
3044 : } else {
3045 7 : segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
3046 7 : segment->add(t);
3047 : }
3048 11 : }
3049 : //----------------------------------------------------
3050 : // Annotation
3051 108 : else if (tag == "Dynamic") {
3052 1 : segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
3053 1 : Dynamic* dyn = Factory::createDynamic(segment);
3054 1 : dyn->setTrack(ctx.track());
3055 1 : readDynamic(dyn, e, ctx);
3056 1 : segment->add(dyn);
3057 107 : } else if (tag == "RehearsalMark") {
3058 1 : segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
3059 1 : RehearsalMark* el = Factory::createRehearsalMark(segment);
3060 1 : el->setTrack(ctx.track());
3061 1 : readText206(e, ctx, el, el);
3062 1 : segment->add(el);
3063 106 : } else if (tag == "Harmony"
3064 103 : || tag == "FretDiagram"
3065 103 : || tag == "TremoloBar"
3066 99 : || tag == "Symbol"
3067 99 : || tag == "InstrumentChange"
3068 98 : || tag == "StaffState"
3069 209 : || tag == "FiguredBass"
3070 : ) {
3071 9 : segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
3072 9 : EngravingItem* el = Factory::createItemByName(tag, segment);
3073 : // hack - needed because tick tags are unreliable in 1.3 scores
3074 : // for symbols attached to anything but a measure
3075 9 : el->setTrack(ctx.track());
3076 9 : read400::TRead::readItem(el, e, ctx);
3077 9 : if (el->staff() && (el->isHarmony() || el->isFretDiagram() || el->isInstrumentChange())) {
3078 4 : adjustPlacement(el);
3079 : }
3080 9 : CompatUtils::migrateOffsetPre302(el, ctx.mscVersion());
3081 :
3082 9 : segment->add(el);
3083 97 : } else if (tag == "Tempo") {
3084 1 : segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
3085 1 : TempoText* tt = Factory::createTempoText(segment);
3086 : // hack - needed because tick tags are unreliable in 1.3 scores
3087 : // for symbols attached to anything but a measure
3088 1 : tt->setTrack(ctx.track());
3089 1 : readTempoText(tt, e, ctx);
3090 1 : segment->add(tt);
3091 96 : } else if (tag == "Marker" || tag == "Jump") {
3092 24 : EngravingItem* el = Factory::createItemByName(tag, ctx.dummy());
3093 24 : el->setTrack(ctx.track());
3094 24 : if (tag == "Marker") {
3095 12 : Marker* ma = toMarker(el);
3096 12 : readMarker(ma, e, ctx);
3097 12 : EngravingItem* markerEl = toEngravingItem(ma);
3098 12 : m->add(markerEl);
3099 : } else {
3100 12 : read400::TRead::readItem(el, e, ctx);
3101 12 : m->add(el);
3102 : }
3103 72 : } else if (tag == "Image") {
3104 0 : if (MScore::noImages) {
3105 0 : e.skipCurrentElement();
3106 : } else {
3107 0 : segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
3108 0 : EngravingItem* el = Factory::createItemByName(tag, segment);
3109 0 : el->setTrack(ctx.track());
3110 0 : read400::TRead::readItem(el, e, ctx);
3111 0 : segment->add(el);
3112 : }
3113 72 : } else if (tag == "stretch") {
3114 : // Ignore measure stretch pre 4.0
3115 4 : e.skipCurrentElement();
3116 68 : } else if (tag == "noOffset") {
3117 0 : m->setMeasureNumberOffset(e.readInt());
3118 68 : } else if (tag == "measureNumberMode") {
3119 0 : m->setMeasureNumberMode(MeasureNumberMode(e.readInt()));
3120 68 : } else if (tag == "irregular") {
3121 0 : m->setExcludeFromNumbering(e.readBool());
3122 68 : } else if (tag == "breakMultiMeasureRest") {
3123 0 : m->setBreakMultiMeasureRest(e.readBool());
3124 68 : } else if (tag == "sysInitBarLineType") {
3125 0 : segment = m->getSegment(SegmentType::BeginBarLine, m->tick());
3126 0 : BarLine* barLine = Factory::createBarLine(segment);
3127 0 : barLine->setTrack(ctx.track());
3128 0 : barLine->setBarLineType(TConv::fromXml(e.readAsciiText(), BarLineType::NORMAL));
3129 0 : segment->add(barLine);
3130 68 : } else if (tag == "Tuplet") {
3131 11 : int tupletId = e.attribute("id").toInt();
3132 11 : Tuplet* tuplet = Factory::createTuplet(m);
3133 11 : tuplet->setTrack(ctx.track());
3134 11 : tuplet->setTick(ctx.tick());
3135 11 : readTuplet206(tuplet, e, ctx);
3136 11 : ctx.addTuplet(tupletId, tuplet);
3137 57 : } else if (tag == "startRepeat") {
3138 3 : m->setRepeatStart(true);
3139 3 : e.readNext();
3140 54 : } else if (tag == "endRepeat") {
3141 3 : m->setRepeatCount(e.readInt());
3142 3 : m->setRepeatEnd(true);
3143 51 : } else if (tag == "vspacer" || tag == "vspacerDown") {
3144 0 : if (!m->vspacerDown(staffIdx)) {
3145 0 : Spacer* spacer = Factory::createSpacer(m);
3146 0 : spacer->setSpacerType(SpacerType::DOWN);
3147 0 : spacer->setTrack(staffIdx * VOICES);
3148 0 : m->add(spacer);
3149 : }
3150 0 : m->vspacerDown(staffIdx)->setGap(Spatium(e.readDouble()));
3151 51 : } else if (tag == "vspacer" || tag == "vspacerUp") {
3152 0 : if (!m->vspacerUp(staffIdx)) {
3153 0 : Spacer* spacer = Factory::createSpacer(m);
3154 0 : spacer->setSpacerType(SpacerType::UP);
3155 0 : spacer->setTrack(staffIdx * VOICES);
3156 0 : m->add(spacer);
3157 : }
3158 0 : m->vspacerUp(staffIdx)->setGap(Spatium(e.readDouble()));
3159 51 : } else if (tag == "visible") {
3160 0 : m->setStaffVisible(staffIdx, e.readInt());
3161 51 : } else if (tag == "slashStyle") {
3162 0 : m->setStaffStemless(staffIdx, e.readInt());
3163 51 : } else if (tag == "Beam") {
3164 2 : int beamId = e.attribute("id").toInt();
3165 2 : Beam* beam = Factory::createBeam(ctx.score());
3166 2 : beam->setTrack(ctx.track());
3167 2 : read400::TRead::read(beam, e, ctx);
3168 2 : ctx.addBeam(beamId, beam);
3169 49 : } else if (tag == "Segment") {
3170 0 : if (segment) {
3171 0 : read400::TRead::read(segment, e, ctx);
3172 : } else {
3173 0 : e.unknown();
3174 : }
3175 49 : } else if (tag == "MeasureNumber") {
3176 0 : MeasureNumber* noText = new MeasureNumber(m);
3177 0 : readText206(e, ctx, noText, m);
3178 0 : noText->setTrack(ctx.track());
3179 0 : m->setMeasureNumber(noText->staffIdx(), noText);
3180 49 : } else if (tag == "SystemDivider") {
3181 0 : SystemDivider* sd = new SystemDivider(ctx.dummy());
3182 0 : read400::TRead::read(sd, e, ctx);
3183 0 : m->add(sd);
3184 49 : } else if (tag == "Ambitus") {
3185 2 : segment = m->getSegment(SegmentType::Ambitus, ctx.tick());
3186 2 : Ambitus* range = Factory::createAmbitus(segment);
3187 2 : readAmbitus(range, e, ctx);
3188 2 : range->setTrack(trackZeroVoice(ctx.track()));
3189 2 : segment->add(range);
3190 47 : } else if (tag == "multiMeasureRest") {
3191 4 : m->setMMRestCount(e.readInt());
3192 : // set tick to previous measure
3193 4 : m->setTick(ctx.lastMeasure()->tick());
3194 4 : ctx.setTick(ctx.lastMeasure()->tick());
3195 43 : } else if (read400::TRead::readProperties(static_cast<MeasureBase*>(m), e, ctx)) {
3196 : } else {
3197 0 : e.unknown();
3198 : }
3199 : }
3200 441 : ctx.checkTuplets();
3201 441 : m->connectTremolo();
3202 441 : }
3203 :
3204 : //---------------------------------------------------------
3205 : // readBox
3206 : //---------------------------------------------------------
3207 :
3208 17 : static void readBox(Box* b, XmlReader& e, ReadContext& ctx)
3209 : {
3210 17 : b->setAutoSizeEnabled(false); // didn't exist in Mu2
3211 :
3212 17 : b->setBoxHeight(0_sp); // override default set in constructor
3213 17 : b->setBoxWidth(0_sp);
3214 :
3215 100 : while (e.readNextStartElement()) {
3216 83 : const AsciiStringView tag(e.name());
3217 83 : if (tag == "HBox") {
3218 0 : HBox* hb = Factory::createHBox(b->score());
3219 0 : read400::TRead::read(hb, e, ctx);
3220 0 : b->add(hb);
3221 83 : } else if (tag == "VBox") {
3222 0 : VBox* vb = Factory::createVBox(b->score());
3223 0 : read400::TRead::read(vb, e, ctx);
3224 0 : b->add(vb);
3225 83 : } else if (tag == "Text") {
3226 : Text* t;
3227 51 : if (b->isTBox()) {
3228 0 : t = toTBox(b)->text();
3229 0 : readText206(e, ctx, t, t);
3230 : } else {
3231 51 : t = Factory::createText(b);
3232 51 : readText206(e, ctx, t, t);
3233 51 : if (t->empty()) {
3234 0 : LOGD("read empty text");
3235 : } else {
3236 51 : b->add(t);
3237 : }
3238 : }
3239 32 : } else if (!read400::TRead::readBoxProperties(b, e, ctx)) {
3240 10 : e.unknown();
3241 : }
3242 : }
3243 17 : }
3244 :
3245 : //---------------------------------------------------------
3246 : // readStaffContent
3247 : //---------------------------------------------------------
3248 :
3249 60 : static void readStaffContent206(Score* score, XmlReader& e, ReadContext& ctx)
3250 : {
3251 60 : int staff = e.intAttribute("id", 1) - 1;
3252 60 : ctx.setTick(Fraction(0, 1));
3253 60 : ctx.setTrack(staff * VOICES);
3254 60 : Box* lastReadBox = nullptr;
3255 60 : bool readMeasureLast = false;
3256 :
3257 60 : if (staff == 0) {
3258 411 : while (e.readNextStartElement()) {
3259 372 : const AsciiStringView tag(e.name());
3260 :
3261 372 : if (tag == "Measure") {
3262 351 : if (lastReadBox) {
3263 15 : lastReadBox->setBottomGap(lastReadBox->bottomGap() + lastReadBox->propertyDefault(Pid::BOTTOM_GAP).value<Spatium>());
3264 15 : lastReadBox = nullptr;
3265 : }
3266 351 : readMeasureLast = true;
3267 :
3268 351 : Measure* measure = nullptr;
3269 351 : measure = Factory::createMeasure(score);
3270 351 : measure->setTick(ctx.tick());
3271 : //
3272 : // inherit timesig from previous measure
3273 : //
3274 351 : Measure* m = ctx.lastMeasure(); // measure->prevMeasure();
3275 351 : Fraction f(m ? m->timesig() : Fraction(4, 4));
3276 351 : measure->setTicks(f);
3277 351 : measure->setTimesig(f);
3278 :
3279 351 : readMeasure206(measure, staff, e, ctx);
3280 351 : measure->checkMeasure(staff);
3281 351 : if (!measure->isMMRest()) {
3282 347 : score->measures()->append(measure);
3283 347 : if (m && m->mmRest()) {
3284 4 : m->mmRest()->setNext(measure);
3285 : }
3286 347 : ctx.setLastMeasure(measure);
3287 347 : ctx.setTick(measure->endTick());
3288 : } else {
3289 : // this is a multi measure rest
3290 : // always preceded by the first measure it replaces
3291 4 : Measure* lm = ctx.lastMeasure();
3292 :
3293 4 : if (lm) {
3294 4 : lm->setMMRest(measure);
3295 4 : measure->setTick(lm->tick());
3296 4 : measure->setPrev(lm->prev());
3297 : }
3298 : }
3299 21 : } else if (tag == "HBox" || tag == "VBox" || tag == "TBox" || tag == "FBox") {
3300 17 : Box* b = toBox(Factory::createItemByName(tag, score->dummy()));
3301 17 : readBox(b, e, ctx);
3302 17 : b->setTick(ctx.tick());
3303 17 : score->measures()->append(b);
3304 :
3305 : // If it's the first box, and comes before any measures, reset to
3306 : // 301 default.
3307 17 : if (!readMeasureLast && !lastReadBox) {
3308 16 : b->setTopGap(b->propertyDefault(Pid::TOP_GAP).value<Spatium>());
3309 16 : b->setPropertyFlags(Pid::TOP_GAP, PropertyFlags::STYLED);
3310 1 : } else if (readMeasureLast) {
3311 0 : b->setTopGap(b->topGap() + b->propertyDefault(Pid::TOP_GAP).value<Spatium>());
3312 : }
3313 :
3314 17 : lastReadBox = b;
3315 17 : readMeasureLast = false;
3316 4 : } else if (tag == "tick") {
3317 4 : ctx.setTick(Fraction::fromTicks(score->fileDivision(e.readInt())));
3318 : } else {
3319 0 : e.unknown();
3320 : }
3321 : }
3322 : } else {
3323 21 : Measure* measure = score->firstMeasure();
3324 111 : while (e.readNextStartElement()) {
3325 90 : const AsciiStringView tag(e.name());
3326 :
3327 90 : if (tag == "Measure") {
3328 90 : if (measure == 0) {
3329 0 : LOGD("Score::readStaff(): missing measure!");
3330 0 : measure = Factory::createMeasure(score);
3331 0 : measure->setTick(ctx.tick());
3332 0 : score->measures()->append(measure);
3333 : }
3334 90 : ctx.setTick(measure->tick());
3335 90 : readMeasure206(measure, staff, e, ctx);
3336 90 : measure->checkMeasure(staff);
3337 90 : if (measure->isMMRest()) {
3338 0 : measure = ctx.lastMeasure()->nextMeasure();
3339 : } else {
3340 90 : ctx.setLastMeasure(measure);
3341 90 : if (measure->mmRest()) {
3342 0 : measure = measure->mmRest();
3343 : } else {
3344 90 : measure = measure->nextMeasure();
3345 : }
3346 : }
3347 0 : } else if (tag == "tick") {
3348 0 : ctx.setTick(Fraction::fromTicks(score->fileDivision(e.readInt())));
3349 : } else {
3350 0 : e.unknown();
3351 : }
3352 : }
3353 : }
3354 60 : }
3355 :
3356 : //---------------------------------------------------------
3357 : // readStyle
3358 : //---------------------------------------------------------
3359 :
3360 39 : static void readStyle206(MStyle* style, XmlReader& e, ReadContext& ctx, ReadChordListHook& readChordListHook)
3361 : {
3362 39 : excessTextStyles206.clear();
3363 292 : while (e.readNextStartElement()) {
3364 253 : AsciiStringView tag = e.name();
3365 253 : if (tag == "TextStyle") {
3366 107 : Read206::readTextStyle206(style, e, ctx, excessTextStyles206);
3367 146 : } else if (tag == "Spatium") {
3368 39 : style->set(Sid::spatium, e.readDouble() * DPMM);
3369 107 : } else if (tag == "page-layout") {
3370 27 : readPageFormat206(style, e);
3371 80 : } else if (tag == "displayInConcertPitch") {
3372 0 : style->set(Sid::concertPitch, bool(e.readInt()));
3373 80 : } else if (tag == "pedalY") {
3374 0 : double y = e.readDouble();
3375 0 : style->set(Sid::pedalPosBelow, PointF(0.0, y));
3376 80 : } else if (tag == "lyricsDistance") {
3377 0 : double y = e.readDouble();
3378 0 : style->set(Sid::lyricsPosBelow, PointF(0.0, y));
3379 80 : } else if (tag == "lyricsMinBottomDistance") {
3380 : // no longer meaningful since it is now measured from skyline rather than staff
3381 : //style->set(Sid::lyricsMinBottomDistance, PointF(0.0, y));
3382 2 : e.skipCurrentElement();
3383 78 : } else if (tag == "ottavaHook") {
3384 1 : double y = std::abs(e.readDouble());
3385 1 : style->set(Sid::ottavaHookAbove, y);
3386 1 : style->set(Sid::ottavaHookBelow, -y);
3387 77 : } else if (tag == "endBarDistance") {
3388 0 : double d = e.readDouble();
3389 0 : d += style->value(Sid::barWidth).toReal();
3390 0 : d += style->value(Sid::endBarWidth).toReal();
3391 0 : style->set(Sid::endBarDistance, d);
3392 77 : } else if (tag == "ChordList") {
3393 0 : readChordListHook.read(e);
3394 77 : } else if (tag == "harmonyY") {
3395 0 : double val = -e.readDouble();
3396 0 : if (val > 0.0) {
3397 0 : style->set(Sid::harmonyPlacement, PlacementV::BELOW);
3398 0 : style->set(Sid::chordSymbolAPosBelow, PointF(.0, val));
3399 : } else {
3400 0 : style->set(Sid::harmonyPlacement, PlacementV::ABOVE);
3401 0 : style->set(Sid::chordSymbolAPosBelow, PointF(.0, val));
3402 : }
3403 77 : } else if (tag == "useStandardNoteNames") { // These settings were collapsed into one enum in 4.6
3404 0 : if (e.readBool()) {
3405 0 : style->set(Sid::chordSymbolSpelling, NoteSpellingType::STANDARD);
3406 : }
3407 77 : } else if (tag == "useGermanNoteNames") {
3408 0 : if (e.readBool()) {
3409 0 : style->set(Sid::chordSymbolSpelling, NoteSpellingType::GERMAN);
3410 : }
3411 77 : } else if (tag == "useFullGermanNoteNames") {
3412 0 : if (e.readBool()) {
3413 0 : style->set(Sid::chordSymbolSpelling, NoteSpellingType::GERMAN_PURE);
3414 : }
3415 77 : } else if (tag == "useSolfeggioNoteNames") {
3416 0 : if (e.readBool()) {
3417 0 : style->set(Sid::chordSymbolSpelling, NoteSpellingType::SOLFEGGIO);
3418 : }
3419 77 : } else if (tag == "useFrenchNoteNames") {
3420 0 : if (e.readBool()) {
3421 0 : style->set(Sid::chordSymbolSpelling, NoteSpellingType::FRENCH);
3422 : }
3423 : } else {
3424 77 : if (!ReadStyleHook::readStyleProperties(style, e)) {
3425 1 : e.skipCurrentElement();
3426 : }
3427 : }
3428 : }
3429 :
3430 39 : readChordListHook.validate();
3431 39 : }
3432 :
3433 39 : bool Read206::readScoreTag(Score* score, XmlReader& e, ReadContext& ctx)
3434 : {
3435 844 : while (e.readNextStartElement()) {
3436 805 : ctx.setTrack(muse::nidx);
3437 805 : const AsciiStringView tag(e.name());
3438 805 : if (tag == "Staff") {
3439 60 : readStaffContent206(score, e, ctx);
3440 745 : } else if (tag == "siglist") {
3441 0 : readTimeSigMap(ctx.compatTimeSigMap(), e, ctx);
3442 745 : } else if (tag == "Omr") {
3443 0 : e.skipCurrentElement();
3444 745 : } else if (tag == "Audio") {
3445 0 : e.skipCurrentElement();
3446 745 : } else if (tag == "showOmr") {
3447 0 : e.skipCurrentElement();
3448 745 : } else if (tag == "playMode") {
3449 0 : e.skipCurrentElement();
3450 745 : } else if (tag == "LayerTag") {
3451 5 : e.skipCurrentElement();
3452 740 : } else if (tag == "Layer") {
3453 0 : e.skipCurrentElement();
3454 740 : } else if (tag == "currentLayer") {
3455 5 : e.skipCurrentElement();
3456 735 : } else if (tag == "Synthesizer") {
3457 25 : score->synthesizerState().read(e);
3458 710 : } else if (tag == "page-offset") {
3459 0 : score->setPageNumberOffset(e.readInt());
3460 710 : } else if (tag == "Division") {
3461 39 : score->setFileDivision(e.readInt());
3462 671 : } else if (tag == "showInvisible") {
3463 39 : score->setShowInvisible(e.readInt());
3464 632 : } else if (tag == "showUnprintable") {
3465 39 : score->setShowUnprintable(e.readInt());
3466 593 : } else if (tag == "showFrames") {
3467 39 : score->setShowFrames(e.readInt());
3468 554 : } else if (tag == "showMargins") {
3469 39 : score->setShowPageborders(e.readInt());
3470 515 : } else if (tag == "Style") {
3471 39 : double sp = score->style().value(Sid::spatium).toReal();
3472 39 : ReadChordListHook clhook(score);
3473 39 : readStyle206(&score->style(), e, ctx, clhook);
3474 39 : if (score->style().styleSt(Sid::musicalTextFont) == "MuseJazz") {
3475 1 : score->style().set(Sid::musicalTextFont, "MuseJazz Text");
3476 : }
3477 39 : if (ctx.overrideSpatium()) {
3478 0 : ctx.setOriginalSpatium(score->style().spatium());
3479 0 : score->style().set(Sid::spatium, sp);
3480 : }
3481 39 : score->setEngravingFont(score->engravingFonts()->fontByName(score->style().styleSt(Sid::musicalSymbolFont).toStdString()));
3482 515 : } else if (tag == "copyright" || tag == "rights") {
3483 0 : Text* text = Factory::createText(score->dummy(), TextStyleType::DEFAULT, false);
3484 0 : readText206(e, ctx, text, text);
3485 0 : score->setMetaTag(u"copyright", text->xmlText());
3486 0 : delete text;
3487 476 : } else if (tag == "movement-number") {
3488 0 : score->setMetaTag(u"movementNumber", e.readText());
3489 476 : } else if (tag == "movement-title") {
3490 0 : score->setMetaTag(u"movementTitle", e.readText());
3491 476 : } else if (tag == "work-number") {
3492 0 : score->setMetaTag(u"workNumber", e.readText());
3493 476 : } else if (tag == "work-title") {
3494 0 : score->setMetaTag(u"workTitle", e.readText());
3495 476 : } else if (tag == "source") {
3496 0 : score->setMetaTag(u"source", e.readText());
3497 476 : } else if (tag == "metaTag") {
3498 381 : String name = e.attribute("name");
3499 381 : score->setMetaTag(name, e.readText());
3500 476 : } else if (tag == "Part") {
3501 55 : Part* part = new Part(score);
3502 55 : Read206::readPart206(part, e, ctx);
3503 55 : score->appendPart(part);
3504 40 : } else if ((tag == "HairPin") // TODO: do this elements exist here?
3505 40 : || (tag == "Ottava")
3506 40 : || (tag == "TextLine")
3507 40 : || (tag == "Volta")
3508 40 : || (tag == "Trill")
3509 40 : || (tag == "Slur")
3510 80 : || (tag == "Pedal")) {
3511 0 : Spanner* s = toSpanner(Factory::createItemByName(tag, score->dummy()));
3512 0 : if (tag == "HairPin") {
3513 0 : readHairpin206(e, ctx, toHairpin(s));
3514 0 : } else if (tag == "Ottava") {
3515 0 : readOttava(e, ctx, toOttava(s));
3516 0 : } else if (tag == "TextLine") {
3517 0 : Read206::readTextLine206(e, ctx, toTextLine(s));
3518 0 : } else if (tag == "Volta") {
3519 0 : readVolta206(e, ctx, toVolta(s));
3520 0 : } else if (tag == "Trill") {
3521 0 : Read206::readTrill206(e, ctx, toTrill(s));
3522 0 : } else if (tag == "Slur") {
3523 0 : readSlur206(e, ctx, toSlur(s));
3524 : } else {
3525 0 : assert(tag == "Pedal");
3526 0 : readPedal(e, ctx, toPedal(s));
3527 : }
3528 0 : score->addSpanner(s);
3529 40 : } else if (tag == "Score") { // recursion
3530 6 : ctx.tracks().clear();
3531 6 : ctx.clearUserTextStyles();
3532 6 : MasterScore* m = score->masterScore();
3533 6 : Score* s = m->createScore();
3534 6 : ReadStyleHook::setupDefaultStyle(s);
3535 6 : Excerpt* ex = new Excerpt(m);
3536 :
3537 6 : ex->setExcerptScore(s);
3538 6 : ctx.setLastMeasure(nullptr);
3539 6 : ReadContext exCtx(s);
3540 :
3541 6 : s->setIsOpen(true);
3542 6 : readScoreTag(s, e, exCtx);
3543 :
3544 6 : ex->setTracksMapping(ctx.tracks());
3545 6 : m->addExcerpt(ex);
3546 40 : } else if (tag == "PageList") {
3547 28 : e.skipCurrentElement();
3548 6 : } else if (tag == "name") {
3549 6 : String n = e.readText();
3550 6 : if (!score->isMaster()) { //ignore the name if it's not a child score
3551 6 : score->excerpt()->setName(n, /*saveAndNotify=*/ false);
3552 : }
3553 6 : } else if (tag == "layoutMode") {
3554 0 : if (ctx.forcePageMode()) {
3555 0 : e.skipCurrentElement();
3556 0 : continue;
3557 : }
3558 0 : String s = e.readText();
3559 0 : if (s == "line") {
3560 0 : score->setLayoutMode(LayoutMode::LINE);
3561 0 : } else if (s == "system") {
3562 0 : score->setLayoutMode(LayoutMode::SYSTEM);
3563 : } else {
3564 0 : LOGD("layoutMode: %s", muPrintable(s));
3565 : }
3566 0 : } else {
3567 0 : e.unknown();
3568 : }
3569 : }
3570 39 : if (e.error() != muse::XmlStreamReader::NoError) {
3571 0 : LOGE() << muse::String(u"XML read error at byte offset %1: %2").arg(e.byteOffset())
3572 0 : .arg(muse::String::fromAscii(e.name().ascii()));
3573 0 : return false;
3574 : }
3575 :
3576 39 : score->setFileDivision(Constants::DIVISION);
3577 :
3578 39 : score->updateTicksAndTimeSigMap();
3579 39 : if (score->isMaster()) {
3580 : // While reading the score, some elements might use `score->repeatList()` (which is incorrect
3581 : // anyway, because the repeatList will be incomplete because the score is incomplete, but some
3582 : // elements still do it).
3583 : // `score->repeatList()` calls `_repeatList->update()`; the repeat list then thinks that it is
3584 : // up-to-date from that point. But we weren't finished reading the score, so the score will still
3585 : // change. We need to tell the repeat list about that, so that it will be updated next time
3586 : // someone uses it.
3587 33 : static_cast<MasterScore*>(score)->invalidateRepeatList();
3588 : }
3589 39 : score->connectTies();
3590 39 : score->undoRemoveStaleTieJumpPoints(false);
3591 :
3592 94 : for (Part* p : score->parts()) {
3593 55 : p->updateHarmonyChannels(false);
3594 : }
3595 :
3596 39 : if (score->isMaster()) {
3597 33 : MasterScore* ms = static_cast<MasterScore*>(score);
3598 33 : ms->rebuildMidiMapping();
3599 33 : ms->updateChannel();
3600 : }
3601 :
3602 39 : if (score->isMaster()) {
3603 33 : CompatUtils::assignInitialPartToExcerpts(score->masterScore()->excerpts());
3604 : }
3605 :
3606 39 : return true;
3607 : }
3608 :
3609 33 : Ret Read206::readScoreFile(Score* score, XmlReader& e, ReadInOutData* out)
3610 : {
3611 33 : ReadContext ctx(score);
3612 33 : if (out) {
3613 33 : if (out->overriddenSpatium.has_value()) {
3614 0 : ctx.setSpatium(out->overriddenSpatium.value());
3615 0 : ctx.setOverrideSpatium(true);
3616 : }
3617 :
3618 33 : ctx.setPropertiesToSkip(out->propertiesToSkip);
3619 33 : ctx.setForcePageMode(out->forcePageMode);
3620 : }
3621 33 : DEFER {
3622 33 : if (out) {
3623 33 : out->settingsCompat = std::move(ctx.settingCompat());
3624 : }
3625 33 : };
3626 :
3627 112 : while (e.readNextStartElement()) {
3628 79 : const AsciiStringView tag(e.name());
3629 79 : if (tag == "programVersion") {
3630 23 : score->setMscoreVersion(e.readText());
3631 56 : } else if (tag == "programRevision") {
3632 23 : score->setMscoreRevision(e.readInt(nullptr, 16));
3633 33 : } else if (tag == "Score") {
3634 33 : if (!readScoreTag(score, e, ctx)) {
3635 0 : if (e.error() == muse::XmlStreamReader::CustomError) {
3636 0 : return make_ret(Err::FileCriticallyCorrupted, e.errorString());
3637 : }
3638 0 : return make_ret(Err::FileBadFormat, e.errorString());
3639 : }
3640 :
3641 33 : if (ctx.overrideSpatium() && out) {
3642 0 : out->originalSpatium = ctx.originalSpatium();
3643 : }
3644 0 : } else if (tag == "Revision") {
3645 0 : e.skipCurrentElement();
3646 : }
3647 : }
3648 :
3649 87 : for (Staff* s : score->staves()) {
3650 54 : s->updateOttava();
3651 : }
3652 :
3653 : // convert int bar line span to bool
3654 :
3655 : // also sets Pid::BARLINE_SPAN to non-generated and affected bar lines
3656 33 : read114::Read114::setBarLineSpanToStaves(score, ctx);
3657 :
3658 : // set local overrides to bar lines
3659 33 : constexpr auto st = SegmentType::BarLineTypes;
3660 33 : const size_t numStaves = score->nstaves();
3661 298 : for (Segment* s = score->firstSegment(st); s; s = s->next1(st)) {
3662 : // optional local bar line span override of this segment
3663 265 : std::optional<size_t> barLineSpan = std::nullopt;
3664 620 : for (staff_idx_t staffIdx = 0; staffIdx < numStaves; ++staffIdx) {
3665 355 : const size_t maxSpan = numStaves - staffIdx - 1;
3666 355 : const track_idx_t trackIdx = staff2track(staffIdx);
3667 355 : EngravingItem* el = s->element(trackIdx);
3668 355 : BarLine* barLine = el && el->isBarLine() ? toBarLine(el) : nullptr;
3669 355 : if (barLine) {
3670 55 : if (const std::optional<size_t> span = ctx.getBarLineSpan(barLine)) {
3671 55 : if (*span > maxSpan) {
3672 0 : LOGW() << "invalid bar line span " << *span << " (max " << maxSpan << ")";
3673 0 : barLineSpan = maxSpan;
3674 : } else {
3675 55 : barLineSpan = std::max(barLineSpan.value_or(0), *span);
3676 : }
3677 : }
3678 : }
3679 :
3680 355 : if (!barLineSpan) {
3681 : // we don't have a local override. Keep the value from staff
3682 288 : continue;
3683 : }
3684 :
3685 67 : const bool shouldBarLineSpan = *barLineSpan != 0;
3686 67 : const bool staffBarLineSpan = score->staff(staffIdx)->barLineSpan();
3687 67 : if (shouldBarLineSpan != staffBarLineSpan) {
3688 : // bar line should have local override
3689 13 : if (!barLine) {
3690 : // clone previous bar line. This is safe because we always have a previous barline when
3691 : // barLineSpan != std::nullopt because it's either the one that was read or the one cloned
3692 : // in the previous iteration
3693 6 : el = s->element(staff2track(staffIdx - 1));
3694 6 : if (el && el->isBarLine()) {
3695 6 : barLine = toBarLine(el)->clone();
3696 6 : barLine->setTrack(trackIdx);
3697 6 : s->add(barLine);
3698 : }
3699 : }
3700 :
3701 13 : if (barLine) {
3702 13 : barLine->setSpanStaff(shouldBarLineSpan);
3703 : }
3704 : }
3705 67 : if (*barLineSpan == 0) {
3706 : // we're done applying the local override
3707 55 : barLineSpan.reset();
3708 : } else {
3709 12 : --(*barLineSpan);
3710 : }
3711 : }
3712 : }
3713 :
3714 33 : return make_ok();
3715 33 : }
3716 :
3717 0 : bool Read206::pasteStaff(XmlReader&, Segment*, staff_idx_t, Fraction)
3718 : {
3719 0 : UNREACHABLE;
3720 : return false;
3721 : }
3722 :
3723 0 : void Read206::pasteSymbols(XmlReader&, ChordRest*)
3724 : {
3725 0 : UNREACHABLE;
3726 : }
3727 :
3728 0 : void Read206::readTremoloCompat(compat::TremoloCompat*, XmlReader&)
3729 : {
3730 0 : UNREACHABLE;
3731 : }
3732 :
3733 0 : void Read206::doReadItem(EngravingItem*, XmlReader&)
3734 : {
3735 0 : UNREACHABLE;
3736 : }
|