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