1 //=============================================================================
2 // MusE Score
3 // Linux Music Score Editor
4 //
5 // Copyright (C) 2002-2020 Werner Schweer and others
6 //
7 // This program is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License version 2.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 //=============================================================================
19
20 /**
21 MusicXML export.
22 */
23
24 // TODO: trill lines need to be handled the same way as slurs
25 // in MuseScore they are measure level elements, while in MusicXML
26 // they are attached to notes (as ornaments)
27
28 //=========================================================
29 // LVI FIXME
30 //
31 // Evaluate paramenter handling between the various classes, could be simplified
32 //=========================================================
33
34 // TODO LVI 2011-10-30: determine how to report export errors.
35 // Currently all output (both debug and error reports) are done using qDebug.
36
37 #include <math.h>
38 #include "config.h"
39
40 #include "thirdparty/qzip/qzipwriter_p.h"
41
42 #include "mscore/preferences.h"
43
44 #include "libmscore/score.h"
45 #include "libmscore/rest.h"
46 #include "libmscore/chord.h"
47 #include "libmscore/sig.h"
48 #include "libmscore/key.h"
49 #include "libmscore/clef.h"
50 #include "libmscore/note.h"
51 #include "libmscore/segment.h"
52 #include "libmscore/xml.h"
53 #include "libmscore/beam.h"
54 #include "libmscore/staff.h"
55 #include "libmscore/part.h"
56 #include "libmscore/measure.h"
57 #include "libmscore/style.h"
58 #include "libmscore/slur.h"
59 #include "libmscore/hairpin.h"
60 #include "libmscore/dynamic.h"
61 #include "libmscore/barline.h"
62 #include "libmscore/timesig.h"
63 #include "libmscore/ottava.h"
64 #include "libmscore/pedal.h"
65 #include "libmscore/text.h"
66 #include "libmscore/tuplet.h"
67 #include "libmscore/lyrics.h"
68 #include "libmscore/volta.h"
69 #include "libmscore/keysig.h"
70 #include "libmscore/bracket.h"
71 #include "libmscore/arpeggio.h"
72 #include "libmscore/jump.h"
73 #include "libmscore/marker.h"
74 #include "libmscore/tremolo.h"
75 #include "libmscore/trill.h"
76 #include "libmscore/harmony.h"
77 #include "libmscore/tempotext.h"
78 #include "libmscore/sym.h"
79 #include "libmscore/pitchspelling.h"
80 #include "libmscore/utils.h"
81 #include "libmscore/articulation.h"
82 #include "libmscore/page.h"
83 #include "libmscore/system.h"
84 #include "libmscore/element.h"
85 #include "libmscore/glissando.h"
86 #include "libmscore/navigate.h"
87 #include "libmscore/spanner.h"
88 #include "libmscore/drumset.h"
89 #include "libmscore/mscore.h"
90 #include "libmscore/accidental.h"
91 #include "libmscore/breath.h"
92 #include "libmscore/chordline.h"
93 #include "libmscore/figuredbass.h"
94 #include "libmscore/stringdata.h"
95 #include "libmscore/rehearsalmark.h"
96 #include "libmscore/fret.h"
97 #include "libmscore/tie.h"
98 #include "libmscore/undo.h"
99 #include "libmscore/textlinebase.h"
100 #include "libmscore/fermata.h"
101 #include "libmscore/textframe.h"
102 #include "libmscore/instrchange.h"
103 #include "libmscore/letring.h"
104 #include "libmscore/palmmute.h"
105 #include "libmscore/vibrato.h"
106
107 #include "musicxml.h"
108 #include "musicxmlfonthandler.h"
109 #include "musicxmlsupport.h"
110
111 namespace Ms {
112
113 //---------------------------------------------------------
114 // local defines for debug output
115 //---------------------------------------------------------
116
117 // #define DEBUG_CLEF true
118 // #define DEBUG_REPEATS true
119 // #define DEBUG_TICK true
120
121 #ifdef DEBUG_CLEF
122 #define clefDebug(...) qDebug(__VA_ARGS__)
123 #else
124 #define clefDebug(...) {}
125 #endif
126
127 //---------------------------------------------------------
128 // typedefs
129 //---------------------------------------------------------
130
131 typedef QMap<int, const FiguredBass*> FigBassMap;
132
133 //---------------------------------------------------------
134 // attributes -- prints <attributes> tag when necessary
135 //---------------------------------------------------------
136
137 class Attributes {
138 bool inAttributes;
139
140 public:
Attributes()141 Attributes() { start(); }
142 void doAttr(XmlWriter& xml, bool attr);
143 void start();
144 void stop(XmlWriter& xml);
145 };
146
147 //---------------------------------------------------------
148 // doAttr - when necessary change state and print <attributes> tag
149 //---------------------------------------------------------
150
doAttr(XmlWriter & xml,bool attr)151 void Attributes::doAttr(XmlWriter& xml, bool attr)
152 {
153 if (!inAttributes && attr) {
154 xml.stag("attributes");
155 inAttributes = true;
156 }
157 else if (inAttributes && !attr) {
158 xml.etag();
159 inAttributes = false;
160 }
161 }
162
163 //---------------------------------------------------------
164 // start -- initialize
165 //---------------------------------------------------------
166
start()167 void Attributes::start()
168 {
169 inAttributes = false;
170 }
171
172 //---------------------------------------------------------
173 // stop -- print </attributes> tag when necessary
174 //---------------------------------------------------------
175
stop(XmlWriter & xml)176 void Attributes::stop(XmlWriter& xml)
177 {
178 if (inAttributes) {
179 xml.etag();
180 inAttributes = false;
181 }
182 }
183
184 //---------------------------------------------------------
185 // notations -- prints <notations> tag when necessary
186 //---------------------------------------------------------
187
188 class Notations {
189 bool notationsPrinted;
190
191 public:
Notations()192 Notations() { notationsPrinted = false; }
193 void tag(XmlWriter& xml);
194 void etag(XmlWriter& xml);
195 };
196
197 //---------------------------------------------------------
198 // articulations -- prints <articulations> tag when necessary
199 //---------------------------------------------------------
200
201 class Articulations {
202 bool articulationsPrinted;
203
204 public:
Articulations()205 Articulations() { articulationsPrinted = false; }
206 void tag(XmlWriter& xml);
207 void etag(XmlWriter& xml);
208 };
209
210 //---------------------------------------------------------
211 // ornaments -- prints <ornaments> tag when necessary
212 //---------------------------------------------------------
213
214 class Ornaments {
215 bool ornamentsPrinted;
216
217 public:
Ornaments()218 Ornaments() { ornamentsPrinted = false; }
219 void tag(XmlWriter& xml);
220 void etag(XmlWriter& xml);
221 };
222
223 //---------------------------------------------------------
224 // technical -- prints <technical> tag when necessary
225 //---------------------------------------------------------
226
227 class Technical {
228 bool technicalPrinted;
229
230 public:
Technical()231 Technical() { technicalPrinted = false; }
232 void tag(XmlWriter& xml);
233 void etag(XmlWriter& xml);
234 };
235
236 //---------------------------------------------------------
237 // slur handler -- prints <slur> tags
238 //---------------------------------------------------------
239
240 class SlurHandler {
241 const Slur* slur[MAX_NUMBER_LEVEL];
242 bool started[MAX_NUMBER_LEVEL];
243 int findSlur(const Slur* s) const;
244
245 public:
246 SlurHandler();
247 void doSlurs(const ChordRest* chordRest, Notations& notations, XmlWriter& xml);
248
249 private:
250 void doSlurStart(const Slur* s, Notations& notations, XmlWriter& xml);
251 void doSlurStop(const Slur* s, Notations& notations, XmlWriter& xml);
252 };
253
254 //---------------------------------------------------------
255 // glissando handler -- prints <glissando> tags
256 //---------------------------------------------------------
257
258 class GlissandoHandler {
259 const Note* glissNote[MAX_NUMBER_LEVEL];
260 const Note* slideNote[MAX_NUMBER_LEVEL];
261 int findNote(const Note* note, int type) const;
262
263 public:
264 GlissandoHandler();
265 void doGlissandoStart(Glissando* gliss, Notations& notations, XmlWriter& xml);
266 void doGlissandoStop(Glissando* gliss, Notations& notations, XmlWriter& xml);
267 };
268
269 //---------------------------------------------------------
270 // MeasureNumberStateHandler
271 //---------------------------------------------------------
272
273 /**
274 State handler used to calculate measure number including implicit flag.
275 To be called once at the start of each measure in a part.
276 */
277
278 class MeasureNumberStateHandler final
279 {
280 public:
281 MeasureNumberStateHandler();
282 void updateForMeasure(const Measure* const m);
283 QString measureNumber() const;
284 bool isFirstActualMeasure() const;
285 private:
286 void init();
287 int _measureNo; // number of next regular measure
288 int _irregularMeasureNo; // number of next irregular measure
289 int _pickupMeasureNo; // number of next pickup measure
290 QString _cachedAttributes; // attributes calculated by updateForMeasure()
291 };
292
293 //---------------------------------------------------------
294 // MeasurePrintContext
295 //---------------------------------------------------------
296
297 struct MeasurePrintContext final
298 {
299 void measureWritten(const Measure* m);
300 bool scoreStart = true;
301 bool pageStart = true;
302 bool systemStart = true;
303 const Measure* prevMeasure = nullptr;
304 const System* prevSystem = nullptr;
305 const System* lastSystemPrevPage = nullptr;
306 };
307
308 //---------------------------------------------------------
309 // ExportMusicXml
310 //---------------------------------------------------------
311
312 typedef QHash<const ChordRest* const, const Trill*> TrillHash;
313 typedef QMap<const Instrument*, int> MxmlInstrumentMap;
314
315 class ExportMusicXml {
316 Score* _score;
317 XmlWriter _xml;
318 SlurHandler sh;
319 GlissandoHandler gh;
320 Fraction _tick;
321 Attributes _attr;
322 TextLineBase const* brackets[MAX_NUMBER_LEVEL];
323 TextLineBase const* dashes[MAX_NUMBER_LEVEL];
324 Hairpin const* hairpins[MAX_NUMBER_LEVEL];
325 Ottava const* ottavas[MAX_NUMBER_LEVEL];
326 Trill const* trills[MAX_NUMBER_LEVEL];
327 int div;
328 double millimeters;
329 int tenths;
330 TrillHash _trillStart;
331 TrillHash _trillStop;
332 MxmlInstrumentMap instrMap;
333
334 int findBracket(const TextLineBase* tl) const;
335 int findDashes(const TextLineBase* tl) const;
336 int findHairpin(const Hairpin* tl) const;
337 int findOttava(const Ottava* tl) const;
338 int findTrill(const Trill* tl) const;
339 void chord(Chord* chord, int staff, const std::vector<Lyrics*>* ll, bool useDrumset);
340 void rest(Rest* chord, int staff);
341 void clef(int staff, const ClefType ct, const QString& extraAttributes = "");
342 void timesig(TimeSig* tsig);
343 void keysig(const KeySig* ks, ClefType ct, int staff = 0, bool visible = true);
344 void barlineLeft(const Measure* const m);
345 void barlineMiddle(const BarLine* bl);
346 void barlineRight(const Measure* const m, const int strack, const int etrack);
347 void lyrics(const std::vector<Lyrics*>* ll, const int trk);
348 void work(const MeasureBase* measure);
349 void calcDivMoveToTick(const Fraction& t);
350 void calcDivisions();
351 void keysigTimesig(const Measure* m, const Part* p);
352 void chordAttributes(Chord* chord, Notations& notations, Technical& technical,
353 TrillHash& trillStart, TrillHash& trillStop);
354 void wavyLineStartStop(const ChordRest* const cr, Notations& notations, Ornaments& ornaments,
355 TrillHash& trillStart, TrillHash& trillStop);
356 void print(const Measure* const m, const int partNr, const int firstStaffOfPart, const int nrStavesInPart, const MeasurePrintContext& mpc);
357 void findAndExportClef(const Measure* const m, const int staves, const int strack, const int etrack);
358 void exportDefaultClef(const Part* const part, const Measure* const m);
359 void writeElement(Element* el, const Measure* m, int sstaff, bool useDrumset);
360 void writeMeasureTracks(const Measure* const m, const int partIndex, const int strack, const int staves, const bool useDrumset, FigBassMap& fbMap, QSet<const Spanner*>& spannersStopped);
361 void writeMeasure(const Measure* const m, const int idx, const int staffCount, MeasureNumberStateHandler& mnsh, FigBassMap& fbMap, const MeasurePrintContext& mpc, QSet<const Spanner*>& spannersStopped);
362 void writeParts();
363
364 public:
ExportMusicXml(Score * s)365 ExportMusicXml(Score* s)
366 : _xml(s)
367 {
368 _score = s; _tick = { 0,1 }; div = 1; tenths = 40;
369 millimeters = _score->spatium() * tenths / (10 * DPMM);
370 }
371 void write(QIODevice* dev);
372 void credits(XmlWriter& xml);
373 void moveToTick(const Fraction& t);
374 void words(TextBase const* const text, int staff);
375 void tboxTextAsWords(TextBase const* const text, int staff, QPointF position);
376 void rehearsal(RehearsalMark const* const rmk, int staff);
377 void hairpin(Hairpin const* const hp, int staff, const Fraction& tick);
378 void ottava(Ottava const* const ot, int staff, const Fraction& tick);
379 void pedal(Pedal const* const pd, int staff, const Fraction& tick);
380 void textLine(TextLineBase const* const tl, int staff, const Fraction& tick);
381 void dynamic(Dynamic const* const dyn, int staff);
382 void symbol(Symbol const* const sym, int staff);
383 void tempoText(TempoText const* const text, int staff);
384 void harmony(Harmony const* const, FretDiagram const* const fd, int offset = 0);
score() const385 Score* score() const { return _score; };
386 double getTenthsFromInches(double) const;
387 double getTenthsFromDots(double) const;
tick() const388 Fraction tick() const { return _tick; }
389 void writeInstrumentDetails(const Instrument* instrument);
390 };
391
392 //---------------------------------------------------------
393 // positionToQString
394 //---------------------------------------------------------
395
positionToQString(const QPointF def,const QPointF rel,const float spatium)396 static QString positionToQString(const QPointF def, const QPointF rel, const float spatium)
397 {
398 // minimum value to export
399 const float positionElipson = 0.1f;
400
401 // convert into tenths for MusicXML
402 const float defaultX = 10 * def.x() / spatium;
403 const float defaultY = -10 * def.y() / spatium;
404 const float relativeX = 10 * rel.x() / spatium;
405 const float relativeY = -10 * rel.y() / spatium;
406
407 // generate string representation
408 QString res;
409 if (fabsf(defaultX) > positionElipson)
410 res += QString(" default-x=\"%1\"").arg(QString::number(defaultX, 'f', 2));
411 if (fabsf(defaultY) > positionElipson)
412 res += QString(" default-y=\"%1\"").arg(QString::number(defaultY, 'f', 2));
413 if (fabsf(relativeX) > positionElipson)
414 res += QString(" relative-x=\"%1\"").arg(QString::number(relativeX, 'f', 2));
415 if (fabsf(relativeY) > positionElipson)
416 res += QString(" relative-y=\"%1\"").arg(QString::number(relativeY, 'f', 2));
417
418 return res;
419 }
420
421 //---------------------------------------------------------
422 // positioningAttributes
423 // According to the specs (common.dtd), all direction-type and note elements must be relative to the measure
424 // while all other elements are relative to their position or the nearest note.
425 //---------------------------------------------------------
426
positioningAttributes(Element const * const el,bool isSpanStart=true)427 static QString positioningAttributes(Element const* const el, bool isSpanStart = true)
428 {
429 if (!preferences.getBool(PREF_EXPORT_MUSICXML_EXPORTLAYOUT))
430 return "";
431
432 //qDebug("single el %p _pos x,y %f %f _userOff x,y %f %f spatium %f",
433 // el, el->ipos().x(), el->ipos().y(), el->offset().x(), el->offset().y(), el->spatium());
434
435 QPointF def;
436 QPointF rel;
437 float spatium = el->spatium();
438
439 const SLine* span = nullptr;
440 if (el->isSLine())
441 span = static_cast<const SLine*>(el);
442
443 if (span && !span->segmentsEmpty()) {
444 if (isSpanStart) {
445 const auto seg = span->frontSegment();
446 const auto offset = seg->offset();
447 const auto p = seg->pos();
448 rel.setX(offset.x());
449 def.setY(p.y());
450
451 //qDebug("sline start seg %p seg->pos x,y %f %f seg->userOff x,y %f %f spatium %f",
452 // seg, p.x(), p.y(), seg->offset().x(), seg->offset().y(), seg->spatium());
453
454 }
455 else {
456 const auto seg = span->backSegment();
457 const auto userOff = seg->offset(); // This is the offset accessible from the inspector
458 const auto userOff2 = seg->userOff2(); // Offset of the actual dragged anchor, which doesn't affect the inspector offset
459 //auto pos = seg->pos();
460 //auto pos2 = seg->pos2();
461
462 //qDebug("sline stop seg %p seg->pos2 x,y %f %f seg->userOff2 x,y %f %f spatium %f",
463 // seg, pos2.x(), pos2.y(), seg->userOff2().x(), seg->userOff2().y(), seg->spatium());
464
465 // For an SLine, the actual offset equals the sum of userOff and userOff2,
466 // as userOff moves the SLine as a whole
467 rel.setX(userOff.x() + userOff2.x());
468
469 // Following would probably required for non-horizontal SLines:
470 //defaultY = pos.y() + pos2.y();
471 }
472 }
473 else {
474 def = el->ipos(); // Note: for some elements, Finale Notepad seems to work slightly better w/o default-x
475 rel = el->offset();
476 }
477
478 return positionToQString(def, rel, spatium);
479 }
480
481 //---------------------------------------------------------
482 // tag
483 //---------------------------------------------------------
484
tag(XmlWriter & xml)485 void Notations::tag(XmlWriter& xml)
486 {
487 if (!notationsPrinted)
488 xml.stag("notations");
489 notationsPrinted = true;
490 }
491
492 //---------------------------------------------------------
493 // etag
494 //---------------------------------------------------------
495
etag(XmlWriter & xml)496 void Notations::etag(XmlWriter& xml)
497 {
498 if (notationsPrinted)
499 xml.etag();
500 notationsPrinted = false;
501 }
502
503 //---------------------------------------------------------
504 // tag
505 //---------------------------------------------------------
506
tag(XmlWriter & xml)507 void Articulations::tag(XmlWriter& xml)
508 {
509 if (!articulationsPrinted)
510 xml.stag("articulations");
511 articulationsPrinted = true;
512 }
513
514 //---------------------------------------------------------
515 // etag
516 //---------------------------------------------------------
517
etag(XmlWriter & xml)518 void Articulations::etag(XmlWriter& xml)
519 {
520 if (articulationsPrinted)
521 xml.etag();
522 articulationsPrinted = false;
523 }
524
525 //---------------------------------------------------------
526 // tag
527 //---------------------------------------------------------
528
tag(XmlWriter & xml)529 void Ornaments::tag(XmlWriter& xml)
530 {
531 if (!ornamentsPrinted)
532 xml.stag("ornaments");
533 ornamentsPrinted = true;
534 }
535
536 //---------------------------------------------------------
537 // etag
538 //---------------------------------------------------------
539
etag(XmlWriter & xml)540 void Ornaments::etag(XmlWriter& xml)
541 {
542 if (ornamentsPrinted)
543 xml.etag();
544 ornamentsPrinted = false;
545 }
546
547 //---------------------------------------------------------
548 // tag
549 //---------------------------------------------------------
550
tag(XmlWriter & xml)551 void Technical::tag(XmlWriter& xml)
552 {
553 if (!technicalPrinted)
554 xml.stag("technical");
555 technicalPrinted = true;
556 }
557
558 //---------------------------------------------------------
559 // etag
560 //---------------------------------------------------------
561
etag(XmlWriter & xml)562 void Technical::etag(XmlWriter& xml)
563 {
564 if (technicalPrinted)
565 xml.etag();
566 technicalPrinted = false;
567 }
568
569 //---------------------------------------------------------
570 // color2xml
571 //---------------------------------------------------------
572
573 /**
574 Return \a el color.
575 */
576
color2xml(const Element * el)577 static QString color2xml(const Element* el)
578 {
579 if (el->color() != MScore::defaultColor)
580 return QString(" color=\"%1\"").arg(el->color().name().toUpper());
581 else
582 return "";
583 }
584
585 //---------------------------------------------------------
586 // fontStyleToXML
587 //---------------------------------------------------------
588
fontStyleToXML(const FontStyle style,bool allowUnderline=true)589 static QString fontStyleToXML(const FontStyle style, bool allowUnderline = true)
590 {
591 QString res;
592 if (style & FontStyle::Bold)
593 res += " font-weight=\"bold\"";
594 if (style & FontStyle::Italic)
595 res += " font-style=\"italic\"";
596 if (allowUnderline && style & FontStyle::Underline)
597 res += " underline=\"1\"";
598 return res;
599 }
600
601 //---------------------------------------------------------
602 // slurHandler
603 //---------------------------------------------------------
604
SlurHandler()605 SlurHandler::SlurHandler()
606 {
607 for (int i = 0; i < MAX_NUMBER_LEVEL; ++i) {
608 slur[i] = 0;
609 started[i] = false;
610 }
611 }
612
slurTieLineStyle(const SlurTie * s)613 static QString slurTieLineStyle(const SlurTie* s)
614 {
615 QString lineType;
616 QString rest;
617 switch (s->lineType()) {
618 case 1:
619 lineType = "dotted";
620 break;
621 case 2:
622 lineType = "dashed";
623 break;
624 default:
625 lineType = "";
626 }
627 if (!lineType.isEmpty())
628 rest = QString(" line-type=\"%1\"").arg(lineType);
629 return rest;
630 }
631
632 //---------------------------------------------------------
633 // findSlur -- get index of slur in slur table
634 // return -1 if not found
635 //---------------------------------------------------------
636
findSlur(const Slur * s) const637 int SlurHandler::findSlur(const Slur* s) const
638 {
639 for (int i = 0; i < MAX_NUMBER_LEVEL; ++i)
640 if (slur[i] == s) return i;
641 return -1;
642 }
643
644 //---------------------------------------------------------
645 // findFirstChordRest -- find first chord or rest (in musical order) for slur s
646 // note that this is not necessarily the same as s->startElement()
647 //---------------------------------------------------------
648
findFirstChordRest(const Slur * s)649 static const ChordRest* findFirstChordRest(const Slur* s)
650 {
651 const Element* e1 = s->startElement();
652 if (!e1 || !(e1->isChordRest())) {
653 qDebug("no valid start element for slur %p", s);
654 return nullptr;
655 }
656
657 const Element* e2 = s->endElement();
658 if (!e2 || !(e2->isChordRest())) {
659 qDebug("no valid end element for slur %p", s);
660 return nullptr;
661 }
662
663 if (e1->tick() < e2->tick())
664 return static_cast<const ChordRest*>(e1);
665 else if (e1->tick() > e2->tick())
666 return static_cast<const ChordRest*>(e2);
667
668 if (e1->isRest() || e2->isRest()) {
669 return nullptr;
670 }
671
672 const auto c1 = static_cast<const Chord*>(e1);
673 const auto c2 = static_cast<const Chord*>(e2);
674
675 // c1->tick() == c2->tick()
676 if (!c1->isGrace() && !c2->isGrace()) {
677 // slur between two regular notes at the same tick
678 // probably shouldn't happen but handle just in case
679 qDebug("invalid slur between chords %p and %p at tick %d", c1, c2, c1->tick().ticks());
680 return 0;
681 }
682 else if (c1->isGraceBefore() && !c2->isGraceBefore())
683 return c1; // easy case: c1 first
684 else if (c1->isGraceAfter() && !c2->isGraceAfter())
685 return c2; // easy case: c2 first
686 else if (c2->isGraceBefore() && !c1->isGraceBefore())
687 return c2; // easy case: c2 first
688 else if (c2->isGraceAfter() && !c1->isGraceAfter())
689 return c1; // easy case: c1 first
690 else {
691 // both are grace before or both are grace after -> compare grace indexes
692 // (note: higher means closer to the non-grace chord it is attached to)
693 if ((c1->isGraceBefore() && c1->graceIndex() < c2->graceIndex())
694 || (c1->isGraceAfter() && c1->graceIndex() > c2->graceIndex()))
695 return c1;
696 else
697 return c2;
698 }
699 }
700
701 //---------------------------------------------------------
702 // doSlurs
703 //---------------------------------------------------------
704
doSlurs(const ChordRest * chordRest,Notations & notations,XmlWriter & xml)705 void SlurHandler::doSlurs(const ChordRest* chordRest, Notations& notations, XmlWriter& xml)
706 {
707 // loop over all slurs twice, first to handle the stops, then the starts
708 for (int i = 0; i < 2; ++i) {
709 // search for slur(s) starting or stopping at this chord
710 for (const auto& it : chordRest->score()->spanner()) {
711 auto sp = it.second;
712 if (sp->generated() || sp->type() != ElementType::SLUR)
713 continue;
714 if (chordRest == sp->startElement() || chordRest == sp->endElement()) {
715 const auto s = static_cast<const Slur*>(sp);
716 const auto firstChordRest = findFirstChordRest(s);
717 if (firstChordRest) {
718 if (i == 0) {
719 // first time: do slur stops
720 if (firstChordRest != chordRest)
721 doSlurStop(s, notations, xml);
722 }
723 else {
724 // second time: do slur starts
725 if (firstChordRest == chordRest)
726 doSlurStart(s, notations, xml);
727 }
728 }
729 }
730 }
731 }
732 }
733
734 //---------------------------------------------------------
735 // doSlurStart
736 //---------------------------------------------------------
737
doSlurStart(const Slur * s,Notations & notations,XmlWriter & xml)738 void SlurHandler::doSlurStart(const Slur* s, Notations& notations, XmlWriter& xml)
739 {
740 // check if on slur list (i.e. stop already seen)
741 int i = findSlur(s);
742 // compose tag
743 QString tagName = "slur";
744 tagName += slurTieLineStyle(s); // define line type
745 tagName += color2xml(s);
746 tagName += QString(" type=\"start\" placement=\"%1\"")
747 .arg(s->up() ? "above" : "below");
748 tagName += positioningAttributes(s, true);
749
750 if (i >= 0) {
751 // remove from list and print start
752 slur[i] = 0;
753 started[i] = false;
754 notations.tag(xml);
755 tagName += QString(" number=\"%1\"").arg(i + 1);
756 xml.tagE(tagName);
757 }
758 else {
759 // find free slot to store it
760 i = findSlur(0);
761 if (i >= 0) {
762 slur[i] = s;
763 started[i] = true;
764 notations.tag(xml);
765 tagName += QString(" number=\"%1\"").arg(i + 1);
766 xml.tagE(tagName);
767 }
768 else
769 qDebug("no free slur slot");
770 }
771 }
772
773 //---------------------------------------------------------
774 // doSlurStop
775 //---------------------------------------------------------
776
777 // Note: a slur may start in a higher voice in the same measure.
778 // In that case it is not yet started (i.e. on the active slur list)
779 // when doSlurStop() is executed. Handle this slur as follows:
780 // - generate stop anyway and put it on the slur list
781 // - doSlurStart() starts slur but doesn't store it
782
doSlurStop(const Slur * s,Notations & notations,XmlWriter & xml)783 void SlurHandler::doSlurStop(const Slur* s, Notations& notations, XmlWriter& xml)
784 {
785 // check if on slur list
786 int i = findSlur(s);
787 if (i < 0) {
788 // if not, find free slot to store it
789 i = findSlur(0);
790 if (i >= 0) {
791 slur[i] = s;
792 started[i] = false;
793 notations.tag(xml);
794 QString tagName = QString("slur type=\"stop\" number=\"%1\"").arg(i + 1);
795 tagName += positioningAttributes(s, false);
796 xml.tagE(tagName);
797 }
798 else
799 qDebug("no free slur slot");
800 }
801 else {
802 // found (already started), stop it and remove from list
803 slur[i] = 0;
804 started[i] = false;
805 notations.tag(xml);
806 QString tagName = QString("slur type=\"stop\" number=\"%1\"").arg(i + 1);
807 tagName += positioningAttributes(s, false);
808 xml.tagE(tagName);
809 }
810 }
811
812 //---------------------------------------------------------
813 // glissando
814 //---------------------------------------------------------
815
816 // <notations>
817 // <slide line-type="solid" number="1" type="start"/>
818 // </notations>
819
820 // <notations>
821 // <glissando line-type="wavy" number="1" type="start"/>
822 // </notations>
823
glissando(const Glissando * gli,int number,bool start,Notations & notations,XmlWriter & xml)824 static void glissando(const Glissando* gli, int number, bool start, Notations& notations, XmlWriter& xml)
825 {
826 GlissandoType st = gli->glissandoType();
827 QString tagName;
828 switch (st) {
829 case GlissandoType::STRAIGHT:
830 tagName = "slide line-type=\"solid\"";
831 break;
832 case GlissandoType::WAVY:
833 tagName = "glissando line-type=\"wavy\"";
834 break;
835 default:
836 qDebug("unknown glissando subtype %d", int(st));
837 return;
838 break;
839 }
840 tagName += QString(" number=\"%1\" type=\"%2\"").arg(number).arg(start ? "start" : "stop");
841 tagName += color2xml(gli);
842 tagName += positioningAttributes(gli, start);
843 notations.tag(xml);
844 if (start && gli->showText() && gli->text() != "")
845 xml.tag(tagName, gli->text());
846 else
847 xml.tagE(tagName);
848 }
849
850 //---------------------------------------------------------
851 // GlissandoHandler
852 //---------------------------------------------------------
853
GlissandoHandler()854 GlissandoHandler::GlissandoHandler()
855 {
856 for (int i = 0; i < MAX_NUMBER_LEVEL; ++i) {
857 glissNote[i] = 0;
858 slideNote[i] = 0;
859 }
860 }
861
862 //---------------------------------------------------------
863 // findNote -- get index of Note in note table for subtype type
864 // return -1 if not found
865 //---------------------------------------------------------
866
findNote(const Note * note,int type) const867 int GlissandoHandler::findNote(const Note* note, int type) const
868 {
869 if (type != 0 && type != 1) {
870 qDebug("GlissandoHandler::findNote: unknown glissando subtype %d", type);
871 return -1;
872 }
873 for (int i = 0; i < MAX_NUMBER_LEVEL; ++i) {
874 if (type == 0 && slideNote[i] == note) return i;
875 if (type == 1 && glissNote[i] == note) return i;
876 }
877 return -1;
878 }
879
880 //---------------------------------------------------------
881 // doGlissandoStart
882 //---------------------------------------------------------
883
doGlissandoStart(Glissando * gliss,Notations & notations,XmlWriter & xml)884 void GlissandoHandler::doGlissandoStart(Glissando* gliss, Notations& notations, XmlWriter& xml)
885 {
886 GlissandoType type = gliss->glissandoType();
887 if (type != GlissandoType::STRAIGHT && type != GlissandoType::WAVY) {
888 qDebug("doGlissandoStart: unknown glissando subtype %d", int(type));
889 return;
890 }
891 Note* note = static_cast<Note*>(gliss->startElement());
892 // check if on chord list
893 int i = findNote(note, int(type));
894 if (i >= 0) {
895 // print error and remove from list
896 qDebug("doGlissandoStart: note for glissando/slide %p already on list", gliss);
897 if (type == GlissandoType::STRAIGHT) slideNote[i] = 0;
898 if (type == GlissandoType::WAVY) glissNote[i] = 0;
899 }
900 // find free slot to store it
901 i = findNote(0, int(type));
902 if (i >= 0) {
903 if (type == GlissandoType::STRAIGHT) slideNote[i] = note;
904 if (type == GlissandoType::WAVY) glissNote[i] = note;
905 glissando(gliss, i + 1, true, notations, xml);
906 }
907 else
908 qDebug("doGlissandoStart: no free slot");
909 }
910
911 //---------------------------------------------------------
912 // doGlissandoStop
913 //---------------------------------------------------------
914
doGlissandoStop(Glissando * gliss,Notations & notations,XmlWriter & xml)915 void GlissandoHandler::doGlissandoStop(Glissando* gliss, Notations& notations, XmlWriter& xml)
916 {
917 GlissandoType type = gliss->glissandoType();
918 if (type != GlissandoType::STRAIGHT && type != GlissandoType::WAVY) {
919 qDebug("doGlissandoStart: unknown glissando subtype %d", int(type));
920 return;
921 }
922 Note* note = static_cast<Note*>(gliss->startElement());
923 for (int i = 0; i < MAX_NUMBER_LEVEL; ++i) {
924 if (type == GlissandoType::STRAIGHT && slideNote[i] == note) {
925 slideNote[i] = 0;
926 glissando(gliss, i + 1, false, notations, xml);
927 return;
928 }
929 if (type == GlissandoType::WAVY && glissNote[i] == note) {
930 glissNote[i] = 0;
931 glissando(gliss, i + 1, false, notations, xml);
932 return;
933 }
934 }
935 qDebug("doGlissandoStop: glissando note %p not found", note);
936 }
937
938 //---------------------------------------------------------
939 // directions anchor -- anchor directions at another element or a specific tick
940 //---------------------------------------------------------
941
942 class DirectionsAnchor {
943 Element* direct; // the element containing the direction
944 Element* anchor; // the element it is attached to
945 bool start; // whether it is attached to start or end
946 Fraction tick; // the timestamp
947
948 public:
DirectionsAnchor(Element * a,bool s,const Fraction & t)949 DirectionsAnchor(Element* a, bool s, const Fraction& t) { direct = 0; anchor = a; start = s; tick = t; }
DirectionsAnchor(const Fraction & t)950 DirectionsAnchor(const Fraction& t) { direct = 0; anchor = 0; start = true; tick = t; }
getDirect()951 Element* getDirect() { return direct; }
getAnchor()952 Element* getAnchor() { return anchor; }
getStart()953 bool getStart() { return start; }
getTick()954 Fraction getTick() { return tick; }
setDirect(Element * d)955 void setDirect(Element* d) { direct = d; }
956 };
957
958 //---------------------------------------------------------
959 // trill handling
960 //---------------------------------------------------------
961
962 // find all trills in this measure and this part
963
findTrills(const Measure * const measure,int strack,int etrack,TrillHash & trillStart,TrillHash & trillStop)964 static void findTrills(const Measure* const measure, int strack, int etrack, TrillHash& trillStart, TrillHash& trillStop)
965 {
966 // loop over all spanners in this measure
967 auto stick = measure->tick();
968 auto etick = measure->tick() + measure->ticks();
969 for (auto it = measure->score()->spanner().lower_bound(stick.ticks()); it != measure->score()->spanner().upper_bound(etick.ticks()); ++it) {
970 auto e = it->second;
971 //qDebug("1 trill %p type %d track %d tick %s", e, e->type(), e->track(), qPrintable(e->tick().print()));
972 if (e->isTrill() && strack <= e->track() && e->track() < etrack
973 && e->tick() >= measure->tick() && e->tick() < (measure->tick() + measure->ticks()))
974 {
975 //qDebug("2 trill %p", e);
976 // a trill is found starting in this segment, trill end time is known
977 // determine notes to write trill start and stop
978
979 const auto tr = toTrill(e);
980 auto elem1 = tr->startElement();
981 auto elem2 = tr->endElement();
982
983 if (elem1 && elem1->isChordRest() && elem2 && elem2->isChordRest()) {
984 trillStart.insert(toChordRest(elem1), tr);
985 trillStop.insert(toChordRest(elem2), tr);
986 }
987 }
988 }
989 }
990
991 //---------------------------------------------------------
992 // helpers for ::calcDivisions
993 //---------------------------------------------------------
994
995 typedef QList<int> IntVector;
996 static IntVector integers;
997 static IntVector primes;
998
999 // check if all integers can be divided by d
1000
canDivideBy(int d)1001 static bool canDivideBy(int d)
1002 {
1003 bool res = true;
1004 for (int i = 0; i < integers.count(); i++) {
1005 if ((integers[i] <= 1) || ((integers[i] % d) != 0)) {
1006 res = false;
1007 }
1008 }
1009 return res;
1010 }
1011
1012 // divide all integers by d
1013
divideBy(int d)1014 static void divideBy(int d)
1015 {
1016 for (int i = 0; i < integers.count(); i++) {
1017 integers[i] /= d;
1018 }
1019 }
1020
addInteger(int len)1021 static void addInteger(int len)
1022 {
1023 if (!integers.contains(len)) {
1024 integers.append(len);
1025 }
1026 }
1027
1028 //---------------------------------------------------------
1029 // calcDivMoveToTick
1030 //---------------------------------------------------------
1031
calcDivMoveToTick(const Fraction & t)1032 void ExportMusicXml::calcDivMoveToTick(const Fraction& t)
1033 {
1034 if (t < _tick) {
1035 #ifdef DEBUG_TICK
1036 qDebug("backup %d", (tick - t).ticks());
1037 #endif
1038 addInteger((_tick - t).ticks());
1039 }
1040 else if (t > _tick) {
1041 #ifdef DEBUG_TICK
1042 qDebug("forward %d", (t - tick).ticks());;
1043 #endif
1044 addInteger((t - _tick).ticks());
1045 }
1046 _tick = t;
1047 }
1048
1049 //---------------------------------------------------------
1050 // isTwoNoteTremolo - determine is chord is part of two note tremolo
1051 //---------------------------------------------------------
1052
isTwoNoteTremolo(Chord * chord)1053 static bool isTwoNoteTremolo(Chord* chord)
1054 {
1055 return (chord->tremolo() && chord->tremolo()->twoNotes());
1056 }
1057
1058 //---------------------------------------------------------
1059 // calcDivisions
1060 //---------------------------------------------------------
1061
1062 // Loop over all voices in all staves and determine a suitable value for divisions.
1063
1064 // Length of time in MusicXML is expressed in "units", which should allow expressing all time values
1065 // as an integral number of units. Divisions contains the number of units in a quarter note.
1066 // MuseScore uses division (480) midi ticks to represent a quarter note, which expresses all note values
1067 // plus triplets and quintuplets as integer values. Solution is to collect all time values required,
1068 // and divide them by the highest common denominator, which is implemented as a series of
1069 // divisions by prime factors. Initialize the list with division to make sure a quarter note can always
1070 // be written as an integral number of units.
1071
1072 /**
1073 */
1074
calcDivisions()1075 void ExportMusicXml::calcDivisions()
1076 {
1077 // init
1078 integers.clear();
1079 primes.clear();
1080 integers.append(MScore::division);
1081 primes.append(2);
1082 primes.append(3);
1083 primes.append(5);
1084
1085 const QList<Part*>& il = _score->parts();
1086
1087 for (int idx = 0; idx < il.size(); ++idx) {
1088
1089 Part* part = il.at(idx);
1090 _tick = { 0,1 };
1091
1092 int staves = part->nstaves();
1093 int strack = _score->staffIdx(part) * VOICES;
1094 int etrack = strack + staves * VOICES;
1095
1096 for (MeasureBase* mb = _score->measures()->first(); mb; mb = mb->next()) {
1097
1098 if (mb->type() != ElementType::MEASURE)
1099 continue;
1100 Measure* m = (Measure*)mb;
1101
1102 for (int st = strack; st < etrack; ++st) {
1103 // sstaff - xml staff number, counting from 1 for this
1104 // instrument
1105 // special number 0 -> don’t show staff number in
1106 // xml output (because there is only one staff)
1107
1108 int sstaff = (staves > 1) ? st - strack + VOICES : 0;
1109 sstaff /= VOICES;
1110
1111 for (Segment* seg = m->first(); seg; seg = seg->next()) {
1112
1113 Element* el = seg->element(st);
1114 if (!el)
1115 continue;
1116
1117 // must ignore start repeat to prevent spurious backup/forward
1118 if (el->type() == ElementType::BAR_LINE && static_cast<BarLine*>(el)->barLineType() == BarLineType::START_REPEAT)
1119 continue;
1120
1121 if (_tick != seg->tick())
1122 calcDivMoveToTick(seg->tick());
1123
1124 if (el->isChordRest()) {
1125 Fraction l = toChordRest(el)->actualTicks();
1126 if (el->isChord()) {
1127 if (isTwoNoteTremolo(toChord(el)))
1128 l = l * Fraction(1,2);
1129 }
1130 #ifdef DEBUG_TICK
1131 qDebug("chordrest %d", l);
1132 #endif
1133 addInteger(l.ticks());
1134 _tick += l;
1135 }
1136 }
1137 }
1138 // move to end of measure (in case of incomplete last voice)
1139 calcDivMoveToTick(m->endTick());
1140 }
1141 }
1142
1143 // do it: divide by all primes as often as possible
1144 for (int u = 0; u < primes.count(); u++)
1145 while (canDivideBy(primes[u]))
1146 divideBy(primes[u]);
1147
1148 div = MScore::division / integers[0];
1149 #ifdef DEBUG_TICK
1150 qDebug("divisions=%d div=%d", integers[0], div);
1151 #endif
1152 }
1153
1154 //---------------------------------------------------------
1155 // writePageFormat
1156 //---------------------------------------------------------
1157
writePageFormat(const Score * const s,XmlWriter & xml,double conversion)1158 static void writePageFormat(const Score* const s, XmlWriter& xml, double conversion)
1159 {
1160 xml.stag("page-layout");
1161
1162 xml.tag("page-height", s->styleD(Sid::pageHeight) * conversion);
1163 xml.tag("page-width", s->styleD(Sid::pageWidth) * conversion);
1164
1165 QString type("both");
1166 if (s->styleB(Sid::pageTwosided)) {
1167 type = "even";
1168 xml.stag(QString("page-margins type=\"%1\"").arg(type));
1169 xml.tag("left-margin", s->styleD(Sid::pageEvenLeftMargin) * conversion);
1170 xml.tag("right-margin", s->styleD(Sid::pageOddLeftMargin) * conversion);
1171 xml.tag("top-margin", s->styleD(Sid::pageEvenTopMargin) * conversion);
1172 xml.tag("bottom-margin", s->styleD(Sid::pageEvenBottomMargin) * conversion);
1173 xml.etag();
1174 type = "odd";
1175 }
1176 xml.stag(QString("page-margins type=\"%1\"").arg(type));
1177 xml.tag("left-margin", s->styleD(Sid::pageOddLeftMargin) * conversion);
1178 xml.tag("right-margin", s->styleD(Sid::pageEvenLeftMargin) * conversion);
1179 xml.tag("top-margin", s->styleD(Sid::pageOddTopMargin) * conversion);
1180 xml.tag("bottom-margin", s->styleD(Sid::pageOddBottomMargin) * conversion);
1181 xml.etag();
1182
1183 xml.etag();
1184 }
1185
1186 //---------------------------------------------------------
1187 // defaults
1188 //---------------------------------------------------------
1189
1190 // _spatium = DPMM * (millimeter * 10.0 / tenths);
1191
defaults(XmlWriter & xml,const Score * const s,double & millimeters,const int & tenths)1192 static void defaults(XmlWriter& xml, const Score* const s, double& millimeters, const int& tenths)
1193 {
1194 xml.stag("defaults");
1195 xml.stag("scaling");
1196 xml.tag("millimeters", millimeters);
1197 xml.tag("tenths", tenths);
1198 xml.etag();
1199
1200 writePageFormat(s, xml, INCH / millimeters * tenths);
1201
1202 // TODO: also write default system layout here
1203 // when exporting only manual or no breaks, system-distance is not written at all
1204
1205 // font defaults
1206 // as MuseScore supports dozens of different styles, while MusicXML only has defaults
1207 // for music (TODO), words and lyrics, use Tid STAFF (typically used for words)
1208 // and LYRIC1 to get MusicXML defaults
1209
1210 // TODO xml.tagE("music-font font-family=\"TBD\" font-size=\"TBD\"");
1211 xml.tagE(QString("word-font font-family=\"%1\" font-size=\"%2\"").arg(s->styleSt(Sid::staffTextFontFace)).arg(s->styleD(Sid::staffTextFontSize)));
1212 xml.tagE(QString("lyric-font font-family=\"%1\" font-size=\"%2\"").arg(s->styleSt(Sid::lyricsOddFontFace)).arg(s->styleD(Sid::lyricsOddFontSize)));
1213 xml.etag();
1214 }
1215
1216 //---------------------------------------------------------
1217 // formatForWords
1218 //---------------------------------------------------------
1219
formatForWords(const Score * const s)1220 static CharFormat formatForWords(const Score* const s)
1221 {
1222 CharFormat defFmt;
1223 defFmt.setFontFamily(s->styleSt(Sid::staffTextFontFace));
1224 defFmt.setFontSize(s->styleD(Sid::staffTextFontSize));
1225 return defFmt;
1226 }
1227
1228 //---------------------------------------------------------
1229 // creditWords
1230 //---------------------------------------------------------
1231
creditWords(XmlWriter & xml,const Score * const s,const int pageNr,const double x,const double y,const QString & just,const QString & val,const QList<TextFragment> & words,const QString & creditType)1232 static void creditWords(XmlWriter& xml, const Score* const s, const int pageNr,
1233 const double x, const double y, const QString& just, const QString& val,
1234 const QList<TextFragment>& words, const QString& creditType)
1235 {
1236 // prevent incorrect MusicXML for empty text
1237 if (words.isEmpty())
1238 return;
1239
1240 const QString mtf = s->styleSt(Sid::MusicalTextFont);
1241 const CharFormat defFmt = formatForWords(s);
1242
1243 // export formatted
1244 xml.stag(QString("credit page=\"%1\"").arg(pageNr));
1245 if (creditType != "")
1246 xml.tag("credit-type", creditType);
1247 QString attr = QString(" default-x=\"%1\"").arg(x);
1248 attr += QString(" default-y=\"%1\"").arg(y);
1249 attr += " justify=\"" + just + "\"";
1250 attr += " valign=\"" + val + "\"";
1251 MScoreTextToMXML mttm("credit-words", attr, defFmt, mtf);
1252 mttm.writeTextFragments(words, xml);
1253 xml.etag();
1254 }
1255
1256 //---------------------------------------------------------
1257 // parentHeight
1258 //---------------------------------------------------------
1259
parentHeight(const Element * element)1260 static double parentHeight(const Element* element)
1261 {
1262 const Element* parent = element->parent();
1263
1264 if (!parent)
1265 return 0;
1266
1267 if (parent->type() == ElementType::VBOX) {
1268 return parent->height();
1269 }
1270
1271 return 0;
1272 }
1273
1274 //---------------------------------------------------------
1275 // tidToCreditType
1276 //---------------------------------------------------------
1277
tidToCreditType(const Tid tid)1278 static QString tidToCreditType(const Tid tid)
1279 {
1280 QString res;
1281 switch (tid) {
1282 case Tid::COMPOSER:
1283 res = "composer";
1284 break;
1285 case Tid::POET:
1286 res = "lyricist";
1287 break;
1288 case Tid::SUBTITLE:
1289 res = "subtitle";
1290 break;
1291 case Tid::TITLE:
1292 res = "title";
1293 break;
1294 default:
1295 break;
1296 }
1297 return res;
1298 }
1299
1300 //---------------------------------------------------------
1301 // textAsCreditWords
1302 //---------------------------------------------------------
1303
1304 // Refactor suggestion: make getTenthsFromInches static instead of ExportMusicXml member function
1305
textAsCreditWords(const ExportMusicXml * const expMxml,XmlWriter & xml,const Score * const s,const int pageNr,const Text * const text)1306 static void textAsCreditWords(const ExportMusicXml* const expMxml, XmlWriter& xml, const Score* const s, const int pageNr, const Text* const text)
1307 {
1308 // determine page formatting
1309 const double h = expMxml->getTenthsFromInches(s->styleD(Sid::pageHeight));
1310 const double w = expMxml->getTenthsFromInches(s->styleD(Sid::pageWidth));
1311 const double lm = expMxml->getTenthsFromInches(s->styleD(Sid::pageOddLeftMargin));
1312 const double rm = expMxml->getTenthsFromInches(s->styleD(Sid::pageEvenLeftMargin));
1313 const double ph = expMxml->getTenthsFromDots(parentHeight(text));
1314
1315 double tx = w / 2;
1316 double ty = h - expMxml->getTenthsFromDots(text->pagePos().y());
1317
1318 Align al = text->align();
1319 QString just;
1320 QString val;
1321
1322 if (al & Align::RIGHT) {
1323 just = "right";
1324 tx = w - rm;
1325 }
1326 else if (al & Align::HCENTER) {
1327 just = "center";
1328 // tx already set correctly
1329 }
1330 else {
1331 just = "left";
1332 tx = lm;
1333 }
1334
1335 if (al & Align::BOTTOM) {
1336 val = "bottom";
1337 ty -= ph;
1338 }
1339 else if (al & Align::VCENTER) {
1340 val = "middle";
1341 ty -= ph / 2;
1342 }
1343 else if (al & Align::BASELINE) {
1344 val = "baseline";
1345 ty -= ph / 2;
1346 }
1347 else {
1348 val = "top";
1349 // ty already set correctly
1350 }
1351
1352 const QString creditType= tidToCreditType(text->tid());
1353
1354 creditWords(xml, s, pageNr, tx, ty, just, val, text->fragmentList(), creditType);
1355 }
1356
1357 //---------------------------------------------------------
1358 // credits
1359 //---------------------------------------------------------
1360
credits(XmlWriter & xml)1361 void ExportMusicXml::credits(XmlWriter& xml)
1362 {
1363 // find the vboxes in every page and write their elements as credit-words
1364 for (const auto page : _score->pages()) {
1365 const auto pageIdx = _score->pageIdx(page);
1366 for (const auto system : page->systems()) {
1367 for (const auto mb : system->measures()) {
1368 if (mb->isVBox()) {
1369 for (const Element* element : mb->el()) {
1370 if (element->isText()) {
1371 const Text* text = toText(element);
1372 textAsCreditWords(this, xml, _score, pageIdx + 1, text);
1373 }
1374 }
1375 }
1376 }
1377 }
1378 }
1379
1380 // put copyright at the bottom center of every page
1381 // note: as the copyright metatag contains plain text, special XML characters must be escaped
1382 // determine page formatting
1383 const QString rights = _score->metaTag("copyright");
1384 if (!rights.isEmpty()) {
1385 const double bm = getTenthsFromInches(_score->styleD(Sid::pageOddBottomMargin));
1386 const double w = getTenthsFromInches(_score->styleD(Sid::pageWidth));
1387 /*
1388 const double h = getTenthsFromInches(_score->styleD(Sid::pageHeight));
1389 const double lm = getTenthsFromInches(_score->styleD(Sid::pageOddLeftMargin));
1390 const double rm = getTenthsFromInches(_score->styleD(Sid::pageEvenLeftMargin));
1391 const double tm = getTenthsFromInches(_score->styleD(Sid::pageOddTopMargin));
1392 qDebug("page h=%g w=%g lm=%g rm=%g tm=%g bm=%g", h, w, lm, rm, tm, bm);
1393 */
1394 TextFragment f(XmlWriter::xmlString(rights));
1395 f.changeFormat(FormatId::FontFamily, _score->styleSt(Sid::footerFontFace));
1396 f.changeFormat(FormatId::FontSize, _score->styleD(Sid::footerFontSize));
1397 QList<TextFragment> list;
1398 list.append(f);
1399 for (int pageIdx = 0; pageIdx < _score->npages(); ++pageIdx)
1400 creditWords(xml, _score, pageIdx + 1, w / 2, bm, "center", "bottom", list, "rights");
1401 }
1402 }
1403
1404 //---------------------------------------------------------
1405 // midipitch2xml
1406 //---------------------------------------------------------
1407
1408 static int alterTab[12] = { 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0 };
1409 static char noteTab[12] = { 'C', 'C', 'D', 'D', 'E', 'F', 'F', 'G', 'G', 'A', 'A', 'B' };
1410
midipitch2xml(int pitch,char & c,int & alter,int & octave)1411 static void midipitch2xml(int pitch, char& c, int& alter, int& octave)
1412 {
1413 // 60 = C 4
1414 c = noteTab[pitch % 12];
1415 alter = alterTab[pitch % 12];
1416 octave = pitch / 12 - 1;
1417 //qDebug("midipitch2xml(pitch %d) step %c, alter %d, octave %d", pitch, c, alter, octave);
1418 }
1419
1420 //---------------------------------------------------------
1421 // tabpitch2xml
1422 //---------------------------------------------------------
1423
tabpitch2xml(const int pitch,const int tpc,QString & s,int & alter,int & octave)1424 static void tabpitch2xml(const int pitch, const int tpc, QString& s, int& alter, int& octave)
1425 {
1426 s = tpc2stepName(tpc);
1427 alter = tpc2alterByKey(tpc, Key::C);
1428 octave = (pitch - alter) / 12 - 1;
1429 if (alter < -2 || 2 < alter)
1430 qDebug("tabpitch2xml(pitch %d, tpc %d) problem: step %s, alter %d, octave %d",
1431 pitch, tpc, qPrintable(s), alter, octave);
1432 /*
1433 else
1434 qDebug("tabpitch2xml(pitch %d, tpc %d) step %s, alter %d, octave %d",
1435 pitch, tpc, qPrintable(s), alter, octave);
1436 */
1437 }
1438
1439 //---------------------------------------------------------
1440 // pitch2xml
1441 //---------------------------------------------------------
1442
1443 // TODO validation
1444
pitch2xml(const Note * note,QString & s,int & alter,int & octave)1445 static void pitch2xml(const Note* note, QString& s, int& alter, int& octave)
1446 {
1447 const auto st = note->staff();
1448 const auto tick = note->tick();
1449 const auto instr = st->part()->instrument(tick);
1450 const auto intval = instr->transpose();
1451
1452 s = tpc2stepName(note->tpc());
1453 alter = tpc2alterByKey(note->tpc(), Key::C);
1454 // note that pitch must be converted to concert pitch
1455 // in order to calculate the correct octave
1456 octave = (note->pitch() - intval.chromatic - alter) / 12 - 1;
1457
1458 //
1459 // HACK:
1460 // On percussion clefs there is no relationship between
1461 // note->pitch() and note->line()
1462 // note->line() is determined by drumMap
1463 //
1464 ClefType ct = st->clef(tick);
1465 if (ct == ClefType::PERC || ct == ClefType::PERC2) {
1466 alter = 0;
1467 octave = line2pitch(note->line(), ct, Key::C) / 12 - 1;
1468 }
1469
1470 // correct for ottava lines
1471 int ottava = 0;
1472 switch (note->ppitch() - note->pitch()) {
1473 case 24: ottava = 2; break;
1474 case 12: ottava = 1; break;
1475 case 0: ottava = 0; break;
1476 case -12: ottava = -1; break;
1477 case -24: ottava = -2; break;
1478 default: qDebug("pitch2xml() tick=%d pitch()=%d ppitch()=%d",
1479 tick.ticks(), note->pitch(), note->ppitch());
1480 }
1481 octave += ottava;
1482
1483 //qDebug("pitch2xml(pitch %d, tpc %d, ottava %d clef %hhd) step %s, alter %d, octave %d",
1484 // note->pitch(), note->tpc(), ottava, clef, qPrintable(s), alter, octave);
1485 }
1486
1487 // unpitch2xml -- calculate display-step and display-octave for an unpitched note
1488 // note:
1489 // even though this produces the correct step/octave according to Recordare's tutorial
1490 // Finale Notepad 2012 does not import a three line staff with percussion clef correctly
1491 // Same goes for Sibelius 6 in case of three or five line staff with percussion clef
1492
unpitch2xml(const Note * note,QString & s,int & octave)1493 static void unpitch2xml(const Note* note, QString& s, int& octave)
1494 {
1495 static char table1[] = "FEDCBAG";
1496
1497 Fraction tick = note->chord()->tick();
1498 Staff* st = note->staff();
1499 ClefType ct = st->clef(tick);
1500 // offset in lines between staff with current clef and with G clef
1501 int clefOffset = ClefInfo::pitchOffset(ct) - ClefInfo::pitchOffset(ClefType::G);
1502 // line note would be on on a five line staff with G clef
1503 // note top line is line 0, bottom line is line 8
1504 int line5g = note->line() - clefOffset;
1505 // in MusicXML with percussion clef, step and octave are determined as if G clef is used
1506 // when stafflines is not equal to five, in MusicXML the bottom line is still E4.
1507 // in MuseScore assumes line 0 is F5
1508 // MS line numbers (top to bottom) plus correction to get lowest line at E4 (line 8)
1509 // 1 line staff: 0 -> correction 8
1510 // 3 line staff: 2, 4, 6 -> correction 2
1511 // 5 line staff: 0, 2, 4, 6, 8 -> correction 0
1512 // TODO handle other # staff lines ?
1513 if (st->lines(Fraction(0,1)) == 1) line5g += 8;
1514 if (st->lines(Fraction(0,1)) == 3) line5g += 2;
1515 // index in table1 to get step
1516 int stepIdx = (line5g + 700) % 7;
1517 // get step
1518 s = table1[stepIdx];
1519 // calculate octave, offset "3" correcting for the fact that an octave starts
1520 // with C instead of F
1521 octave =(3 - line5g + 700) / 7 + 5 - 100;
1522 // qDebug("ExportMusicXml::unpitch2xml(%p) clef %d clef.po %d clefOffset %d staff.lines %d note.line %d line5g %d step %c oct %d",
1523 // note, ct, clefTable[ct].pitchOffset, clefOffset, st->lines(), note->line(), line5g, step, octave);
1524 }
1525
1526 //---------------------------------------------------------
1527 // tick2xml
1528 // set type + dots depending on tick len
1529 //---------------------------------------------------------
1530
tick2xml(const Fraction & ticks,int * dots)1531 static QString tick2xml(const Fraction& ticks, int* dots)
1532 {
1533 TDuration t(ticks);
1534 *dots = t.dots();
1535 if (ticks == Fraction(0,1)) {
1536 t.setType("measure");
1537 *dots = 0;
1538 }
1539 return t.name();
1540 }
1541
1542 //---------------------------------------------------------
1543 // findVolta -- find volta starting in measure m
1544 //---------------------------------------------------------
1545
findVolta(const Measure * const m,bool left)1546 static Volta* findVolta(const Measure* const m, bool left)
1547 {
1548 Fraction stick = m->tick();
1549 Fraction etick = m->tick() + m->ticks();
1550 auto spanners = m->score()->spannerMap().findOverlapping(stick.ticks(), etick.ticks());
1551 for (auto i : spanners) {
1552 Spanner* el = i.value;
1553 if (el->type() != ElementType::VOLTA)
1554 continue;
1555 if (left && el->tick() == stick)
1556 return (Volta*) el;
1557 if (!left && el->tick2() == etick)
1558 return (Volta*) el;
1559 }
1560 return 0;
1561 }
1562
1563 //---------------------------------------------------------
1564 // ending
1565 //---------------------------------------------------------
1566
ending(XmlWriter & xml,Volta * v,bool left)1567 static void ending(XmlWriter& xml, Volta* v, bool left)
1568 {
1569 QString number = "";
1570 QString type = "";
1571 for (int i : v->endings()) {
1572 if (!number.isEmpty())
1573 number += ", ";
1574 number += QString("%1").arg(i);
1575 }
1576 if (left) {
1577 type = "start";
1578 }
1579 else {
1580 Volta::Type st = v->voltaType();
1581 switch (st) {
1582 case Volta::Type::OPEN:
1583 type = "discontinue";
1584 break;
1585 case Volta::Type::CLOSED:
1586 type = "stop";
1587 break;
1588 default:
1589 qDebug("unknown volta subtype %d", int(st));
1590 return;
1591 }
1592 }
1593 QString voltaXml = QString("ending number=\"%1\" type=\"%2\"").arg(number, type);
1594 voltaXml += positioningAttributes(v, left);
1595 xml.tagE(voltaXml);
1596 }
1597
1598 //---------------------------------------------------------
1599 // barlineLeft -- search for and handle barline left
1600 //---------------------------------------------------------
1601
barlineLeft(const Measure * const m)1602 void ExportMusicXml::barlineLeft(const Measure* const m)
1603 {
1604 bool rs = m->repeatStart();
1605 Volta* volta = findVolta(m, true);
1606 if (!rs && !volta) return;
1607 _attr.doAttr(_xml, false);
1608 _xml.stag(QString("barline location=\"left\""));
1609 if (rs)
1610 _xml.tag("bar-style", QString("heavy-light"));
1611 if (volta)
1612 ending(_xml, volta, true);
1613 if (rs)
1614 _xml.tagE("repeat direction=\"forward\"");
1615 _xml.etag();
1616 }
1617
1618 //---------------------------------------------------------
1619 // shortBarlineStyle -- recognize normal but shorter barline styles
1620 //---------------------------------------------------------
1621
shortBarlineStyle(const BarLine * bl)1622 static QString shortBarlineStyle(const BarLine* bl)
1623 {
1624 if (bl->barLineType() == BarLineType::NORMAL && !bl->spanStaff()) {
1625 if (bl->spanTo() < 0) {
1626 // lowest point of barline above lowest staff line
1627 if (bl->spanFrom() < 0) {
1628 return "tick"; // highest point of barline above highest staff line
1629 }
1630 else
1631 return "short"; // highest point of barline below highest staff line
1632 }
1633 }
1634
1635 return "";
1636 }
1637
1638 //---------------------------------------------------------
1639 // normalBarlineStyle -- recognize other barline styles
1640 //---------------------------------------------------------
1641
normalBarlineStyle(const BarLine * bl)1642 static QString normalBarlineStyle(const BarLine* bl)
1643 {
1644 const auto bst = bl->barLineType();
1645
1646 switch (bst) {
1647 case BarLineType::NORMAL:
1648 return "regular";
1649 case BarLineType::DOUBLE:
1650 return "light-light";
1651 case BarLineType::END_REPEAT:
1652 case BarLineType::REVERSE_END:
1653 return "light-heavy";
1654 case BarLineType::BROKEN:
1655 return "dashed";
1656 case BarLineType::DOTTED:
1657 return "dotted";
1658 case BarLineType::END:
1659 case BarLineType::END_START_REPEAT:
1660 return "light-heavy";
1661 case BarLineType::HEAVY:
1662 return "heavy";
1663 case BarLineType::DOUBLE_HEAVY:
1664 return "heavy-heavy";
1665 default:
1666 qDebug("bar subtype %d not supported", int(bst));
1667 }
1668
1669 return "";
1670 }
1671
1672 //---------------------------------------------------------
1673 // barlineMiddle -- handle barline middle
1674 //---------------------------------------------------------
1675
barlineMiddle(const BarLine * bl)1676 void ExportMusicXml::barlineMiddle(const BarLine* bl)
1677 {
1678 auto vis = bl->visible();
1679 auto shortStyle = shortBarlineStyle(bl);
1680 auto normalStyle = normalBarlineStyle(bl);
1681 QString barStyle;
1682 if (!vis)
1683 barStyle = "none";
1684 else if (shortStyle != "")
1685 barStyle = shortStyle;
1686 else
1687 barStyle = normalStyle;
1688
1689 if (barStyle != "") {
1690 _xml.stag(QString("barline location=\"middle\""));
1691 _xml.tag("bar-style", barStyle);
1692 _xml.etag();
1693 }
1694 }
1695
1696 //---------------------------------------------------------
1697 // fermataPosition - return fermata y position as MusicXML string
1698 //---------------------------------------------------------
1699
fermataPosition(const Fermata * const fermata)1700 static QString fermataPosition(const Fermata* const fermata)
1701 {
1702 QString res;
1703
1704 if (preferences.getBool(PREF_EXPORT_MUSICXML_EXPORTLAYOUT)) {
1705 constexpr qreal SPATIUM2TENTHS = 10;
1706 constexpr qreal EPSILON = 0.01;
1707 const auto spatium = fermata->spatium();
1708 const auto defY = -1* SPATIUM2TENTHS* fermata->ipos().y() / spatium;
1709 const auto relY = -1* SPATIUM2TENTHS* fermata->offset().y() / spatium;
1710
1711 if (qAbs(defY) >= EPSILON)
1712 res += QString(" default-y=\"%1\"").arg(QString::number(defY,'f',2));
1713 if (qAbs(relY) >= EPSILON)
1714 res += QString(" relative-y=\"%1\"").arg(QString::number(relY,'f',2));
1715 }
1716
1717 return res;
1718 }
1719
1720 //---------------------------------------------------------
1721 // fermata - write a fermata
1722 //---------------------------------------------------------
1723
fermata(const Fermata * const a,XmlWriter & xml)1724 static void fermata(const Fermata* const a, XmlWriter& xml)
1725 {
1726 QString tagName = "fermata";
1727 tagName += QString(" type=\"%1\"").arg(a->placement() == Placement::ABOVE ? "upright" : "inverted");
1728 tagName += fermataPosition(a);
1729 tagName += color2xml(a);
1730 SymId id = a->symId();
1731 if (id == SymId::fermataAbove || id == SymId::fermataBelow)
1732 xml.tagE(tagName);
1733 else if (id == SymId::fermataShortAbove || id == SymId::fermataShortBelow) {
1734 xml.tag(tagName, "angled");
1735 }
1736 else if (id == SymId::fermataLongAbove || id == SymId::fermataLongBelow) {
1737 xml.tag(tagName, "square");
1738 }
1739 else if (id == SymId::fermataVeryShortAbove || id == SymId::fermataVeryShortBelow) {
1740 xml.tag(tagName, "double-angled");
1741 }
1742 else if (id == SymId::fermataVeryLongAbove || id == SymId::fermataVeryLongBelow) {
1743 xml.tag(tagName, "double-square");
1744 }
1745 else if (id == SymId::fermataLongHenzeAbove || id == SymId::fermataLongHenzeBelow) {
1746 xml.tag(tagName, "double-dot");
1747 }
1748 else if (id == SymId::fermataShortHenzeAbove || id == SymId::fermataShortHenzeBelow) {
1749 xml.tag(tagName, "half-curve");
1750 }
1751 else
1752 qDebug("unknown fermata sim id %d", int(id));
1753 }
1754
1755 //---------------------------------------------------------
1756 // barlineHasFermata -- search for fermata on barline
1757 //---------------------------------------------------------
1758
barlineHasFermata(const BarLine * const barline,const int strack,const int etrack)1759 static bool barlineHasFermata(const BarLine* const barline, const int strack, const int etrack) // TODO: track
1760 {
1761 const Segment* seg = barline ? barline->segment() : 0;
1762 if (seg) {
1763 for (const auto anno : seg->annotations()) {
1764 if (anno->isFermata() && strack <= anno->track() && anno->track() < etrack)
1765 return true;
1766 }
1767 }
1768
1769 return false;
1770 }
1771
1772 //---------------------------------------------------------
1773 // writeBarlineFermata -- write fermata on barline
1774 //---------------------------------------------------------
1775
writeBarlineFermata(const BarLine * const barline,XmlWriter & xml,const int strack,const int etrack)1776 static void writeBarlineFermata(const BarLine* const barline, XmlWriter& xml, const int strack, const int etrack)
1777 {
1778 const Segment* seg = barline ? barline->segment() : 0;
1779 if (seg) {
1780 for (const auto anno : seg->annotations()) {
1781 if (anno->isFermata() && strack <= anno->track() && anno->track() < etrack)
1782 fermata(toFermata(anno), xml);
1783 }
1784 }
1785 }
1786
1787 //---------------------------------------------------------
1788 // barlineRight -- search for and handle barline right
1789 //---------------------------------------------------------
1790
barlineRight(const Measure * const m,const int strack,const int etrack)1791 void ExportMusicXml::barlineRight(const Measure* const m, const int strack, const int etrack)
1792 {
1793 const Measure* mmR1 = m->mmRest1(); // the multi measure rest this measure is covered by
1794 const Measure* mmRLst = mmR1->isMMRest() ? mmR1->mmRestLast() : 0; // last measure of replaced sequence of empty measures
1795 // note: use barlinetype as found in multi measure rest for last measure of replaced sequence
1796 BarLineType bst = m == mmRLst ? mmR1->endBarLineType() : m->endBarLineType();
1797 bool visible = m->endBarLineVisible();
1798
1799 bool needBarStyle = (bst != BarLineType::NORMAL && bst != BarLineType::START_REPEAT) || !visible;
1800 Volta* volta = findVolta(m, false);
1801 // detect short and tick barlines
1802 QString special = "";
1803 if (bst == BarLineType::NORMAL) {
1804 const BarLine* bl = m->endBarLine();
1805 if (bl && !bl->spanStaff()) {
1806 if (bl->spanFrom() == BARLINE_SPAN_TICK1_FROM && bl->spanTo() == BARLINE_SPAN_TICK1_TO)
1807 special = "tick";
1808 if (bl->spanFrom() == BARLINE_SPAN_TICK2_FROM && bl->spanTo() == BARLINE_SPAN_TICK2_TO)
1809 special = "tick";
1810 if (bl->spanFrom() == BARLINE_SPAN_SHORT1_FROM && bl->spanTo() == BARLINE_SPAN_SHORT1_TO)
1811 special = "short";
1812 if (bl->spanFrom() == BARLINE_SPAN_SHORT2_FROM && bl->spanTo() == BARLINE_SPAN_SHORT2_FROM)
1813 special = "short";
1814 }
1815 }
1816
1817 // check fermata
1818 // no need to take mmrest into account, MS does not create mmrests for measure with fermatas
1819 const auto hasFermata = barlineHasFermata(m->endBarLine(), strack, etrack);
1820
1821 if (!needBarStyle && !volta && special.isEmpty() && !hasFermata)
1822 return;
1823
1824 _xml.stag(QString("barline location=\"right\""));
1825 if (needBarStyle) {
1826 if (!visible) {
1827 _xml.tag("bar-style", QString("none"));
1828 }
1829 else {
1830 switch (bst) {
1831 case BarLineType::DOUBLE:
1832 _xml.tag("bar-style", QString("light-light"));
1833 break;
1834 case BarLineType::END_REPEAT:
1835 case BarLineType::REVERSE_END:
1836 _xml.tag("bar-style", QString("light-heavy"));
1837 break;
1838 case BarLineType::BROKEN:
1839 _xml.tag("bar-style", QString("dashed"));
1840 break;
1841 case BarLineType::DOTTED:
1842 _xml.tag("bar-style", QString("dotted"));
1843 break;
1844 case BarLineType::END:
1845 case BarLineType::END_START_REPEAT:
1846 _xml.tag("bar-style", QString("light-heavy"));
1847 break;
1848 case BarLineType::HEAVY:
1849 _xml.tag("bar-style", QString("heavy"));
1850 break;
1851 case BarLineType::DOUBLE_HEAVY:
1852 _xml.tag("bar-style", QString("heavy-heavy"));
1853 break;
1854 default:
1855 qDebug("ExportMusicXml::bar(): bar subtype %d not supported", int(bst));
1856 break;
1857 }
1858 }
1859 }
1860 else if (!special.isEmpty()) {
1861 _xml.tag("bar-style", special);
1862 }
1863
1864 writeBarlineFermata(m->endBarLine(), _xml, strack, etrack);
1865
1866 if (volta) {
1867 ending(_xml, volta, false);
1868 }
1869
1870 if (bst == BarLineType::END_REPEAT || bst == BarLineType::END_START_REPEAT) {
1871 if (m->repeatCount() > 2) {
1872 _xml.tagE(QString("repeat direction=\"backward\" times=\"%1\"").arg(m->repeatCount()));
1873 } else {
1874 _xml.tagE("repeat direction=\"backward\"");
1875 }
1876 }
1877
1878 _xml.etag();
1879 }
1880
1881 //---------------------------------------------------------
1882 // calculateTimeDeltaInDivisions
1883 //---------------------------------------------------------
1884
calculateTimeDeltaInDivisions(const Fraction & t1,const Fraction & t2,const int divisions)1885 static int calculateTimeDeltaInDivisions(const Fraction& t1, const Fraction& t2, const int divisions)
1886 {
1887 return (t1 - t2).ticks() / divisions;
1888 }
1889
1890 //---------------------------------------------------------
1891 // moveToTick
1892 //---------------------------------------------------------
1893
moveToTick(const Fraction & t)1894 void ExportMusicXml::moveToTick(const Fraction& t)
1895 {
1896 //qDebug("ExportMusicXml::moveToTick(t=%s) _tick=%s", qPrintable(t.print()), qPrintable(_tick.print()));
1897 if (t < _tick) {
1898 #ifdef DEBUG_TICK
1899 qDebug(" -> backup");
1900 #endif
1901 _attr.doAttr(_xml, false);
1902 _xml.stag("backup");
1903 _xml.tag("duration", calculateTimeDeltaInDivisions(_tick, t, div));
1904 _xml.etag();
1905 }
1906 else if (t > _tick) {
1907 #ifdef DEBUG_TICK
1908 qDebug(" -> forward");
1909 #endif
1910 _attr.doAttr(_xml, false);
1911 _xml.stag("forward");
1912 _xml.tag("duration", calculateTimeDeltaInDivisions(t, _tick, div));
1913 _xml.etag();
1914 }
1915 _tick = t;
1916 }
1917
1918 //---------------------------------------------------------
1919 // timesig
1920 //---------------------------------------------------------
1921
timesig(TimeSig * tsig)1922 void ExportMusicXml::timesig(TimeSig* tsig)
1923 {
1924 TimeSigType st = tsig->timeSigType();
1925 Fraction ts = tsig->sig();
1926 int z = ts.numerator();
1927 int n = ts.denominator();
1928 QString ns = tsig->numeratorString();
1929
1930 _attr.doAttr(_xml, true);
1931 QString tagName = "time";
1932 if (st == TimeSigType::FOUR_FOUR)
1933 tagName += " symbol=\"common\"";
1934 else if (st == TimeSigType::ALLA_BREVE)
1935 tagName += " symbol=\"cut\"";
1936 else if (st == TimeSigType::CUT_BACH)
1937 tagName += " symbol=\"cut2\"";
1938 else if (st == TimeSigType::CUT_TRIPLE)
1939 tagName += " symbol=\"cut3\"";
1940 if (!tsig->visible())
1941 tagName += " print-object=\"no\"";
1942 tagName += color2xml(tsig);
1943 _xml.stag(tagName);
1944
1945 QRegExp rx("^\\d+(\\+\\d+)+$"); // matches a compound numerator
1946 if (rx.exactMatch(ns))
1947 // if compound numerator, exported as is
1948 _xml.tag("beats", ns);
1949 else
1950 // else fall back and use the numerator as integer
1951 _xml.tag("beats", z);
1952 _xml.tag("beat-type", n);
1953 _xml.etag();
1954 }
1955
1956 //---------------------------------------------------------
1957 // accSymId2alter
1958 //---------------------------------------------------------
1959
accSymId2alter(SymId id)1960 static double accSymId2alter(SymId id)
1961 {
1962 double res = 0;
1963 switch (id) {
1964 case SymId::accidentalDoubleFlat: res = -2; break;
1965 case SymId::accidentalThreeQuarterTonesFlatZimmermann: res = -1.5; break;
1966 case SymId::accidentalFlat: res = -1; break;
1967 case SymId::accidentalQuarterToneFlatStein: res = -0.5; break;
1968 case SymId::accidentalNatural: res = 0; break;
1969 case SymId::accidentalQuarterToneSharpStein: res = 0.5; break;
1970 case SymId::accidentalSharp: res = 1; break;
1971 case SymId::accidentalThreeQuarterTonesSharpStein: res = 1.5; break;
1972 case SymId::accidentalDoubleSharp: res = 2; break;
1973 default: qDebug("accSymId2alter: unsupported sym %s", Sym::id2name(id));
1974 }
1975 return res;
1976 }
1977
1978 //---------------------------------------------------------
1979 // keysig
1980 //---------------------------------------------------------
1981
keysig(const KeySig * ks,ClefType ct,int staff,bool visible)1982 void ExportMusicXml::keysig(const KeySig* ks, ClefType ct, int staff, bool visible)
1983 {
1984 static char table2[] = "CDEFGAB";
1985 int po = ClefInfo::pitchOffset(ct); // actually 7 * oct + step for topmost staff line
1986 //qDebug("keysig st %d key %d custom %d ct %hhd st %d", staff, kse.key(), kse.custom(), ct, staff);
1987 //qDebug(" pitch offset clef %d stp %d oct %d ", po, po % 7, po / 7);
1988
1989 QString tagName = "key";
1990 if (staff)
1991 tagName += QString(" number=\"%1\"").arg(staff);
1992 if (!visible)
1993 tagName += " print-object=\"no\"";
1994 tagName += color2xml(ks);
1995 _attr.doAttr(_xml, true);
1996 _xml.stag(tagName);
1997
1998 const KeySigEvent kse = ks->keySigEvent();
1999 const QList<KeySym> keysyms = kse.keySymbols();
2000 if (kse.custom() && !kse.isAtonal() && keysyms.size() > 0) {
2001
2002 // non-traditional key signature
2003 // MusicXML order is left-to-right order, while KeySims in keySymbols()
2004 // are in insertion order -> sorting required
2005
2006 // first put the KeySyms in a map
2007 QMap<qreal, KeySym> map;
2008 for (const KeySym& ksym : keysyms) {
2009 map.insert(ksym.spos.x(), ksym);
2010 }
2011 // then write them (automatically sorted on key)
2012 for (const KeySym& ksym : map) {
2013 int line = static_cast<int>(round(2 * ksym.spos.y()));
2014 int step = (po - line) % 7;
2015 //qDebug(" keysym sym %d spos %g,%g pos %g,%g -> line %d step %d",
2016 // ksym.sym, ksym.spos.x(), ksym.spos.y(), ksym.pos.x(), ksym.pos.y(), line, step);
2017 _xml.tag("key-step", QString(QChar(table2[step])));
2018 _xml.tag("key-alter", accSymId2alter(ksym.sym));
2019 _xml.tag("key-accidental", accSymId2MxmlString(ksym.sym));
2020 }
2021 }
2022 else {
2023 // traditional key signature
2024 _xml.tag("fifths", static_cast<int>(kse.key()));
2025 switch (kse.mode()) {
2026 case KeyMode::NONE: _xml.tag("mode", "none"); break;
2027 case KeyMode::MAJOR: _xml.tag("mode", "major"); break;
2028 case KeyMode::MINOR: _xml.tag("mode", "minor"); break;
2029 case KeyMode::DORIAN: _xml.tag("mode", "dorian"); break;
2030 case KeyMode::PHRYGIAN: _xml.tag("mode", "phrygian"); break;
2031 case KeyMode::LYDIAN: _xml.tag("mode", "lydian"); break;
2032 case KeyMode::MIXOLYDIAN: _xml.tag("mode", "mixolydian"); break;
2033 case KeyMode::AEOLIAN: _xml.tag("mode", "aeolian"); break;
2034 case KeyMode::IONIAN: _xml.tag("mode", "ionian"); break;
2035 case KeyMode::LOCRIAN: _xml.tag("mode", "locrian"); break;
2036 case KeyMode::UNKNOWN: // fall thru
2037 default:
2038 if (kse.custom())
2039 _xml.tag("mode", "none");
2040 }
2041 }
2042 _xml.etag();
2043 }
2044
2045 //---------------------------------------------------------
2046 // clef
2047 //---------------------------------------------------------
2048
clef(int staff,const ClefType ct,const QString & extraAttributes)2049 void ExportMusicXml::clef(int staff, const ClefType ct, const QString& extraAttributes)
2050 {
2051 clefDebug("ExportMusicXml::clef(staff %d, clef %hhd)", staff, ct);
2052
2053 QString tagName = "clef";
2054 if (staff)
2055 tagName += QString(" number=\"%1\"").arg(staff);
2056 tagName += extraAttributes;
2057 _attr.doAttr(_xml, true);
2058 _xml.stag(tagName);
2059
2060 QString sign = ClefInfo::sign(ct);
2061 int line = ClefInfo::line(ct);
2062 _xml.tag("sign", sign);
2063 _xml.tag("line", line);
2064 if (ClefInfo::octChng(ct))
2065 _xml.tag("clef-octave-change", ClefInfo::octChng(ct));
2066 _xml.etag();
2067 }
2068
2069 //---------------------------------------------------------
2070 // tupletNesting
2071 //---------------------------------------------------------
2072
2073 /*
2074 * determine the tuplet nesting level for cr
2075 * 0 = not part of tuplet
2076 * 1 = part of a single tuplet
2077 * 2 = part of two nested tuplets
2078 * etc.
2079 */
2080
tupletNesting(const ChordRest * const cr)2081 static int tupletNesting(const ChordRest* const cr)
2082 {
2083 const DurationElement* el { cr->tuplet() };
2084 int nesting { 0 };
2085 while (el) {
2086 nesting++;
2087 el = el->tuplet();
2088 }
2089 return nesting;
2090 }
2091
2092 //---------------------------------------------------------
2093 // isSimpleTuplet
2094 //---------------------------------------------------------
2095
2096 /*
2097 * determine if t is simple, i.e. all its children are chords or rests
2098 */
2099
isSimpleTuplet(const Tuplet * const t)2100 static bool isSimpleTuplet(const Tuplet* const t)
2101 {
2102 if (t->tuplet())
2103 return false;
2104 for (const auto el : t->elements()) {
2105 if (!el->isChordRest())
2106 return false;
2107 }
2108 return true;
2109 }
2110
2111 //---------------------------------------------------------
2112 // isTupletStart
2113 //---------------------------------------------------------
2114
2115 /*
2116 * determine if t is the starting element of a tuplet
2117 */
2118
isTupletStart(const DurationElement * const el)2119 static bool isTupletStart(const DurationElement* const el)
2120 {
2121 const auto t = el->tuplet();
2122 if (!t)
2123 return false;
2124 return el == t->elements().front();
2125 }
2126
2127 //---------------------------------------------------------
2128 // isTupletStop
2129 //---------------------------------------------------------
2130
2131 /*
2132 * determine if t is the stopping element of a tuplet
2133 */
2134
isTupletStop(const DurationElement * const el)2135 static bool isTupletStop(const DurationElement* const el)
2136 {
2137 const auto t = el->tuplet();
2138 if (!t)
2139 return false;
2140 return el == t->elements().back();
2141 }
2142
2143 //---------------------------------------------------------
2144 // startTupletAtLevel
2145 //---------------------------------------------------------
2146
2147 /*
2148 * return the tuplet starting at tuplet nesting level, if any
2149 */
2150
startTupletAtLevel(const DurationElement * const cr,const int level)2151 static const Tuplet* startTupletAtLevel(const DurationElement* const cr, const int level)
2152 {
2153 const DurationElement* el { cr };
2154 if (!el->tuplet())
2155 return nullptr;
2156 for (int i = 0; i < level; ++i) {
2157 if (!isTupletStart(el)) {
2158 return nullptr;
2159 }
2160 el = el->tuplet();
2161 }
2162 return toTuplet(el);
2163 }
2164
2165 //---------------------------------------------------------
2166 // stopTupletAtLevel
2167 //---------------------------------------------------------
2168
2169 /*
2170 * return the tuplet stopping at tuplet nesting level, if any
2171 */
2172
stopTupletAtLevel(const DurationElement * const cr,const int level)2173 static const Tuplet* stopTupletAtLevel(const DurationElement* const cr, const int level)
2174 {
2175 const DurationElement* el { cr };
2176 if (!el->tuplet())
2177 return nullptr;
2178 for (int i = 0; i < level; ++i) {
2179 if (!isTupletStop(el)) {
2180 return nullptr;
2181 }
2182 el = el->tuplet();
2183 }
2184 return toTuplet(el);
2185 }
2186
2187 //---------------------------------------------------------
2188 // tupletTypeAndDots
2189 //---------------------------------------------------------
2190
tupletTypeAndDots(const QString & type,const int dots,XmlWriter & xml)2191 static void tupletTypeAndDots(const QString& type, const int dots, XmlWriter& xml)
2192 {
2193 xml.tag("tuplet-type", type);
2194 for (int i = 0; i < dots; ++i)
2195 xml.tagE("tuplet-dot");
2196 }
2197
2198 //---------------------------------------------------------
2199 // tupletActualAndNormal
2200 //---------------------------------------------------------
2201
tupletActualAndNormal(const Tuplet * const t,XmlWriter & xml)2202 static void tupletActualAndNormal(const Tuplet* const t, XmlWriter& xml)
2203 {
2204 xml.stag("tuplet-actual");
2205 xml.tag("tuplet-number", t->ratio().numerator());
2206 int dots { 0 };
2207 const auto s = tick2xml(t->baseLen().ticks(), &dots);
2208 tupletTypeAndDots(s, dots, xml);
2209 xml.etag();
2210 xml.stag("tuplet-normal");
2211 xml.tag("tuplet-number", t->ratio().denominator());
2212 tupletTypeAndDots(s, dots, xml);
2213 xml.etag();
2214 }
2215
2216 //---------------------------------------------------------
2217 // tupletStart
2218 //---------------------------------------------------------
2219
2220 // LVIFIX: add placement to tuplet support
2221 // <notations>
2222 // <tuplet type="start" placement="above" bracket="no"/>
2223 // </notations>
2224
tupletStart(const Tuplet * const t,const int number,const bool needActualAndNormal,Notations & notations,XmlWriter & xml)2225 static void tupletStart(const Tuplet* const t, const int number, const bool needActualAndNormal, Notations& notations, XmlWriter& xml)
2226 {
2227 notations.tag(xml);
2228 QString tupletTag = "tuplet type=\"start\"";
2229 if (!isSimpleTuplet(t))
2230 tupletTag += QString(" number=\"%1\"").arg(number);
2231 tupletTag += " bracket=";
2232 tupletTag += t->hasBracket() ? "\"yes\"" : "\"no\"";
2233 if (t->numberType() == TupletNumberType::SHOW_RELATION)
2234 tupletTag += " show-number=\"both\"";
2235 if (t->numberType() == TupletNumberType::NO_TEXT)
2236 tupletTag += " show-number=\"none\"";
2237 if (needActualAndNormal) {
2238 xml.stag(tupletTag);
2239 tupletActualAndNormal(t, xml);
2240 xml.etag();
2241 }
2242 else
2243 xml.tagE(tupletTag);
2244 }
2245
2246 //---------------------------------------------------------
2247 // tupletStop
2248 //---------------------------------------------------------
2249
tupletStop(const Tuplet * const t,const int number,Notations & notations,XmlWriter & xml)2250 static void tupletStop(const Tuplet* const t, const int number, Notations& notations, XmlWriter& xml)
2251 {
2252 notations.tag(xml);
2253 QString tupletTag = "tuplet type=\"stop\"";
2254 if (!isSimpleTuplet(t))
2255 tupletTag += QString(" number=\"%1\"").arg(number);
2256 xml.tagE(tupletTag);
2257 }
2258
2259 //---------------------------------------------------------
2260 // tupletStartStop
2261 //---------------------------------------------------------
2262
tupletStartStop(ChordRest * cr,Notations & notations,XmlWriter & xml)2263 static void tupletStartStop(ChordRest* cr, Notations& notations, XmlWriter& xml)
2264 {
2265 const auto nesting = tupletNesting(cr);
2266 bool doActualAndNormal = (nesting > 1);
2267 if (cr->isChord() && isTwoNoteTremolo(toChord(cr))) {
2268 doActualAndNormal = true;
2269 }
2270 for (int level = nesting - 1; level >= 0; --level) {
2271 const auto startTuplet = startTupletAtLevel(cr, level + 1);
2272 if (startTuplet)
2273 tupletStart(startTuplet, nesting - level, doActualAndNormal, notations, xml);
2274 const auto stopTuplet = stopTupletAtLevel(cr, level + 1);
2275 if (stopTuplet)
2276 tupletStop(stopTuplet, nesting - level, notations, xml);
2277 }
2278 }
2279
2280 //---------------------------------------------------------
2281 // findTrill -- get index of trill in trill table
2282 // return -1 if not found
2283 //---------------------------------------------------------
2284
findTrill(const Trill * tr) const2285 int ExportMusicXml::findTrill(const Trill* tr) const
2286 {
2287 for (int i = 0; i < MAX_NUMBER_LEVEL; ++i)
2288 if (trills[i] == tr) return i;
2289 return -1;
2290 }
2291
2292 //---------------------------------------------------------
2293 // writeAccidental
2294 //---------------------------------------------------------
2295
writeAccidental(XmlWriter & xml,const QString & tagName,const Accidental * const acc)2296 static void writeAccidental(XmlWriter& xml, const QString& tagName, const Accidental* const acc)
2297 {
2298 if (acc) {
2299 QString s = accidentalType2MxmlString(acc->accidentalType());
2300 if (s != "") {
2301 QString tag = tagName;
2302 if (acc->bracket() != AccidentalBracket::NONE)
2303 tag += " parentheses=\"yes\"";
2304 xml.tag(tag, s);
2305 }
2306 }
2307 }
2308
2309 //---------------------------------------------------------
2310 // wavyLineStart
2311 //---------------------------------------------------------
2312
wavyLineStart(const Trill * tr,const int number,Notations & notations,Ornaments & ornaments,XmlWriter & xml)2313 static void wavyLineStart(const Trill* tr, const int number, Notations& notations, Ornaments& ornaments, XmlWriter& xml)
2314 {
2315 // mscore only supports wavy-line with trill-mark
2316 notations.tag(xml);
2317 ornaments.tag(xml);
2318 xml.tagE("trill-mark");
2319 writeAccidental(xml, "accidental-mark", tr->accidental());
2320 QString tagName = "wavy-line type=\"start\"";
2321 tagName += QString(" number=\"%1\"").arg(number + 1);
2322 tagName += color2xml(tr);
2323 tagName += positioningAttributes(tr, true);
2324 xml.tagE(tagName);
2325 }
2326
2327 //---------------------------------------------------------
2328 // wavyLineStop
2329 //---------------------------------------------------------
2330
wavyLineStop(const Trill * tr,const int number,Notations & notations,Ornaments & ornaments,XmlWriter & xml)2331 static void wavyLineStop(const Trill* tr, const int number, Notations& notations, Ornaments& ornaments, XmlWriter& xml)
2332 {
2333 notations.tag(xml);
2334 ornaments.tag(xml);
2335 QString trillXml = QString("wavy-line type=\"stop\" number=\"%1\"").arg(number + 1);
2336 trillXml += positioningAttributes(tr, false);
2337 xml.tagE(trillXml);
2338 }
2339
2340 //---------------------------------------------------------
2341 // wavyLineStartStop
2342 //---------------------------------------------------------
2343
wavyLineStartStop(const ChordRest * const cr,Notations & notations,Ornaments & ornaments,TrillHash & trillStart,TrillHash & trillStop)2344 void ExportMusicXml::wavyLineStartStop(const ChordRest* const cr, Notations& notations, Ornaments& ornaments,
2345 TrillHash& trillStart, TrillHash& trillStop)
2346 {
2347 if (trillStart.contains(cr) && trillStop.contains(cr)) {
2348 const auto tr = trillStart.value(cr);
2349 auto n = findTrill(0);
2350 if (n >= 0) {
2351 wavyLineStart(tr, n, notations, ornaments, _xml);
2352 wavyLineStop(tr, n, notations, ornaments, _xml);
2353 }
2354 else
2355 qDebug("too many overlapping trills (cr %p staff %d tick %d)",
2356 cr, cr->staffIdx(), cr->tick().ticks());
2357 }
2358 else {
2359 if (trillStop.contains(cr)) {
2360 const auto tr = trillStop.value(cr);
2361 auto n = findTrill(tr);
2362 if (n >= 0)
2363 // trill stop after trill start
2364 trills[n] = 0;
2365 else {
2366 // trill stop before trill start
2367 n = findTrill(0);
2368 if (n >= 0)
2369 trills[n] = tr;
2370 else
2371 qDebug("too many overlapping trills (cr %p staff %d tick %d)",
2372 cr, cr->staffIdx(), cr->tick().ticks());
2373 }
2374 if (n >= 0) {
2375 wavyLineStop(tr, n, notations, ornaments, _xml);
2376 }
2377 trillStop.remove(cr);
2378 }
2379 if (trillStart.contains(cr)) {
2380 const auto tr = trillStart.value(cr);
2381 auto n = findTrill(tr);
2382 if (n >= 0)
2383 qDebug("wavyLineStartStop error");
2384 else {
2385 n = findTrill(0);
2386 if (n >= 0) {
2387 trills[n] = tr;
2388 wavyLineStart(tr, n, notations, ornaments, _xml);
2389 }
2390 else
2391 qDebug("too many overlapping trills (cr %p staff %d tick %d)",
2392 cr, cr->staffIdx(), cr->tick().ticks());
2393 trillStart.remove(cr);
2394 }
2395 }
2396 }
2397 }
2398
2399 //---------------------------------------------------------
2400 // tremoloSingleStartStop
2401 //---------------------------------------------------------
2402
tremoloSingleStartStop(Chord * chord,Notations & notations,Ornaments & ornaments,XmlWriter & xml)2403 static void tremoloSingleStartStop(Chord* chord, Notations& notations, Ornaments& ornaments, XmlWriter& xml)
2404 {
2405 if (chord->tremolo()) {
2406 Tremolo* tr = chord->tremolo();
2407 int count = 0;
2408 TremoloType st = tr->tremoloType();
2409 QString type = "";
2410
2411 if (chord->tremoloChordType() == TremoloChordType::TremoloSingle) {
2412 type = "single";
2413 switch (st) {
2414 case TremoloType::R8: count = 1; break;
2415 case TremoloType::R16: count = 2; break;
2416 case TremoloType::R32: count = 3; break;
2417 case TremoloType::R64: count = 4; break;
2418 default: qDebug("unknown tremolo single %d", int(st)); break;
2419 }
2420 }
2421 else if (chord->tremoloChordType() == TremoloChordType::TremoloFirstNote) {
2422 type = "start";
2423 switch (st) {
2424 case TremoloType::C8: count = 1; break;
2425 case TremoloType::C16: count = 2; break;
2426 case TremoloType::C32: count = 3; break;
2427 case TremoloType::C64: count = 4; break;
2428 default: qDebug("unknown tremolo double %d", int(st)); break;
2429 }
2430 }
2431 else if (chord->tremoloChordType() == TremoloChordType::TremoloSecondNote) {
2432 type = "stop";
2433 switch (st) {
2434 case TremoloType::C8: count = 1; break;
2435 case TremoloType::C16: count = 2; break;
2436 case TremoloType::C32: count = 3; break;
2437 case TremoloType::C64: count = 4; break;
2438 default: qDebug("unknown tremolo double %d", int(st)); break;
2439 }
2440 }
2441 else qDebug("unknown tremolo subtype %d", int(st));
2442
2443
2444 if (type != "" && count > 0) {
2445 notations.tag(xml);
2446 ornaments.tag(xml);
2447 QString tagName = "tremolo";
2448 tagName += QString(" type=\"%1\"").arg(type);
2449 if (type == "single" || type == "start")
2450 tagName += color2xml(tr);
2451 xml.tag(tagName, count);
2452 }
2453 }
2454 }
2455
2456 //---------------------------------------------------------
2457 // fermatas
2458 //---------------------------------------------------------
2459
fermatas(const QVector<Element * > & cra,XmlWriter & xml,Notations & notations)2460 static void fermatas(const QVector<Element*>& cra, XmlWriter& xml, Notations& notations)
2461 {
2462 for (const Element* e : cra) {
2463 if (!e->isFermata())
2464 continue;
2465 notations.tag(xml);
2466 fermata(toFermata(e), xml);
2467 }
2468 }
2469
2470 //---------------------------------------------------------
2471 // symIdToArtic
2472 //---------------------------------------------------------
2473
symIdToArtic(const SymId sid)2474 static QString symIdToArtic(const SymId sid)
2475 {
2476 switch (sid) {
2477 case SymId::articAccentAbove:
2478 case SymId::articAccentBelow:
2479 return "accent";
2480 break;
2481
2482 case SymId::articStaccatoAbove:
2483 case SymId::articStaccatoBelow:
2484 case SymId::articAccentStaccatoAbove:
2485 case SymId::articAccentStaccatoBelow:
2486 case SymId::articMarcatoStaccatoAbove:
2487 case SymId::articMarcatoStaccatoBelow:
2488 return "staccato";
2489 break;
2490
2491 case SymId::articStaccatissimoAbove:
2492 case SymId::articStaccatissimoBelow:
2493 case SymId::articStaccatissimoStrokeAbove:
2494 case SymId::articStaccatissimoStrokeBelow:
2495 case SymId::articStaccatissimoWedgeAbove:
2496 case SymId::articStaccatissimoWedgeBelow:
2497 return "staccatissimo";
2498 break;
2499
2500 case SymId::articTenutoAbove:
2501 case SymId::articTenutoBelow:
2502 return "tenuto";
2503 break;
2504
2505 case SymId::articMarcatoAbove:
2506 case SymId::articMarcatoBelow:
2507 return "strong-accent";
2508 break;
2509
2510 case SymId::articTenutoStaccatoAbove:
2511 case SymId::articTenutoStaccatoBelow:
2512 return "detached-legato";
2513 break;
2514
2515 case SymId::articSoftAccentAbove:
2516 case SymId::articSoftAccentBelow:
2517 return "soft-accent";
2518 break;
2519
2520 case SymId::articStressAbove:
2521 case SymId::articStressBelow:
2522 return "stress";
2523 break;
2524
2525 case SymId::articUnstressAbove:
2526 case SymId::articUnstressBelow:
2527 return "unstress";
2528 break;
2529
2530 default:
2531 ; // nothing
2532 break;
2533 }
2534
2535 return "";
2536 }
2537
2538 //---------------------------------------------------------
2539 // symIdToOrnam
2540 //---------------------------------------------------------
2541
symIdToOrnam(const SymId sid)2542 static QString symIdToOrnam(const SymId sid)
2543 {
2544 switch (sid) {
2545 case SymId::ornamentTurnInverted:
2546 case SymId::ornamentTurnSlash:
2547 return "inverted-turn";
2548 break;
2549 case SymId::ornamentTurn:
2550 return "turn";
2551 break;
2552 case SymId::ornamentTrill:
2553 return "trill-mark";
2554 break;
2555 case SymId::ornamentMordent:
2556 return "mordent";
2557 break;
2558 case SymId::ornamentShortTrill:
2559 // return "short-trill";
2560 return "inverted-mordent";
2561 break;
2562 case SymId::ornamentTremblement:
2563 return "inverted-mordent long=\"yes\"";
2564 break;
2565 case SymId::ornamentPrallMordent:
2566 return "mordent long=\"yes\"";
2567 break;
2568 case SymId::ornamentUpPrall:
2569 return "inverted-mordent long=\"yes\" approach=\"below\"";
2570 break;
2571 case SymId::ornamentPrecompMordentUpperPrefix:
2572 return "inverted-mordent long=\"yes\" approach=\"above\"";
2573 break;
2574 case SymId::ornamentUpMordent:
2575 return "mordent long=\"yes\" approach=\"below\"";
2576 break;
2577 case SymId::ornamentDownMordent:
2578 return "mordent long=\"yes\" approach=\"above\"";
2579 break;
2580 case SymId::ornamentPrallDown:
2581 return "inverted-mordent long=\"yes\" departure=\"below\"";
2582 break;
2583 case SymId::ornamentPrallUp:
2584 return "inverted-mordent long=\"yes\" departure=\"above\"";
2585 break;
2586 case SymId::ornamentLinePrall:
2587 // MusicXML 3.0 does not distinguish between downprall and lineprall
2588 return "inverted-mordent long=\"yes\" approach=\"above\"";
2589 break;
2590 case SymId::ornamentPrecompSlide:
2591 return "schleifer";
2592 break;
2593
2594 default:
2595 ; // nothing
2596 break;
2597 }
2598
2599 return "";
2600 }
2601
2602 //---------------------------------------------------------
2603 // symIdToTechn
2604 //---------------------------------------------------------
2605
symIdToTechn(const SymId sid)2606 static QString symIdToTechn(const SymId sid)
2607 {
2608 switch (sid) {
2609 case SymId::brassMuteClosed:
2610 return "stopped";
2611 break;
2612 case SymId::stringsHarmonic:
2613 return "harmonic";
2614 break;
2615 case SymId::stringsUpBow:
2616 return "up-bow";
2617 break;
2618 case SymId::stringsDownBow:
2619 return "down-bow";
2620 break;
2621 case SymId::pluckedSnapPizzicatoAbove:
2622 return "snap-pizzicato";
2623 break;
2624 case SymId::brassMuteOpen:
2625 return "open-string";
2626 break;
2627 case SymId::stringsThumbPosition:
2628 return "thumb-position";
2629 break;
2630 default:
2631 ; // nothing
2632 break;
2633 }
2634
2635 return "";
2636 }
2637
2638 //---------------------------------------------------------
2639 // writeChordLines
2640 //---------------------------------------------------------
2641
writeChordLines(const Chord * const chord,XmlWriter & xml,Notations & notations,Articulations & articulations)2642 static void writeChordLines(const Chord* const chord, XmlWriter& xml, Notations& notations, Articulations& articulations)
2643 {
2644 for (Element* e : chord->el()) {
2645 qDebug("writeChordLines: el %p type %d (%s)", e, int(e->type()), e->name());
2646 if (e->type() == ElementType::CHORDLINE) {
2647 ChordLine const* const cl = static_cast<ChordLine*>(e);
2648 QString subtype;
2649 switch (cl->chordLineType()) {
2650 case ChordLineType::FALL:
2651 subtype = "falloff";
2652 break;
2653 case ChordLineType::DOIT:
2654 subtype = "doit";
2655 break;
2656 case ChordLineType::PLOP:
2657 subtype = "plop";
2658 break;
2659 case ChordLineType::SCOOP:
2660 subtype = "scoop";
2661 break;
2662 default:
2663 qDebug("unknown ChordLine subtype %d", int(cl->chordLineType()));
2664 }
2665 if (subtype != "") {
2666 notations.tag(xml);
2667 articulations.tag(xml);
2668 xml.tagE(subtype);
2669 }
2670 }
2671 }
2672 }
2673
2674 //---------------------------------------------------------
2675 // chordAttributes
2676 //---------------------------------------------------------
2677
chordAttributes(Chord * chord,Notations & notations,Technical & technical,TrillHash & trillStart,TrillHash & trillStop)2678 void ExportMusicXml::chordAttributes(Chord* chord, Notations& notations, Technical& technical,
2679 TrillHash& trillStart, TrillHash& trillStop)
2680 {
2681 QVector<Element*> fl;
2682 for (Element* e : chord->segment()->annotations()) {
2683 if (e->track() == chord->track() && e->isFermata())
2684 fl.push_back(e);
2685 }
2686 fermatas(fl, _xml, notations);
2687
2688 const QVector<Articulation*> na = chord->articulations();
2689 // first the attributes whose elements are children of <articulations>
2690 Articulations articulations;
2691 for (const Articulation* a : na) {
2692 auto sid = a->symId();
2693 auto mxmlArtic = symIdToArtic(sid);
2694
2695 if (mxmlArtic != "") {
2696 if (sid == SymId::articMarcatoAbove || sid == SymId::articMarcatoBelow) {
2697 if (a->up())
2698 mxmlArtic += " type=\"up\"";
2699 else
2700 mxmlArtic += " type=\"down\"";
2701 }
2702
2703 notations.tag(_xml);
2704 articulations.tag(_xml);
2705 _xml.tagE(mxmlArtic);
2706 }
2707 }
2708
2709 if (Breath* b = chord->hasBreathMark()) {
2710 notations.tag(_xml);
2711 articulations.tag(_xml);
2712 _xml.tagE(b->isCaesura() ? "caesura" : "breath-mark");
2713 }
2714
2715 writeChordLines(chord, _xml, notations, articulations);
2716
2717 articulations.etag(_xml);
2718
2719 // then the attributes whose elements are children of <ornaments>
2720 Ornaments ornaments;
2721 for (const Articulation* a : na) {
2722 auto sid = a->symId();
2723 auto mxmlOrnam = symIdToOrnam(sid);
2724
2725 if (mxmlOrnam != "") {
2726 notations.tag(_xml);
2727 ornaments.tag(_xml);
2728 _xml.tagE(mxmlOrnam);
2729 }
2730 }
2731
2732 tremoloSingleStartStop(chord, notations, ornaments, _xml);
2733 wavyLineStartStop(chord, notations, ornaments, trillStart, trillStop);
2734 ornaments.etag(_xml);
2735
2736 // and finally the attributes whose elements are children of <technical>
2737 for (const Articulation* a : na) {
2738 auto sid = a->symId();
2739 QString placement;
2740 QString direction;
2741
2742 QString attr;
2743 if (!a->isStyled(Pid::ARTICULATION_ANCHOR) && a->anchor() != ArticulationAnchor::CHORD) {
2744 placement = (a->anchor() == ArticulationAnchor::BOTTOM_STAFF || a->anchor() == ArticulationAnchor::BOTTOM_CHORD) ? "below" : "above";
2745 }
2746 else if (!a->isStyled(Pid::DIRECTION) && a->direction() != Direction::AUTO) {
2747 direction = (a->direction() == Direction::DOWN) ? "down" : "up";
2748 }
2749 /* For future use if/when implemented font details for articulation
2750 if (!a->isStyled(Pid::FONT_FACE))
2751 attr += QString(" font-family=\"%1\"").arg(a->getProperty(Pid::FONT_FACE).toString());
2752 if (!a->isStyled(Pid::FONT_SIZE))
2753 attr += QString(" font-size=\"%1\"").arg(a->getProperty(Pid::FONT_SIZE).toReal());
2754 if (!a->isStyled(Pid::FONT_STYLE))
2755 attr += fontStyleToXML(static_cast<FontStyle>(a->getProperty(Pid::FONT_STYLE).toInt()), false);
2756 */
2757
2758 auto mxmlTechn = symIdToTechn(sid);
2759 if (mxmlTechn != "") {
2760 notations.tag(_xml);
2761 technical.tag(_xml);
2762 if (sid == SymId::stringsHarmonic) {
2763 if (placement != "")
2764 attr += QString(" placement=\"%1\"").arg(placement);
2765 _xml.stag(mxmlTechn + attr);
2766 _xml.tagE("natural");
2767 _xml.etag();
2768 }
2769 else // TODO: check additional modifier (attr) for other symbols
2770 _xml.tagE(mxmlTechn);
2771 }
2772 }
2773
2774 // check if all articulations were handled
2775 for (const Articulation* a : na) {
2776 auto sid = a->symId();
2777 if (symIdToArtic(sid) == ""
2778 && symIdToOrnam(sid) == ""
2779 && symIdToTechn(sid) == ""
2780 && !isLaissezVibrer(sid)) {
2781 qDebug("unknown chord attribute %d %s", int(sid), qPrintable(a->userName()));
2782 }
2783 }
2784 }
2785
2786 //---------------------------------------------------------
2787 // arpeggiate
2788 //---------------------------------------------------------
2789
2790 // <notations>
2791 // <arpeggiate direction="up"/>
2792 // </notations>
2793
arpeggiate(Arpeggio * arp,bool front,bool back,XmlWriter & xml,Notations & notations)2794 static void arpeggiate(Arpeggio* arp, bool front, bool back, XmlWriter& xml, Notations& notations)
2795 {
2796 QString tagName;
2797 switch (arp->arpeggioType()) {
2798 case ArpeggioType::NORMAL:
2799 notations.tag(xml);
2800 tagName = "arpeggiate";
2801 break;
2802 case ArpeggioType::UP: // fall through
2803 case ArpeggioType::UP_STRAIGHT: // not supported by MusicXML, export as normal arpeggio
2804 notations.tag(xml);
2805 tagName = "arpeggiate direction=\"up\"";
2806 break;
2807 case ArpeggioType::DOWN: // fall through
2808 case ArpeggioType::DOWN_STRAIGHT: // not supported by MusicXML, export as normal arpeggio
2809 notations.tag(xml);
2810 tagName = "arpeggiate direction=\"down\"";
2811 break;
2812 case ArpeggioType::BRACKET:
2813 if (front) {
2814 notations.tag(xml);
2815 tagName = "non-arpeggiate type=\"bottom\"";
2816 }
2817 if (back) {
2818 notations.tag(xml);
2819 tagName = "non-arpeggiate type=\"top\"";
2820 }
2821 break;
2822 default:
2823 qDebug("unknown arpeggio subtype %d", int(arp->arpeggioType()));
2824 break;
2825 }
2826
2827 if (tagName != "") {
2828 tagName += positioningAttributes(arp);
2829 xml.tagE(tagName);
2830 }
2831 }
2832
2833 //---------------------------------------------------------
2834 // determineTupletNormalTicks
2835 //---------------------------------------------------------
2836
2837 /**
2838 Determine the ticks in the normal type for the tuplet \a chord.
2839 This is non-zero only if chord if part of a tuplet containing
2840 different length duration elements.
2841 TODO determine how to handle baselen with dots and verify correct behaviour.
2842 TODO verify if baseLen should always be correctly set
2843 (it seems after MusicXMLimport this is not the case)
2844 */
2845
determineTupletNormalTicks(Tuplet const * const t)2846 static int determineTupletNormalTicks(Tuplet const* const t)
2847 {
2848 if (!t)
2849 return 0;
2850 /*
2851 qDebug("determineTupletNormalTicks t %p baselen %s", t, qPrintable(t->baseLen().ticks().print()));
2852 for (int i = 0; i < t->elements().size(); ++i)
2853 qDebug("determineTupletNormalTicks t %p i %d ticks %s", t, i, qPrintable(t->elements().at(i)->ticks().print()));
2854 */
2855 for (unsigned int i = 1; i < t->elements().size(); ++i)
2856 if (t->elements().at(0)->ticks() != t->elements().at(i)->ticks())
2857 return t->baseLen().ticks().ticks();
2858 if (t->elements().size() != (unsigned)(t->ratio().numerator()))
2859 return t->baseLen().ticks().ticks();
2860 return 0;
2861 }
2862
2863 //---------------------------------------------------------
2864 // beamFanAttribute
2865 //---------------------------------------------------------
2866
beamFanAttribute(const Beam * const b)2867 static QString beamFanAttribute(const Beam* const b)
2868 {
2869 const qreal epsilon = 0.1;
2870
2871 QString fan;
2872 if ((b->growRight() - b->growLeft() > epsilon))
2873 fan = "accel";
2874
2875 if ((b->growLeft() - b->growRight() > epsilon))
2876 fan = "rit";
2877
2878 if (fan != "")
2879 return QString(" fan=\"%1\"").arg(fan);
2880
2881 return "";
2882 }
2883
2884 //---------------------------------------------------------
2885 // writeBeam
2886 //---------------------------------------------------------
2887
2888 // beaming
2889 // <beam number="1">start</beam>
2890 // <beam number="1">end</beam>
2891 // <beam number="1">continue</beam>
2892 // <beam number="1">backward hook</beam>
2893 // <beam number="1">forward hook</beam>
2894
writeBeam(XmlWriter & xml,ChordRest * const cr,Beam * const b)2895 static void writeBeam(XmlWriter& xml, ChordRest* const cr, Beam* const b)
2896 {
2897 const auto& elements = b->elements();
2898 const int idx = elements.indexOf(cr);
2899 if (idx == -1) {
2900 qDebug("Beam::writeMusicXml(): cannot find ChordRest");
2901 return;
2902 }
2903 int blp = -1; // beam level previous chord
2904 int blc = -1; // beam level current chord
2905 int bln = -1; // beam level next chord
2906 // find beam level previous chord
2907 for (int i = idx - 1; blp == -1 && i >= 0; --i) {
2908 const auto crst = elements[i];
2909 if (crst->isChord())
2910 blp = toChord(crst)->beams();
2911 }
2912 // find beam level current chord
2913 if (cr->isChord())
2914 blc = toChord(cr)->beams();
2915 // find beam level next chord
2916 for (int i = idx + 1; bln == -1 && i < elements.size(); ++i) {
2917 const auto crst = elements[i];
2918 if (crst->isChord())
2919 bln = toChord(crst)->beams();
2920 }
2921 // find beam type and write
2922 for (int i = 1; i <= blc; ++i) {
2923 QString text;
2924 if (blp < i && bln >= i) text = "begin";
2925 else if (blp < i && bln < i) {
2926 if (bln > 0) text = "forward hook";
2927 else if (blp > 0) text = "backward hook";
2928 }
2929 else if (blp >= i && bln < i)
2930 text = "end";
2931 else if (blp >= i && bln >= i)
2932 text = "continue";
2933 if (text != "") {
2934 QString tag = "beam";
2935 tag += QString(" number=\"%1\"").arg(i);
2936 if (text == "begin")
2937 tag += beamFanAttribute(b);
2938 xml.tag(tag, text);
2939 }
2940 }
2941 }
2942
2943 //---------------------------------------------------------
2944 // instrId
2945 //---------------------------------------------------------
2946
instrId(int partNr,int instrNr)2947 static QString instrId(int partNr, int instrNr)
2948 {
2949 return QString("id=\"P%1-I%2\"").arg(partNr).arg(instrNr);
2950 }
2951
2952 //---------------------------------------------------------
2953 // writeNotehead
2954 //---------------------------------------------------------
2955
writeNotehead(XmlWriter & xml,const Note * const note)2956 static void writeNotehead(XmlWriter& xml, const Note* const note)
2957 {
2958 QString noteheadTagname = QString("notehead");
2959 noteheadTagname += color2xml(note);
2960 bool leftParenthesis = false, rightParenthesis = false;
2961 for (Element* elem : note->el()) {
2962 if (elem->type() == ElementType::SYMBOL) {
2963 Symbol* s = static_cast<Symbol*>(elem);
2964 if (s->sym() == SymId::noteheadParenthesisLeft)
2965 leftParenthesis = true;
2966 else if (s->sym() == SymId::noteheadParenthesisRight)
2967 rightParenthesis = true;
2968 }
2969 }
2970 if (rightParenthesis && leftParenthesis)
2971 noteheadTagname += " parentheses=\"yes\"";
2972 if (note->headType() == NoteHead::Type::HEAD_QUARTER)
2973 noteheadTagname += " filled=\"yes\"";
2974 else if ((note->headType() == NoteHead::Type::HEAD_HALF) || (note->headType() == NoteHead::Type::HEAD_WHOLE))
2975 noteheadTagname += " filled=\"no\"";
2976 if (note->headGroup() == NoteHead::Group::HEAD_SLASH)
2977 xml.tag(noteheadTagname, "slash");
2978 else if (note->headGroup() == NoteHead::Group::HEAD_TRIANGLE_UP)
2979 xml.tag(noteheadTagname, "triangle");
2980 else if (note->headGroup() == NoteHead::Group::HEAD_DIAMOND)
2981 xml.tag(noteheadTagname, "diamond");
2982 else if (note->headGroup() == NoteHead::Group::HEAD_PLUS)
2983 xml.tag(noteheadTagname, "cross");
2984 else if (note->headGroup() == NoteHead::Group::HEAD_CROSS)
2985 xml.tag(noteheadTagname, "x");
2986 else if (note->headGroup() == NoteHead::Group::HEAD_XCIRCLE)
2987 xml.tag(noteheadTagname, "circle-x");
2988 else if (note->headGroup() == NoteHead::Group::HEAD_TRIANGLE_DOWN)
2989 xml.tag(noteheadTagname, "inverted triangle");
2990 else if (note->headGroup() == NoteHead::Group::HEAD_SLASHED1)
2991 xml.tag(noteheadTagname, "slashed");
2992 else if (note->headGroup() == NoteHead::Group::HEAD_SLASHED2)
2993 xml.tag(noteheadTagname, "back slashed");
2994 else if (note->headGroup() == NoteHead::Group::HEAD_DO)
2995 xml.tag(noteheadTagname, "do");
2996 else if (note->headGroup() == NoteHead::Group::HEAD_RE)
2997 xml.tag(noteheadTagname, "re");
2998 else if (note->headGroup() == NoteHead::Group::HEAD_MI)
2999 xml.tag(noteheadTagname, "mi");
3000 else if (note->headGroup() == NoteHead::Group::HEAD_FA && !note->chord()->up())
3001 xml.tag(noteheadTagname, "fa");
3002 else if (note->headGroup() == NoteHead::Group::HEAD_FA && note->chord()->up())
3003 xml.tag(noteheadTagname, "fa up");
3004 else if (note->headGroup() == NoteHead::Group::HEAD_LA)
3005 xml.tag(noteheadTagname, "la");
3006 else if (note->headGroup() == NoteHead::Group::HEAD_TI)
3007 xml.tag(noteheadTagname, "ti");
3008 else if (note->headGroup() == NoteHead::Group::HEAD_SOL)
3009 xml.tag(noteheadTagname, "so");
3010 else if (note->color() != MScore::defaultColor)
3011 xml.tag(noteheadTagname, "normal");
3012 else if (rightParenthesis && leftParenthesis)
3013 xml.tag(noteheadTagname, "normal");
3014 else if (note->headType() != NoteHead::Type::HEAD_AUTO)
3015 xml.tag(noteheadTagname, "normal");
3016 }
3017
3018 //---------------------------------------------------------
3019 // writeFingering
3020 //---------------------------------------------------------
3021
writeFingering(XmlWriter & xml,Notations & notations,Technical & technical,const Note * const note)3022 static void writeFingering(XmlWriter& xml, Notations& notations, Technical& technical, const Note* const note)
3023 {
3024 for (const Element* e : note->el()) {
3025 if (e->type() == ElementType::FINGERING) {
3026 const TextBase* f = toTextBase(e);
3027 notations.tag(xml);
3028 technical.tag(xml);
3029 QString t = MScoreTextToMXML::toPlainText(f->xmlText());
3030 QString attr;
3031 if (!f->isStyled(Pid::PLACEMENT) || f->placement() == Placement::BELOW)
3032 attr = QString(" placement=\"%1\"").arg((f->placement() == Placement::BELOW) ? "below" : "above");
3033 if (!f->isStyled(Pid::FONT_FACE))
3034 attr += QString(" font-family=\"%1\"").arg(f->getProperty(Pid::FONT_FACE).toString());
3035 if (!f->isStyled(Pid::FONT_SIZE))
3036 attr += QString(" font-size=\"%1\"").arg(f->getProperty(Pid::FONT_SIZE).toReal());
3037 if (!f->isStyled(Pid::FONT_STYLE))
3038 attr += fontStyleToXML(static_cast<FontStyle>(f->getProperty(Pid::FONT_STYLE).toInt()), false);
3039
3040 if (f->tid() == Tid::RH_GUITAR_FINGERING)
3041 xml.tag("pluck" + attr, t);
3042 else if (f->tid() == Tid::LH_GUITAR_FINGERING)
3043 xml.tag("fingering" + attr, t);
3044 else if (f->tid() == Tid::FINGERING) {
3045 // for generic fingering, try to detect plucking
3046 // (backwards compatibility with MuseScore 1.x)
3047 // p, i, m, a, c represent the plucking finger
3048 if (t == "p" || t == "i" || t == "m" || t == "a" || t == "c")
3049 xml.tag("pluck" + attr, t);
3050 else
3051 xml.tag("fingering" + attr, t);
3052 }
3053 else if (f->tid() == Tid::STRING_NUMBER) {
3054 bool ok;
3055 int i = t.toInt(&ok);
3056 if (ok) {
3057 if (i == 0)
3058 xml.tagE("open-string" + attr);
3059 else if (i > 0)
3060 xml.tag("string" + attr, t);
3061 }
3062 if (!ok || i < 0)
3063 qDebug("invalid string number '%s'", qPrintable(t));
3064 }
3065 else
3066 qDebug("unknown fingering style");
3067 }
3068 else {
3069 // TODO
3070 }
3071 }
3072 }
3073
3074 //---------------------------------------------------------
3075 // stretchCorrActFraction
3076 //---------------------------------------------------------
3077
stretchCorrActFraction(const Note * const note)3078 static Fraction stretchCorrActFraction(const Note* const note)
3079 {
3080 // time signature stretch factor
3081 const Fraction str = note->chord()->staff()->timeStretch(note->chord()->tick());
3082 // chord's actual ticks corrected for stretch
3083 return note->chord()->actualTicks() * str;
3084 }
3085
3086 //---------------------------------------------------------
3087 // tremoloCorrection
3088 //---------------------------------------------------------
3089
3090 // duration correction for two note tremolo
tremoloCorrection(const Note * const note)3091 static int tremoloCorrection(const Note* const note)
3092 {
3093 int tremCorr = 1;
3094 if (isTwoNoteTremolo(note->chord())) tremCorr = 2;
3095 return tremCorr;
3096 }
3097
3098 //---------------------------------------------------------
3099 // isSmallNote
3100 //---------------------------------------------------------
3101
isSmallNote(const Note * const note)3102 static bool isSmallNote(const Note* const note)
3103 {
3104 return note->small() || note->chord()->small();
3105 }
3106
3107 //---------------------------------------------------------
3108 // isCueNote
3109 //---------------------------------------------------------
3110
isCueNote(const Note * const note)3111 static bool isCueNote(const Note* const note)
3112 {
3113 return (!note->chord()->isGrace()) && isSmallNote(note) && !note->play();
3114 }
3115
3116 //---------------------------------------------------------
3117 // timeModification
3118 //---------------------------------------------------------
3119
timeModification(const Tuplet * const tuplet,const int tremolo=1)3120 static Fraction timeModification(const Tuplet* const tuplet, const int tremolo = 1)
3121 {
3122 int actNotes { tremolo };
3123 int nrmNotes { 1 };
3124 const Tuplet* t { tuplet };
3125
3126 while (t) {
3127 // cannot use Fraction::operator*() as it contains a reduce(),
3128 // which would change a 6:4 tuplet into 3:2
3129 actNotes *= t->ratio().numerator();
3130 nrmNotes *= t->ratio().denominator();
3131 t = t->tuplet();
3132 }
3133
3134 return { actNotes, nrmNotes };
3135 }
3136
3137 //---------------------------------------------------------
3138 // writeTypeAndDots
3139 //---------------------------------------------------------
3140
writeTypeAndDots(XmlWriter & xml,const Note * const note)3141 static void writeTypeAndDots(XmlWriter& xml, const Note* const note)
3142 {
3143 // type
3144 int dots { 0 };
3145 const auto ratio = timeModification(note->chord()->tuplet());
3146
3147 const auto strActFraction = stretchCorrActFraction(note);
3148 const Fraction tt = strActFraction * ratio * tremoloCorrection(note);
3149 const QString s { tick2xml(tt, &dots) };
3150 if (s.isEmpty())
3151 qDebug("no note type found for fraction %d / %d", strActFraction.numerator(), strActFraction.denominator());
3152
3153 // small notes are indicated by size=cue, but for grace and cue notes this is implicit
3154 if (isSmallNote(note) && !isCueNote(note) && !note->chord()->isGrace())
3155 xml.tag("type size=\"cue\"", s);
3156 else
3157 xml.tag("type", s);
3158 for (int ni = dots; ni > 0; ni--)
3159 xml.tagE("dot");
3160 }
3161
3162 //---------------------------------------------------------
3163 // writeTimeModification
3164 //---------------------------------------------------------
3165
writeTimeModification(XmlWriter & xml,const Tuplet * const tuplet,const int tremolo=1)3166 static void writeTimeModification(XmlWriter& xml, const Tuplet* const tuplet, const int tremolo = 1)
3167 {
3168 const auto ratio = timeModification(tuplet, tremolo);
3169 if (ratio != Fraction(1, 1)) {
3170 const auto actNotes = ratio.numerator();
3171 const auto nrmNotes = ratio.denominator();
3172
3173 const auto nrmTicks = determineTupletNormalTicks(tuplet);
3174 xml.stag("time-modification");
3175 xml.tag("actual-notes", actNotes);
3176 xml.tag("normal-notes", nrmNotes);
3177 //qDebug("nrmTicks %d", nrmTicks);
3178 if (nrmTicks > 0) {
3179 int nrmDots { 0 };
3180 const QString nrmType { tick2xml(Fraction::fromTicks(nrmTicks), &nrmDots) };
3181 if (nrmType.isEmpty())
3182 qDebug("no note type found for ticks %d", nrmTicks);
3183 else {
3184 xml.tag("normal-type", nrmType);
3185 for (int ni = nrmDots; ni > 0; ni--)
3186 xml.tagE("normal-dot");
3187 }
3188 }
3189 xml.etag();
3190 }
3191 }
3192
3193 //---------------------------------------------------------
3194 // writePitch
3195 //---------------------------------------------------------
3196
writePitch(XmlWriter & xml,const Note * const note,const bool useDrumset)3197 static void writePitch(XmlWriter& xml, const Note* const note, const bool useDrumset)
3198 {
3199 // step / alter / octave
3200 QString step;
3201 int alter = 0;
3202 int octave = 0;
3203 const auto chord = note->chord();
3204 if (chord->staff() && chord->staff()->isTabStaff(Fraction(0,1))) {
3205 tabpitch2xml(note->pitch(), note->tpc(), step, alter, octave);
3206 }
3207 else {
3208 if (!useDrumset) {
3209 pitch2xml(note, step, alter, octave);
3210 }
3211 else {
3212 unpitch2xml(note, step, octave);
3213 }
3214 }
3215 xml.stag(useDrumset ? "unpitched" : "pitch");
3216 xml.tag(useDrumset ? "display-step" : "step", step);
3217 // Check for microtonal accidentals and overwrite "alter" tag
3218 auto acc = note->accidental();
3219 double alter2 = 0.0;
3220 if (acc) {
3221 switch (acc->accidentalType()) {
3222 case AccidentalType::MIRRORED_FLAT: alter2 = -0.5; break;
3223 case AccidentalType::SHARP_SLASH: alter2 = 0.5; break;
3224 case AccidentalType::MIRRORED_FLAT2: alter2 = -1.5; break;
3225 case AccidentalType::SHARP_SLASH4: alter2 = 1.5; break;
3226 default: break;
3227 }
3228 }
3229 if (alter && !alter2)
3230 xml.tag("alter", alter);
3231 if (!alter && alter2)
3232 xml.tag("alter", alter2);
3233 // TODO what if both alter and alter2 are present? For Example: playing with transposing instruments
3234 xml.tag(useDrumset ? "display-octave" : "octave", octave);
3235 xml.etag();
3236 }
3237
3238 //---------------------------------------------------------
3239 // notePosition
3240 //---------------------------------------------------------
3241
notePosition(const ExportMusicXml * const expMxml,const Note * const note)3242 static QString notePosition(const ExportMusicXml* const expMxml, const Note* const note)
3243 {
3244 QString res;
3245
3246 if (preferences.getBool(PREF_EXPORT_MUSICXML_EXPORTLAYOUT)) {
3247 const double pageHeight = expMxml->getTenthsFromInches(expMxml->score()->styleD(Sid::pageHeight));
3248
3249 const auto chord = note->chord();
3250
3251 double measureX = expMxml->getTenthsFromDots(chord->measure()->pagePos().x());
3252 double measureY = pageHeight - expMxml->getTenthsFromDots(chord->measure()->pagePos().y());
3253 double noteX = expMxml->getTenthsFromDots(note->pagePos().x());
3254 double noteY = pageHeight - expMxml->getTenthsFromDots(note->pagePos().y());
3255
3256 res += QString(" default-x=\"%1\"").arg(QString::number(noteX - measureX,'f',2));
3257 res += QString(" default-y=\"%1\"").arg(QString::number(noteY - measureY,'f',2));
3258 }
3259
3260 return res;
3261 }
3262
3263 //---------------------------------------------------------
3264 // chord
3265 //---------------------------------------------------------
3266
3267 /**
3268 Write \a chord on \a staff with lyriclist \a ll.
3269
3270 For a single-staff part, \a staff equals zero, suppressing the <staff> element.
3271 */
3272
chord(Chord * chord,int staff,const std::vector<Lyrics * > * ll,bool useDrumset)3273 void ExportMusicXml::chord(Chord* chord, int staff, const std::vector<Lyrics*>* ll, bool useDrumset)
3274 {
3275 Part* part = chord->score()->staff(chord->track() / VOICES)->part();
3276 int partNr = _score->parts().indexOf(part);
3277 int instNr = instrMap.value(part->instrument(_tick), -1);
3278 /*
3279 qDebug("chord() %p parent %p isgrace %d #gracenotes %d graceidx %d",
3280 chord, chord->parent(), chord->isGrace(), chord->graceNotes().size(), chord->graceIndex());
3281 qDebug("track %d tick %d part %p nr %d instr %p nr %d",
3282 chord->track(), chord->tick(), part, partNr, part->instrument(tick), instNr);
3283 for (Element* e : chord->el())
3284 qDebug("chord %p el %p", chord, e);
3285 */
3286 std::vector<Note*> nl = chord->notes();
3287 bool grace = chord->isGrace();
3288 if (!grace) _tick += chord->actualTicks();
3289 #ifdef DEBUG_TICK
3290 qDebug("ExportMusicXml::chord() oldtick=%d", tick);
3291 qDebug("notetype=%d grace=%d", gracen, grace);
3292 qDebug(" newtick=%d", tick);
3293 #endif
3294
3295 for (Note* note : nl) {
3296 _attr.doAttr(_xml, false);
3297 QString noteTag = QString("note");
3298
3299 noteTag += notePosition(this, note);
3300
3301 if (!note->visible()) {
3302 noteTag += QString(" print-object=\"no\"");
3303 }
3304 //TODO support for OFFSET_VAL
3305 if (note->veloType() == Note::ValueType::USER_VAL) {
3306 int velo = note->veloOffset();
3307 noteTag += QString(" dynamics=\"%1\"").arg(QString::number(velo * 100.0 / 90.0,'f',2));
3308 }
3309 _xml.stag(noteTag);
3310
3311 if (grace) {
3312 if (note->noteType() == NoteType::ACCIACCATURA)
3313 _xml.tagE("grace slash=\"yes\"");
3314 else
3315 _xml.tagE("grace");
3316 }
3317 if (isCueNote(note))
3318 _xml.tagE("cue");
3319 if (note != nl.front())
3320 _xml.tagE("chord");
3321
3322 writePitch(_xml, note, useDrumset);
3323
3324 // duration
3325 if (!grace)
3326 _xml.tag("duration", stretchCorrActFraction(note).ticks() / div);
3327
3328 if (note->tieBack())
3329 _xml.tagE("tie type=\"stop\"");
3330 if (note->tieFor())
3331 _xml.tagE("tie type=\"start\"");
3332
3333 // instrument for multi-instrument or unpitched parts
3334 if (!useDrumset) {
3335 if (instrMap.size() > 1 && instNr >= 0)
3336 _xml.tagE(QString("instrument %1").arg(instrId(partNr + 1, instNr + 1)));
3337 }
3338 else
3339 _xml.tagE(QString("instrument %1").arg(instrId(partNr + 1, note->pitch() + 1)));
3340
3341 // voice
3342 // for a single-staff part, staff is 0, which needs to be corrected
3343 // to calculate the correct voice number
3344 int voice = (staff-1) * VOICES + note->chord()->voice() + 1;
3345 if (staff == 0)
3346 voice += VOICES;
3347
3348 _xml.tag("voice", voice);
3349
3350 writeTypeAndDots(_xml, note);
3351 writeAccidental(_xml, "accidental", note->accidental());
3352 writeTimeModification(_xml, note->chord()->tuplet(), tremoloCorrection(note));
3353
3354 // no stem for whole notes and beyond
3355 if (chord->noStem() || chord->measure()->stemless(chord->staffIdx())) {
3356 _xml.tag("stem", QString("none"));
3357 }
3358 else if (note->chord()->stem()) {
3359 _xml.tag("stem", QString(note->chord()->up() ? "up" : "down"));
3360 }
3361
3362 writeNotehead(_xml, note);
3363
3364 // LVIFIX: check move() handling
3365 if (staff)
3366 _xml.tag("staff", staff + note->chord()->staffMove());
3367
3368 if (note == nl.front() && chord->beam())
3369 writeBeam(_xml, chord, chord->beam());
3370
3371 Notations notations;
3372 Technical technical;
3373
3374 const Tie* tieBack = note->tieBack();
3375 if (tieBack) {
3376 notations.tag(_xml);
3377 _xml.tagE("tied type=\"stop\"");
3378 }
3379 const Tie* tieFor = note->tieFor();
3380 if (tieFor) {
3381 notations.tag(_xml);
3382 QString rest = slurTieLineStyle(tieFor);
3383 _xml.tagE(QString("tied type=\"start\"%1").arg(rest));
3384 }
3385 if (hasLaissezVibrer(chord)) {
3386 notations.tag(_xml);
3387 _xml.tagE("tied type=\"let-ring\"");
3388 }
3389
3390 if (note == nl.front()) {
3391 if (!grace)
3392 tupletStartStop(chord, notations, _xml);
3393
3394 sh.doSlurs(chord, notations, _xml);
3395
3396 chordAttributes(chord, notations, technical, _trillStart, _trillStop);
3397 }
3398
3399 writeFingering(_xml, notations, technical, note);
3400
3401 // write tablature string / fret
3402 if (chord->staff() && chord->staff()->isTabStaff(Fraction(0,1)))
3403 if (note->fret() >= 0 && note->string() >= 0) {
3404 notations.tag(_xml);
3405 technical.tag(_xml);
3406 _xml.tag("string", note->string() + 1);
3407 _xml.tag("fret", note->fret());
3408 }
3409
3410 technical.etag(_xml);
3411 if (chord->arpeggio()) {
3412 arpeggiate(chord->arpeggio(), note == nl.front(), note == nl.back(), _xml, notations);
3413 }
3414 for (Spanner* spanner : note->spannerFor())
3415 if (spanner->type() == ElementType::GLISSANDO) {
3416 gh.doGlissandoStart(static_cast<Glissando*>(spanner), notations, _xml);
3417 }
3418 for (Spanner* spanner : note->spannerBack())
3419 if (spanner->type() == ElementType::GLISSANDO) {
3420 gh.doGlissandoStop(static_cast<Glissando*>(spanner), notations, _xml);
3421 }
3422 // write glissando (only for last note)
3423 /*
3424 Chord* ch = nextChord(chord);
3425 if ((note == nl.back()) && ch && ch->glissando()) {
3426 gh.doGlissandoStart(ch, notations, xml);
3427 }
3428 if (chord->glissando()) {
3429 gh.doGlissandoStop(chord, notations, xml);
3430 }
3431 */
3432 notations.etag(_xml);
3433 // write lyrics (only for first note)
3434 if (!grace && (note == nl.front()) && ll)
3435 lyrics(ll, chord->track());
3436 _xml.etag();
3437 }
3438 }
3439
3440 //---------------------------------------------------------
3441 // rest
3442 //---------------------------------------------------------
3443
3444 /**
3445 Write \a rest on \a staff.
3446
3447 For a single-staff part, \a staff equals zero, suppressing the <staff> element.
3448 */
3449
rest(Rest * rest,int staff)3450 void ExportMusicXml::rest(Rest* rest, int staff)
3451 {
3452 static char table2[] = "CDEFGAB";
3453 #ifdef DEBUG_TICK
3454 qDebug("ExportMusicXml::rest() oldtick=%d", tick);
3455 #endif
3456 _attr.doAttr(_xml, false);
3457
3458 QString noteTag = QString("note");
3459 noteTag += color2xml(rest);
3460 if (!rest->visible() ) {
3461 noteTag += QString(" print-object=\"no\"");
3462 }
3463 _xml.stag(noteTag);
3464
3465 int yOffsSt = 0;
3466 int oct = 0;
3467 int stp = 0;
3468 ClefType clef = rest->staff()->clef(rest->tick());
3469 int po = ClefInfo::pitchOffset(clef);
3470
3471 // Determine y position, but leave at zero in case of tablature staff
3472 // as no display-step or display-octave should be written for a tablature staff,
3473
3474 if (clef != ClefType::TAB && clef != ClefType::TAB_SERIF && clef != ClefType::TAB4 && clef != ClefType::TAB4_SERIF) {
3475 double yOffsSp = rest->offset().y() / rest->spatium(); // y offset in spatium (negative = up)
3476 yOffsSt = -2 * int(yOffsSp > 0.0 ? yOffsSp + 0.5 : yOffsSp - 0.5); // same rounded to int (positive = up)
3477
3478 po -= 4; // pitch middle staff line (two lines times two steps lower than top line)
3479 po += yOffsSt; // rest "pitch"
3480 oct = po / 7; // octave
3481 stp = po % 7; // step
3482 }
3483
3484 QString restTag { "rest" };
3485 const TDuration d = rest->durationType();
3486 if (d.type() == TDuration::DurationType::V_MEASURE)
3487 restTag += " measure=\"yes\"";
3488 // Either <rest/>
3489 // or <rest><display-step>F</display-step><display-octave>5</display-octave></rest>
3490 if (yOffsSt == 0) {
3491 _xml.tagE(restTag);
3492 }
3493 else {
3494 _xml.stag(restTag);
3495 _xml.tag("display-step", QString(QChar(table2[stp])));
3496 _xml.tag("display-octave", oct - 1);
3497 _xml.etag();
3498 }
3499
3500 Fraction tickLen = rest->actualTicks();
3501 if (d.type() == TDuration::DurationType::V_MEASURE) {
3502 // to avoid forward since rest->ticklen=0 in this case.
3503 tickLen = rest->measure()->ticks();
3504 }
3505 _tick += tickLen;
3506 #ifdef DEBUG_TICK
3507 qDebug(" tickLen=%d newtick=%d", tickLen, tick);
3508 #endif
3509
3510 _xml.tag("duration", tickLen.ticks() / div);
3511
3512 // for a single-staff part, staff is 0, which needs to be corrected
3513 // to calculate the correct voice number
3514 int voice = (staff-1) * VOICES + rest->voice() + 1;
3515 if (staff == 0)
3516 voice += VOICES;
3517 _xml.tag("voice", voice);
3518
3519 // do not output a "type" element for whole measure rest
3520 if (d.type() != TDuration::DurationType::V_MEASURE) {
3521 QString s = d.name();
3522 int dots = rest->dots();
3523 if (rest->small())
3524 _xml.tag("type size=\"cue\"", s);
3525 else
3526 _xml.tag("type", s);
3527 for (int i = dots; i > 0; i--)
3528 _xml.tagE("dot");
3529 }
3530
3531 writeTimeModification(_xml, rest->tuplet());
3532
3533 if (staff)
3534 _xml.tag("staff", staff);
3535
3536 Notations notations;
3537 QVector<Element*> fl;
3538 for (Element* e : rest->segment()->annotations()) {
3539 if (e->isFermata() && e->track() == rest->track())
3540 fl.push_back(e);
3541 }
3542 fermatas(fl, _xml, notations);
3543
3544 Articulations articulations;
3545 if (Breath* b = rest->hasBreathMark()) {
3546 notations.tag(_xml);
3547 articulations.tag(_xml);
3548 _xml.tagE(b->isCaesura() ? "caesura" : "breath-mark");
3549 }
3550 articulations.etag(_xml);
3551
3552 Ornaments ornaments;
3553 wavyLineStartStop(rest, notations, ornaments, _trillStart, _trillStop);
3554 ornaments.etag(_xml);
3555
3556 sh.doSlurs(rest, notations, _xml);
3557
3558 tupletStartStop(rest, notations, _xml);
3559 notations.etag(_xml);
3560
3561 _xml.etag();
3562 }
3563
3564 //---------------------------------------------------------
3565 // directionTag
3566 //---------------------------------------------------------
3567
directionTag(XmlWriter & xml,Attributes & attr,Element const * const el=0)3568 static void directionTag(XmlWriter& xml, Attributes& attr, Element const* const el = 0)
3569 {
3570 attr.doAttr(xml, false);
3571 QString tagname = QString("direction");
3572 if (el) {
3573 /*
3574 qDebug("directionTag() spatium=%g elem=%p tp=%d (%s)\ndirectionTag() x=%g y=%g xsp,ysp=%g,%g w=%g h=%g userOff.y=%g",
3575 el->spatium(),
3576 el,
3577 el->type(),
3578 el->name(),
3579 el->x(), el->y(),
3580 el->x()/el->spatium(), el->y()/el->spatium(),
3581 el->width(), el->height(),
3582 el->offset().y()
3583 );
3584 */
3585 const Element* pel = 0;
3586 const LineSegment* seg = 0;
3587 if (el->type() == ElementType::HAIRPIN || el->type() == ElementType::OTTAVA
3588 || el->type() == ElementType::PEDAL || el->type() == ElementType::TEXTLINE
3589 || el->type() == ElementType::LET_RING || el->type() == ElementType::PALM_MUTE) {
3590 // handle elements derived from SLine
3591 // find the system containing the first linesegment
3592 const SLine* sl = static_cast<const SLine*>(el);
3593 if (!sl->segmentsEmpty()) {
3594 seg = toLineSegment(sl->frontSegment());
3595 /*
3596 qDebug("directionTag() seg=%p x=%g y=%g w=%g h=%g cpx=%g cpy=%g userOff.y=%g",
3597 seg, seg->x(), seg->y(),
3598 seg->width(), seg->height(),
3599 seg->pagePos().x(), seg->pagePos().y(),
3600 seg->offset().y());
3601 */
3602 pel = seg->parent();
3603 }
3604 }
3605 else if (el->type() == ElementType::DYNAMIC
3606 || el->type() == ElementType::INSTRUMENT_CHANGE
3607 || el->type() == ElementType::REHEARSAL_MARK
3608 || el->type() == ElementType::STAFF_TEXT
3609 || el->type() == ElementType::SYMBOL
3610 || el->type() == ElementType::TEXT) {
3611 // handle other elements attached (e.g. via Segment / Measure) to a system
3612 // find the system containing this element
3613 for (const Element* e = el; e; e = e->parent()) {
3614 if (e->type() == ElementType::SYSTEM) pel = e;
3615 }
3616 }
3617 else
3618 qDebug("directionTag() element %p tp=%d (%s) not supported",
3619 el, int(el->type()), el->name());
3620
3621 /*
3622 if (pel) {
3623 qDebug("directionTag() prnt tp=%d (%s) x=%g y=%g w=%g h=%g userOff.y=%g",
3624 pel->type(),
3625 pel->name(),
3626 pel->x(), pel->y(),
3627 pel->width(), pel->height(),
3628 pel->offset().y());
3629 }
3630 */
3631
3632 if (pel && pel->type() == ElementType::SYSTEM) {
3633 /*
3634 const System* sys = static_cast<const System*>(pel);
3635 QRectF bb = sys->staff(el->staffIdx())->bbox();
3636 qDebug("directionTag() syst=%p sys x=%g y=%g cpx=%g cpy=%g",
3637 sys, sys->pos().x(), sys->pos().y(),
3638 sys->pagePos().x(),
3639 sys->pagePos().y()
3640 );
3641 qDebug("directionTag() staf x=%g y=%g w=%g h=%g",
3642 bb.x(), bb.y(),
3643 bb.width(), bb.height());
3644 // element is above the staff if center of bbox is above center of staff
3645 qDebug("directionTag() center diff=%g", el->y() + el->height() / 2 - bb.y() - bb.height() / 2);
3646 */
3647
3648 if (el->isHairpin() || el->isOttava() || el->isPedal() || el->isTextLine()) {
3649 // for the line type elements the reference point is vertically centered
3650 // actual position info is in the segments
3651 // compare the segment's canvas ypos with the staff's center height
3652 // if (seg->pagePos().y() < sys->pagePos().y() + bb.y() + bb.height() / 2)
3653 if (el->placement() == Placement::ABOVE)
3654 tagname += " placement=\"above\"";
3655 else
3656 tagname += " placement=\"below\"";
3657 }
3658 else if (el->isDynamic()) {
3659 tagname += " placement=\"";
3660 tagname += el->placement() == Placement::ABOVE ? "above" : "below";
3661 tagname += "\"";
3662 }
3663 else {
3664 /*
3665 qDebug("directionTag() staf ely=%g elh=%g bby=%g bbh=%g",
3666 el->y(), el->height(),
3667 bb.y(), bb.height());
3668 */
3669 // if (el->y() + el->height() / 2 < /*bb.y() +*/ bb.height() / 2)
3670 if (el->placement() == Placement::ABOVE)
3671 tagname += " placement=\"above\"";
3672 else
3673 tagname += " placement=\"below\"";
3674 }
3675 } // if (pel && ...
3676 }
3677 xml.stag(tagname);
3678 }
3679
3680 //---------------------------------------------------------
3681 // directionETag
3682 //---------------------------------------------------------
3683
directionETag(XmlWriter & xml,int staff,int offs=0)3684 static void directionETag(XmlWriter& xml, int staff, int offs = 0)
3685 {
3686 if (offs)
3687 xml.tag("offset", offs);
3688 if (staff)
3689 xml.tag("staff", staff);
3690 xml.etag();
3691 }
3692
3693 //---------------------------------------------------------
3694 // partGroupStart
3695 //---------------------------------------------------------
3696
partGroupStart(XmlWriter & xml,int number,BracketType bracket)3697 static void partGroupStart(XmlWriter& xml, int number, BracketType bracket)
3698 {
3699 xml.stag(QString("part-group type=\"start\" number=\"%1\"").arg(number));
3700 QString br = "";
3701 switch (bracket) {
3702 case BracketType::NO_BRACKET:
3703 br = "none";
3704 break;
3705 case BracketType::NORMAL:
3706 br = "bracket";
3707 break;
3708 case BracketType::BRACE:
3709 br = "brace";
3710 break;
3711 case BracketType::LINE:
3712 br = "line";
3713 break;
3714 case BracketType::SQUARE:
3715 br = "square";
3716 break;
3717 default:
3718 qDebug("bracket subtype %d not understood", int(bracket));
3719 }
3720 if (br != "")
3721 xml.tag("group-symbol", br);
3722 xml.etag();
3723 }
3724
3725 //---------------------------------------------------------
3726 // findUnit
3727 //---------------------------------------------------------
3728
findUnit(TDuration::DurationType val,QString & unit)3729 static bool findUnit(TDuration::DurationType val, QString& unit)
3730 {
3731 unit = "";
3732 switch (val) {
3733 case TDuration::DurationType::V_HALF: unit = "half"; break;
3734 case TDuration::DurationType::V_QUARTER: unit = "quarter"; break;
3735 case TDuration::DurationType::V_EIGHTH: unit = "eighth"; break;
3736 default: qDebug("findUnit: unknown DurationType %d", int(val));
3737 }
3738 return true;
3739 }
3740
3741 //---------------------------------------------------------
3742 // findMetronome
3743 //---------------------------------------------------------
3744
findMetronome(const QList<TextFragment> & list,QList<TextFragment> & wordsLeft,bool & hasParen,QString & metroLeft,QString & metroRight,QList<TextFragment> & wordsRight)3745 static bool findMetronome(const QList<TextFragment>& list,
3746 QList<TextFragment>& wordsLeft, // words left of metronome
3747 bool& hasParen, // parenthesis
3748 QString& metroLeft, // left part of metronome
3749 QString& metroRight, // right part of metronome
3750 QList<TextFragment>& wordsRight // words right of metronome
3751 )
3752 {
3753 QString words = MScoreTextToMXML::toPlainTextPlusSymbols(list);
3754 //qDebug("findMetronome('%s')", qPrintable(words));
3755 hasParen = false;
3756 metroLeft = "";
3757 metroRight = "";
3758 int metroPos = -1; // metronome start position
3759 int metroLen = 0; // metronome length
3760
3761 int indEq = words.indexOf('=');
3762 if (indEq <= 0)
3763 return false;
3764
3765 int len1 = 0;
3766 TDuration dur;
3767
3768 // find first note, limiting search to the part left of the first '=',
3769 // to prevent matching the second note in a "note1 = note2" metronome
3770 int pos1 = TempoText::findTempoDuration(words.left(indEq), len1, dur);
3771 QRegExp eq("\\s*=\\s*");
3772 int pos2 = eq.indexIn(words, pos1 + len1);
3773 if (pos1 != -1 && pos2 == pos1 + len1) {
3774 int len2 = eq.matchedLength();
3775 if (words.length() > pos2 + len2) {
3776 QString s1 = words.mid(0, pos1); // string to the left of metronome
3777 QString s2 = words.mid(pos1, len1); // first note
3778 //QString s3 = words.mid(pos2, len2); // equals sign
3779 QString s4 = words.mid(pos2 + len2); // string to the right of equals sign
3780 /*
3781 qDebug("found note and equals: '%s'%s'%s'%s'",
3782 qPrintable(s1),
3783 qPrintable(s2),
3784 qPrintable(s3),
3785 qPrintable(s4)
3786 );
3787 */
3788
3789 // now determine what is to the right of the equals sign
3790 // must have either a (dotted) note or a number at start of s4
3791 int len3 = 0;
3792 QRegExp nmb("\\d+");
3793 int pos3 = TempoText::findTempoDuration(s4, len3, dur);
3794 if (pos3 == -1) {
3795 // did not find note, try to find a number
3796 pos3 = nmb.indexIn(s4);
3797 if (pos3 == 0)
3798 len3 = nmb.matchedLength();
3799 }
3800 if (pos3 == -1)
3801 // neither found
3802 return false;
3803
3804 QString s5 = s4.mid(0, len3); // number or second note
3805 QString s6 = s4.mid(len3); // string to the right of metronome
3806 /*
3807 qDebug("found right part: '%s'%s'",
3808 qPrintable(s5),
3809 qPrintable(s6)
3810 );
3811 */
3812
3813 // determine if metronome has parentheses
3814 // left part of string must end with parenthesis plus optional spaces
3815 // right part of string must have parenthesis (but not in first pos)
3816 int lparen = s1.indexOf("(");
3817 int rparen = s6.indexOf(")");
3818 hasParen = (lparen == s1.length() - 1 && rparen == 0);
3819
3820 metroLeft = s2;
3821 metroRight = s5;
3822
3823 metroPos = pos1; // metronome position
3824 metroLen = len1 + len2 + len3; // metronome length
3825 if (hasParen) {
3826 metroPos -= 1; // move left one position
3827 metroLen += 2; // add length of '(' and ')'
3828 }
3829
3830 // calculate starting position corrected for surrogate pairs
3831 // (which were ignored by toPlainTextPlusSymbols())
3832 int corrPos = metroPos;
3833 for (int i = 0; i < metroPos; ++i)
3834 if (words.at(i).isHighSurrogate())
3835 --corrPos;
3836 metroPos = corrPos;
3837
3838 /*
3839 qDebug("-> found '%s'%s' hasParen %d metro pos %d len %d",
3840 qPrintable(metroLeft),
3841 qPrintable(metroRight),
3842 hasParen, metroPos, metroLen
3843 );
3844 */
3845 QList<TextFragment> mid; // not used
3846 MScoreTextToMXML::split(list, metroPos, metroLen, wordsLeft, mid, wordsRight);
3847 return true;
3848 }
3849 }
3850 return false;
3851 }
3852
3853 //---------------------------------------------------------
3854 // beatUnit
3855 //---------------------------------------------------------
3856
beatUnit(XmlWriter & xml,const TDuration dur)3857 static void beatUnit(XmlWriter& xml, const TDuration dur)
3858 {
3859 int dots = dur.dots();
3860 QString unit;
3861 findUnit(dur.type(), unit);
3862 xml.tag("beat-unit", unit);
3863 while (dots > 0) {
3864 xml.tagE("beat-unit-dot");
3865 --dots;
3866 }
3867 }
3868
3869 //---------------------------------------------------------
3870 // wordsMetrome
3871 //---------------------------------------------------------
3872
wordsMetrome(XmlWriter & xml,Score * s,TextBase const * const text,const int offset)3873 static void wordsMetrome(XmlWriter& xml, Score* s, TextBase const* const text, const int offset)
3874 {
3875 //qDebug("wordsMetrome('%s')", qPrintable(text->xmlText()));
3876 const QList<TextFragment> list = text->fragmentList();
3877 QList<TextFragment> wordsLeft; // words left of metronome
3878 bool hasParen; // parenthesis
3879 QString metroLeft; // left part of metronome
3880 QString metroRight; // right part of metronome
3881 QList<TextFragment> wordsRight; // words right of metronome
3882
3883 // set the default words format
3884 const QString mtf = s->styleSt(Sid::MusicalTextFont);
3885 const CharFormat defFmt = formatForWords(s);
3886
3887 if (findMetronome(list, wordsLeft, hasParen, metroLeft, metroRight, wordsRight)) {
3888 if (wordsLeft.size() > 0) {
3889 xml.stag("direction-type");
3890 QString attr = positioningAttributes(text);
3891 MScoreTextToMXML mttm("words", attr, defFmt, mtf);
3892 mttm.writeTextFragments(wordsLeft, xml);
3893 xml.etag();
3894 }
3895
3896 xml.stag("direction-type");
3897 QString tagName = QString("metronome parentheses=\"%1\"").arg(hasParen ? "yes" : "no");
3898 tagName += positioningAttributes(text);
3899 xml.stag(tagName);
3900 int len1 = 0;
3901 TDuration dur;
3902 TempoText::findTempoDuration(metroLeft, len1, dur);
3903 beatUnit(xml, dur);
3904
3905 if (TempoText::findTempoDuration(metroRight, len1, dur) != -1)
3906 beatUnit(xml, dur);
3907 else
3908 xml.tag("per-minute", metroRight);
3909
3910 xml.etag();
3911 xml.etag();
3912
3913 if (wordsRight.size() > 0) {
3914 xml.stag("direction-type");
3915 QString attr = positioningAttributes(text);
3916 MScoreTextToMXML mttm("words", attr, defFmt, mtf);
3917 mttm.writeTextFragments(wordsRight, xml);
3918 xml.etag();
3919 }
3920 }
3921
3922 else {
3923 xml.stag("direction-type");
3924 QString attr;
3925 if (text->hasFrame()) {
3926 if (text->circle())
3927 attr = " enclosure=\"circle\"";
3928 else
3929 attr = " enclosure=\"rectangle\"";
3930 }
3931 attr += positioningAttributes(text);
3932 MScoreTextToMXML mttm("words", attr, defFmt, mtf);
3933 //qDebug("words('%s')", qPrintable(text->text()));
3934 mttm.writeTextFragments(text->fragmentList(), xml);
3935 xml.etag();
3936 }
3937
3938 if (offset)
3939 xml.tag("offset", offset);
3940 }
3941
3942 //---------------------------------------------------------
3943 // tempoText
3944 //---------------------------------------------------------
3945
tempoText(TempoText const * const text,int staff)3946 void ExportMusicXml::tempoText(TempoText const* const text, int staff)
3947 {
3948 const auto offset = calculateTimeDeltaInDivisions(text->tick(), tick(), div);
3949 /*
3950 qDebug("tick %s text->tick %s offset %d xmlText='%s')",
3951 qPrintable(tick().print()),
3952 qPrintable(text->tick().print()),
3953 offset,
3954 qPrintable(text->xmlText()));
3955 */
3956 _attr.doAttr(_xml, false);
3957 _xml.stag(QString("direction placement=\"%1\"").arg((text->placement() ==Placement::BELOW ) ? "below" : "above"));
3958 wordsMetrome(_xml, _score, text, offset);
3959
3960 if (staff)
3961 _xml.tag("staff", staff);
3962 _xml.tagE(QString("sound tempo=\"%1\"").arg(QString::number(text->tempo()*60.0)));
3963 _xml.etag();
3964 }
3965
3966 //---------------------------------------------------------
3967 // words
3968 //---------------------------------------------------------
3969
words(TextBase const * const text,int staff)3970 void ExportMusicXml::words(TextBase const* const text, int staff)
3971 {
3972 const auto offset = calculateTimeDeltaInDivisions(text->tick(), tick(), div);
3973 /*
3974 qDebug("tick %s text->tick %s offset %d userOff.x=%f userOff.y=%f xmlText='%s' plainText='%s'",
3975 qPrintable(tick().print()),
3976 qPrintable(text->tick().print()),
3977 offset,
3978 text->offset().x(), text->offset().y(),
3979 qPrintable(text->xmlText()),
3980 qPrintable(text->plainText()));
3981 */
3982
3983 if (text->plainText() == "") {
3984 // sometimes empty Texts are present, exporting would result
3985 // in invalid MusicXML (as an empty direction-type would be created)
3986 return;
3987 }
3988
3989 directionTag(_xml, _attr, text);
3990 wordsMetrome(_xml, _score, text, offset);
3991 directionETag(_xml, staff);
3992 }
3993
3994 //---------------------------------------------------------
3995 // positioningAttributesForTboxText
3996 //---------------------------------------------------------
3997
positioningAttributesForTboxText(const QPointF position,float spatium)3998 static QString positioningAttributesForTboxText(const QPointF position, float spatium)
3999 {
4000 if (!preferences.getBool(PREF_EXPORT_MUSICXML_EXPORTLAYOUT))
4001 return "";
4002
4003 QPointF relative; // use zero relative position
4004 return positionToQString(position, relative, spatium);
4005 }
4006
4007 //---------------------------------------------------------
4008 // tboxTextAsWords
4009 //---------------------------------------------------------
4010
tboxTextAsWords(TextBase const * const text,const int staff,const QPointF relativePosition)4011 void ExportMusicXml::tboxTextAsWords(TextBase const* const text, const int staff, const QPointF relativePosition)
4012 {
4013 if (text->plainText() == "") {
4014 // sometimes empty Texts are present, exporting would result
4015 // in invalid MusicXML (as an empty direction-type would be created)
4016 return;
4017 }
4018
4019 // set the default words format
4020 const QString mtf = _score->styleSt(Sid::MusicalTextFont);
4021 const CharFormat defFmt = formatForWords(_score);
4022
4023 QString tagname { "direction" };
4024 tagname += " placement=";
4025 tagname += (relativePosition.y() < 0) ? "\"above\"" : "\"below\"";
4026 _xml.stag(tagname);
4027 _xml.stag("direction-type");
4028 QString attr;
4029 if (text->hasFrame()) {
4030 if (text->circle())
4031 attr = " enclosure=\"circle\"";
4032 else
4033 attr = " enclosure=\"rectangle\"";
4034 }
4035 attr += positioningAttributesForTboxText(relativePosition, text->spatium());
4036 attr += " valign=\"top\"";
4037 MScoreTextToMXML mttm("words", attr, defFmt, mtf);
4038 mttm.writeTextFragments(text->fragmentList(), _xml);
4039 _xml.etag();
4040 directionETag(_xml, staff);
4041 }
4042
4043 //---------------------------------------------------------
4044 // rehearsal
4045 //---------------------------------------------------------
4046
rehearsal(RehearsalMark const * const rmk,int staff)4047 void ExportMusicXml::rehearsal(RehearsalMark const* const rmk, int staff)
4048 {
4049 if (rmk->plainText() == "") {
4050 // sometimes empty Texts are present, exporting would result
4051 // in invalid MusicXML (as an empty direction-type would be created)
4052 return;
4053 }
4054
4055 directionTag(_xml, _attr, rmk);
4056 _xml.stag("direction-type");
4057 QString attr = positioningAttributes(rmk);
4058 if (!rmk->hasFrame()) attr = " enclosure=\"none\"";
4059 // set the default words format
4060 const QString mtf = _score->styleSt(Sid::MusicalTextFont);
4061 const CharFormat defFmt = formatForWords(_score);
4062 // write formatted
4063 MScoreTextToMXML mttm("rehearsal", attr, defFmt, mtf);
4064 mttm.writeTextFragments(rmk->fragmentList(), _xml);
4065 _xml.etag();
4066 const auto offset = calculateTimeDeltaInDivisions(rmk->tick(), tick(), div);
4067 if (offset)
4068 _xml.tag("offset", offset);
4069 directionETag(_xml, staff);
4070 }
4071
4072 //---------------------------------------------------------
4073 // findDashes -- get index of hairpin in dashes table
4074 // return -1 if not found
4075 //---------------------------------------------------------
4076
findDashes(const TextLineBase * hp) const4077 int ExportMusicXml::findDashes(const TextLineBase* hp) const
4078 {
4079 for (int i = 0; i < MAX_NUMBER_LEVEL; ++i)
4080 if (dashes[i] == hp) return i;
4081 return -1;
4082 }
4083
4084 //---------------------------------------------------------
4085 // findHairpin -- get index of hairpin in hairpin table
4086 // return -1 if not found
4087 //---------------------------------------------------------
4088
findHairpin(const Hairpin * hp) const4089 int ExportMusicXml::findHairpin(const Hairpin* hp) const
4090 {
4091 for (int i = 0; i < MAX_NUMBER_LEVEL; ++i)
4092 if (hairpins[i] == hp) return i;
4093 return -1;
4094 }
4095
4096 //---------------------------------------------------------
4097 // writeHairpinText
4098 //---------------------------------------------------------
4099
writeHairpinText(XmlWriter & xml,const TextLineBase * const tlb,bool isStart=true)4100 static void writeHairpinText(XmlWriter& xml, const TextLineBase* const tlb, bool isStart = true)
4101 {
4102 auto text = isStart ? tlb->beginText() : tlb->endText();
4103 while (text != "") {
4104 int dynamicLength { 0 };
4105 QString dynamicsType;
4106 auto dynamicPosition = Dynamic::findInString(text, dynamicLength, dynamicsType);
4107 if (dynamicPosition == -1 || dynamicPosition > 0) {
4108 // text remaining and either no dynamic of not at front of text
4109 xml.stag("direction-type");
4110 QString tag = "words";
4111 tag += QString(" font-family=\"%1\"").arg(tlb->getProperty(isStart ? Pid::BEGIN_FONT_FACE : Pid::END_FONT_FACE).toString());
4112 tag += QString(" font-size=\"%1\"").arg(tlb->getProperty(isStart ? Pid::BEGIN_FONT_SIZE : Pid::END_FONT_SIZE).toReal());
4113 tag += fontStyleToXML(static_cast<FontStyle>(tlb->getProperty(isStart ? Pid::BEGIN_FONT_STYLE : Pid::END_FONT_STYLE).toInt()));
4114 tag += positioningAttributes(tlb, isStart);
4115 xml.tag(tag, dynamicPosition == -1 ? text : text.left(dynamicPosition));
4116 xml.etag();
4117 if (dynamicPosition == -1)
4118 text = "";
4119 else if (dynamicPosition > 0) {
4120 text.remove(0, dynamicPosition);
4121 dynamicPosition = 0;
4122 }
4123 }
4124 if (dynamicPosition == 0) {
4125 // dynamic at front of text
4126 xml.stag("direction-type");
4127 QString tag = "dynamics";
4128 tag += positioningAttributes(tlb, isStart);
4129 xml.stag(tag);
4130 xml.tagE(dynamicsType);
4131 xml.etag();
4132 xml.etag();
4133 text.remove(0, dynamicLength);
4134 }
4135 }
4136 }
4137
4138 //---------------------------------------------------------
4139 // hairpin
4140 //---------------------------------------------------------
4141
hairpin(Hairpin const * const hp,int staff,const Fraction & tick)4142 void ExportMusicXml::hairpin(Hairpin const* const hp, int staff, const Fraction& tick)
4143 {
4144 const auto isLineType = hp->isLineType();
4145 int n;
4146 if (isLineType) {
4147 n = findDashes(hp);
4148 if (n >= 0)
4149 dashes[n] = nullptr;
4150 else {
4151 n = findDashes(nullptr);
4152 if (n >= 0)
4153 dashes[n] = hp;
4154 else {
4155 qDebug("too many overlapping dashes (hp %p staff %d tick %d)", hp, staff, tick.ticks());
4156 return;
4157 }
4158 }
4159 }
4160 else {
4161 n = findHairpin(hp);
4162 if (n >= 0)
4163 hairpins[n] = nullptr;
4164 else {
4165 n = findHairpin(nullptr);
4166 if (n >= 0)
4167 hairpins[n] = hp;
4168 else {
4169 qDebug("too many overlapping hairpins (hp %p staff %d tick %d)", hp, staff, tick.ticks());
4170 return;
4171 }
4172 }
4173 }
4174
4175 directionTag(_xml, _attr, hp);
4176 if (hp->tick() == tick)
4177 writeHairpinText(_xml, hp, hp->tick() == tick);
4178 if (isLineType) {
4179 if (hp->tick() == tick) {
4180 _xml.stag("direction-type");
4181 QString tag = "dashes type=\"start\"";
4182 tag += QString(" number=\"%1\"").arg(n + 1);
4183 tag += positioningAttributes(hp, hp->tick() == tick);
4184 _xml.tagE(tag);
4185 _xml.etag();
4186 }
4187 else {
4188 _xml.stag("direction-type");
4189 _xml.tagE(QString("dashes type=\"stop\" number=\"%1\"").arg(n + 1));
4190 _xml.etag();
4191 }
4192 }
4193 else {
4194 _xml.stag("direction-type");
4195 QString tag = "wedge type=";
4196 if (hp->tick() == tick) {
4197 if (hp->hairpinType() == HairpinType::CRESC_HAIRPIN) {
4198 tag += "\"crescendo\"";
4199 if (hp->hairpinCircledTip()) {
4200 tag += " niente=\"yes\"";
4201 }
4202 }
4203 else {
4204 tag += "\"diminuendo\"";
4205 }
4206 }
4207 else {
4208 tag += "\"stop\"";
4209 if (hp->hairpinCircledTip() && hp->hairpinType() == HairpinType::DECRESC_HAIRPIN) {
4210 tag += " niente=\"yes\"";
4211 }
4212 }
4213 tag += QString(" number=\"%1\"").arg(n + 1);
4214 tag += positioningAttributes(hp, hp->tick() == tick);
4215 _xml.tagE(tag);
4216 _xml.etag();
4217 }
4218 if (hp->tick() != tick)
4219 writeHairpinText(_xml, hp, hp->tick() == tick);
4220 directionETag(_xml, staff);
4221 }
4222
4223 //---------------------------------------------------------
4224 // findOttava -- get index of ottava in ottava table
4225 // return -1 if not found
4226 //---------------------------------------------------------
4227
findOttava(const Ottava * ot) const4228 int ExportMusicXml::findOttava(const Ottava* ot) const
4229 {
4230 for (int i = 0; i < MAX_NUMBER_LEVEL; ++i)
4231 if (ottavas[i] == ot) return i;
4232 return -1;
4233 }
4234
4235 //---------------------------------------------------------
4236 // ottava
4237 // <octave-shift type="down" size="8" relative-y="14"/>
4238 // <octave-shift type="stop" size="8"/>
4239 //---------------------------------------------------------
4240
ottava(Ottava const * const ot,int staff,const Fraction & tick)4241 void ExportMusicXml::ottava(Ottava const* const ot, int staff, const Fraction& tick)
4242 {
4243 auto n = findOttava(ot);
4244 if (n >= 0)
4245 ottavas[n] = 0;
4246 else {
4247 n = findOttava(0);
4248 if (n >= 0)
4249 ottavas[n] = ot;
4250 else {
4251 qDebug("too many overlapping ottavas (ot %p staff %d tick %d)", ot, staff, tick.ticks());
4252 return;
4253 }
4254 }
4255
4256 QString octaveShiftXml;
4257 const auto st = ot->ottavaType();
4258 if (ot->tick() == tick) {
4259 const char* sz = 0;
4260 const char* tp = 0;
4261 switch (st) {
4262 case OttavaType::OTTAVA_8VA:
4263 sz = "8";
4264 tp = "down";
4265 break;
4266 case OttavaType::OTTAVA_15MA:
4267 sz = "15";
4268 tp = "down";
4269 break;
4270 case OttavaType::OTTAVA_8VB:
4271 sz = "8";
4272 tp = "up";
4273 break;
4274 case OttavaType::OTTAVA_15MB:
4275 sz = "15";
4276 tp = "up";
4277 break;
4278 default:
4279 qDebug("ottava subtype %d not understood", int(st));
4280 }
4281 if (sz && tp)
4282 octaveShiftXml = QString("octave-shift type=\"%1\" size=\"%2\" number=\"%3\"").arg(tp, sz).arg(n + 1);
4283 }
4284 else {
4285 if (st == OttavaType::OTTAVA_8VA || st == OttavaType::OTTAVA_8VB)
4286 octaveShiftXml = QString("octave-shift type=\"stop\" size=\"8\" number=\"%1\"").arg(n + 1);
4287 else if (st == OttavaType::OTTAVA_15MA || st == OttavaType::OTTAVA_15MB)
4288 octaveShiftXml = QString("octave-shift type=\"stop\" size=\"15\" number=\"%1\"").arg(n + 1);
4289 else
4290 qDebug("ottava subtype %d not understood", int(st));
4291 }
4292
4293 if (octaveShiftXml != "") {
4294 directionTag(_xml, _attr, ot);
4295 _xml.stag("direction-type");
4296 octaveShiftXml += positioningAttributes(ot, ot->tick() == tick);
4297 _xml.tagE(octaveShiftXml);
4298 _xml.etag();
4299 directionETag(_xml, staff);
4300 }
4301 }
4302
4303 //---------------------------------------------------------
4304 // pedal
4305 //---------------------------------------------------------
4306
pedal(Pedal const * const pd,int staff,const Fraction & tick)4307 void ExportMusicXml::pedal(Pedal const* const pd, int staff, const Fraction& tick)
4308 {
4309 directionTag(_xml, _attr, pd);
4310 _xml.stag("direction-type");
4311 QString pedalXml;
4312 if (pd->tick() == tick)
4313 pedalXml = "pedal type=\"start\" line=\"yes\"";
4314 else
4315 pedalXml = "pedal type=\"stop\" line=\"yes\"";
4316 pedalXml += positioningAttributes(pd, pd->tick() == tick);
4317 _xml.tagE(pedalXml);
4318 _xml.etag();
4319 directionETag(_xml, staff);
4320 }
4321
4322 //---------------------------------------------------------
4323 // findBracket -- get index of bracket in bracket table
4324 // return -1 if not found
4325 //---------------------------------------------------------
4326
findBracket(const TextLineBase * tl) const4327 int ExportMusicXml::findBracket(const TextLineBase* tl) const
4328 {
4329 for (int i = 0; i < MAX_NUMBER_LEVEL; ++i)
4330 if (brackets[i] == tl) return i;
4331 return -1;
4332 }
4333
4334 //---------------------------------------------------------
4335 // textLine
4336 //---------------------------------------------------------
4337
textLine(TextLineBase const * const tl,int staff,const Fraction & tick)4338 void ExportMusicXml::textLine(TextLineBase const* const tl, int staff, const Fraction& tick)
4339 {
4340 int n;
4341 // special case: a dashed line w/o hooks is written as dashes
4342 const auto isDashes = tl->lineStyle() == Qt::DashLine && (tl->beginHookType() == HookType::NONE) && (tl->endHookType() == HookType::NONE);
4343
4344 if (isDashes) {
4345 n = findDashes(tl);
4346 if (n >= 0)
4347 dashes[n] = nullptr;
4348 else {
4349 n = findBracket(nullptr);
4350 if (n >= 0)
4351 dashes[n] = tl;
4352 else {
4353 qDebug("too many overlapping dashes (tl %p staff %d tick %d)", tl, staff, tick.ticks());
4354 return;
4355 }
4356 }
4357 }
4358 else {
4359 n = findBracket(tl);
4360 if (n >= 0)
4361 brackets[n] = nullptr;
4362 else {
4363 n = findBracket(nullptr);
4364 if (n >= 0)
4365 brackets[n] = tl;
4366 else {
4367 qDebug("too many overlapping textlines (tl %p staff %d tick %d)", tl, staff, tick.ticks());
4368 return;
4369 }
4370 }
4371 }
4372
4373 QString rest;
4374 QPointF p;
4375
4376 QString lineEnd = "none";
4377 QString type;
4378 bool hook = false;
4379 double hookHeight = 0.0;
4380 if (tl->tick() == tick) {
4381 if (!isDashes) {
4382 QString lineType;
4383 switch (tl->lineStyle()) {
4384 case Qt::SolidLine:
4385 lineType = "solid";
4386 break;
4387 case Qt::DashLine:
4388 lineType = "dashed";
4389 break;
4390 case Qt::DotLine:
4391 lineType = "dotted";
4392 break;
4393 default:
4394 lineType = "solid";
4395 }
4396 rest += QString(" line-type=\"%1\"").arg(lineType);
4397 }
4398 hook = tl->beginHookType() != HookType::NONE;
4399 hookHeight = tl->beginHookHeight().val();
4400 if (!tl->segmentsEmpty())
4401 p = tl->frontSegment()->offset();
4402 // offs = tl->mxmlOff();
4403 type = "start";
4404 }
4405 else {
4406 hook = tl->endHookType() != HookType::NONE;
4407 hookHeight = tl->endHookHeight().val();
4408 if (!tl->segmentsEmpty())
4409 p = (toLineSegment(tl->backSegment()))->userOff2();
4410 // offs = tl->mxmlOff2();
4411 type = "stop";
4412 }
4413
4414 if (hook) {
4415 if (hookHeight < 0.0) {
4416 lineEnd = "up";
4417 hookHeight *= -1.0;
4418 }
4419 else
4420 lineEnd = "down";
4421 rest += QString(" end-length=\"%1\"").arg(hookHeight * 10);
4422 }
4423
4424 rest += positioningAttributes(tl, tl->tick() == tick);
4425
4426 directionTag(_xml, _attr, tl);
4427
4428 if (!tl->beginText().isEmpty() && tl->tick() == tick) {
4429 _xml.stag("direction-type");
4430 _xml.tag("words", tl->beginText());
4431 _xml.etag();
4432 }
4433
4434 _xml.stag("direction-type");
4435 if (isDashes)
4436 _xml.tagE(QString("dashes type=\"%1\" number=\"%2\"").arg(type, QString::number(n + 1)));
4437 else
4438 _xml.tagE(QString("bracket type=\"%1\" number=\"%2\" line-end=\"%3\"%4").arg(type, QString::number(n + 1), lineEnd, rest));
4439 _xml.etag();
4440
4441 if (!tl->endText().isEmpty() && tl->tick() != tick) {
4442 _xml.stag("direction-type");
4443 _xml.tag("words", tl->endText());
4444 _xml.etag();
4445 }
4446
4447 /*
4448 if (offs)
4449 xml.tag("offset", offs);
4450 */
4451
4452 directionETag(_xml, staff);
4453 }
4454
4455 //---------------------------------------------------------
4456 // dynamic
4457 //---------------------------------------------------------
4458
4459 // In MuseScore dynamics are essentially user-defined texts, therefore the ones
4460 // supported by MusicXML need to be filtered out. Everything not recognized
4461 // as MusicXML dynamics is written as other-dynamics.
4462
dynamic(Dynamic const * const dyn,int staff)4463 void ExportMusicXml::dynamic(Dynamic const* const dyn, int staff)
4464 {
4465 QSet<QString> set; // the valid MusicXML dynamics
4466 set << "f" << "ff" << "fff" << "ffff" << "fffff" << "ffffff"
4467 << "fp" << "fz"
4468 << "mf" << "mp"
4469 << "p" << "pp" << "ppp" << "pppp" << "ppppp" << "pppppp"
4470 << "rf" << "rfz"
4471 << "sf" << "sffz" << "sfp" << "sfpp" << "sfz";
4472
4473 directionTag(_xml, _attr, dyn);
4474
4475 _xml.stag("direction-type");
4476
4477 QString tagName = "dynamics";
4478 tagName += positioningAttributes(dyn);
4479 _xml.stag(tagName);
4480 const QString dynTypeName = dyn->dynamicTypeName();
4481
4482 if (set.contains(dynTypeName)) {
4483 _xml.tagE(dynTypeName);
4484 }
4485 else if (dynTypeName != "") {
4486 std::map<ushort, QChar> map;
4487 map[0xE520] = 'p';
4488 map[0xE521] = 'm';
4489 map[0xE522] = 'f';
4490 map[0xE523] = 'r';
4491 map[0xE524] = 's';
4492 map[0xE525] = 'z';
4493 map[0xE526] = 'n';
4494
4495 QString dynText = dynTypeName;
4496 if (dyn->dynamicType() == Dynamic::Type::OTHER)
4497 dynText = dyn->plainText();
4498
4499 // collect consecutive runs of either dynamics glyphs
4500 // or other characters and write the runs.
4501 QString text;
4502 bool inDynamicsSym = false;
4503 for (const auto& ch : qAsConst(dynText)) {
4504 const auto it = map.find(ch.unicode());
4505 if (it != map.end()) {
4506 // found a SMUFL single letter dynamics glyph
4507 if (!inDynamicsSym) {
4508 if (text != "") {
4509 _xml.tag("other-dynamics", text);
4510 text = "";
4511 }
4512 inDynamicsSym = true;
4513 }
4514 text += it->second;
4515 }
4516 else {
4517 // found a non-dynamics character
4518 if (inDynamicsSym) {
4519 if (text != "") {
4520 if (set.contains(text))
4521 _xml.tagE(text);
4522 else
4523 _xml.tag("other-dynamics", text);
4524 text = "";
4525 }
4526 inDynamicsSym = false;
4527 }
4528 text += ch;
4529 }
4530 }
4531 if (text != "") {
4532 if (inDynamicsSym && set.contains(text))
4533 _xml.tagE(text);
4534 else
4535 _xml.tag("other-dynamics", text);
4536 }
4537 }
4538
4539 _xml.etag();
4540
4541 _xml.etag();
4542
4543 const auto offset = calculateTimeDeltaInDivisions(dyn->tick(), tick(), div);
4544 if (offset)
4545 _xml.tag("offset", offset);
4546
4547 if (staff)
4548 _xml.tag("staff", staff);
4549
4550 if (dyn->velocity() > 0)
4551 _xml.tagE(QString("sound dynamics=\"%1\"").arg(QString::number(dyn->velocity() * 100.0 / 90.0, 'f', 2)));
4552
4553 _xml.etag();
4554 }
4555
4556 //---------------------------------------------------------
4557 // symbol
4558 //---------------------------------------------------------
4559
4560 // TODO: remove dependency on symbol name and replace by a more stable interface
4561 // changes in sym.cpp r2494 broke MusicXML export of pedals (again)
4562
symbol(Symbol const * const sym,int staff)4563 void ExportMusicXml::symbol(Symbol const* const sym, int staff)
4564 {
4565 QString name = Sym::id2name(sym->sym());
4566 QString mxmlName = "";
4567 if (name == "keyboardPedalPed")
4568 mxmlName = "pedal type=\"start\"";
4569 else if (name == "keyboardPedalUp")
4570 mxmlName = "pedal type=\"stop\"";
4571 else {
4572 qDebug("ExportMusicXml::symbol(): %s not supported", qPrintable(name));
4573 return;
4574 }
4575 directionTag(_xml, _attr, sym);
4576 mxmlName += positioningAttributes(sym);
4577 _xml.stag("direction-type");
4578 _xml.tagE(mxmlName);
4579 _xml.etag();
4580 const auto offset = calculateTimeDeltaInDivisions(sym->tick(), tick(), div);
4581 if (offset)
4582 _xml.tag("offset", offset);
4583 directionETag(_xml, staff);
4584 }
4585
4586 //---------------------------------------------------------
4587 // lyrics
4588 //---------------------------------------------------------
4589
lyrics(const std::vector<Lyrics * > * ll,const int trk)4590 void ExportMusicXml::lyrics(const std::vector<Lyrics*>* ll, const int trk)
4591 {
4592 for (const Lyrics* l :* ll) {
4593 if (l && !l->xmlText().isEmpty()) {
4594 if ((l)->track() == trk) {
4595 QString lyricXml = QString("lyric number=\"%1\"").arg((l)->no() + 1);
4596 lyricXml += color2xml(l);
4597 lyricXml += positioningAttributes(l);
4598 _xml.stag(lyricXml);
4599 Lyrics::Syllabic syl = (l)->syllabic();
4600 QString s = "";
4601 switch (syl) {
4602 case Lyrics::Syllabic::SINGLE: s = "single"; break;
4603 case Lyrics::Syllabic::BEGIN: s = "begin"; break;
4604 case Lyrics::Syllabic::END: s = "end"; break;
4605 case Lyrics::Syllabic::MIDDLE: s = "middle"; break;
4606 default:
4607 qDebug("unknown syllabic %d", int(syl));
4608 }
4609 _xml.tag("syllabic", s);
4610 QString attr; // TODO TBD
4611 // set the default words format
4612 const QString mtf = _score->styleSt(Sid::MusicalTextFont);
4613 CharFormat defFmt;
4614 defFmt.setFontFamily(_score->styleSt(Sid::lyricsEvenFontFace));
4615 defFmt.setFontSize(_score->styleD(Sid::lyricsOddFontSize));
4616 // write formatted
4617 MScoreTextToMXML mttm("text", attr, defFmt, mtf);
4618 mttm.writeTextFragments(l->fragmentList(), _xml);
4619 #if 0
4620 /*
4621 Temporarily disabled because it doesn't work yet (and thus breaks the regression test).
4622 See MusicXml::xmlLyric: "// TODO-WS l->setTick(tick);"
4623 if((l)->endTick() > 0)
4624 xml.tagE("extend");
4625 */
4626 #else
4627 if (l->ticks().isNotZero())
4628 _xml.tagE("extend");
4629 #endif
4630 _xml.etag();
4631 }
4632 }
4633 }
4634 }
4635
4636 //---------------------------------------------------------
4637 // directionJump -- write jump
4638 //---------------------------------------------------------
4639
4640 // LVIFIX: TODO coda and segno should be numbered uniquely
4641
directionJump(XmlWriter & xml,const Jump * const jp)4642 static void directionJump(XmlWriter& xml, const Jump* const jp)
4643 {
4644 Jump::Type jtp = jp->jumpType();
4645 QString words = "";
4646 QString type = "";
4647 QString sound = "";
4648 if (jtp == Jump::Type::DC) {
4649 if (jp->xmlText() == "")
4650 words = "D.C.";
4651 else
4652 words = jp->xmlText();
4653 sound = "dacapo=\"yes\"";
4654 }
4655 else if (jtp == Jump::Type::DC_AL_FINE) {
4656 if (jp->xmlText() == "")
4657 words = "D.C. al Fine";
4658 else
4659 words = jp->xmlText();
4660 sound = "dacapo=\"yes\"";
4661 }
4662 else if (jtp == Jump::Type::DC_AL_CODA) {
4663 if (jp->xmlText() == "")
4664 words = "D.C. al Coda";
4665 else
4666 words = jp->xmlText();
4667 sound = "dacapo=\"yes\"";
4668 }
4669 else if (jtp == Jump::Type::DS_AL_CODA) {
4670 if (jp->xmlText() == "")
4671 words = "D.S. al Coda";
4672 else
4673 words = jp->xmlText();
4674 if (jp->jumpTo() == "")
4675 sound = "dalsegno=\"1\"";
4676 else
4677 sound = "dalsegno=\"" + jp->jumpTo() + "\"";
4678 }
4679 else if (jtp == Jump::Type::DS_AL_FINE) {
4680 if (jp->xmlText() == "")
4681 words = "D.S. al Fine";
4682 else
4683 words = jp->xmlText();
4684 if (jp->jumpTo() == "")
4685 sound = "dalsegno=\"1\"";
4686 else
4687 sound = "dalsegno=\"" + jp->jumpTo() + "\"";
4688 }
4689 else if (jtp == Jump::Type::DS) {
4690 words = "D.S.";
4691 if (jp->jumpTo() == "")
4692 sound = "dalsegno=\"1\"";
4693 else
4694 sound = "dalsegno=\"" + jp->jumpTo() + "\"";
4695 }
4696 else
4697 qDebug("jump type=%d not implemented", static_cast<int>(jtp));
4698
4699 if (sound != "") {
4700 xml.stag(QString("direction placement=\"%1\"").arg((jp->placement() == Placement::BELOW ) ? "below" : "above"));
4701 xml.stag("direction-type");
4702 QString positioning = positioningAttributes(jp);
4703 if (type != "") xml.tagE(type + positioning);
4704 if (words != "") xml.tag("words" + positioning, words);
4705 xml.etag();
4706 if (sound != "") xml.tagE(QString("sound ") + sound);
4707 xml.etag();
4708 }
4709 }
4710
4711 //---------------------------------------------------------
4712 // directionMarker -- write marker
4713 //---------------------------------------------------------
4714
directionMarker(XmlWriter & xml,const Marker * const m)4715 static void directionMarker(XmlWriter& xml, const Marker* const m)
4716 {
4717 Marker::Type mtp = m->markerType();
4718 QString words = "";
4719 QString type = "";
4720 QString sound = "";
4721 if (mtp == Marker::Type::CODA) {
4722 type = "coda";
4723 if (m->label() == "")
4724 sound = "coda=\"1\"";
4725 else
4726 // LVIFIX hack: force label to "coda" to match to coda label
4727 // sound = "coda=\"" + m->label() + "\"";
4728 sound = "coda=\"coda\"";
4729 }
4730 else if (mtp == Marker::Type::SEGNO) {
4731 type = "segno";
4732 if (m->label() == "")
4733 sound = "segno=\"1\"";
4734 else
4735 sound = "segno=\"" + m->label() + "\"";
4736 }
4737 else if (mtp == Marker::Type::FINE) {
4738 words = "Fine";
4739 sound = "fine=\"yes\"";
4740 }
4741 else if (mtp == Marker::Type::TOCODA ||
4742 mtp == Marker::Type::TOCODASYM) {
4743 if (m->xmlText() == "")
4744 words = "To Coda";
4745 else
4746 words = m->xmlText();
4747 if (m->label() == "")
4748 sound = "tocoda=\"1\"";
4749 else
4750 sound = "tocoda=\"" + m->label() + "\"";
4751 }
4752 else
4753 qDebug("marker type=%d not implemented", int(mtp));
4754
4755 if (sound != "") {
4756 xml.stag(QString("direction placement=\"%1\"").arg((m->placement() == Placement::BELOW ) ? "below" : "above"));
4757 xml.stag("direction-type");
4758 QString positioning = positioningAttributes(m);
4759 if (type != "") xml.tagE(type + positioning);
4760 if (words != "") xml.tag("words" + positioning, words);
4761 xml.etag();
4762 if (sound != "") xml.tagE(QString("sound ") + sound);
4763 xml.etag();
4764 }
4765 }
4766
4767 //---------------------------------------------------------
4768 // findTrackForAnnotations
4769 //---------------------------------------------------------
4770
4771 // An annotation is attached to the staff, with track set
4772 // to the lowest track in the staff. Find a track for it
4773 // (the lowest track in this staff that has a chord or rest)
4774
findTrackForAnnotations(int track,Segment * seg)4775 static int findTrackForAnnotations(int track, Segment* seg)
4776 {
4777 if (seg->segmentType() != SegmentType::ChordRest)
4778 return -1;
4779
4780 int staff = track / VOICES;
4781 int strack = staff * VOICES; // start track of staff containing track
4782 int etrack = strack + VOICES; // end track of staff containing track + 1
4783
4784 for (int i = strack; i < etrack; i++)
4785 if (seg->element(i))
4786 return i;
4787
4788 return -1;
4789 }
4790
4791 //---------------------------------------------------------
4792 // repeatAtMeasureStart -- write repeats at begin of measure
4793 //---------------------------------------------------------
4794
repeatAtMeasureStart(XmlWriter & xml,Attributes & attr,const Measure * const m,int strack,int etrack,int track)4795 static void repeatAtMeasureStart(XmlWriter& xml, Attributes& attr, const Measure* const m, int strack, int etrack, int track)
4796 {
4797 // loop over all segments
4798 for (Element* e : m->el()) {
4799 int wtrack = -1; // track to write jump
4800 if (strack <= e->track() && e->track() < etrack)
4801 wtrack = findTrackForAnnotations(e->track(), m->first(SegmentType::ChordRest));
4802 if (track != wtrack)
4803 continue;
4804 switch (e->type()) {
4805 case ElementType::MARKER:
4806 {
4807 // filter out the markers at measure Start
4808 const Marker* const mk = toMarker(e);
4809 Marker::Type mtp = mk->markerType();
4810 if ( mtp == Marker::Type::SEGNO
4811 || mtp == Marker::Type::CODA
4812 ) {
4813 qDebug(" -> handled");
4814 attr.doAttr(xml, false);
4815 directionMarker(xml, mk);
4816 }
4817 else if ( mtp == Marker::Type::FINE ||
4818 mtp == Marker::Type::TOCODA ||
4819 mtp == Marker::Type::TOCODASYM
4820 ) {
4821 // ignore
4822 }
4823 else {
4824 qDebug("repeatAtMeasureStart: marker %d not implemented", int(mtp));
4825 }
4826 }
4827 break;
4828 default:
4829 qDebug("repeatAtMeasureStart: direction type %s at tick %d not implemented",
4830 Element::name(e->type()), m->tick().ticks());
4831 break;
4832 }
4833 }
4834 }
4835
4836 //---------------------------------------------------------
4837 // repeatAtMeasureStop -- write repeats at end of measure
4838 //---------------------------------------------------------
4839
repeatAtMeasureStop(XmlWriter & xml,const Measure * const m,int strack,int etrack,int track)4840 static void repeatAtMeasureStop(XmlWriter& xml, const Measure* const m, int strack, int etrack, int track)
4841 {
4842 for (Element* e : m->el()) {
4843 int wtrack = -1; // track to write jump
4844 if (strack <= e->track() && e->track() < etrack)
4845 wtrack = findTrackForAnnotations(e->track(), m->first(SegmentType::ChordRest));
4846 if (track != wtrack)
4847 continue;
4848 switch (e->type()) {
4849 case ElementType::MARKER:
4850 {
4851 // filter out the markers at measure stop
4852 const Marker* const mk = toMarker(e);
4853 Marker::Type mtp = mk->markerType();
4854 if (mtp == Marker::Type::FINE ||
4855 mtp == Marker::Type::TOCODA ||
4856 mtp == Marker::Type::TOCODASYM) {
4857 directionMarker(xml, mk);
4858 }
4859 else if (mtp == Marker::Type::SEGNO || mtp == Marker::Type::CODA) {
4860 // ignore
4861 }
4862 else {
4863 qDebug("repeatAtMeasureStop: marker %d not implemented", int(mtp));
4864 }
4865 }
4866 break;
4867 case ElementType::JUMP:
4868 directionJump(xml, toJump(e));
4869 break;
4870 default:
4871 qDebug("repeatAtMeasureStop: direction type %s at tick %d not implemented",
4872 Element::name(e->type()), m->tick().ticks());
4873 break;
4874 }
4875 }
4876 }
4877
4878 //---------------------------------------------------------
4879 // work -- write the <work> element
4880 // note that order must be work-number, work-title
4881 // also write <movement-number> and <movement-title>
4882 // data is taken from the score metadata instead of the Text elements
4883 //---------------------------------------------------------
4884
work(const MeasureBase *)4885 void ExportMusicXml::work(const MeasureBase* /*measure*/)
4886 {
4887 QString workTitle = _score->metaTag("workTitle");
4888 QString workNumber = _score->metaTag("workNumber");
4889 if (!(workTitle.isEmpty() && workNumber.isEmpty())) {
4890 _xml.stag("work");
4891 if (!workNumber.isEmpty())
4892 _xml.tag("work-number", workNumber);
4893 if (!workTitle.isEmpty())
4894 _xml.tag("work-title", workTitle);
4895 _xml.etag();
4896 }
4897 if (!_score->metaTag("movementNumber").isEmpty())
4898 _xml.tag("movement-number", _score->metaTag("movementNumber"));
4899 if (!_score->metaTag("movementTitle").isEmpty())
4900 _xml.tag("movement-title", _score->metaTag("movementTitle"));
4901 }
4902
4903 #if 0
4904 //---------------------------------------------------------
4905 // elementRighter // used for harmony order
4906 //---------------------------------------------------------
4907
4908 static bool elementRighter(const Element* e1, const Element* e2)
4909 {
4910 return e1->x() < e2->x();
4911 }
4912 #endif
4913
4914 //---------------------------------------------------------
4915 // measureStyle -- write measure-style
4916 //---------------------------------------------------------
4917
4918 // this is done at the first measure of a multi-meaure rest
4919 // note: for a normal measure, mmRest1 is the measure itself,
4920 // for a multi-meaure rest, it is the replacing measure
4921
measureStyle(XmlWriter & xml,Attributes & attr,const Measure * const m)4922 static void measureStyle(XmlWriter& xml, Attributes& attr, const Measure* const m)
4923 {
4924 const Measure* mmR1 = m->mmRest1();
4925 if (m != mmR1 && m == mmR1->mmRestFirst()) {
4926 attr.doAttr(xml, true);
4927 xml.stag("measure-style");
4928 xml.tag("multiple-rest", mmR1->mmRestCount());
4929 xml.etag();
4930 }
4931 }
4932
4933 //---------------------------------------------------------
4934 // commonAnnotations
4935 //---------------------------------------------------------
4936
commonAnnotations(ExportMusicXml * exp,const Element * e,int sstaff)4937 static bool commonAnnotations(ExportMusicXml* exp, const Element* e, int sstaff)
4938 {
4939 bool instrChangeHandled = false;
4940
4941 // note: write the changed instrument details (transposition) here,
4942 // optionally writing the associated staff text is done below
4943 if (e->isInstrumentChange()) {
4944 const auto instrChange = toInstrumentChange(e);
4945 exp->writeInstrumentDetails(instrChange->instrument());
4946 instrChangeHandled = true;
4947 }
4948
4949 if (e->isSymbol())
4950 exp->symbol(toSymbol(e), sstaff);
4951 else if (e->isTempoText())
4952 exp->tempoText(toTempoText(e), sstaff);
4953 else if (e->isStaffText() || e->isSystemText() || e->isText() || (e->isInstrumentChange() && e->visible()))
4954 exp->words(toTextBase(e), sstaff);
4955 else if (e->isDynamic())
4956 exp->dynamic(toDynamic(e), sstaff);
4957 else if (e->isRehearsalMark())
4958 exp->rehearsal(toRehearsalMark(e), sstaff);
4959 else
4960 return instrChangeHandled;
4961
4962 return true;
4963 }
4964
4965 //---------------------------------------------------------
4966 // annotations
4967 //---------------------------------------------------------
4968
4969 // Only handle common annotations, others are handled elsewhere
4970
annotations(ExportMusicXml * exp,int strack,int etrack,int track,int sstaff,Segment * seg)4971 static void annotations(ExportMusicXml* exp, int strack, int etrack, int track, int sstaff, Segment* seg)
4972 {
4973 for (const Element* e : seg->annotations()) {
4974
4975 int wtrack = -1; // track to write annotation
4976
4977 if (strack <= e->track() && e->track() < etrack)
4978 wtrack = findTrackForAnnotations(e->track(), seg);
4979
4980 if (track == wtrack) {
4981 if (commonAnnotations(exp, e, sstaff)) {
4982 ; // already handled
4983 }
4984 }
4985 }
4986 }
4987
4988 //---------------------------------------------------------
4989 // harmonies
4990 //---------------------------------------------------------
4991
4992 /*
4993 * Helper method to export harmonies and chord diagrams for a single segment.
4994 */
4995
segmentHarmonies(ExportMusicXml * exp,int track,Segment * seg,int offset)4996 static void segmentHarmonies(ExportMusicXml* exp, int track, Segment* seg, int offset)
4997 {
4998 const std::vector<Element*> diagrams = seg->findAnnotations(ElementType::FRET_DIAGRAM, track, track);
4999 std::vector<Element*> harmonies = seg->findAnnotations(ElementType::HARMONY, track, track);
5000
5001 for (const Element* e : diagrams) {
5002 const FretDiagram* diagram = toFretDiagram(e);
5003 const Harmony* harmony = diagram->harmony();
5004 if (harmony) {
5005 exp->harmony(harmony, diagram, offset);
5006 }
5007 else if (!harmonies.empty()) {
5008 const Element* defaultHarmony = harmonies.back();
5009 exp->harmony(toHarmony(defaultHarmony), diagram, offset);
5010 harmonies.pop_back();
5011 }
5012 else {
5013 // Found a fret diagram with no harmony, ignore
5014 qDebug("segmentHarmonies() seg %p found fretboard diagram %p w/o harmony: cannot write", seg, diagram);
5015 }
5016 }
5017
5018 for (const Element* e: harmonies)
5019 exp->harmony(toHarmony(e), 0, offset);
5020 }
5021
5022 /*
5023 * Write harmonies and fret diagrams that are attached to chords or rests.
5024 *
5025 * There are fondamental differences between the ways Musescore and MusicXML handle harmonies (Chord symbols)
5026 * and fretboard diagrams.
5027 *
5028 * In MuseScore, the Harmony element is now a child of FretboardDiagram BUT in previous versions,
5029 * both elements were independant siblings so we have to handle both cases.
5030 * In MusicXML, fretboard diagram is always contained in a harmony element.
5031 *
5032 * In MuseScore, Harmony elements are not always linked to notes, and each Harmony will be contained
5033 * in a `ChordRest` Segment.
5034 * In MusicXML, those successive Harmony elements must be exported before the note with different offsets.
5035 *
5036 * Edge cases that we simply cannot handle:
5037 * - as of MusicXML 3.1, there is no way to represent a diagram without an associated chord symbol,
5038 * so when we encounter such an object in MuseScore, we simply cannot export it.
5039 * - If a ChordRest segment contans a FretboardDiagram with no harmonies and several different Harmony siblings,
5040 * we simply have to pick a random one to export.
5041 */
5042
harmonies(ExportMusicXml * exp,int track,Segment * seg,int divisions)5043 static void harmonies(ExportMusicXml* exp, int track, Segment* seg, int divisions)
5044 {
5045 int offset = 0;
5046 segmentHarmonies(exp, track, seg, offset);
5047
5048 // Edge case: find remaining `harmony` elements.
5049 // Suppose you have one single whole note in the measure but several chord symbols.
5050 // In MuseScore, each `Harmony` object will be stored in a `ChordRest` Segment that contains
5051 // no other Chords.
5052 // But in MusicXML, you are supposed to output all `harmony` elements before the first `note`,
5053 // with different `offset` parameters.
5054 //
5055 // That's why we need to explore the remaining segments to find
5056 // `Harmony` and `FretDiagram` elements in Segments without Chords and output them now.
5057 for (auto seg1 = seg->next(); seg1; seg1 = seg1->next()) {
5058 if (!seg1->isChordRestType())
5059 continue;
5060
5061 const auto el1 = seg1->element(track);
5062 if (el1) // found a ChordRest, next harmony will be attached to this one
5063 break;
5064
5065 offset = (seg1->tick() - seg->tick()).ticks() / divisions;
5066 segmentHarmonies(exp, track, seg1, offset);
5067 }
5068 }
5069
5070 //---------------------------------------------------------
5071 // figuredBass
5072 //---------------------------------------------------------
5073
figuredBass(XmlWriter & xml,int strack,int etrack,int track,const ChordRest * cr,FigBassMap & fbMap,int divisions)5074 static void figuredBass(XmlWriter& xml, int strack, int etrack, int track, const ChordRest* cr, FigBassMap& fbMap, int divisions)
5075 {
5076 Segment* seg = cr->segment();
5077 if (seg->segmentType() == SegmentType::ChordRest) {
5078 for (const Element* e : seg->annotations()) {
5079
5080 int wtrack = -1; // track to write annotation
5081
5082 if (strack <= e->track() && e->track() < etrack)
5083 wtrack = findTrackForAnnotations(e->track(), seg);
5084
5085 if (track == wtrack) {
5086 if (e->type() == ElementType::FIGURED_BASS) {
5087 const FiguredBass* fb = dynamic_cast<const FiguredBass*>(e);
5088 //qDebug("figuredbass() track %d seg %p fb %p seg %p tick %d ticks %d cr %p tick %d ticks %d",
5089 // track, seg, fb, fb->segment(), fb->segment()->tick(), fb->ticks(), cr, cr->tick(), cr->actualTicks());
5090 bool extend = fb->ticks() > cr->actualTicks();
5091 if (extend) {
5092 //qDebug("figuredbass() extend to %d + %d = %d",
5093 // cr->tick(), fb->ticks(), cr->tick() + fb->ticks());
5094 fbMap.insert(strack, fb);
5095 }
5096 else
5097 fbMap.remove(strack);
5098 const Fraction crEndTick = cr->tick() + cr->actualTicks();
5099 const Fraction fbEndTick = fb->segment()->tick() + fb->ticks();
5100 const bool writeDuration = fb->ticks() < cr->actualTicks();
5101 fb->writeMusicXML(xml, true, crEndTick.ticks(), fbEndTick.ticks(),
5102 writeDuration, divisions);
5103
5104 // Check for changing figures under a single note (each figure stored in a separate segment)
5105 for (Segment* segNext = seg->next(); segNext && segNext->element(track) == NULL; segNext = segNext->next()) {
5106 for (Element* annot : segNext->annotations()) {
5107 if (annot->type() == ElementType::FIGURED_BASS && annot->track() == track) {
5108 fb = dynamic_cast<const FiguredBass*>(annot);
5109 fb->writeMusicXML(xml, true, 0, 0, true, divisions);
5110 }
5111 }
5112 }
5113 // no extend can be pending
5114 return;
5115 }
5116 }
5117 }
5118 // check for extend pending
5119 if (fbMap.contains(strack)) {
5120 const FiguredBass* fb = fbMap.value(strack);
5121 Fraction crEndTick = cr->tick() + cr->actualTicks();
5122 Fraction fbEndTick = fb->segment()->tick() + fb->ticks();
5123 bool writeDuration = fb->ticks() < cr->actualTicks();
5124 if (cr->tick() < fbEndTick) {
5125 //qDebug("figuredbass() at tick %d extend only", cr->tick());
5126 fb->writeMusicXML(xml, false, crEndTick.ticks(), fbEndTick.ticks(), writeDuration, divisions);
5127 }
5128 if (fbEndTick <= crEndTick) {
5129 //qDebug("figuredbass() at tick %d extend done", cr->tick() + cr->actualTicks());
5130 fbMap.remove(strack);
5131 }
5132 }
5133 }
5134 }
5135
5136 //---------------------------------------------------------
5137 // spannerStart
5138 //---------------------------------------------------------
5139
5140 // for each spanner start:
5141 // find start track
5142 // find stop track
5143 // if stop track < start track
5144 // get data from list of already stopped spanners
5145 // else
5146 // calculate data
5147 // write start if in right track
5148
spannerStart(ExportMusicXml * exp,int strack,int etrack,int track,int sstaff,Segment * seg)5149 static void spannerStart(ExportMusicXml* exp, int strack, int etrack, int track, int sstaff, Segment* seg)
5150 {
5151 if (seg->segmentType() == SegmentType::ChordRest) {
5152 Fraction stick = seg->tick();
5153 for (auto it = exp->score()->spanner().lower_bound(stick.ticks()); it != exp->score()->spanner().upper_bound(stick.ticks()); ++it) {
5154 Spanner* e = it->second;
5155
5156 int wtrack = -1; // track to write spanner
5157 if (strack <= e->track() && e->track() < etrack)
5158 wtrack = findTrackForAnnotations(e->track(), seg);
5159
5160 if (track == wtrack) {
5161 switch (e->type()) {
5162 case ElementType::HAIRPIN:
5163 exp->hairpin(toHairpin(e), sstaff, seg->tick());
5164 break;
5165 case ElementType::OTTAVA:
5166 exp->ottava(toOttava(e), sstaff, seg->tick());
5167 break;
5168 case ElementType::PEDAL:
5169 exp->pedal(toPedal(e), sstaff, seg->tick());
5170 break;
5171 case ElementType::TEXTLINE:
5172 exp->textLine(toTextLineBase(e), sstaff, seg->tick());
5173 break;
5174 case ElementType::LET_RING:
5175 exp->textLine(toLetRing(e), sstaff, seg->tick());
5176 break;
5177 case ElementType::PALM_MUTE:
5178 exp->textLine(toPalmMute(e), sstaff, seg->tick());
5179 break;
5180 case ElementType::TRILL:
5181 // ignore (written as <note><notations><ornaments><wavy-line>)
5182 break;
5183 case ElementType::SLUR:
5184 // ignore (written as <note><notations><slur>)
5185 break;
5186 default:
5187 qDebug("spannerStart: direction type %d ('%s') at tick %d not implemented",
5188 int(e->type()), Element::name(e->type()), seg->tick().ticks());
5189 break;
5190 }
5191 }
5192 } // for
5193 }
5194 }
5195
5196 //---------------------------------------------------------
5197 // spannerStop
5198 //---------------------------------------------------------
5199
5200 // called after writing each chord or rest to check if a spanner must be stopped
5201 // loop over all spanners and find spanners in strack ending at tick2
5202 // note that more than one voice may contains notes ending at tick2,
5203 // remember which spanners have already been stopped (the "stopped" set)
5204
spannerStop(ExportMusicXml * exp,int strack,int etrack,const Fraction & tick2,int sstaff,QSet<const Spanner * > & stopped)5205 static void spannerStop(ExportMusicXml* exp, int strack, int etrack, const Fraction& tick2, int sstaff, QSet<const Spanner*>& stopped)
5206 {
5207 for (auto it : exp->score()->spanner()) {
5208 Spanner* e = it.second;
5209
5210 if (e->tick2() != tick2 || e->track() < strack || e->track() >= etrack)
5211 continue;
5212
5213 if (!stopped.contains(e)) {
5214 stopped.insert(e);
5215 switch (e->type()) {
5216 case ElementType::HAIRPIN:
5217 exp->hairpin(toHairpin(e), sstaff, Fraction(-1,1));
5218 break;
5219 case ElementType::OTTAVA:
5220 exp->ottava(toOttava(e), sstaff, Fraction(-1,1));
5221 break;
5222 case ElementType::PEDAL:
5223 exp->pedal(toPedal(e), sstaff, Fraction(-1,1));
5224 break;
5225 case ElementType::TEXTLINE:
5226 exp->textLine(toTextLineBase(e), sstaff, Fraction(-1,1));
5227 break;
5228 case ElementType::LET_RING:
5229 exp->textLine(toLetRing(e), sstaff, Fraction(-1,1));
5230 break;
5231 case ElementType::PALM_MUTE:
5232 exp->textLine(toPalmMute(e), sstaff, Fraction(-1,1));
5233 break;
5234 case ElementType::TRILL:
5235 // ignore (written as <note><notations><ornaments><wavy-line>
5236 break;
5237 case ElementType::SLUR:
5238 // ignore (written as <note><notations><slur>)
5239 break;
5240 default:
5241 qDebug("spannerStop: direction type %s at tick2 %d not implemented",
5242 Element::name(e->type()), tick2.ticks());
5243 break;
5244 }
5245 }
5246 } // for
5247 }
5248
5249 //---------------------------------------------------------
5250 // keysigTimesig
5251 //---------------------------------------------------------
5252
5253 /**
5254 Output attributes at start of measure: key, time
5255 */
5256
keysigTimesig(const Measure * m,const Part * p)5257 void ExportMusicXml::keysigTimesig(const Measure* m, const Part* p)
5258 {
5259 int strack = p->startTrack();
5260 int etrack = p->endTrack();
5261 //qDebug("keysigTimesig m %p strack %d etrack %d", m, strack, etrack);
5262
5263 // search all staves for non-generated key signatures
5264 QMap<int, KeySig*> keysigs; // map staff to key signature
5265 for (Segment* seg = m->first(); seg; seg = seg->next()) {
5266 if (seg->tick() > m->tick())
5267 break;
5268 for (int t = strack; t < etrack; t += VOICES) {
5269 Element* el = seg->element(t);
5270 if (!el)
5271 continue;
5272 if (el->type() == ElementType::KEYSIG) {
5273 //qDebug(" found keysig %p track %d", el, el->track());
5274 int st = (t - strack) / VOICES;
5275 if (!el->generated())
5276 keysigs[st] = static_cast<KeySig*>(el);
5277 }
5278 }
5279 }
5280
5281 //ClefType ct = rest->staff()->clef(rest->tick());
5282
5283 // write the key signatues
5284 if (!keysigs.isEmpty()) {
5285 // determine if all staves have a keysig and all keysigs are identical
5286 // in that case a single <key> is written, without number=... attribute
5287 int nstaves = p->nstaves();
5288 bool singleKey = true;
5289 // check if all staves have a keysig
5290 for (int i = 0; i < nstaves; i++)
5291 if (!keysigs.contains(i))
5292 singleKey = false;
5293 // check if all keysigs are identical
5294 if (singleKey)
5295 for (int i = 1; i < nstaves; i++)
5296 if (!(keysigs.value(i)->key() == keysigs.value(0)->key()))
5297 singleKey = false;
5298
5299 // write the keysigs
5300 //qDebug(" singleKey %d", singleKey);
5301 if (singleKey) {
5302 // keysig applies to all staves
5303 keysig(keysigs.value(0), p->staff(0)->clef(m->tick()), 0, keysigs.value(0)->visible());
5304 }
5305 else {
5306 // staff-specific keysigs
5307 for (int st : keysigs.keys())
5308 keysig(keysigs.value(st), p->staff(st)->clef(m->tick()), st + 1, keysigs.value(st)->visible());
5309 }
5310 }
5311 else {
5312 // always write a keysig at tick = 0
5313 if (m->tick().isZero()) {
5314 //KeySigEvent kse;
5315 //kse.setKey(Key::C);
5316 KeySig* ks = new KeySig(_score);
5317 ks->setKey(Key::C);
5318 keysig(ks, p->staff(0)->clef(m->tick()));
5319 delete ks;
5320 }
5321 }
5322
5323 TimeSig* tsig = 0;
5324 for (Segment* seg = m->first(); seg; seg = seg->next()) {
5325 if (seg->tick() > m->tick())
5326 break;
5327 Element* el = seg->element(strack);
5328 if (el && el->type() == ElementType::TIMESIG)
5329 tsig = (TimeSig*) el;
5330 }
5331 if (tsig)
5332 timesig(tsig);
5333 }
5334
5335 //---------------------------------------------------------
5336 // identification -- write the identification
5337 //---------------------------------------------------------
5338
identification(XmlWriter & xml,Score const * const score)5339 static void identification(XmlWriter& xml, Score const* const score)
5340 {
5341 xml.stag("identification");
5342
5343 QStringList creators;
5344 // the creator types commonly found in MusicXML
5345 creators << "arranger" << "composer" << "lyricist" << "poet" << "translator";
5346 for (const QString &type : qAsConst(creators)) {
5347 QString creator = score->metaTag(type);
5348 if (!creator.isEmpty())
5349 xml.tag(QString("creator type=\"%1\"").arg(type), creator);
5350 }
5351
5352 if (!score->metaTag("copyright").isEmpty())
5353 xml.tag("rights", score->metaTag("copyright"));
5354
5355 xml.stag("encoding");
5356
5357 if (MScore::debugMode) {
5358 xml.tag("software", QString("MuseScore 0.7.0"));
5359 xml.tag("encoding-date", QString("2007-09-10"));
5360 }
5361 else {
5362 xml.tag("software", QString("MuseScore ") + QString(VERSION));
5363 xml.tag("encoding-date", QDate::currentDate().toString(Qt::ISODate));
5364 }
5365
5366 // specify supported elements
5367 xml.tagE("supports element=\"accidental\" type=\"yes\"");
5368 xml.tagE("supports element=\"beam\" type=\"yes\"");
5369 // set support for print new-page and new-system to match user preference
5370 // for MusicxmlExportBreaks::MANUAL support is "no" because "yes" breaks Finale NotePad import
5371 if (preferences.getBool(PREF_EXPORT_MUSICXML_EXPORTLAYOUT)
5372 && preferences.musicxmlExportBreaks() == MusicxmlExportBreaks::ALL) {
5373 xml.tagE("supports element=\"print\" attribute=\"new-page\" type=\"yes\" value=\"yes\"");
5374 xml.tagE("supports element=\"print\" attribute=\"new-system\" type=\"yes\" value=\"yes\"");
5375 }
5376 else {
5377 xml.tagE("supports element=\"print\" attribute=\"new-page\" type=\"no\"");
5378 xml.tagE("supports element=\"print\" attribute=\"new-system\" type=\"no\"");
5379 }
5380 xml.tagE("supports element=\"stem\" type=\"yes\"");
5381
5382 xml.etag();
5383
5384 if (!score->metaTag("source").isEmpty())
5385 xml.tag("source", score->metaTag("source"));
5386
5387 xml.etag();
5388 }
5389
5390 //---------------------------------------------------------
5391 // findPartGroupNumber
5392 //---------------------------------------------------------
5393
findPartGroupNumber(int * partGroupEnd)5394 static int findPartGroupNumber(int* partGroupEnd)
5395 {
5396 // find part group number
5397 for (int number = 0; number < MAX_PART_GROUPS; ++number)
5398 if (partGroupEnd[number] == -1)
5399 return number;
5400 qDebug("no free part group number");
5401 return MAX_PART_GROUPS;
5402 }
5403
5404 //---------------------------------------------------------
5405 // scoreInstrument
5406 //---------------------------------------------------------
5407
scoreInstrument(XmlWriter & xml,const int partNr,const int instrNr,const QString & instrName)5408 static void scoreInstrument(XmlWriter& xml, const int partNr, const int instrNr, const QString& instrName)
5409 {
5410 xml.stag(QString("score-instrument %1").arg(instrId(partNr, instrNr)));
5411 xml.tag("instrument-name", instrName);
5412 xml.etag();
5413 }
5414
5415 //---------------------------------------------------------
5416 // midiInstrument
5417 //---------------------------------------------------------
5418
midiInstrument(XmlWriter & xml,const int partNr,const int instrNr,const Instrument * instr,const Score * score,const int unpitched=0)5419 static void midiInstrument(XmlWriter& xml, const int partNr, const int instrNr,
5420 const Instrument* instr, const Score* score, const int unpitched = 0)
5421 {
5422 xml.stag(QString("midi-instrument %1").arg(instrId(partNr, instrNr)));
5423 int midiChannel = score->masterScore()->midiChannel(instr->channel(0)->channel());
5424 if (midiChannel >= 0 && midiChannel < 16)
5425 xml.tag("midi-channel", midiChannel + 1);
5426 int midiProgram = instr->channel(0)->program();
5427 if (midiProgram >= 0 && midiProgram < 128)
5428 xml.tag("midi-program", midiProgram + 1);
5429 if (unpitched > 0)
5430 xml.tag("midi-unpitched", unpitched);
5431 xml.tag("volume", (instr->channel(0)->volume() / 127.0) * 100); //percent
5432 xml.tag("pan", int(((instr->channel(0)->pan() - 63.5) / 63.5) * 90)); //-90 hard left, +90 hard right xml.etag();
5433 xml.etag();
5434 }
5435
5436 //---------------------------------------------------------
5437 // initInstrMap
5438 //---------------------------------------------------------
5439
5440 /**
5441 Initialize the Instrument* to number map for a Part
5442 Used to generate instrument numbers for a multi-instrument part
5443 */
5444
initInstrMap(MxmlInstrumentMap & im,const InstrumentList * il,const Score *)5445 static void initInstrMap(MxmlInstrumentMap& im, const InstrumentList* il, const Score* /*score*/)
5446 {
5447 im.clear();
5448 for (auto i = il->begin(); i != il->end(); ++i) {
5449 const Instrument* pinstr = i->second;
5450 if (!im.contains(pinstr))
5451 im.insert(pinstr, im.size());
5452 }
5453 }
5454
5455 //---------------------------------------------------------
5456 // initReverseInstrMap
5457 //---------------------------------------------------------
5458
5459 typedef QMap<int, const Instrument*> MxmlReverseInstrumentMap;
5460
5461 /**
5462 Initialize the number t Instrument* map for a Part
5463 Used to iterate in sequence over instrument numbers for a multi-instrument part
5464 */
5465
initReverseInstrMap(MxmlReverseInstrumentMap & rim,const MxmlInstrumentMap & im)5466 static void initReverseInstrMap(MxmlReverseInstrumentMap& rim, const MxmlInstrumentMap& im)
5467 {
5468 rim.clear();
5469 for (const Instrument* i : im.keys()) {
5470 int instNr = im.value(i);
5471 rim.insert(instNr, i);
5472 }
5473 }
5474
5475 //---------------------------------------------------------
5476 // hasPageBreak
5477 //---------------------------------------------------------
5478
lastMeasureBase(const System * const system)5479 static MeasureBase* lastMeasureBase(const System* const system)
5480 {
5481 MeasureBase* mb = nullptr;
5482 if (system) {
5483 const auto& measures = system->measures();
5484 Q_ASSERT(!(measures.empty()));
5485 mb = measures.back();
5486 }
5487 return mb;
5488 }
5489
5490 //---------------------------------------------------------
5491 // hasPageBreak
5492 //---------------------------------------------------------
5493
hasPageBreak(const System * const system)5494 static bool hasPageBreak(const System* const system)
5495 {
5496 const MeasureBase* mb = nullptr;
5497 if (system) {
5498 const auto& measures = system->measures();
5499 Q_ASSERT(!(measures.empty()));
5500 mb = measures.back();
5501 }
5502
5503 return mb && mb->pageBreak();
5504 }
5505
5506 //---------------------------------------------------------
5507 // print
5508 //---------------------------------------------------------
5509
5510 /**
5511 Handle the <print> element.
5512 When exporting layout and all breaks, a <print> with layout information
5513 is generated for the first measure in the score, in a system or on a page.
5514 When exporting layout but only manual or no breaks, a <print> with
5515 layout information is generated only for the first measure in the score,
5516 as it is assumed the system layout is broken by the importing application
5517 anyway and is thus useless.
5518
5519 a page break is explicit (manual) if:
5520 - the last system on the previous page has a page break
5521 a system break is explicit (manual) if:
5522 - the previous system in the score has a system or layout break
5523 - if the previous system in the score does not have measures
5524 (i.e. only has (a) frame(s))
5525 */
5526
print(const Measure * const m,const int partNr,const int firstStaffOfPart,const int nrStavesInPart,const MeasurePrintContext & mpc)5527 void ExportMusicXml::print(const Measure* const m, const int partNr, const int firstStaffOfPart, const int nrStavesInPart, const MeasurePrintContext& mpc)
5528 {
5529 const MeasureBase* const prevSysMB = lastMeasureBase(mpc.prevSystem);
5530
5531 const bool prevMeasLineBreak = prevSysMB ? prevSysMB->lineBreak() : false;
5532 const bool prevMeasSectionBreak = prevSysMB ? prevSysMB->sectionBreak() : false;
5533 const bool prevPageBreak = hasPageBreak(mpc.lastSystemPrevPage);
5534
5535 QString newSystemOrPage; // new-[system|page]="yes" or empty
5536 if (!mpc.scoreStart) {
5537 if (preferences.musicxmlExportBreaks() == MusicxmlExportBreaks::ALL) {
5538 if (mpc.pageStart) newSystemOrPage = " new-page=\"yes\"";
5539 else if (mpc.systemStart) newSystemOrPage = " new-system=\"yes\"";
5540 }
5541 else if (preferences.musicxmlExportBreaks() == MusicxmlExportBreaks::MANUAL) {
5542 if (mpc.pageStart && prevPageBreak)
5543 newSystemOrPage = " new-page=\"yes\"";
5544 else if (mpc.systemStart && (prevMeasLineBreak || prevMeasSectionBreak))
5545 newSystemOrPage = " new-system=\"yes\"";
5546 }
5547 }
5548
5549 bool doBreak = mpc.scoreStart || (newSystemOrPage != "");
5550 bool doLayout = preferences.getBool(PREF_EXPORT_MUSICXML_EXPORTLAYOUT);
5551
5552 if (doBreak) {
5553 if (doLayout) {
5554 _xml.stag(QString("print%1").arg(newSystemOrPage));
5555 const double pageWidth = getTenthsFromInches(score()->styleD(Sid::pageWidth));
5556 const double lm = getTenthsFromInches(score()->styleD(Sid::pageOddLeftMargin));
5557 const double rm = getTenthsFromInches(score()->styleD(Sid::pageWidth)
5558 - score()->styleD(Sid::pagePrintableWidth) - score()->styleD(Sid::pageOddLeftMargin));
5559 const double tm = getTenthsFromInches(score()->styleD(Sid::pageOddTopMargin));
5560
5561 // System Layout
5562
5563 // For a multi-meaure rest positioning is valid only
5564 // in the replacing measure
5565 // note: for a normal measure, mmRest1 is the measure itself,
5566 // for a multi-meaure rest, it is the replacing measure
5567 const Measure* mmR1 = m->mmRest1();
5568 const System* system = mmR1->system();
5569
5570 // Put the system print suggestions only for the first part in a score...
5571 if (partNr == 0) {
5572
5573 // Find the right margin of the system.
5574 double systemLM = getTenthsFromDots(mmR1->pagePos().x() - system->page()->pagePos().x()) - lm;
5575 double systemRM = pageWidth - rm - (getTenthsFromDots(system->bbox().width()) + lm);
5576
5577 _xml.stag("system-layout");
5578 _xml.stag("system-margins");
5579 _xml.tag("left-margin", QString("%1").arg(QString::number(systemLM,'f',2)));
5580 _xml.tag("right-margin", QString("%1").arg(QString::number(systemRM,'f',2)) );
5581 _xml.etag();
5582
5583 if (mpc.systemStart && !mpc.pageStart) {
5584 // see System::layout2() for the factor 2 * score()->spatium()
5585 const double sysDist = getTenthsFromDots(mmR1->pagePos().y()
5586 - mpc.prevMeasure->pagePos().y()
5587 - mpc.prevMeasure->bbox().height()
5588 + 2 * score()->spatium()
5589 );
5590 _xml.tag("system-distance",
5591 QString("%1").arg(QString::number(sysDist,'f',2)));
5592 }
5593 if (mpc.pageStart || mpc.scoreStart) {
5594 const double topSysDist = getTenthsFromDots(mmR1->pagePos().y()) - tm;
5595 _xml.tag("top-system-distance", QString("%1").arg(QString::number(topSysDist,'f',2)) );
5596 }
5597
5598 _xml.etag();
5599 }
5600
5601 // Staff layout elements.
5602 for (int staffIdx = (firstStaffOfPart == 0) ? 1 : 0; staffIdx < nrStavesInPart; staffIdx++) {
5603
5604 // calculate distance between this and previous staff using the bounding boxes
5605 const auto staffNr = firstStaffOfPart + staffIdx;
5606 const auto prevBbox = system->staff(staffNr - 1)->bbox();
5607 const auto staffDist = system->staff(staffNr)->bbox().y() - prevBbox.y() - prevBbox.height();
5608
5609 _xml.stag(QString("staff-layout number=\"%1\"").arg(staffIdx + 1));
5610 _xml.tag("staff-distance", QString("%1").arg(QString::number(getTenthsFromDots(staffDist),'f',2)));
5611 _xml.etag();
5612 }
5613
5614 _xml.etag();
5615 }
5616 else if (newSystemOrPage != "") {
5617 _xml.tagE(QString("print%1").arg(newSystemOrPage));
5618 }
5619 }
5620 }
5621
5622 //---------------------------------------------------------
5623 // exportDefaultClef
5624 //---------------------------------------------------------
5625
5626 /**
5627 In case no clef is found, export a default clef with type determined by staff type.
5628 Note that a multi-measure rest starting in the first measure should be handled correctly.
5629 */
5630
exportDefaultClef(const Part * const part,const Measure * const m)5631 void ExportMusicXml::exportDefaultClef(const Part* const part, const Measure* const m)
5632 {
5633 const auto staves = part->nstaves();
5634
5635 if (m->tick() == Fraction(0,1)) {
5636 const auto clefSeg = m->findSegment(SegmentType::HeaderClef, Fraction(0,1));
5637
5638 if (clefSeg) {
5639 for (int i = 0; i < staves; ++i) {
5640
5641 // sstaff - xml staff number, counting from 1 for this
5642 // instrument
5643 // special number 0 -> don’t show staff number in
5644 // xml output (because there is only one staff)
5645
5646 auto sstaff = (staves > 1) ? i + 1 : 0;
5647 auto track = part->startTrack() + VOICES * i;
5648
5649 if (clefSeg->element(track) == nullptr) {
5650 ClefType ct { ClefType::G };
5651 QString stafftype;
5652 switch (part->staff(i)->staffType(Fraction(0,1))->group()) {
5653 case StaffGroup::TAB:
5654 ct = ClefType::TAB;
5655 stafftype = "tab";
5656 break;
5657 case StaffGroup::STANDARD:
5658 ct = ClefType::G;
5659 stafftype = "std";
5660 break;
5661 case StaffGroup::PERCUSSION:
5662 ct = ClefType::PERC;
5663 stafftype = "perc";
5664 break;
5665 }
5666 qDebug("no clef found in first measure track %d (stafftype %s)", track, qPrintable(stafftype));
5667 clef(sstaff, ct, " print-object=\"no\"");
5668 }
5669 }
5670 }
5671 }
5672 }
5673
5674 //---------------------------------------------------------
5675 // findAndExportClef
5676 //---------------------------------------------------------
5677
5678 /**
5679 Make sure clefs at end of measure get exported at start of next measure.
5680 */
5681
findAndExportClef(const Measure * const m,const int staves,const int strack,const int etrack)5682 void ExportMusicXml::findAndExportClef(const Measure* const m, const int staves, const int strack, const int etrack)
5683 {
5684 Measure* prevMeasure = m->prevMeasure();
5685 Measure* mmR = m->mmRest(); // the replacing measure in a multi-measure rest
5686 Fraction tick = m->tick();
5687 Segment* cs1;
5688 Segment* cs2 = m->findSegment(SegmentType::Clef, tick);
5689 Segment* cs3;
5690 Segment* seg = 0;
5691
5692 if (prevMeasure)
5693 cs1 = prevMeasure->findSegment(SegmentType::Clef, tick);
5694 else
5695 cs1 = m->findSegment(SegmentType::HeaderClef, tick);
5696
5697 if (mmR) {
5698 cs3 = mmR->findSegment(SegmentType::HeaderClef, tick);
5699 if (!cs3)
5700 cs3 = mmR->findSegment(SegmentType::Clef, tick);
5701 }
5702 else
5703 cs3 = 0;
5704
5705 if (cs1 && cs2) {
5706 // should only happen at begin of new system
5707 // when previous system ends with a non-generated clef
5708 seg = cs1;
5709 }
5710 else if (cs1)
5711 seg = cs1;
5712 else if (cs3) {
5713 // happens when the first measure is a multi-measure rest
5714 // containing a generated clef
5715 seg = cs3;
5716 }
5717 else
5718 seg = cs2;
5719 clefDebug("exportxml: clef segments cs1=%p cs2=%p cs3=%p seg=%p", cs1, cs2, cs3, seg);
5720
5721 // output attribute at start of measure: clef
5722 if (seg) {
5723 for (int st = strack; st < etrack; st += VOICES) {
5724 // sstaff - xml staff number, counting from 1 for this
5725 // instrument
5726 // special number 0 -> don’t show staff number in
5727 // xml output (because there is only one staff)
5728
5729 int sstaff = (staves > 1) ? st - strack + VOICES : 0;
5730 sstaff /= VOICES;
5731
5732 Clef* cle = static_cast<Clef*>(seg->element(st));
5733 if (cle) {
5734 clefDebug("exportxml: clef at start measure ti=%d ct=%d gen=%d", tick, int(cle->clefType()), cle->generated());
5735 // output only clef changes, not generated clefs at line beginning
5736 // exception: at tick=0, export clef anyway
5737 if ((tick.isZero() || !cle->generated()) && ((seg->measure() != m) || ((seg->segmentType() == SegmentType::HeaderClef) && !cle->otherClef()))) {
5738 clefDebug("exportxml: clef exported");
5739 clef(sstaff, cle->clefType(), color2xml(cle));
5740 }
5741 else {
5742 clefDebug("exportxml: clef not exported");
5743 }
5744 }
5745 }
5746 }
5747 }
5748
5749 //---------------------------------------------------------
5750 // findPitchesUsed
5751 //---------------------------------------------------------
5752
5753 /**
5754 Find the set of pitches actually used in a part.
5755 */
5756
5757 typedef QSet<int> pitchSet; // the set of pitches used
5758
addChordPitchesToSet(const Chord * c,pitchSet & set)5759 static void addChordPitchesToSet(const Chord* c, pitchSet& set)
5760 {
5761 for (const Note* note : c->notes()) {
5762 qDebug("chord %p note %p pitch %d", c, note, note->pitch() + 1);
5763 set.insert(note->pitch());
5764 }
5765 }
5766
findPitchesUsed(const Part * part,pitchSet & set)5767 static void findPitchesUsed(const Part* part, pitchSet& set)
5768 {
5769 int strack = part->startTrack();
5770 int etrack = part->endTrack();
5771
5772 // loop over all chords in the part
5773 for (const MeasureBase* mb = part->score()->measures()->first(); mb; mb = mb->next()) {
5774 if (mb->type() != ElementType::MEASURE)
5775 continue;
5776 const Measure* m = static_cast<const Measure*>(mb);
5777 for (int st = strack; st < etrack; ++st) {
5778 for (Segment* seg = m->first(); seg; seg = seg->next()) {
5779 const Element* el = seg->element(st);
5780 if (!el)
5781 continue;
5782 if (el->type() == ElementType::CHORD)
5783 {
5784 // add grace and non-grace note pitches to the result set
5785 const Chord* c = static_cast<const Chord*>(el);
5786 if (c) {
5787 for (const Chord* g : c->graceNotesBefore()) {
5788 addChordPitchesToSet(g, set);
5789 }
5790 addChordPitchesToSet(c, set);
5791 for (const Chord* g : c->graceNotesAfter()) {
5792 addChordPitchesToSet(g, set);
5793 }
5794 }
5795 }
5796 }
5797 }
5798 }
5799 }
5800
5801 //---------------------------------------------------------
5802 // partList
5803 //---------------------------------------------------------
5804
5805 /**
5806 Write the part list to \a xml.
5807 */
5808
partList(XmlWriter & xml,Score * score,MxmlInstrumentMap & instrMap)5809 static void partList(XmlWriter& xml, Score* score, MxmlInstrumentMap& instrMap)
5810 {
5811 xml.stag("part-list");
5812 int staffCount = 0; // count sum of # staves in parts
5813 const auto& parts = score->parts();
5814 int partGroupEnd[MAX_PART_GROUPS]; // staff where part group ends (bracketSpan is in staves, not parts)
5815 for (int i = 0; i < MAX_PART_GROUPS; i++)
5816 partGroupEnd[i] = -1;
5817 for (int idx = 0; idx < parts.size(); ++idx) {
5818 const auto part = parts.at(idx);
5819 bool bracketFound = false;
5820 // handle brackets
5821 for (int i = 0; i < part->nstaves(); i++) {
5822 Staff* st = part->staff(i);
5823 if (st) {
5824 for (int j = 0; j < st->bracketLevels() + 1; j++) {
5825 if (st->bracketType(j) != BracketType::NO_BRACKET) {
5826 bracketFound = true;
5827 if (i == 0) {
5828 // OK, found bracket in first staff of part
5829 // filter out implicit brackets
5830 if (!(st->bracketSpan(j) == part->nstaves()
5831 && st->bracketType(j) == BracketType::BRACE)) {
5832 // add others
5833 int number = findPartGroupNumber(partGroupEnd);
5834 if (number < MAX_PART_GROUPS) {
5835 partGroupStart(xml, number + 1, st->bracketType(j));
5836 partGroupEnd[number] = staffCount + st->bracketSpan(j);
5837 }
5838 }
5839 }
5840 else {
5841 // bracket in other staff not supported in MusicXML
5842 qDebug("bracket starting in staff %d not supported", i + 1);
5843 }
5844 }
5845 }
5846 }
5847 }
5848 // handle bracket none
5849 if (!bracketFound && part->nstaves() > 1) {
5850 int number = findPartGroupNumber(partGroupEnd);
5851 if (number < MAX_PART_GROUPS) {
5852 partGroupStart(xml, number + 1, BracketType::NO_BRACKET);
5853 partGroupEnd[number] = idx + part->nstaves();
5854 }
5855 }
5856
5857 xml.stag(QString("score-part id=\"P%1\"").arg(idx+1));
5858 initInstrMap(instrMap, part->instruments(), score);
5859 // by default export the parts long name as part-name
5860 if (part->longName() != "")
5861 xml.tag("part-name", MScoreTextToMXML::toPlainText(part->longName()));
5862 else {
5863 if (part->partName() != "") {
5864 // use the track name if no part long name
5865 // to prevent an empty track name on import
5866 xml.tag("part-name print-object=\"no\"", MScoreTextToMXML::toPlainText(part->partName()));
5867 }
5868 else
5869 // part-name is required
5870 xml.tag("part-name", "");
5871 }
5872 if (!part->shortName().isEmpty())
5873 xml.tag("part-abbreviation", MScoreTextToMXML::toPlainText(part->shortName()));
5874
5875 if (part->instrument()->useDrumset()) {
5876 const Drumset* drumset = part->instrument()->drumset();
5877 pitchSet pitches;
5878 findPitchesUsed(part, pitches);
5879 for (int i = 0; i < 128; ++i) {
5880 DrumInstrument di = drumset->drum(i);
5881 if (di.notehead != NoteHead::Group::HEAD_INVALID)
5882 scoreInstrument(xml, idx + 1, i + 1, di.name);
5883 else if (pitches.contains(i))
5884 scoreInstrument(xml, idx + 1, i + 1, QString("Instrument %1").arg(i + 1));
5885 }
5886 int midiPort = part->midiPort() + 1;
5887 if (midiPort >= 1 && midiPort <= 16)
5888 xml.tag(QString("midi-device port=\"%1\"").arg(midiPort), "");
5889
5890 for (int i = 0; i < 128; ++i) {
5891 DrumInstrument di = drumset->drum(i);
5892 if (di.notehead != NoteHead::Group::HEAD_INVALID || pitches.contains(i))
5893 midiInstrument(xml, idx + 1, i + 1, part->instrument(), score, i + 1);
5894 }
5895 }
5896 else {
5897 MxmlReverseInstrumentMap rim;
5898 initReverseInstrMap(rim, instrMap);
5899 for (int instNr : rim.keys()) {
5900 scoreInstrument(xml, idx + 1, instNr + 1, MScoreTextToMXML::toPlainText(rim.value(instNr)->trackName()));
5901 }
5902 for (auto ii = rim.constBegin(); ii != rim.constEnd(); ii++) {
5903 int instNr = ii.key();
5904 int midiPort = part->midiPort() + 1;
5905 if (ii.value()->channel().size() > 0)
5906 midiPort = score->masterScore()->midiMapping(ii.value()->channel(0)->channel())->port() + 1;
5907 if (midiPort >= 1 && midiPort <= 16)
5908 xml.tag(QString("midi-device %1 port=\"%2\"").arg(instrId(idx+1, instNr + 1)).arg(midiPort), "");
5909 else
5910 xml.tag(QString("midi-device %1").arg(instrId(idx+1, instNr + 1)), "");
5911 midiInstrument(xml, idx + 1, instNr + 1, rim.value(instNr), score);
5912 }
5913 }
5914
5915 xml.etag();
5916 staffCount += part->nstaves();
5917 for (int i = MAX_PART_GROUPS - 1; i >= 0; i--) {
5918 int end = partGroupEnd[i];
5919 if (end >= 0) {
5920 if (staffCount >= end) {
5921 xml.tagE(QString("part-group type=\"stop\" number=\"%1\"").arg(i + 1));
5922 partGroupEnd[i] = -1;
5923 }
5924 }
5925 }
5926 }
5927 xml.etag();
5928
5929 }
5930
5931 //---------------------------------------------------------
5932 // tickIsInMiddleOfMeasure
5933 //---------------------------------------------------------
5934
tickIsInMiddleOfMeasure(const Fraction ti,const Measure * m)5935 static bool tickIsInMiddleOfMeasure(const Fraction ti, const Measure* m)
5936 {
5937 return ti != m->tick() && ti != m->endTick();
5938 }
5939
5940 //---------------------------------------------------------
5941 // writeElement
5942 //---------------------------------------------------------
5943
5944 /**
5945 Write \a el.
5946 */
5947
writeElement(Element * el,const Measure * m,int sstaff,bool useDrumset)5948 void ExportMusicXml::writeElement(Element* el, const Measure* m, int sstaff, bool useDrumset)
5949 {
5950 if (el->isClef()) {
5951 // output only clef changes, not generated clefs
5952 // at line beginning
5953 // also ignore clefs at the start of a measure,
5954 // these have already been output
5955 // also ignore clefs at the end of a measure
5956 // these will be output at the start of the next measure
5957 const auto cle = toClef(el);
5958 const auto ti = cle->segment()->tick();
5959 clefDebug("exportxml: clef in measure ti=%d ct=%d gen=%d", ti, int(cle->clefType()), el->generated());
5960 if (el->generated()) {
5961 clefDebug("exportxml: generated clef not exported");
5962 }
5963 else if (!el->generated() && tickIsInMiddleOfMeasure(ti, m))
5964 clef(sstaff, cle->clefType(), color2xml(cle));
5965 else if (!el->generated() && (ti == m->tick()) && (cle->segment()->segmentType() != SegmentType::HeaderClef))
5966 clef(sstaff, cle->clefType(), color2xml(cle) + QString(" after-barline=\"yes\""));
5967 else
5968 clefDebug("exportxml: clef not exported");
5969 }
5970 else if (el->isChord()) {
5971 const auto c = toChord(el);
5972 // ise grace after
5973 if (c) {
5974 const auto ll = &c->lyrics();
5975 for (const auto g : c->graceNotesBefore()) {
5976 chord(g, sstaff, ll, useDrumset);
5977 }
5978 chord(c, sstaff, ll, useDrumset);
5979 for (const auto g : c->graceNotesAfter()) {
5980 chord(g, sstaff, ll, useDrumset);
5981 }
5982 }
5983 }
5984 else if (el->isRest()) {
5985 const auto r = toRest(el);
5986 if (!(r->isGap()))
5987 rest(r, sstaff);
5988 }
5989 else if (el->isBarLine()) {
5990 const auto barln = toBarLine(el);
5991 if (tickIsInMiddleOfMeasure(barln->tick(), m))
5992 barlineMiddle(barln);
5993 }
5994 else if (el->isKeySig() || el->isTimeSig() || el->isBreath()) {
5995 // handled elsewhere
5996 }
5997 else
5998 qDebug("ExportMusicXml::write unknown segment type %s", el->name());
5999 }
6000
6001 //---------------------------------------------------------
6002 // writeStaffDetails
6003 //---------------------------------------------------------
6004
6005 /**
6006 Write the staff details for \a part to \a xml.
6007 */
6008
writeStaffDetails(XmlWriter & xml,const Part * part)6009 static void writeStaffDetails(XmlWriter& xml, const Part* part)
6010 {
6011 const Instrument* instrument = part->instrument();
6012 int staves = part->nstaves();
6013
6014 // staff details
6015 // TODO: decide how to handle linked regular / TAB staff
6016 // currently exported as a two staff part ...
6017 for (int i = 0; i < staves; i++) {
6018 Staff* st = part->staff(i);
6019 if (st->lines(Fraction(0,1)) != 5 || st->isTabStaff(Fraction(0,1))) {
6020 if (staves > 1)
6021 xml.stag(QString("staff-details number=\"%1\"").arg(i+1));
6022 else
6023 xml.stag("staff-details");
6024 xml.tag("staff-lines", st->lines(Fraction(0,1)));
6025 if (st->isTabStaff(Fraction(0,1)) && instrument->stringData()) {
6026 QList<instrString> l = instrument->stringData()->stringList();
6027 for (int ii = 0; ii < l.size(); ii++) {
6028 char step = ' ';
6029 int alter = 0;
6030 int octave = 0;
6031 midipitch2xml(l.at(ii).pitch, step, alter, octave);
6032 xml.stag(QString("staff-tuning line=\"%1\"").arg(ii+1));
6033 xml.tag("tuning-step", QString("%1").arg(step));
6034 if (alter)
6035 xml.tag("tuning-alter", alter);
6036 xml.tag("tuning-octave", octave);
6037 xml.etag();
6038 }
6039 }
6040 xml.etag();
6041 }
6042 }
6043 }
6044
6045 //---------------------------------------------------------
6046 // writeInstrumentDetails
6047 //---------------------------------------------------------
6048
6049 /**
6050 Write the instrument details for \a instrument.
6051 */
6052
writeInstrumentDetails(const Instrument * instrument)6053 void ExportMusicXml::writeInstrumentDetails(const Instrument* instrument)
6054 {
6055 if (instrument->transpose().chromatic) {
6056 _attr.doAttr(_xml, true);
6057 _xml.stag("transpose");
6058 _xml.tag("diatonic", instrument->transpose().diatonic % 7);
6059 _xml.tag("chromatic", instrument->transpose().chromatic % 12);
6060 int octaveChange = instrument->transpose().chromatic / 12;
6061 if (octaveChange != 0)
6062 _xml.tag("octave-change", octaveChange);
6063 _xml.etag();
6064 _attr.doAttr(_xml, false);
6065 }
6066 }
6067
6068 //---------------------------------------------------------
6069 // annotationsWithoutNote
6070 //---------------------------------------------------------
6071
6072 /**
6073 Write the annotations that could not be attached to notes.
6074 */
6075
annotationsWithoutNote(ExportMusicXml * exp,const int strack,const int staves,const Measure * const measure)6076 static void annotationsWithoutNote(ExportMusicXml* exp, const int strack, const int staves, const Measure* const measure)
6077 {
6078 for (auto segment = measure->first(); segment; segment = segment->next()) {
6079 if (segment->segmentType() == SegmentType::ChordRest) {
6080 for (const auto element : segment->annotations()) {
6081 if (!element->isFiguredBass() && !element->isHarmony()) { // handled elsewhere
6082 const auto wtrack = findTrackForAnnotations(element->track(), segment); // track to write annotation
6083 if (strack <= element->track() && element->track() < (strack + VOICES * staves) && wtrack < 0)
6084 commonAnnotations(exp, element, staves > 1 ? 1 : 0);
6085 }
6086 }
6087 }
6088
6089 }
6090 }
6091
6092 //---------------------------------------------------------
6093 // MeasureNumberStateHandler
6094 //---------------------------------------------------------
6095
MeasureNumberStateHandler()6096 MeasureNumberStateHandler::MeasureNumberStateHandler()
6097 {
6098 init();
6099 }
6100
init()6101 void MeasureNumberStateHandler::init()
6102 {
6103 _measureNo = 1;
6104 _irregularMeasureNo = 1;
6105 _pickupMeasureNo = 1;
6106 }
6107
6108
updateForMeasure(const Measure * const m)6109 void MeasureNumberStateHandler::updateForMeasure(const Measure* const m)
6110 {
6111 // restart measure numbering after a section break if startWithMeasureOne is set
6112 // check the previous MeasureBase instead of Measure to catch breaks in frames too
6113 const MeasureBase* previousMB = m->prev();
6114 if (previousMB)
6115 previousMB = previousMB->findPotentialSectionBreak();
6116
6117 if (previousMB) {
6118 const auto layoutSectionBreak = previousMB->sectionBreakElement();
6119 if (layoutSectionBreak && layoutSectionBreak->startWithMeasureOne())
6120 init();
6121 }
6122
6123 // update measure numbers and cache result
6124 _cachedAttributes = " number=";
6125 if ((_irregularMeasureNo + _measureNo) == 2 && m->irregular()) {
6126 _cachedAttributes += "\"0\" implicit=\"yes\"";
6127 _pickupMeasureNo++;
6128 }
6129 else if (m->irregular())
6130 _cachedAttributes += QString("\"X%1\" implicit=\"yes\"").arg(_irregularMeasureNo++);
6131 else
6132 _cachedAttributes += QString("\"%1\"").arg(_measureNo++);
6133 }
6134
measureNumber() const6135 QString MeasureNumberStateHandler::measureNumber() const
6136 {
6137 return _cachedAttributes;
6138 }
6139
isFirstActualMeasure() const6140 bool MeasureNumberStateHandler::isFirstActualMeasure() const
6141 {
6142 return (_irregularMeasureNo + _measureNo + _pickupMeasureNo) == 4;
6143 }
6144
6145 //---------------------------------------------------------
6146 // findLastSystemWithMeasures
6147 //---------------------------------------------------------
6148
findLastSystemWithMeasures(const Page * const page)6149 static System* findLastSystemWithMeasures(const Page* const page)
6150 {
6151 for (int i = page->systems().size() - 1; i >= 0; --i) {
6152 const auto s = page->systems().at(i);
6153 const auto m = s->firstMeasure();
6154 if (m)
6155 return s;
6156 }
6157 return nullptr;
6158 }
6159
6160 //---------------------------------------------------------
6161 // isFirstMeasureInSystem
6162 //---------------------------------------------------------
6163
isFirstMeasureInSystem(const Measure * const measure)6164 static bool isFirstMeasureInSystem(const Measure* const measure)
6165 {
6166 const auto system = measure->mmRest1()->system();
6167 const auto firstMeasureInSystem = system->firstMeasure();
6168 const auto realFirstMeasureInSystem = firstMeasureInSystem->isMMRest() ? firstMeasureInSystem->mmRestFirst() : firstMeasureInSystem;
6169 return measure == realFirstMeasureInSystem;
6170 }
6171 //---------------------------------------------------------
6172 // isFirstMeasureInLastSystem
6173 //---------------------------------------------------------
6174
isFirstMeasureInLastSystem(const Measure * const measure)6175 static bool isFirstMeasureInLastSystem(const Measure* const measure)
6176 {
6177 const auto system = measure->mmRest1()->system();
6178 const auto page = system->page();
6179
6180 /*
6181 Notes on multi-measure rest handling:
6182 Function mmRest1() returns either the measure itself (if not part of multi-measure rest)
6183 or the replacing multi-measure rest measure.
6184 Using this is required as a measure that is covered by a multi-measure rest has no system.
6185 Furthermore, the first measure in a system starting with a multi-measure rest is the a multi-
6186 measure rest itself instead of the first covered measure.
6187 */
6188
6189 const auto lastSystem = findLastSystemWithMeasures(page);
6190 if (!lastSystem)
6191 return false; // degenerate case: no system with measures found
6192 const auto firstMeasureInLastSystem = lastSystem->firstMeasure();
6193 const auto realFirstMeasureInLastSystem = firstMeasureInLastSystem->isMMRest() ? firstMeasureInLastSystem->mmRestFirst() : firstMeasureInLastSystem;
6194 return measure == realFirstMeasureInLastSystem;
6195 }
6196
6197 //---------------------------------------------------------
6198 // systemHasMeasures
6199 //---------------------------------------------------------
6200
systemHasMeasures(const System * const system)6201 static bool systemHasMeasures(const System* const system)
6202 {
6203 return system->firstMeasure();
6204 }
6205
6206 //---------------------------------------------------------
6207 // findTextFramesToWriteAsWordsAbove
6208 //---------------------------------------------------------
6209
findTextFramesToWriteAsWordsAbove(const Measure * const measure)6210 static std::vector<TBox*> findTextFramesToWriteAsWordsAbove(const Measure* const measure)
6211 {
6212 const auto system = measure->mmRest1()->system();
6213 const auto page = system->page();
6214 const auto systemIndex = page->systems().indexOf(system);
6215 std::vector<TBox*> tboxes;
6216 if (isFirstMeasureInSystem(measure)) {
6217 for (auto idx = systemIndex - 1; idx >= 0 && !systemHasMeasures(page->system(idx)); --idx) {
6218 const auto sys = page->system(idx);
6219 for (const auto mb : sys->measures()) {
6220 if (mb->isTBox()) {
6221 auto tbox = toTBox(mb);
6222 tboxes.insert(tboxes.begin(), tbox);
6223 }
6224 }
6225 }
6226 }
6227 return tboxes;
6228 }
6229
6230 //---------------------------------------------------------
6231 // findTextFramesToWriteAsWordsBelow
6232 //---------------------------------------------------------
6233
findTextFramesToWriteAsWordsBelow(const Measure * const measure)6234 static std::vector<TBox*> findTextFramesToWriteAsWordsBelow(const Measure* const measure)
6235 {
6236 const auto system = measure->mmRest1()->system();
6237 const auto page = system->page();
6238 const auto systemIndex = page->systems().indexOf(system);
6239 std::vector<TBox*> tboxes;
6240 if (isFirstMeasureInLastSystem(measure)) {
6241 for (auto idx = systemIndex + 1; idx < page->systems().size() /* && !systemHasMeasures(page->system(idx))*/; ++idx) {
6242 const auto sys = page->system(idx);
6243 for (const auto mb : sys->measures()) {
6244 if (mb->isTBox()) {
6245 auto tbox = toTBox(mb);
6246 tboxes.insert(tboxes.begin(), tbox);
6247 }
6248 }
6249 }
6250 }
6251 return tboxes;
6252 }
6253
6254 //---------------------------------------------------------
6255 // writeMeasureTracks
6256 //---------------------------------------------------------
6257
6258 /**
6259 Write data contained in the measure's tracks.
6260 */
6261
writeMeasureTracks(const Measure * const m,const int partIndex,const int strack,const int staves,const bool useDrumset,FigBassMap & fbMap,QSet<const Spanner * > & spannersStopped)6262 void ExportMusicXml::writeMeasureTracks(const Measure* const m,
6263 const int partIndex,
6264 const int strack, const int staves, // TODO remove ??
6265 const bool useDrumset,
6266 FigBassMap& fbMap,
6267 QSet<const Spanner*>& spannersStopped)
6268 {
6269 bool tboxesAboveWritten = false;
6270 const auto tboxesAbove = findTextFramesToWriteAsWordsAbove(m);
6271
6272 bool tboxesBelowWritten = false;
6273 const auto tboxesBelow = findTextFramesToWriteAsWordsBelow(m);
6274
6275 const int etrack = strack + VOICES * staves;
6276
6277 for (int st = strack; st < etrack; ++st) {
6278 // sstaff - xml staff number, counting from 1 for this
6279 // instrument
6280 // special number 0 -> don’t show staff number in
6281 // xml output (because there is only one staff)
6282
6283 int sstaff = (staves > 1) ? st - strack + VOICES : 0;
6284 sstaff /= VOICES;
6285 for (auto seg = m->first(); seg; seg = seg->next()) {
6286 const auto el = seg->element(st);
6287 if (!el) {
6288 continue;
6289 }
6290 // must ignore start repeat to prevent spurious backup/forward
6291 if (el->isBarLine() && toBarLine(el)->barLineType() == BarLineType::START_REPEAT)
6292 continue;
6293
6294 // generate backup or forward to the start time of the element
6295 if (_tick != seg->tick()) {
6296 _attr.doAttr(_xml, false);
6297 moveToTick(seg->tick());
6298 }
6299
6300 // handle annotations and spanners (directions attached to this note or rest)
6301 if (el->isChordRest()) {
6302 _attr.doAttr(_xml, false);
6303 const bool isFirstPart = (partIndex == 0);
6304 const bool isLastPart = (partIndex == (_score->parts().size() - 1));
6305 if (!tboxesAboveWritten && isFirstPart) {
6306 for (const auto tbox : tboxesAbove) {
6307 // note: use mmRest1() to get at a possible multi-measure rest,
6308 // as the covered measure would be positioned at 0,0.
6309 tboxTextAsWords(tbox->text(), 0, tbox->text()->canvasPos() - m->mmRest1()->canvasPos());
6310 }
6311 tboxesAboveWritten = true;
6312 }
6313 if (!tboxesBelowWritten && isLastPart && (etrack - VOICES) <= st) {
6314 for (const auto tbox : tboxesBelow) {
6315 const auto lastStaffNr = st / VOICES;
6316 const auto sys = m->mmRest1()->system();
6317 auto textPos = tbox->text()->canvasPos() - m->mmRest1()->canvasPos();
6318 if (lastStaffNr < sys->staves()->size()) {
6319 // convert to position relative to last staff of system
6320 textPos.setY(textPos.y() - (sys->staffCanvasYpage(lastStaffNr) - sys->staffCanvasYpage(0)));
6321 }
6322 tboxTextAsWords(tbox->text(), sstaff, textPos);
6323 }
6324 tboxesBelowWritten = true;
6325 }
6326 harmonies(this, st, seg, div);
6327 annotations(this, strack, etrack, st, sstaff, seg);
6328 figuredBass(_xml, strack, etrack, st, static_cast<const ChordRest*>(el), fbMap, div);
6329 spannerStart(this, strack, etrack, st, sstaff, seg);
6330 }
6331
6332 // write element el if necessary
6333 writeElement(el, m, sstaff, useDrumset);
6334
6335 // handle annotations and spanners (directions attached to this note or rest)
6336 if (el->isChordRest()) {
6337 const int spannerStaff = st / VOICES;
6338 const int starttrack = spannerStaff * VOICES;
6339 const int endtrack = (spannerStaff + 1) * VOICES;
6340 spannerStop(this, starttrack, endtrack, _tick, sstaff, spannersStopped);
6341 }
6342
6343 } // for (Segment* seg = ...
6344 _attr.stop(_xml);
6345 } // for (int st = ...
6346 }
6347
6348 //---------------------------------------------------------
6349 // writeMeasure
6350 //---------------------------------------------------------
6351
6352 /**
6353 Write a measure.
6354 */
6355
writeMeasure(const Measure * const m,const int partIndex,const int staffCount,MeasureNumberStateHandler & mnsh,FigBassMap & fbMap,const MeasurePrintContext & mpc,QSet<const Spanner * > & spannersStopped)6356 void ExportMusicXml::writeMeasure(const Measure* const m,
6357 const int partIndex,
6358 const int staffCount,
6359 MeasureNumberStateHandler& mnsh,
6360 FigBassMap& fbMap,
6361 const MeasurePrintContext& mpc,
6362 QSet<const Spanner*>& spannersStopped)
6363 {
6364 const auto part = _score->parts().at(partIndex);
6365 const int staves = part->nstaves();
6366 const int strack = part->startTrack();
6367 const int etrack = part->endTrack();
6368
6369 // pickup and other irregular measures need special care
6370 QString measureTag = "measure";
6371 mnsh.updateForMeasure(m);
6372 measureTag += mnsh.measureNumber();
6373 const bool isFirstActualMeasure = mnsh.isFirstActualMeasure();
6374
6375 if (preferences.getBool(PREF_EXPORT_MUSICXML_EXPORTLAYOUT))
6376 measureTag += QString(" width=\"%1\"").arg(QString::number(m->bbox().width() / DPMM / millimeters * tenths,'f',2));
6377
6378 _xml.stag(measureTag);
6379
6380 print(m, partIndex, staffCount, staves, mpc);
6381
6382 _attr.start();
6383
6384 findTrills(m, strack, etrack, _trillStart, _trillStop);
6385
6386 // barline left must be the first element in a measure
6387 barlineLeft(m);
6388
6389 // output attributes with the first actual measure (pickup or regular)
6390 if (isFirstActualMeasure) {
6391 _attr.doAttr(_xml, true);
6392 _xml.tag("divisions", MScore::division / div);
6393 }
6394
6395 // output attributes at start of measure: key, time
6396 keysigTimesig(m, part);
6397
6398 // output attributes with the first actual measure (pickup or regular) only
6399 if (isFirstActualMeasure) {
6400 if (staves > 1)
6401 _xml.tag("staves", staves);
6402 if (instrMap.size() > 1)
6403 _xml.tag("instruments", instrMap.size());
6404 }
6405
6406 // make sure clefs at end of measure get exported at start of next measure
6407 findAndExportClef(m, staves, strack, etrack);
6408
6409 // make sure a clef gets exported if none is found
6410 exportDefaultClef(part, m);
6411
6412 // output attributes with the first actual measure (pickup or regular) only
6413 if (isFirstActualMeasure) {
6414 writeStaffDetails(_xml, part);
6415 writeInstrumentDetails(part->instrument());
6416 }
6417
6418 // output attribute at start of measure: measure-style
6419 measureStyle(_xml, _attr, m);
6420
6421 // MuseScore limitation: repeats are always in the first part
6422 // and are implicitly placed at either measure start or stop
6423 if (partIndex == 0)
6424 repeatAtMeasureStart(_xml, _attr, m, strack, etrack, strack);
6425
6426 // write data in the tracks
6427 writeMeasureTracks(m, partIndex, strack, staves, part->instrument()->useDrumset(), fbMap, spannersStopped);
6428
6429 // write the annotations that could not be attached to notes
6430 annotationsWithoutNote(this, strack, staves, m);
6431
6432 // move to end of measure (in case of incomplete last voice)
6433 #ifdef DEBUG_TICK
6434 qDebug("end of measure");
6435 #endif
6436 moveToTick(m->endTick());
6437 if (partIndex == 0)
6438 repeatAtMeasureStop(_xml, m, strack, etrack, strack);
6439 // note: don't use "m->repeatFlags() & Repeat::END" here, because more
6440 // barline types need to be handled besides repeat end ("light-heavy")
6441 barlineRight(m, strack, etrack);
6442 _xml.etag();
6443 }
6444
6445 //---------------------------------------------------------
6446 // measureWritten
6447 //---------------------------------------------------------
6448
measureWritten(const Measure * m)6449 void MeasurePrintContext::measureWritten(const Measure* m)
6450 {
6451 scoreStart = false;
6452 pageStart = false;
6453 systemStart = false;
6454 prevMeasure = m;
6455 }
6456
6457 //---------------------------------------------------------
6458 // writeParts
6459 //---------------------------------------------------------
6460
6461 /**
6462 Write all parts.
6463 */
6464
writeParts()6465 void ExportMusicXml::writeParts()
6466 {
6467 int staffCount = 0;
6468 const auto& parts = _score->parts();
6469
6470 for (int partIndex = 0; partIndex < parts.size(); ++partIndex) {
6471 const auto part = parts.at(partIndex);
6472 _tick = { 0,1 };
6473 _xml.stag(QString("part id=\"P%1\"").arg(partIndex+1));
6474
6475 _trillStart.clear();
6476 _trillStop.clear();
6477 initInstrMap(instrMap, part->instruments(), _score);
6478
6479 MeasureNumberStateHandler mnsh;
6480 FigBassMap fbMap; // pending figured bass extends
6481
6482 // set of spanners already stopped in this part
6483 // required to prevent multiple spanner stops for the same spanner
6484 QSet<const Spanner*> spannersStopped;
6485
6486 const auto& pages = _score->pages();
6487 MeasurePrintContext mpc;
6488
6489 for (int pageIndex = 0; pageIndex < pages.size(); ++pageIndex) {
6490 const auto page = pages.at(pageIndex);
6491 mpc.pageStart = true;
6492 const auto& systems = page->systems();
6493
6494 for (int systemIndex = 0; systemIndex < systems.size(); ++systemIndex) {
6495 const auto system = systems.at(systemIndex);
6496 mpc.systemStart = true;
6497
6498 for (const auto mb : system->measures()) {
6499 if (!mb->isMeasure())
6500 continue;
6501 const auto m = toMeasure(mb);
6502
6503 // write the measure or, in case of a multi measure rest,
6504 // the measure range it replaces
6505 if (m->isMMRest()) {
6506 auto m1 = m->mmRestFirst();
6507 const auto m2 = m->mmRestLast();
6508 for (;; ) {
6509 if (m1->isMeasure()) {
6510 writeMeasure(m1, partIndex, staffCount, mnsh, fbMap, mpc, spannersStopped);
6511 mpc.measureWritten(m1);
6512 }
6513 if (m1 == m2)
6514 break;
6515 m1 = m1->nextMeasure();
6516 }
6517 }
6518 else {
6519 writeMeasure(m, partIndex, staffCount, mnsh, fbMap, mpc, spannersStopped);
6520 mpc.measureWritten(m);
6521 }
6522
6523 }
6524 mpc.prevSystem = system;
6525 }
6526 mpc.lastSystemPrevPage = mpc.prevSystem;
6527 }
6528
6529 staffCount += part->nstaves();
6530 _xml.etag();
6531 }
6532 }
6533
6534 //---------------------------------------------------------
6535 // write
6536 //---------------------------------------------------------
6537
6538 /**
6539 Write the score to \a dev in MusicXML format.
6540 */
6541
write(QIODevice * dev)6542 void ExportMusicXml::write(QIODevice* dev)
6543 {
6544 // must export in transposed pitch to prevent
6545 // losing the transposition information
6546 // if necessary, switch concert pitch mode off
6547 // before export and restore it after export
6548 bool concertPitch = score()->styleB(Sid::concertPitch);
6549 if (concertPitch) {
6550 score()->startCmd();
6551 score()->undo(new ChangeStyleVal(score(), Sid::concertPitch, false));
6552 score()->doLayout(); // this is only allowed in a cmd context to not corrupt the undo/redo stack
6553 }
6554
6555 calcDivisions();
6556
6557 for (int i = 0; i < MAX_NUMBER_LEVEL; ++i) {
6558 brackets[i] = nullptr;
6559 dashes[i] = nullptr;
6560 hairpins[i] = nullptr;
6561 ottavas[i] = nullptr;
6562 trills[i] = nullptr;
6563 }
6564
6565 _xml.setDevice(dev);
6566 _xml.setCodec("UTF-8");
6567 _xml << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
6568 _xml << "<!DOCTYPE score-partwise PUBLIC \"-//Recordare//DTD MusicXML 3.1 Partwise//EN\" \"http://www.musicxml.org/dtds/partwise.dtd\">\n";
6569
6570 _xml.stag("score-partwise version=\"3.1\"");
6571
6572 work(_score->measures()->first());
6573 identification(_xml, _score);
6574
6575 if (preferences.getBool(PREF_EXPORT_MUSICXML_EXPORTLAYOUT)) {
6576 defaults(_xml, _score, millimeters, tenths);
6577 credits(_xml);
6578 }
6579
6580 partList(_xml, _score, instrMap);
6581 writeParts();
6582
6583 _xml.etag();
6584
6585 if (concertPitch) {
6586 // restore concert pitch
6587 score()->endCmd(true); // rollback
6588 }
6589 }
6590
6591 //---------------------------------------------------------
6592 // saveXml
6593 // return false on error
6594 //---------------------------------------------------------
6595
6596 /**
6597 Save Score as MusicXML file \a name.
6598
6599 Return false on error.
6600 */
6601
saveXml(Score * score,QIODevice * device)6602 bool saveXml(Score* score, QIODevice* device)
6603 {
6604 ExportMusicXml em(score);
6605 em.write(device);
6606 return true;
6607 }
6608
saveXml(Score * score,const QString & name)6609 bool saveXml(Score* score, const QString& name)
6610 {
6611 QFile f(name);
6612 if (!f.open(QIODevice::WriteOnly))
6613 return false;
6614
6615 bool res = saveXml(score, &f) && (f.error() == QFile::NoError);
6616 f.close();
6617 return res;
6618 }
6619
6620 //---------------------------------------------------------
6621 // saveMxl
6622 // return false on error
6623 //---------------------------------------------------------
6624
6625 /**
6626 Save Score as compressed MusicXML file \a name.
6627
6628 Return false on error.
6629 */
6630
6631 // META-INF/container.xml:
6632 // <?xml version="1.0" encoding="UTF-8"?>
6633 // <container>
6634 // <rootfiles>
6635 // <rootfile full-path="testHello.xml"/>
6636 // </rootfiles>
6637 // </container>
6638
writeMxlArchive(Score * score,MQZipWriter & zipwriter,const QString & filename)6639 static void writeMxlArchive(Score* score, MQZipWriter& zipwriter, const QString& filename)
6640 {
6641 QBuffer cbuf;
6642 cbuf.open(QIODevice::ReadWrite);
6643
6644 XmlWriter xml(score);
6645 xml.setDevice(&cbuf);
6646 xml.setCodec("UTF-8");
6647 xml << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
6648 xml.stag("container");
6649 xml.stag("rootfiles");
6650 xml.stag(QString("rootfile full-path=\"%1\"").arg(filename));
6651 xml.etag();
6652 xml.etag();
6653 xml.etag();
6654 cbuf.seek(0);
6655
6656 //uz.addDirectory("META-INF");
6657 zipwriter.addFile("META-INF/container.xml", cbuf.data());
6658
6659 QBuffer dbuf;
6660 dbuf.open(QIODevice::ReadWrite);
6661 ExportMusicXml em(score);
6662 em.write(&dbuf);
6663 dbuf.seek(0);
6664 zipwriter.addFile(filename, dbuf.data());
6665 }
6666
saveMxl(Score * score,QIODevice * device)6667 bool saveMxl(Score* score, QIODevice* device)
6668 {
6669 MQZipWriter uz(device);
6670
6671 //anonymized filename since we don't know the actual one here
6672 QString fn = "score.xml";
6673 writeMxlArchive(score, uz, fn);
6674 uz.close();
6675
6676 return true;
6677 }
6678
saveMxl(Score * score,const QString & name)6679 bool saveMxl(Score* score, const QString& name)
6680 {
6681 MQZipWriter uz(name);
6682
6683 QFileInfo fi(name);
6684 QString fn = fi.completeBaseName() + ".xml";
6685 writeMxlArchive(score, uz, fn);
6686
6687 return true;
6688 }
6689
getTenthsFromInches(double inches) const6690 double ExportMusicXml::getTenthsFromInches(double inches) const
6691 {
6692 return inches * INCH / millimeters * tenths;
6693 }
6694
getTenthsFromDots(double dots) const6695 double ExportMusicXml::getTenthsFromDots(double dots) const
6696 {
6697 return dots / DPMM / millimeters * tenths;
6698 }
6699
6700 //---------------------------------------------------------
6701 // harmony
6702 //---------------------------------------------------------
6703
harmony(Harmony const * const h,FretDiagram const * const fd,int offset)6704 void ExportMusicXml::harmony(Harmony const* const h, FretDiagram const* const fd, int offset)
6705 {
6706 // this code was probably in place to allow chord symbols shifted *right* to export with offset
6707 // since this was at once time the only way to get a chord to appear over beat 3 in an empty 4/4 measure
6708 // but the value was calculated incorrectly (should be divided by spatium) and would be better off using offset anyhow
6709 // since we now support placement of chord symbols over "empty" beats directly,
6710 // and we don't generally export position info for other elements
6711 // it's just as well to not bother doing so here
6712 //double rx = h->offset().x()*10;
6713 //QString relative;
6714 //if (rx > 0) {
6715 // relative = QString(" relative-x=\"%1\"").arg(QString::number(rx,'f',2));
6716 // }
6717 int rootTpc = h->rootTpc();
6718 if (rootTpc != Tpc::TPC_INVALID) {
6719 QString tagName = "harmony";
6720 bool frame = h->hasFrame();
6721 tagName += QString(" print-frame=\"%1\"").arg(frame ? "yes" : "no"); // .append(relative));
6722 tagName += color2xml(h);
6723 _xml.stag(tagName);
6724 _xml.stag("root");
6725 _xml.tag("root-step", tpc2stepName(rootTpc));
6726 int alter = int(tpc2alter(rootTpc));
6727 if (alter)
6728 _xml.tag("root-alter", alter);
6729 _xml.etag();
6730
6731 if (!h->xmlKind().isEmpty()) {
6732 QString s = "kind";
6733 QString kindText = h->musicXmlText();
6734 if (h->musicXmlText() != "")
6735 s += " text=\"" + kindText + "\"";
6736 if (h->xmlSymbols() == "yes")
6737 s += " use-symbols=\"yes\"";
6738 if (h->xmlParens() == "yes")
6739 s += " parentheses-degrees=\"yes\"";
6740 _xml.tag(s, h->xmlKind());
6741 QStringList l = h->xmlDegrees();
6742 if (!l.isEmpty()) {
6743 for (QString tag : qAsConst(l)) {
6744 QString degreeText;
6745 if (h->xmlKind().startsWith("suspended")
6746 && tag.startsWith("add") && tag[3].isDigit()
6747 && !kindText.isEmpty() && kindText[0].isDigit()) {
6748 // hack to correct text for suspended chords whose kind text has degree information baked in
6749 // (required by some other applications)
6750 int tagDegree = tag.midRef(3).toInt();
6751 QString kindTextExtension;
6752 for (int i = 0; i < kindText.length() && kindText[i].isDigit(); ++i)
6753 kindTextExtension[i] = kindText[i];
6754 int kindExtension = kindTextExtension.toInt();
6755 if (tagDegree <= kindExtension && (tagDegree & 1) && (kindExtension & 1))
6756 degreeText = " text=\"\"";
6757 }
6758 _xml.stag("degree");
6759 alter = 0;
6760 int idx = 3;
6761 if (tag[idx] == '#') {
6762 alter = 1;
6763 ++idx;
6764 }
6765 else if (tag[idx] == 'b') {
6766 alter = -1;
6767 ++idx;
6768 }
6769 _xml.tag(QString("degree-value%1").arg(degreeText), tag.mid(idx));
6770 _xml.tag("degree-alter", alter); // finale insists on this even if 0
6771 if (tag.startsWith("add"))
6772 _xml.tag(QString("degree-type%1").arg(degreeText), "add");
6773 else if (tag.startsWith("sub"))
6774 _xml.tag("degree-type", "subtract");
6775 else if (tag.startsWith("alt"))
6776 _xml.tag("degree-type", "alter");
6777 _xml.etag();
6778 }
6779 }
6780 }
6781 else {
6782 if (h->extensionName() == 0)
6783 _xml.tag("kind", "");
6784 else
6785 _xml.tag(QString("kind text=\"%1\"").arg(h->extensionName()), "");
6786 }
6787
6788 int baseTpc = h->baseTpc();
6789 if (baseTpc != Tpc::TPC_INVALID) {
6790 _xml.stag("bass");
6791 _xml.tag("bass-step", tpc2stepName(baseTpc));
6792 alter = int(tpc2alter(baseTpc));
6793 if (alter) {
6794 _xml.tag("bass-alter", alter);
6795 }
6796 _xml.etag();
6797 }
6798 if (offset > 0)
6799 _xml.tag("offset", offset);
6800 if (fd)
6801 fd->writeMusicXML(_xml);
6802
6803 _xml.etag();
6804 }
6805 else {
6806 //
6807 // export an unrecognized Chord
6808 // which may contain arbitrary text
6809 //
6810 if (h->hasFrame())
6811 _xml.stag(QString("harmony print-frame=\"yes\"")); // .append(relative));
6812 else
6813 _xml.stag(QString("harmony print-frame=\"no\"")); // .append(relative));
6814 const auto textNameEscaped = h->hTextName().toHtmlEscaped();
6815 switch (h->harmonyType()) {
6816 case HarmonyType::NASHVILLE: {
6817 _xml.tag("function", h->hFunction());
6818 QString k = "kind text=\"" + textNameEscaped + "\"";
6819 _xml.tag(k, "none");
6820 }
6821 break;
6822 case HarmonyType::ROMAN: {
6823 // TODO: parse?
6824 _xml.tag("function", h->hTextName()); // note: HTML escape done by tag()
6825 QString k = "kind text=\"\"";
6826 _xml.tag(k, "none");
6827 }
6828 break;
6829 case HarmonyType::STANDARD:
6830 default: {
6831 _xml.stag("root");
6832 _xml.tag("root-step text=\"\"", "C");
6833 _xml.etag(); // root
6834 QString k = "kind text=\"" + textNameEscaped + "\"";
6835 _xml.tag(k, "none");
6836 }
6837 break;
6838 }
6839 _xml.etag(); // harmony
6840 #if 0
6841 // prior to 2.0, MuseScore exported unrecognized chords as plain text
6842 xml.stag("direction");
6843 xml.stag("direction-type");
6844 xml.tag("words", h->text());
6845 xml.etag();
6846 xml.etag();
6847 #endif
6848 }
6849 #if 0
6850 // this is very old code that may never have actually been used
6851 xml.tag(QString("kind text=\"%1\"").arg(h->extensionName()), extension);
6852 for (int i = 0; i < h->numberOfDegrees(); i++) {
6853 HDegree hd = h->degree(i);
6854 HDegreeType tp = hd.type();
6855 if (tp == HDegreeType::ADD || tp == HDegreeType::ALTER || tp == HDegreeType::SUBTRACT) {
6856 xml.stag("degree");
6857 xml.tag("degree-value", hd.value());
6858 xml.tag("degree-alter", hd.alter());
6859 switch (tp) {
6860 case HDegreeType::ADD:
6861 xml.tag("degree-type", "add");
6862 break;
6863 case HDegreeType::ALTER:
6864 xml.tag("degree-type", "alter");
6865 break;
6866 case HDegreeType::SUBTRACT:
6867 xml.tag("degree-type", "subtract");
6868 break;
6869 default:
6870 break;
6871 }
6872 xml.etag();
6873 }
6874 }
6875 #endif
6876 }
6877
6878 }
6879