1 //=============================================================================
2 //  MuseScore
3 //  Linux Music Score Editor
4 //
5 //  Copyright (C) 2015 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 #include "libmscore/box.h"
21 #include "libmscore/chordrest.h"
22 #include "libmscore/instrtemplate.h"
23 #include "libmscore/measure.h"
24 #include "libmscore/page.h"
25 #include "libmscore/part.h"
26 #include "libmscore/staff.h"
27 #include "libmscore/stringdata.h"
28 #include "libmscore/sym.h"
29 #include "libmscore/symbol.h"
30 #include "libmscore/timesig.h"
31 #include "libmscore/style.h"
32 #include "libmscore/spanner.h"
33 #include "libmscore/bracketItem.h"
34 
35 #include "importmxmllogger.h"
36 #include "importmxmlnoteduration.h"
37 #include "importmxmlpass1.h"
38 #include "importmxmlpass2.h"
39 
40 #include "mscore/preferences.h"
41 
42 namespace Ms {
43 
44 //---------------------------------------------------------
45 //   allocateStaves
46 //---------------------------------------------------------
47 
48 /**
49  Allocate MuseScore staff to MusicXML voices.
50  For each staff, allocate at most VOICES voices to the staff.
51  */
52 
53 // for regular (non-overlapping) voices:
54 // 1) assign voice to a staff (allocateStaves)
55 // 2) assign voice numbers (allocateVoices)
56 // due to cross-staving, it is not a priori clear to which staff
57 // a voice has to be assigned
58 // allocate ordered by number of chordrests in the MusicXML voice
59 //
60 // for overlapping voices:
61 // 1) assign voice to staves it is found in (allocateStaves)
62 // 2) assign voice numbers (allocateVoices)
63 
allocateStaves(VoiceList & vcLst)64 static void allocateStaves(VoiceList& vcLst)
65       {
66       // initialize
67       int voicesAllocated[MAX_STAVES]; // number of voices allocated on each staff
68       for (int i = 0; i < MAX_STAVES; ++i)
69             voicesAllocated[i] = 0;
70 
71       // handle regular (non-overlapping) voices
72       // note: outer loop executed vcLst.size() times, as each inner loop handles exactly one item
73       for (int i = 0; i < vcLst.size(); ++i) {
74             // find the regular voice containing the highest number of chords and rests that has not been handled yet
75             int max = 0;
76             QString key;
77             for (VoiceList::const_iterator j = vcLst.constBegin(); j != vcLst.constEnd(); ++j) {
78                   if (!j.value().overlaps() && j.value().numberChordRests() > max && j.value().staff() == -1) {
79                         max = j.value().numberChordRests();
80                         key = j.key();
81                         }
82                   }
83             if (key != "") {
84                   int prefSt = vcLst.value(key).preferredStaff();
85                   if (voicesAllocated[prefSt] < VOICES) {
86                         vcLst[key].setStaff(prefSt);
87                         voicesAllocated[prefSt]++;
88                         }
89                   else
90                         // out of voices: mark as used but not allocated
91                         vcLst[key].setStaff(-2);
92                   }
93             }
94 
95       // handle overlapping voices
96       // for every staff allocate remaining voices (if space allows)
97       // the ones with the highest number of chords and rests get allocated first
98       for (int h = 0; h < MAX_STAVES; ++h) {
99             // note: middle loop executed vcLst.size() times, as each inner loop handles exactly one item
100             for (int i = 0; i < vcLst.size(); ++i) {
101                   // find the overlapping voice containing the highest number of chords and rests that has not been handled yet
102                   int max = 0;
103                   QString key;
104                   for (VoiceList::const_iterator j = vcLst.constBegin(); j != vcLst.constEnd(); ++j) {
105                         if (j.value().overlaps() && j.value().numberChordRests(h) > max && j.value().staffAlloc(h) == -1) {
106                               max = j.value().numberChordRests(h);
107                               key = j.key();
108                               }
109                         }
110                   if (key != "") {
111                         int prefSt = h;
112                         if (voicesAllocated[prefSt] < VOICES) {
113                               vcLst[key].setStaffAlloc(prefSt, 1);
114                               voicesAllocated[prefSt]++;
115                               }
116                         else
117                               // out of voices: mark as used but not allocated
118                               vcLst[key].setStaffAlloc(prefSt, -2);
119                         }
120                   }
121             }
122       }
123 
124 //---------------------------------------------------------
125 //   allocateVoices
126 //---------------------------------------------------------
127 
128 /**
129  Allocate MuseScore voice to MusicXML voices.
130  For each staff, the voices are number 1, 2, 3, 4
131  in the same order they are numbered in the MusicXML file.
132  */
133 
allocateVoices(VoiceList & vcLst)134 static void allocateVoices(VoiceList& vcLst)
135       {
136       int nextVoice[MAX_STAVES]; // number of voices allocated on each staff
137       for (int i = 0; i < MAX_STAVES; ++i)
138             nextVoice[i] = 0;
139       // handle regular (non-overlapping) voices
140       // a voice is allocated on one specific staff
141       for (VoiceList::const_iterator i = vcLst.constBegin(); i != vcLst.constEnd(); ++i) {
142             int staff = i.value().staff();
143             QString key   = i.key();
144             if (staff >= 0) {
145                   vcLst[key].setVoice(nextVoice[staff]);
146                   nextVoice[staff]++;
147                   }
148             }
149       // handle overlapping voices
150       // each voice may be in every staff
151       for (VoiceList::const_iterator i = vcLst.constBegin(); i != vcLst.constEnd(); ++i) {
152             for (int j = 0; j < MAX_STAVES; ++j) {
153                   int staffAlloc = i.value().staffAlloc(j);
154                   QString key   = i.key();
155                   if (staffAlloc >= 0) {
156                         vcLst[key].setVoice(j, nextVoice[j]);
157                         nextVoice[j]++;
158                         }
159                   }
160             }
161       }
162 
163 
164 //---------------------------------------------------------
165 //   copyOverlapData
166 //---------------------------------------------------------
167 
168 /**
169  Copy the overlap data from the overlap detector to the voice list.
170  */
171 
copyOverlapData(VoiceOverlapDetector & vod,VoiceList & vcLst)172 static void copyOverlapData(VoiceOverlapDetector& vod, VoiceList& vcLst)
173       {
174       for (VoiceList::const_iterator i = vcLst.constBegin(); i != vcLst.constEnd(); ++i) {
175             QString key = i.key();
176             if (vod.stavesOverlap(key))
177                   vcLst[key].setOverlap(true);
178             }
179       }
180 
181 //---------------------------------------------------------
182 //   MusicXMLParserPass1
183 //---------------------------------------------------------
184 
MusicXMLParserPass1(Score * score,MxmlLogger * logger)185 MusicXMLParserPass1::MusicXMLParserPass1(Score* score, MxmlLogger* logger)
186       : _divs(0), _score(score), _logger(logger), _hasBeamingInfo(false)
187       {
188       // nothing
189       }
190 
191 //---------------------------------------------------------
192 //   initPartState
193 //---------------------------------------------------------
194 
195 /**
196  Initialize members as required for reading the MusicXML part element.
197  TODO: factor out part reading into a separate class
198  TODO: preferably use automatically initialized variables
199  Note that Qt automatically initializes new elements in QVector (tuplets).
200  */
201 
initPartState(const QString &)202 void MusicXMLParserPass1::initPartState(const QString& /* partId */)
203       {
204       _timeSigDura = Fraction(0, 0);       // invalid
205       _octaveShifts.clear();
206       }
207 
208 //---------------------------------------------------------
209 //   determineMeasureLength
210 //---------------------------------------------------------
211 
212 /**
213  Determine the length in ticks of each measure in all parts.
214  Return false on error.
215  */
216 
determineMeasureLength(QVector<Fraction> & ml) const217 bool MusicXMLParserPass1::determineMeasureLength(QVector<Fraction>& ml) const
218       {
219       ml.clear();
220 
221       // determine number of measures: max number of measures in any part
222       int nMeasures = 0;
223       foreach (const MusicXmlPart &part, _parts) {
224             if (part.nMeasures() > nMeasures)
225                   nMeasures = part.nMeasures();
226             }
227 
228       // determine max length of a specific measure in all parts
229       for (int i = 0; i < nMeasures; ++i) {
230             Fraction maxMeasDur;
231             foreach (const MusicXmlPart &part, _parts) {
232                   if (i < part.nMeasures()) {
233                         Fraction measDurPartJ = part.measureDuration(i);
234                         if (measDurPartJ > maxMeasDur)
235                               maxMeasDur = measDurPartJ;
236                         }
237                   }
238             //qDebug("determineMeasureLength() measure %d %s (%d)", i, qPrintable(maxMeasDur.print()), maxMeasDur.ticks());
239             ml.append(maxMeasDur);
240             }
241       return true;
242       }
243 
244 //---------------------------------------------------------
245 //   getVoiceList
246 //---------------------------------------------------------
247 
248 /**
249  Get the VoiceList for part \a id.
250  Return an empty VoiceList on error.
251  */
252 
getVoiceList(const QString id) const253 VoiceList MusicXMLParserPass1::getVoiceList(const QString id) const
254       {
255       if (_parts.contains(id))
256             return _parts.value(id).voicelist;
257       return VoiceList();
258       }
259 
260 //---------------------------------------------------------
261 //   getInstrList
262 //---------------------------------------------------------
263 
264 /**
265  Get the MusicXmlInstrList for part \a id.
266  Return an empty MusicXmlInstrList on error.
267  */
268 
getInstrList(const QString id) const269 MusicXmlInstrList MusicXMLParserPass1::getInstrList(const QString id) const
270       {
271       if (_parts.contains(id))
272             return _parts.value(id)._instrList;
273       return MusicXmlInstrList();
274       }
275 
276 //---------------------------------------------------------
277 //   getIntervals
278 //---------------------------------------------------------
279 
280 /**
281  Get the MusicXmlIntervalList for part \a id.
282  Return an empty MusicXmlIntervalList on error.
283  */
284 
getIntervals(const QString id) const285 MusicXmlIntervalList MusicXMLParserPass1::getIntervals(const QString id) const
286       {
287       if (_parts.contains(id))
288             return _parts.value(id)._intervals;
289       return MusicXmlIntervalList();
290       }
291 
292 //---------------------------------------------------------
293 //   determineMeasureLength
294 //---------------------------------------------------------
295 
296 /**
297  Set default notehead, line and stem direction
298  for instrument \a instrId in part \a id.
299  Called from pass 2, notehead, line and stemDirection are not read in pass 1.
300  */
301 
setDrumsetDefault(const QString & id,const QString & instrId,const NoteHead::Group hg,const int line,const Direction sd)302 void MusicXMLParserPass1::setDrumsetDefault(const QString& id,
303                                             const QString& instrId,
304                                             const NoteHead::Group hg,
305                                             const int line,
306                                             const Direction sd)
307       {
308       if (_instruments.contains(id)
309           && _instruments[id].contains(instrId)) {
310             _instruments[id][instrId].notehead = hg;
311             _instruments[id][instrId].line = line;
312             _instruments[id][instrId].stemDirection = sd;
313             }
314       }
315 
316 
317 //---------------------------------------------------------
318 //   determineStaffMoveVoice
319 //---------------------------------------------------------
320 
321 /**
322  For part \a id, determine MuseScore (ms) staffmove, track and voice from MusicXML (mx) staff and voice
323  MusicXML staff is 0 for the first staff, 1 for the second.
324  Note: track is the first track of the ms staff in the score, add ms voice for elements in a voice
325  Return true if OK, false on error
326  TODO: finalize
327  */
328 
determineStaffMoveVoice(const QString & id,const int mxStaff,const QString & mxVoice,int & msMove,int & msTrack,int & msVoice) const329 bool MusicXMLParserPass1::determineStaffMoveVoice(const QString& id, const int mxStaff, const QString& mxVoice,
330                                                   int& msMove, int& msTrack, int& msVoice) const
331       {
332       VoiceList voicelist = getVoiceList(id);
333       msMove = 0; // TODO
334       msTrack = 0; // TODO
335       msVoice = 0; // TODO
336 
337 
338       // Musicxml voices are counted for all staves of an
339       // instrument. They are not limited. In mscore voices are associated
340       // with a staff. Every staff can have at most VOICES voices.
341 
342       // The following lines map musicXml voices to mscore voices.
343       // If a voice crosses two staves, this is expressed with the
344       // "move" parameter in mscore.
345 
346       // Musicxml voices are unique within a part, but not across parts.
347 
348       //qDebug("voice mapper before: voice='%s' staff=%d", qPrintable(mxVoice), mxStaff);
349       int s; // staff mapped by voice mapper
350       int v; // voice mapped by voice mapper
351       if (voicelist.value(mxVoice).overlaps()) {
352             // for overlapping voices, the staff does not change
353             // and the voice is mapped and staff-dependent
354             s = mxStaff;
355             v = voicelist.value(mxVoice).voice(s);
356             }
357       else {
358             // for non-overlapping voices, both staff and voice are
359             // set by the voice mapper
360             s = voicelist.value(mxVoice).staff();
361             v = voicelist.value(mxVoice).voice();
362             }
363 
364       //qDebug("voice mapper mapped: s=%d v=%d", s, v);
365       if (s < 0 || v < 0) {
366             qDebug("too many voices (staff=%d voice='%s' -> s=%d v=%d)",
367                    mxStaff + 1, qPrintable(mxVoice), s, v);
368             return false;
369             }
370 
371       msMove  = mxStaff - s;
372       msVoice = v;
373 
374       // make score-relative instead on part-relative
375       Part* part = _partMap.value(id);
376       Q_ASSERT(part);
377       int scoreRelStaff = _score->staffIdx(part); // zero-based number of parts first staff in the score
378       msTrack = (scoreRelStaff + s) * VOICES;
379 
380       //qDebug("voice mapper after: scoreRelStaff=%d partRelStaff=%d msMove=%d msTrack=%d msVoice=%d",
381       //       scoreRelStaff, s, msMove, msTrack, msVoice);
382       // note: relStaff is the staff number relative to the parts first staff
383       //       voice is the voice number in the staff
384 
385       return true;
386       }
387 
388 //---------------------------------------------------------
389 //   hasPart
390 //---------------------------------------------------------
391 
392 /**
393  Check if part \a id is found.
394  */
395 
hasPart(const QString & id) const396 bool MusicXMLParserPass1::hasPart(const QString& id) const
397       {
398       return _parts.contains(id);
399       }
400 
401 //---------------------------------------------------------
402 //   trackForPart
403 //---------------------------------------------------------
404 
405 /**
406  Return the (score relative) track number for the first staff of part \a id.
407  */
408 
trackForPart(const QString & id) const409 int MusicXMLParserPass1::trackForPart(const QString& id) const
410       {
411       Part* part = _partMap.value(id);
412       Q_ASSERT(part);
413       int scoreRelStaff = _score->staffIdx(part); // zero-based number of parts first staff in the score
414       return scoreRelStaff * VOICES;
415       }
416 
417 //---------------------------------------------------------
418 //   getMeasureStart
419 //---------------------------------------------------------
420 
421 /**
422  Return the measure start time for measure \a i.
423  */
424 
getMeasureStart(const int i) const425 Fraction MusicXMLParserPass1::getMeasureStart(const int i) const
426       {
427       if (0 <= i && i < _measureStart.size())
428             return _measureStart.at(i);
429       else
430             return Fraction(0, 0);       // invalid
431       }
432 
433 //---------------------------------------------------------
434 //   octaveShift
435 //---------------------------------------------------------
436 
437 /**
438  Return the octave shift for part \a id in \a staff at \a f.
439  */
440 
octaveShift(const QString & id,const int staff,const Fraction f) const441 int MusicXMLParserPass1::octaveShift(const QString& id, const int staff, const Fraction f) const
442       {
443       if (_parts.contains(id))
444             return _parts.value(id).octaveShift(staff, f);
445 
446       return 0;
447       }
448 
449 //---------------------------------------------------------
450 //   skipLogCurrElem
451 //---------------------------------------------------------
452 
453 /**
454  Skip the current element, log debug as info.
455  */
456 
skipLogCurrElem()457 void MusicXMLParserPass1::skipLogCurrElem()
458       {
459       _logger->logDebugInfo(QString("skipping '%1'").arg(_e.name().toString()), &_e);
460       _e.skipCurrentElement();
461       }
462 
463 //---------------------------------------------------------
464 //   addBreak
465 //---------------------------------------------------------
466 
addBreak(Score * const score,MeasureBase * const mb,const LayoutBreak::Type type)467 static void addBreak(Score* const score, MeasureBase* const mb, const LayoutBreak::Type type)
468       {
469       LayoutBreak* lb = new LayoutBreak(score);
470       lb->setLayoutBreakType(type);
471       mb->add(lb);
472       }
473 
474 //---------------------------------------------------------
475 //   addBreakToPreviousMeasureBase
476 //---------------------------------------------------------
477 
addBreakToPreviousMeasureBase(Score * const score,MeasureBase * const mb,const LayoutBreak::Type type)478 static void addBreakToPreviousMeasureBase(Score* const score, MeasureBase* const mb, const LayoutBreak::Type type)
479       {
480       const auto pm = mb->prev();
481       if (pm && preferences.getBool(PREF_IMPORT_MUSICXML_IMPORTBREAKS))
482             addBreak(score, pm, type);
483       }
484 
485 //---------------------------------------------------------
486 //   addText
487 //---------------------------------------------------------
488 
489 /**
490  Add text \a strTxt to VBox \a vbx using Tid \a stl.
491  */
492 
addText(VBox * vbx,Score * s,const QString strTxt,const Tid stl)493 static void addText(VBox* vbx, Score* s, const QString strTxt, const Tid stl)
494       {
495       if (!strTxt.isEmpty()) {
496             Text* text = new Text(s, stl);
497             text->setXmlText(strTxt);
498             vbx->add(text);
499             }
500       }
501 
502 //---------------------------------------------------------
503 //   addText2
504 //---------------------------------------------------------
505 
506 /**
507  Add text \a strTxt to VBox \a vbx using Tid \a stl.
508  Also sets Align and Yoff.
509  */
510 
addText2(VBox * vbx,Score * s,const QString strTxt,const Tid stl,const Align align,const double yoffs)511 static void addText2(VBox* vbx, Score* s, const QString strTxt, const Tid stl, const Align align, const double yoffs)
512       {
513       if (!strTxt.isEmpty()) {
514             Text* text = new Text(s, stl);
515             text->setXmlText(strTxt);
516             text->setAlign(align);
517             text->setPropertyFlags(Pid::ALIGN, PropertyFlags::UNSTYLED);
518             text->setOffset(QPointF(0.0, yoffs));
519             text->setPropertyFlags(Pid::OFFSET, PropertyFlags::UNSTYLED);
520             vbx->add(text);
521             }
522       }
523 
524 //---------------------------------------------------------
525 //   findYMinYMaxInWords
526 //---------------------------------------------------------
527 
findYMinYMaxInWords(const std::vector<const CreditWords * > & words,int & miny,int & maxy)528 static void findYMinYMaxInWords(const std::vector<const CreditWords*>& words, int& miny, int& maxy)
529       {
530       miny = 0;
531       maxy = 0;
532 
533       if (words.empty())
534             return;
535 
536       miny = words.at(0)->defaultY;
537       maxy = words.at(0)->defaultY;
538       for (const auto w : words) {
539             if (w->defaultY < miny) miny = w->defaultY;
540             if (w->defaultY > maxy) maxy = w->defaultY;
541             }
542       }
543 
544 //---------------------------------------------------------
545 //   alignForCreditWords
546 //---------------------------------------------------------
547 
alignForCreditWords(const CreditWords * const w,const int pageWidth)548 static Align alignForCreditWords(const CreditWords* const w, const int pageWidth)
549       {
550       Align align = Align::LEFT;
551       if (w->defaultX > (pageWidth / 3)) {
552             if (w->defaultX < (2 * pageWidth / 3))
553                   align = Align::HCENTER;
554             else
555                   align = Align::RIGHT;
556             }
557       return align;
558       }
559 
560 //---------------------------------------------------------
561 //   creditWordTypeToTid
562 //---------------------------------------------------------
563 
creditWordTypeToTid(const QString & type)564 static Tid creditWordTypeToTid(const QString& type)
565       {
566       if (type == "composer")
567             return Tid::COMPOSER;
568       else if (type == "lyricist")
569             return Tid::POET;
570       /*
571       else if (type == "page number")
572             return Tid::;
573       else if (type == "rights")
574             return Tid::;
575        */
576       else if (type == "subtitle")
577             return Tid::SUBTITLE;
578       else if (type == "title")
579             return Tid::TITLE;
580       else
581             return Tid::DEFAULT;
582       }
583 
584 //---------------------------------------------------------
585 //   creditWordTypeGuess
586 //---------------------------------------------------------
587 
creditWordTypeGuess(const CreditWords * const word,std::vector<const CreditWords * > & words,const int pageWidth)588 static Tid creditWordTypeGuess(const CreditWords* const word, std::vector<const CreditWords*>& words, const int pageWidth)
589       {
590       const auto pw1 = pageWidth / 3;
591       const auto pw2 = pageWidth * 2 / 3;
592       const auto defx = word->defaultX;
593       // composer is in the right column
594       if (pw2 < defx) {
595             // found composer
596             return Tid::COMPOSER;
597             }
598       // poet is in the left column
599       else if (defx < pw1) {
600             // found poet/lyricist
601             return Tid::POET;
602             }
603       // title is in the middle column
604       else {
605             // if another word in the middle column has a larger font size, this word is not the title
606             for (const auto w : words) {
607                   if (w == word) {
608                         continue;         // it's me
609                         }
610                   if (w->defaultX < pw1 || pw2 < w->defaultX) {
611                         continue;         // it's not in the middle column
612                         }
613                   if (word->fontSize < w->fontSize) {
614                         return Tid::SUBTITLE;          // word does not have the largest font size, assume subtitle
615                         }
616                   }
617             return Tid::TITLE;            // no better title candidate found
618             }
619       }
620 
621 //---------------------------------------------------------
622 //   tidForCreditWords
623 //---------------------------------------------------------
624 
tidForCreditWords(const CreditWords * const word,std::vector<const CreditWords * > & words,const int pageWidth)625 static Tid tidForCreditWords(const CreditWords* const word, std::vector<const CreditWords*>& words, const int pageWidth)
626       {
627       const Tid tid = creditWordTypeToTid(word->type);
628       if (tid != Tid::DEFAULT) {
629             // type recognized, done
630             return tid;
631             }
632       else {
633             // type not recognized, guess
634             return creditWordTypeGuess(word, words, pageWidth);
635             }
636       }
637 
638 //---------------------------------------------------------
639 //   createAndAddVBoxForCreditWords
640 //---------------------------------------------------------
641 
createAndAddVBoxForCreditWords(Score * const score,const int miny=0,const int maxy=75)642 static VBox* createAndAddVBoxForCreditWords(Score* const score, const int miny = 0, const int maxy = 75)
643       {
644       auto vbox = new VBox(score);
645       qreal vboxHeight = 10;                         // default height in tenths
646       double diff = maxy - miny;                     // calculate height in tenths
647       if (diff > vboxHeight)                         // and size is reasonable
648             vboxHeight = diff;
649       vboxHeight /= 10;                              // height in spatium
650       vboxHeight += 2.5;                             // guesstimated correction for last line
651 
652       vbox->setBoxHeight(Spatium(vboxHeight));
653       score->measures()->add(vbox);
654       return vbox;
655       }
656 
657 //---------------------------------------------------------
658 //   mustAddWordToVbox
659 //---------------------------------------------------------
660 
661 // determine if specific types of credit words must be added: do not add copyright and page number,
662 // as these typically conflict with MuseScore's style and/or layout
663 
mustAddWordToVbox(const QString & creditType)664 static bool mustAddWordToVbox(const QString& creditType)
665       {
666       return creditType != "rights" && creditType != "page number";
667       }
668 
669 //---------------------------------------------------------
670 //   addCreditWords
671 //---------------------------------------------------------
672 
addCreditWords(Score * const score,const CreditWordsList & crWords,const int pageNr,const QSize pageSize,const bool top)673 static VBox* addCreditWords(Score* const score, const CreditWordsList& crWords,
674                             const int pageNr, const QSize pageSize,
675                             const bool top)
676       {
677       VBox* vbox = nullptr;
678 
679       std::vector<const CreditWords*> headerWords;
680       std::vector<const CreditWords*> footerWords;
681       for (const auto w : crWords) {
682             if (w->page == pageNr) {
683                   if (w->defaultY > (pageSize.height() / 2))
684                         headerWords.push_back(w);
685                   else
686                         footerWords.push_back(w);
687                   }
688             }
689 
690       std::vector<const CreditWords*> words;
691       if (pageNr == 1) {
692             // if there are more credit words in the footer than in header,
693             // swap heaer and footer, assuming this will result in a vertical
694             // frame with the title on top of the page.
695             // Sibelius (direct export) typically exports no header
696             // and puts the title etc. in the footer
697             const bool doSwap = footerWords.size() > headerWords.size();
698             if (top) {
699                   words = doSwap ? footerWords : headerWords;
700                   }
701             else {
702                   words = doSwap ? headerWords : footerWords;
703                   }
704             }
705       else {
706             words = top ? headerWords : footerWords;
707             }
708 
709       int miny = 0;
710       int maxy = 0;
711       findYMinYMaxInWords(words, miny, maxy);
712 
713       for (const auto w : words) {
714             if (mustAddWordToVbox(w->type)) {
715                   const auto align = alignForCreditWords(w, pageSize.width());
716                   const auto tid = (pageNr == 1 && top) ? tidForCreditWords(w, words, pageSize.width()) : Tid::DEFAULT;
717                   double yoffs = (maxy - w->defaultY) * score->spatium() / 10;
718                   if (!vbox)
719                         vbox = createAndAddVBoxForCreditWords(score, miny, maxy);
720                   addText2(vbox, score, w->words, tid, align, yoffs);
721                   }
722             }
723 
724       return vbox;
725       }
726 
727 //---------------------------------------------------------
728 //   createMeasuresAndVboxes
729 //---------------------------------------------------------
730 
createDefaultHeader(Score * const score)731 static void createDefaultHeader(Score* const score)
732       {
733       QString strTitle;
734       QString strSubTitle;
735       QString strComposer;
736       QString strPoet;
737       QString strTranslator;
738 
739       if (!(score->metaTag("movementTitle").isEmpty() && score->metaTag("workTitle").isEmpty())) {
740             strTitle = score->metaTag("movementTitle");
741             if (strTitle.isEmpty())
742                   strTitle = score->metaTag("workTitle");
743             }
744       if (!(score->metaTag("movementNumber").isEmpty() && score->metaTag("workNumber").isEmpty())) {
745             strSubTitle = score->metaTag("movementNumber");
746             if (strSubTitle.isEmpty())
747                   strSubTitle = score->metaTag("workNumber");
748             }
749       QString metaComposer = score->metaTag("composer");
750       QString metaPoet = score->metaTag("poet");
751       QString metaTranslator = score->metaTag("translator");
752       if (!metaComposer.isEmpty()) strComposer = metaComposer;
753       if (metaPoet.isEmpty()) metaPoet = score->metaTag("lyricist");
754       if (!metaPoet.isEmpty()) strPoet = metaPoet;
755       if (!metaTranslator.isEmpty()) strTranslator = metaTranslator;
756 
757       const auto vbox = createAndAddVBoxForCreditWords(score);
758       addText(vbox, score, strTitle.toHtmlEscaped(),      Tid::TITLE);
759       addText(vbox, score, strSubTitle.toHtmlEscaped(),   Tid::SUBTITLE);
760       addText(vbox, score, strComposer.toHtmlEscaped(),   Tid::COMPOSER);
761       addText(vbox, score, strPoet.toHtmlEscaped(),       Tid::POET);
762       addText(vbox, score, strTranslator.toHtmlEscaped(), Tid::TRANSLATOR);
763       }
764 
765 //---------------------------------------------------------
766 //   createMeasuresAndVboxes
767 //---------------------------------------------------------
768 
769 /**
770  Create required measures with correct number, start tick and length for Score \a score.
771  */
772 
createMeasuresAndVboxes(Score * const score,const QVector<Fraction> & ml,const QVector<Fraction> & ms,const std::set<int> & systemStartMeasureNrs,const std::set<int> & pageStartMeasureNrs,const CreditWordsList & crWords,const QSize pageSize)773 static void createMeasuresAndVboxes(Score* const score,
774                                     const QVector<Fraction>& ml, const QVector<Fraction>& ms,
775                                     const std::set<int>& systemStartMeasureNrs,
776                                     const std::set<int>& pageStartMeasureNrs,
777                                     const CreditWordsList& crWords,
778                                     const QSize pageSize)
779       {
780       if (crWords.empty())
781             createDefaultHeader(score);
782 
783       int pageNr = 0;
784       for (int i = 0; i < ml.size(); ++i) {
785 
786             VBox* vbox = nullptr;
787 
788             // add a header vbox if the this measure is the first in the score or the first on a new page
789             if (pageStartMeasureNrs.count(i) || i == 0) {
790                   ++pageNr;
791                   vbox = addCreditWords(score, crWords, pageNr, pageSize, true);
792                   }
793 
794             // create and add the measure
795             Measure* measure  = new Measure(score);
796             measure->setTick(ms.at(i));
797             measure->setTicks(ml.at(i));
798             measure->setNo(i);
799             score->measures()->add(measure);
800 
801             // add break to previous measure or vbox
802             MeasureBase* mb = vbox;
803             if (!mb) mb = measure;
804             if (pageStartMeasureNrs.count(i))
805                   addBreakToPreviousMeasureBase(score, mb, LayoutBreak::Type::PAGE);
806             else if (systemStartMeasureNrs.count(i))
807                   addBreakToPreviousMeasureBase(score, mb, LayoutBreak::Type::LINE);
808 
809             // add a footer vbox if the next measure is on a new page or end of score has been reached
810             if (pageStartMeasureNrs.count(i+1) || i == (ml.size() - 1))
811                   addCreditWords(score, crWords, pageNr, pageSize, false);
812             }
813       }
814 
815 //---------------------------------------------------------
816 //   determineMeasureStart
817 //---------------------------------------------------------
818 
819 /**
820  Determine the start ticks of each measure
821  i.e. the sum of all previous measures length
822  or start tick measure equals start tick previous measure plus length previous measure
823  */
824 
determineMeasureStart(const QVector<Fraction> & ml,QVector<Fraction> & ms)825 static void determineMeasureStart(const QVector<Fraction>& ml, QVector<Fraction>& ms)
826       {
827       ms.resize(ml.size());
828       if (!(ms.size() > 0))
829             return;  // no parts read
830 
831       // first measure starts at t = 0
832       ms[0] = Fraction(0, 1);
833       // all others start at start time previous measure plus length previous measure
834       for (int i = 1; i < ml.size(); i++)
835             ms[i] = ms.at(i - 1) + ml.at(i - 1);
836       //for (int i = 0; i < ms.size(); i++)
837       //      qDebug("measurestart ms[%d] %s", i + 1, qPrintable(ms.at(i).print()));
838       }
839 
840 //---------------------------------------------------------
841 //   dumpPageSize
842 //---------------------------------------------------------
843 
dumpPageSize(const QSize & pageSize)844 static void dumpPageSize(const QSize& pageSize)
845       {
846 #if 0
847       qDebug("page size width=%d height=%d", pageSize.width(), pageSize.height());
848 #else
849       Q_UNUSED(pageSize);
850 #endif
851       }
852 
853 //---------------------------------------------------------
854 //   dumpCredits
855 //---------------------------------------------------------
856 
dumpCredits(const CreditWordsList & credits)857 static void dumpCredits(const CreditWordsList& credits)
858       {
859 #if 0
860       for (const auto w : credits) {
861             qDebug("credit-words pg=%d tp='%s' defx=%g defy=%g just=%s hal=%s val=%s words='%s'",
862                    w->page,
863                    qPrintable(w->type),
864                    w->defaultX,
865                    w->defaultY,
866                    qPrintable(w->justify),
867                    qPrintable(w->hAlign),
868                    qPrintable(w->vAlign),
869                    qPrintable(w->words));
870             }
871 #else
872       Q_UNUSED(credits);
873 #endif
874       }
875 
876 //---------------------------------------------------------
877 //   fixupSigmap
878 //---------------------------------------------------------
879 
880 /**
881  To enable error handling in pass2, ensure sigmap contains a valid entry at tick = 0.
882  Required by TimeSigMap::tickValues(), called (indirectly) by Segment::add().
883  */
884 
fixupSigmap(MxmlLogger * logger,Score * score,const QVector<Fraction> & measureLength)885 static void fixupSigmap(MxmlLogger* logger, Score* score, const QVector<Fraction>& measureLength)
886       {
887       auto it = score->sigmap()->find(0);
888 
889       if (it == score->sigmap()->end()) {
890             // no valid timesig at tick = 0
891             logger->logDebugInfo("no valid time signature at tick = 0");
892             // use length of first measure instead time signature.
893             // if there is no first measure, we probably don't care,
894             // but set a default anyway.
895             Fraction tsig = measureLength.isEmpty() ? Fraction(4, 4) : measureLength.at(0);
896             score->sigmap()->add(0, tsig);
897             }
898       }
899 
900 //---------------------------------------------------------
901 //   parse
902 //---------------------------------------------------------
903 
904 /**
905  Parse MusicXML in \a device and extract pass 1 data.
906  */
907 
parse(QIODevice * device)908 Score::FileError MusicXMLParserPass1::parse(QIODevice* device)
909       {
910       _logger->logDebugTrace("MusicXMLParserPass1::parse device");
911       _parts.clear();
912       _e.setDevice(device);
913       auto res = parse();
914       if (res != Score::FileError::FILE_NO_ERROR)
915             return res;
916 
917       // Determine the start tick of each measure in the part
918       determineMeasureLength(_measureLength);
919       determineMeasureStart(_measureLength, _measureStart);
920       // Fixup timesig at tick = 0 if necessary
921       fixupSigmap(_logger, _score, _measureLength);
922       // Debug: dump gae size and credits read
923       dumpPageSize(_pageSize);
924       dumpCredits(_credits);
925       // Create the measures
926       createMeasuresAndVboxes(_score, _measureLength, _measureStart, _systemStartMeasureNrs, _pageStartMeasureNrs, _credits, _pageSize);
927 
928       return res;
929       }
930 
931 //---------------------------------------------------------
932 //   parse
933 //---------------------------------------------------------
934 
935 /**
936  Start the parsing process, after verifying the top-level node is score-partwise
937  */
938 
parse()939 Score::FileError MusicXMLParserPass1::parse()
940       {
941       _logger->logDebugTrace("MusicXMLParserPass1::parse");
942 
943       bool found = false;
944       while (_e.readNextStartElement()) {
945             if (_e.name() == "score-partwise") {
946                   found = true;
947                   scorePartwise();
948                   }
949             else {
950                   _logger->logError(QString("this is not a MusicXML score-partwise file (top-level node '%1')")
951                                     .arg(_e.name().toString()), &_e);
952                   _e.skipCurrentElement();
953                   return Score::FileError::FILE_BAD_FORMAT;
954                   }
955             }
956 
957       if (!found) {
958             _logger->logError("this is not a MusicXML score-partwise file, node <score-partwise> not found", &_e);
959             return Score::FileError::FILE_BAD_FORMAT;
960             }
961 
962       return Score::FileError::FILE_NO_ERROR;
963       }
964 
965 //---------------------------------------------------------
966 //   allStaffGroupsIdentical
967 //---------------------------------------------------------
968 
969 /**
970  Return true if all staves in Part \a p have the same staff group
971  */
972 
allStaffGroupsIdentical(Part const * const p)973 static bool allStaffGroupsIdentical(Part const* const p)
974       {
975       for (int i = 1; i < p->nstaves(); ++i) {
976             if (p->staff(0)->constStaffType(Fraction(0,1))->group() != p->staff(i)->constStaffType(Fraction(0,1))->group())
977                   return false;
978             }
979       return true;
980       }
981 
982 //---------------------------------------------------------
983 //   scorePartwise
984 //---------------------------------------------------------
985 
986 /**
987  Parse the MusicXML top-level (XPath /score-partwise) node.
988  */
989 
scorePartwise()990 void MusicXMLParserPass1::scorePartwise()
991       {
992       Q_ASSERT(_e.isStartElement() && _e.name() == "score-partwise");
993       _logger->logDebugTrace("MusicXMLParserPass1::scorePartwise", &_e);
994 
995       MusicXmlPartGroupList partGroupList;
996 
997       while (_e.readNextStartElement()) {
998             if (_e.name() == "part")
999                   part();
1000             else if (_e.name() == "part-list")
1001                   partList(partGroupList);
1002             else if (_e.name() == "work") {
1003                   while (_e.readNextStartElement()) {
1004                         if (_e.name() == "work-number")
1005                               _score->setMetaTag("workNumber", _e.readElementText());
1006                         else if (_e.name() == "work-title")
1007                               _score->setMetaTag("workTitle", _e.readElementText());
1008                         else
1009                               skipLogCurrElem();
1010                         }
1011                   }
1012             else if (_e.name() == "identification")
1013                   identification();
1014             else if (_e.name() == "defaults")
1015                   defaults();
1016             else if (_e.name() == "movement-number")
1017                   _score->setMetaTag("movementNumber", _e.readElementText());
1018             else if (_e.name() == "movement-title")
1019                   _score->setMetaTag("movementTitle", _e.readElementText());
1020             else if (_e.name() == "credit")
1021                   credit(_credits);
1022             else
1023                   skipLogCurrElem();
1024             }
1025 
1026       // add brackets where required
1027 
1028       /*
1029        qDebug("partGroupList");
1030        for (size_t i = 0; i < partGroupList.size(); i++) {
1031        MusicXmlPartGroup* pg = partGroupList[i];
1032        qDebug("part-group span %d start %d type %hhd barlinespan %d",
1033        pg->span, pg->start, pg->type, pg->barlineSpan);
1034        }
1035        */
1036 
1037       // set of (typically multi-staff) parts containing one or more explicit brackets
1038       // spanning only that part: these won't get an implicit brace later
1039       // e.g. a two-staff piano part with an explicit brace
1040       QSet<Part const* const> partSet;
1041 
1042       // handle the explicit brackets
1043       const QList<Part*>& il = _score->parts();
1044       for (size_t i = 0; i < partGroupList.size(); i++) {
1045             MusicXmlPartGroup* pg = partGroupList[i];
1046             // add part to set
1047             if (pg->span == 1)
1048                   partSet << il.at(pg->start);
1049             // determine span in staves
1050             int stavesSpan = 0;
1051             for (int j = 0; j < pg->span; j++)
1052                   stavesSpan += il.at(pg->start + j)->nstaves();
1053             // add bracket and set the span
1054             // TODO: use group-symbol default-x to determine horizontal order of brackets
1055             Staff* staff = il.at(pg->start)->staff(0);
1056             if (pg->type != BracketType::NO_BRACKET) {
1057                   staff->setBracketType(pg->column, pg->type);
1058                   staff->setBracketSpan(pg->column, stavesSpan);
1059                   }
1060             if (pg->barlineSpan)
1061                   staff->setBarLineSpan(pg->span);
1062             }
1063 
1064       // handle the implicit brackets:
1065       // multi-staff parts w/o explicit brackets get a brace
1066       foreach(Part const* const p, il) {
1067             if (p->nstaves() > 1 && !partSet.contains(p)) {
1068                   const int column = p->staff(0)->bracketLevels() + 1;
1069                   p->staff(0)->setBracketType(column, BracketType::BRACE);
1070                   p->staff(0)->setBracketSpan(column, p->nstaves());
1071                   if (allStaffGroupsIdentical(p)) {
1072                         // span only if the same types
1073                         p->staff(0)->setBarLineSpan(p->nstaves());
1074                         }
1075                   }
1076             }
1077       }
1078 
1079 //---------------------------------------------------------
1080 //   identification
1081 //---------------------------------------------------------
1082 
1083 /**
1084  Parse the /score-partwise/identification node:
1085  read the metadata.
1086  */
1087 
identification()1088 void MusicXMLParserPass1::identification()
1089       {
1090       Q_ASSERT(_e.isStartElement() && _e.name() == "identification");
1091       _logger->logDebugTrace("MusicXMLParserPass1::identification", &_e);
1092 
1093       while (_e.readNextStartElement()) {
1094             if (_e.name() == "creator") {
1095                   // type is an arbitrary label
1096                   QString strType = _e.attributes().value("type").toString();
1097                   _score->setMetaTag(strType, _e.readElementText());
1098                   }
1099             else if (_e.name() == "rights")
1100                   _score->setMetaTag("copyright", _e.readElementText());
1101             else if (_e.name() == "encoding") {
1102                   // TODO
1103                   while (_e.readNextStartElement()) {
1104                         if (_e.name() == "supports" && _e.attributes().value("element") == "beam" && _e.attributes().value("type") == "yes")
1105                               _hasBeamingInfo = true;
1106                         _e.skipCurrentElement();
1107                         }
1108                   // _score->setMetaTag("encoding", _e.readElementText()); works with DOM but not with pull parser
1109                   // temporarily fake the encoding tag (compliant with DOM parser) to help the autotester
1110                   if (MScore::debugMode)
1111                         _score->setMetaTag("encoding", "MuseScore 0.7.02007-09-10");
1112                   }
1113             else if (_e.name() == "source")
1114                   _score->setMetaTag("source", _e.readElementText());
1115             else if (_e.name() == "miscellaneous")
1116                   // TODO
1117                   _e.skipCurrentElement();  // skip but don't log
1118             else
1119                   skipLogCurrElem();
1120             }
1121       }
1122 
1123 //---------------------------------------------------------
1124 //   text2syms
1125 //---------------------------------------------------------
1126 
1127 /**
1128  Convert SMuFL code points to MuseScore <sym>...</sym>
1129  */
1130 
text2syms(const QString & t)1131 static QString text2syms(const QString& t)
1132       {
1133       //QTime time;
1134       //time.start();
1135 
1136       // first create a map from symbol (Unicode) text to symId
1137       // note that this takes about 1 msec on a Core i5,
1138       // caching does not gain much
1139 
1140       ScoreFont* sf = ScoreFont::fallbackFont();
1141       QMap<QString, SymId> map;
1142       int maxStringSize = 0;        // maximum string size found
1143 
1144       for (int i = int(SymId::noSym); i < int(SymId::lastSym); ++i) {
1145             SymId id((SymId(i)));
1146             QString string(sf->toString(id));
1147             // insert all syms except space to prevent matching all regular spaces
1148             if (id != SymId::space)
1149                   map.insert(string, id);
1150             if (string.size() > maxStringSize)
1151                   maxStringSize = string.size();
1152             }
1153       //qDebug("text2syms map count %d maxsz %d filling time elapsed: %d ms",
1154       //       map.size(), maxStringSize, time.elapsed());
1155 
1156       // then look for matches
1157       QString in = t;
1158       QString res;
1159 
1160       while (in != "") {
1161             // try to find the largest match possible
1162             int maxMatch = qMin(in.size(), maxStringSize);
1163             QString sym;
1164             while (maxMatch > 0) {
1165                   QString toBeMatched = in.left(maxMatch);
1166                   if (map.contains(toBeMatched)) {
1167                         sym = Sym::id2name(map.value(toBeMatched));
1168                         break;
1169                         }
1170                   maxMatch--;
1171                   }
1172             if (maxMatch > 0) {
1173                   // found a match, add sym to res and remove match from string in
1174                   res += "<sym>";
1175                   res += sym;
1176                   res += "</sym>";
1177                   in.remove(0, maxMatch);
1178                   }
1179             else {
1180                   // not found, move one char from res to in
1181                   res += in.leftRef(1);
1182                   in.remove(0, 1);
1183                   }
1184             }
1185 
1186       //qDebug("text2syms total time elapsed: %d ms, res '%s'", time.elapsed(), qPrintable(res));
1187       return res;
1188       }
1189 
1190 //---------------------------------------------------------
1191 //   decodeEntities
1192 //---------------------------------------------------------
1193 
1194 /**
1195  Decode &#...; in string \a src into UNICODE (utf8) character.
1196  */
1197 
decodeEntities(const QString & src)1198 static QString decodeEntities( const QString& src )
1199       {
1200       QString ret(src);
1201       QRegExp re("&#([0-9]+);");
1202       re.setMinimal(true);
1203 
1204       int pos = 0;
1205       while ( (pos = re.indexIn(src, pos)) != -1 ) {
1206             ret = ret.replace(re.cap(0), QChar(re.cap(1).toInt(0,10)));
1207             pos += re.matchedLength();
1208             }
1209       return ret;
1210       }
1211 
1212 //---------------------------------------------------------
1213 //   nextPartOfFormattedString
1214 //---------------------------------------------------------
1215 
1216 // TODO: probably should be shared between pass 1 and 2
1217 
1218 /**
1219  Read the next part of a MusicXML formatted string and convert to MuseScore internal encoding.
1220  */
1221 
nextPartOfFormattedString(QXmlStreamReader & e)1222 static QString nextPartOfFormattedString(QXmlStreamReader& e)
1223       {
1224       //QString lang       = e.attribute(QString("xml:lang"), "it");
1225       QString fontWeight = e.attributes().value("font-weight").toString();
1226       QString fontSize   = e.attributes().value("font-size").toString();
1227       QString fontStyle  = e.attributes().value("font-style").toString();
1228       QString underline  = e.attributes().value("underline").toString();
1229       QString fontFamily = e.attributes().value("font-family").toString();
1230       // TODO: color, enclosure, yoffset in only part of the text, ...
1231 
1232       QString txt        = e.readElementText();
1233       // replace HTML entities
1234       txt = decodeEntities(txt);
1235       QString syms       = text2syms(txt);
1236 
1237       QString importedtext;
1238 
1239       if (!fontSize.isEmpty()) {
1240             bool ok = true;
1241             float size = fontSize.toFloat(&ok);
1242             if (ok)
1243                   importedtext += QString("<font size=\"%1\"/>").arg(size);
1244             }
1245 
1246       bool needUseDefaultFont = preferences.getBool(PREF_MIGRATION_APPLY_EDWIN_FOR_XML_FILES);
1247 
1248       if (!fontFamily.isEmpty() && txt == syms && !needUseDefaultFont) {
1249             // add font family only if no <sym> replacement made
1250             importedtext += QString("<font face=\"%1\"/>").arg(fontFamily);
1251             }
1252       if (fontWeight == "bold")
1253             importedtext += "<b>";
1254       if (fontStyle == "italic")
1255             importedtext += "<i>";
1256       if (!underline.isEmpty()) {
1257             bool ok = true;
1258             int lines = underline.toInt(&ok);
1259             if (ok && (lines > 0))  // 1,2, or 3 underlines are imported as single underline
1260                   importedtext += "<u>";
1261             else
1262                   underline = "";
1263             }
1264       if (txt == syms) {
1265             txt.replace(QString("\r"), QString("")); // convert Windows line break \r\n -> \n
1266             importedtext += txt.toHtmlEscaped();
1267             }
1268       else {
1269             // <sym> replacement made, should be no need for line break or other conversions
1270             importedtext += syms;
1271             }
1272       if (underline != "")
1273             importedtext += "</u>";
1274       if (fontStyle == "italic")
1275             importedtext += "</i>";
1276       if (fontWeight == "bold")
1277             importedtext += "</b>";
1278       //qDebug("importedtext '%s'", qPrintable(importedtext));
1279       return importedtext;
1280       }
1281 
1282 //---------------------------------------------------------
1283 //   credit
1284 //---------------------------------------------------------
1285 
1286 /**
1287  Parse the /score-partwise/credit node:
1288  read the credits for later handling by doCredits().
1289  */
1290 
credit(CreditWordsList & credits)1291 void MusicXMLParserPass1::credit(CreditWordsList& credits)
1292       {
1293       Q_ASSERT(_e.isStartElement() && _e.name() == "credit");
1294       _logger->logDebugTrace("MusicXMLParserPass1::credit", &_e);
1295 
1296       const auto page = _e.attributes().value("page").toString().toInt();       // ignoring errors implies incorrect conversion defaults to the first page
1297       // multiple credit-words elements may be present,
1298       // which are appended
1299       // use the position info from the first one
1300       // font information is ignored, credits will be styled
1301       bool creditWordsRead = false;
1302       double defaultx = 0;
1303       double defaulty = 0;
1304       double fontSize = 0;
1305       QString justify;
1306       QString halign;
1307       QString valign;
1308       QStringList crtypes;
1309       QString crwords;
1310       while (_e.readNextStartElement()) {
1311             if (_e.name() == "credit-words") {
1312                   // IMPORT_LAYOUT
1313                   if (!creditWordsRead) {
1314                         defaultx = _e.attributes().value("default-x").toString().toDouble();
1315                         defaulty = _e.attributes().value("default-y").toString().toDouble();
1316                         fontSize = _e.attributes().value("font-size").toString().toDouble();
1317                         justify  = _e.attributes().value("justify").toString();
1318                         halign   = _e.attributes().value("halign").toString();
1319                         valign   = _e.attributes().value("valign").toString();
1320                         creditWordsRead = true;
1321                         }
1322                   crwords += nextPartOfFormattedString(_e);
1323                   }
1324             else if (_e.name() == "credit-type") {
1325                   // multiple credit-type elements may be present, supported by
1326                   // e.g. Finale v26.3 for Mac.
1327                   crtypes += _e.readElementText();
1328                   }
1329             else
1330                   skipLogCurrElem();
1331             }
1332       if (crwords != "") {
1333             // as the meaning of multiple credit-types is undocumented,
1334             // use credit-type only if exactly one was found
1335             QString crtype = (crtypes.size() == 1) ? crtypes.at(0) : "";
1336             CreditWords* cw = new CreditWords(page, crtype, defaultx, defaulty, fontSize, justify, halign, valign, crwords);
1337             credits.append(cw);
1338             }
1339 
1340       Q_ASSERT(_e.isEndElement() && _e.name() == "credit");
1341       }
1342 
1343 //---------------------------------------------------------
1344 //   isTitleFrameStyle
1345 //---------------------------------------------------------
1346 
1347 /**
1348  Determine if tid is a style type used in a title frame
1349  */
1350 
isTitleFrameStyle(const Tid tid)1351 static bool isTitleFrameStyle(const Tid tid)
1352       {
1353       return tid == Tid::TITLE
1354              || tid == Tid::SUBTITLE
1355              || tid == Tid::COMPOSER
1356              || tid == Tid::POET;
1357       }
1358 
1359 //---------------------------------------------------------
1360 //   updateStyles
1361 //---------------------------------------------------------
1362 
1363 /**
1364  Update the style definitions to match the MusicXML word-font and lyric-font.
1365  */
1366 
updateStyles(Score * score,const QString & wordFamily,const QString & wordSize,const QString & lyricFamily,const QString & lyricSize)1367 static void updateStyles(Score* score,
1368                          const QString& wordFamily, const QString& wordSize,
1369                          const QString& lyricFamily, const QString& lyricSize)
1370       {
1371       const auto dblWordSize = wordSize.toDouble();   // note conversion error results in value 0.0
1372       const auto dblLyricSize = lyricSize.toDouble(); // but avoid comparing (double) floating point number with exact value later
1373       const auto epsilon = 0.001;                     // use epsilon instead
1374 
1375       bool needUseDefaultFont = preferences.getBool(PREF_MIGRATION_APPLY_EDWIN_FOR_XML_FILES);
1376 
1377       // loop over all text styles (except the empty, always hidden, first one)
1378       // set all text styles to the MusicXML defaults
1379       for (const auto tid : allTextStyles()) {
1380 
1381             // The MusicXML specification does not specify to which kinds of text
1382             // the word-font setting applies. Setting all sizes to the size specified
1383             // gives bad results, so a selection is made:
1384             // exclude lyrics odd and even lines (handled separately),
1385             // Roman numeral analysis (special case, leave untouched)
1386             // and text types used in the title frame
1387             // Some further tweaking may still be required.
1388 
1389             if (tid == Tid::LYRICS_ODD || tid == Tid::LYRICS_EVEN
1390                 || tid == Tid::HARMONY_ROMAN
1391                 || isTitleFrameStyle(tid))
1392                   continue;
1393             const TextStyle* ts = textStyle(tid);
1394             for (const StyledProperty& a :* ts) {
1395                   if (a.pid == Pid::FONT_FACE && wordFamily != "" && !needUseDefaultFont)
1396                         score->style().set(a.sid, wordFamily);
1397                   else if (a.pid == Pid::FONT_SIZE && dblWordSize > epsilon)
1398                         score->style().set(a.sid, dblWordSize);
1399                   }
1400             }
1401 
1402       // handle lyrics odd and even lines separately
1403       if (lyricFamily != "" && !needUseDefaultFont) {
1404             score->style().set(Sid::lyricsOddFontFace, lyricFamily);
1405             score->style().set(Sid::lyricsEvenFontFace, lyricFamily);
1406             }
1407       if (dblLyricSize > epsilon) {
1408             score->style().set(Sid::lyricsOddFontSize, QVariant(dblLyricSize));
1409             score->style().set(Sid::lyricsEvenFontSize, QVariant(dblLyricSize));
1410             }
1411       }
1412 
1413 //---------------------------------------------------------
1414 //   setPageFormat
1415 //---------------------------------------------------------
1416 
setPageFormat(Score * score,const PageFormat & pf)1417 static void setPageFormat(Score* score, const PageFormat& pf)
1418       {
1419       score->style().set(Sid::pageWidth, pf.size.width());
1420       score->style().set(Sid::pageHeight, pf.size.height());
1421       score->style().set(Sid::pagePrintableWidth, pf.printableWidth);
1422       score->style().set(Sid::pageEvenLeftMargin, pf.evenLeftMargin);
1423       score->style().set(Sid::pageOddLeftMargin, pf.oddLeftMargin);
1424       score->style().set(Sid::pageEvenTopMargin, pf.evenTopMargin);
1425       score->style().set(Sid::pageEvenBottomMargin, pf.evenBottomMargin);
1426       score->style().set(Sid::pageOddTopMargin, pf.oddTopMargin);
1427       score->style().set(Sid::pageOddBottomMargin, pf.oddBottomMargin);
1428       score->style().set(Sid::pageTwosided, pf.twosided);
1429       }
1430 
1431 //---------------------------------------------------------
1432 //   defaults
1433 //---------------------------------------------------------
1434 
1435 /**
1436  Parse the /score-partwise/defaults node:
1437  read the general score layout settings.
1438  */
1439 
defaults()1440 void MusicXMLParserPass1::defaults()
1441       {
1442       Q_ASSERT(_e.isStartElement() && _e.name() == "defaults");
1443       //_logger->logDebugTrace("MusicXMLParserPass1::defaults", &_e);
1444 
1445       double millimeter = _score->spatium()/10.0;
1446       double tenths = 1.0;
1447       QString lyricFontFamily;
1448       QString lyricFontSize;
1449       QString wordFontFamily;
1450       QString wordFontSize;
1451 
1452       while (_e.readNextStartElement()) {
1453             if (_e.name() == "appearance")
1454                   _e.skipCurrentElement();  // skip but don't log
1455             else if (_e.name() == "scaling") {
1456                   while (_e.readNextStartElement()) {
1457                         if (_e.name() == "millimeters")
1458                               millimeter = _e.readElementText().toDouble();
1459                         else if (_e.name() == "tenths")
1460                               tenths = _e.readElementText().toDouble();
1461                         else
1462                               skipLogCurrElem();
1463                         }
1464                   double _spatium = DPMM * (millimeter * 10.0 / tenths);
1465                   if (preferences.getBool(PREF_IMPORT_MUSICXML_IMPORTLAYOUT))
1466                         _score->setSpatium(_spatium);
1467                   }
1468             else if (_e.name() == "page-layout") {
1469                   PageFormat pf;
1470                   pageLayout(pf, millimeter / (tenths * INCH));
1471                   if (preferences.getBool(PREF_IMPORT_MUSICXML_IMPORTLAYOUT))
1472                         setPageFormat(_score, pf);
1473                   }
1474             else if (_e.name() == "system-layout") {
1475                   while (_e.readNextStartElement()) {
1476                         if (_e.name() == "system-dividers")
1477                               _e.skipCurrentElement();  // skip but don't log
1478                         else if (_e.name() == "system-margins")
1479                               _e.skipCurrentElement();  // skip but don't log
1480                         else if (_e.name() == "system-distance") {
1481                               Spatium val(_e.readElementText().toDouble() / 10.0);
1482                               if (preferences.getBool(PREF_IMPORT_MUSICXML_IMPORTLAYOUT)) {
1483                                     _score->style().set(Sid::minSystemDistance, val);
1484                                     //qDebug("system distance %f", val.val());
1485                                     }
1486                               }
1487                         else if (_e.name() == "top-system-distance")
1488                               _e.skipCurrentElement();  // skip but don't log
1489                         else
1490                               skipLogCurrElem();
1491                         }
1492                   }
1493             else if (_e.name() == "staff-layout") {
1494                   while (_e.readNextStartElement()) {
1495                         if (_e.name() == "staff-distance") {
1496                               Spatium val(_e.readElementText().toDouble() / 10.0);
1497                               if (preferences.getBool(PREF_IMPORT_MUSICXML_IMPORTLAYOUT))
1498                                     _score->style().set(Sid::staffDistance, val);
1499                               }
1500                         else
1501                               skipLogCurrElem();
1502                         }
1503                   }
1504             else if (_e.name() == "music-font")
1505                   _e.skipCurrentElement();  // skip but don't log
1506             else if (_e.name() == "word-font") {
1507                   wordFontFamily = _e.attributes().value("font-family").toString();
1508                   wordFontSize = _e.attributes().value("font-size").toString();
1509                   _e.skipCurrentElement();
1510                   }
1511             else if (_e.name() == "lyric-font") {
1512                   lyricFontFamily = _e.attributes().value("font-family").toString();
1513                   lyricFontSize = _e.attributes().value("font-size").toString();
1514                   _e.skipCurrentElement();
1515                   }
1516             else if (_e.name() == "lyric-language")
1517                   _e.skipCurrentElement();  // skip but don't log
1518             else
1519                   skipLogCurrElem();
1520             }
1521 
1522       /*
1523       qDebug("word font family '%s' size '%s' lyric font family '%s' size '%s'",
1524              qPrintable(wordFontFamily), qPrintable(wordFontSize),
1525              qPrintable(lyricFontFamily), qPrintable(lyricFontSize));
1526       */
1527       updateStyles(_score, wordFontFamily, wordFontSize, lyricFontFamily, lyricFontSize);
1528 
1529       _score->setDefaultsRead(true); // TODO only if actually succeeded ?
1530       }
1531 
1532 //---------------------------------------------------------
1533 //   pageLayout
1534 //---------------------------------------------------------
1535 
1536 /**
1537  Parse the /score-partwise/defaults/page-layout node: read the page layout.
1538  Note that MuseScore does not support a separate value for left and right margins
1539  for odd and even pages. Only odd and even left margins are used, together  with
1540  the printable width, which is calculated from the left and right margins in the
1541  MusicXML file.
1542  */
1543 
pageLayout(PageFormat & pf,const qreal conversion)1544 void MusicXMLParserPass1::pageLayout(PageFormat& pf, const qreal conversion)
1545       {
1546       Q_ASSERT(_e.isStartElement() && _e.name() == "page-layout");
1547       _logger->logDebugTrace("MusicXMLParserPass1::pageLayout", &_e);
1548 
1549       qreal _oddRightMargin  = 0.0;
1550       qreal _evenRightMargin = 0.0;
1551       QSizeF size;
1552 
1553       while (_e.readNextStartElement()) {
1554             if (_e.name() == "page-margins") {
1555                   QString type = _e.attributes().value("type").toString();
1556                   if (type == "")
1557                         type = "both";
1558                   qreal lm = 0.0, rm = 0.0, tm = 0.0, bm = 0.0;
1559                   while (_e.readNextStartElement()) {
1560                         if (_e.name() == "left-margin")
1561                               lm = _e.readElementText().toDouble() * conversion;
1562                         else if (_e.name() == "right-margin")
1563                               rm = _e.readElementText().toDouble() * conversion;
1564                         else if (_e.name() == "top-margin")
1565                               tm = _e.readElementText().toDouble() * conversion;
1566                         else if (_e.name() == "bottom-margin")
1567                               bm = _e.readElementText().toDouble() * conversion;
1568                         else
1569                               skipLogCurrElem();
1570                         }
1571                   pf.twosided = type == "odd" || type == "even";
1572                   if (type == "odd" || type == "both") {
1573                         pf.oddLeftMargin = lm;
1574                         _oddRightMargin = rm;
1575                         pf.oddTopMargin = tm;
1576                         pf.oddBottomMargin = bm;
1577                         }
1578                   if (type == "even" || type == "both") {
1579                         pf.evenLeftMargin = lm;
1580                         _evenRightMargin = rm;
1581                         pf.evenTopMargin = tm;
1582                         pf.evenBottomMargin = bm;
1583                         }
1584                   }
1585             else if (_e.name() == "page-height") {
1586                   double val = _e.readElementText().toDouble();
1587                   size.rheight() = val * conversion;
1588                   // set pageHeight and pageWidth for use by doCredits()
1589                   _pageSize.setHeight(static_cast<int>(val + 0.5));
1590                   }
1591             else if (_e.name() == "page-width") {
1592                   double val = _e.readElementText().toDouble();
1593                   size.rwidth() = val * conversion;
1594                   // set pageHeight and pageWidth for use by doCredits()
1595                   _pageSize.setWidth(static_cast<int>(val + 0.5));
1596                   }
1597             else
1598                   skipLogCurrElem();
1599             }
1600       pf.size = size;
1601       qreal w1 = size.width() - pf.oddLeftMargin - _oddRightMargin;
1602       qreal w2 = size.width() - pf.evenLeftMargin - _evenRightMargin;
1603       pf.printableWidth = qMax(w1, w2);   // silently adjust right margins
1604       }
1605 
1606 //---------------------------------------------------------
1607 //   partList
1608 //---------------------------------------------------------
1609 
1610 /**
1611  Parse the /score-partwise/part-list:
1612  create the parts and for each part set id and name.
1613  Also handle the part-groups.
1614  */
1615 
partList(MusicXmlPartGroupList & partGroupList)1616 void MusicXMLParserPass1::partList(MusicXmlPartGroupList& partGroupList)
1617       {
1618       Q_ASSERT(_e.isStartElement() && _e.name() == "part-list");
1619       _logger->logDebugTrace("MusicXMLParserPass1::partList", &_e);
1620 
1621       int scoreParts = 0; // number of score-parts read sofar
1622       MusicXmlPartGroupMap partGroups;
1623 
1624       while (_e.readNextStartElement()) {
1625             if (_e.name() == "part-group")
1626                   partGroup(scoreParts, partGroupList, partGroups);
1627             else if (_e.name() == "score-part") {
1628                   scorePart();
1629                   scoreParts++;
1630                   }
1631             else
1632                   skipLogCurrElem();
1633             }
1634       }
1635 
1636 //---------------------------------------------------------
1637 //   createPart
1638 //---------------------------------------------------------
1639 
1640 /**
1641  Create the part, set its \a id and insert it in PartMap \a pm.
1642  Part name (if any) will be set later.
1643  */
1644 
createPart(Score * score,const QString & id,PartMap & pm)1645 static void createPart(Score* score, const QString& id, PartMap& pm)
1646       {
1647       Part* part = new Part(score);
1648       pm.insert(id, part);
1649       part->setId(id);
1650       score->appendPart(part);
1651       Staff* staff = new Staff(score);
1652       staff->setPart(part);
1653       part->staves()->push_back(staff);
1654       score->staves().push_back(staff);
1655       // TODO TBD tuplets.resize(VOICES); // part now contains one staff, thus VOICES voices
1656       }
1657 
1658 //---------------------------------------------------------
1659 //   partGroupStart
1660 //---------------------------------------------------------
1661 
1662 typedef std::map<int,MusicXmlPartGroup*> MusicXmlPartGroupMap;
1663 
1664 /**
1665  Store part-group start with number \a n, first part \a p and symbol / \a s in the partGroups
1666  map \a pgs for later reference, as at this time insufficient information is available to be able
1667  to generate the brackets.
1668  */
1669 
partGroupStart(MusicXmlPartGroupMap & pgs,int n,int p,QString s,bool barlineSpan)1670 static void partGroupStart(MusicXmlPartGroupMap& pgs, int n, int p, QString s, bool barlineSpan)
1671       {
1672       //qDebug("partGroupStart number=%d part=%d symbol=%s", n, p, qPrintable(s));
1673 
1674       if (pgs.count(n) > 0) {
1675             qDebug("part-group number=%d already active", n);
1676             return;
1677             }
1678 
1679       BracketType bracketType = BracketType::NO_BRACKET;
1680       if (s == "")
1681             ;        // ignore (handle as NO_BRACKET)
1682       else if (s == "none")
1683             ;        // already set to NO_BRACKET
1684       else if (s == "brace")
1685             bracketType = BracketType::BRACE;
1686       else if (s == "bracket")
1687             bracketType = BracketType::NORMAL;
1688       else if (s == "line")
1689             bracketType = BracketType::LINE;
1690       else if (s == "square")
1691             bracketType = BracketType::SQUARE;
1692       else {
1693             qDebug("part-group symbol=%s not supported", qPrintable(s));
1694             return;
1695             }
1696 
1697       MusicXmlPartGroup* pg = new MusicXmlPartGroup;
1698       pg->span = 0;
1699       pg->start = p;
1700       pg->barlineSpan = barlineSpan,
1701       pg->type = bracketType;
1702       pg->column = n;
1703       pgs[n] = pg;
1704       }
1705 
1706 //---------------------------------------------------------
1707 //   partGroupStop
1708 //---------------------------------------------------------
1709 
1710 /**
1711  Handle part-group stop with number \a n and part \a p.
1712 
1713  For part group n, the start part, span (in parts) and type are now known.
1714  To generate brackets, the span in staves must also be known.
1715  */
1716 
partGroupStop(MusicXmlPartGroupMap & pgs,int n,int p,MusicXmlPartGroupList & pgl)1717 static void partGroupStop(MusicXmlPartGroupMap& pgs, int n, int p,
1718                           MusicXmlPartGroupList& pgl)
1719       {
1720       if (pgs.count(n) == 0) {
1721             qDebug("part-group number=%d not active", n);
1722             return;
1723             }
1724 
1725       pgs[n]->span = p - pgs[n]->start;
1726       //qDebug("partgroupstop number=%d start=%d span=%d type=%hhd",
1727       //       n, pgs[n]->start, pgs[n]->span, pgs[n]->type);
1728       pgl.push_back(pgs[n]);
1729       pgs.erase(n);
1730       }
1731 
1732 //---------------------------------------------------------
1733 //   partGroup
1734 //---------------------------------------------------------
1735 
1736 /**
1737  Parse the /score-partwise/part-list/part-group node.
1738  */
1739 
partGroup(const int scoreParts,MusicXmlPartGroupList & partGroupList,MusicXmlPartGroupMap & partGroups)1740 void MusicXMLParserPass1::partGroup(const int scoreParts,
1741                                     MusicXmlPartGroupList& partGroupList,
1742                                     MusicXmlPartGroupMap& partGroups)
1743       {
1744       Q_ASSERT(_e.isStartElement() && _e.name() == "part-group");
1745       _logger->logDebugTrace("MusicXMLParserPass1::partGroup", &_e);
1746       bool barlineSpan = true;
1747       int number = _e.attributes().value("number").toInt();
1748       if (number > 0) number--;
1749       QString symbol = "";
1750       QString type = _e.attributes().value("type").toString();
1751 
1752       while (_e.readNextStartElement()) {
1753             if (_e.name() == "group-name")
1754                   _e.skipCurrentElement();  // skip but don't log
1755             else if (_e.name() == "group-abbreviation")
1756                   symbol = _e.readElementText();
1757             else if (_e.name() == "group-symbol")
1758                   symbol = _e.readElementText();
1759             else if (_e.name() == "group-barline") {
1760                   if (_e.readElementText() == "no")
1761                         barlineSpan = false;
1762                   }
1763             else
1764                   skipLogCurrElem();
1765             }
1766 
1767       if (type == "start")
1768             partGroupStart(partGroups, number, scoreParts, symbol, barlineSpan);
1769       else if (type == "stop")
1770             partGroupStop(partGroups, number, scoreParts, partGroupList);
1771       else
1772             _logger->logError(QString("part-group type '%1' not supported").arg(type), &_e);
1773       }
1774 
1775 //---------------------------------------------------------
1776 //   findInstrument
1777 //---------------------------------------------------------
1778 
1779 /**
1780  Find the first InstrumentTemplate with musicXMLid instrSound
1781  and a non-empty set of channels.
1782  */
1783 
1784 #if 0 // not used
1785 static const InstrumentTemplate* findInstrument(const QString& instrSound)
1786       {
1787       const InstrumentTemplate* instr = nullptr;
1788 
1789       for (const InstrumentGroup* group : instrumentGroups) {
1790             for (const InstrumentTemplate* templ : group->instrumentTemplates) {
1791                   if (templ->musicXMLid == instrSound && !templ->channel.isEmpty()) {
1792                         return templ;
1793                         }
1794                   }
1795             }
1796       return instr;
1797       }
1798 #endif
1799 
1800 //---------------------------------------------------------
1801 //   scorePart
1802 //---------------------------------------------------------
1803 
1804 /**
1805  Parse the /score-partwise/part-list/score-part node:
1806  create the part and sets id and name.
1807  Note that a part is created even if no part-name is present
1808  which is invalid MusicXML but is (sometimes ?) generated by NWC2MusicXML.
1809  */
1810 
scorePart()1811 void MusicXMLParserPass1::scorePart()
1812       {
1813       Q_ASSERT(_e.isStartElement() && _e.name() == "score-part");
1814       _logger->logDebugTrace("MusicXMLParserPass1::scorePart", &_e);
1815       QString id = _e.attributes().value("id").toString().trimmed();
1816 
1817       if (_parts.contains(id)) {
1818             _logger->logError(QString("duplicate part id '%1'").arg(id), &_e);
1819             skipLogCurrElem();
1820             return;
1821             }
1822       else {
1823             _parts.insert(id, MusicXmlPart(id));
1824             _instruments.insert(id, MusicXMLInstruments());
1825             createPart(_score, id, _partMap);
1826             }
1827 
1828       while (_e.readNextStartElement()) {
1829             if (_e.name() == "part-name") {
1830                   // Element part-name contains the displayed (full) part name
1831                   // It is displayed by default, but can be suppressed (print-object=”no”)
1832                   // As of MusicXML 3.0, formatting is deprecated, with part-name in plain text
1833                   // and the formatted version in the part-name-display element
1834                   _parts[id].setPrintName(!(_e.attributes().value("print-object") == "no"));
1835                   QString name = _e.readElementText();
1836                   _parts[id].setName(name);
1837                   }
1838             else if (_e.name() == "part-name-display") {
1839                   // TODO
1840                   _e.skipCurrentElement(); // skip but don't log
1841                   }
1842             else if (_e.name() == "part-abbreviation") {
1843                   // Element part-name contains the displayed (abbreviated) part name
1844                   // It is displayed by default, but can be suppressed (print-object=”no”)
1845                   // As of MusicXML 3.0, formatting is deprecated, with part-name in plain text
1846                   // and the formatted version in the part-abbreviation-display element
1847                   _parts[id].setPrintAbbr(!(_e.attributes().value("print-object") == "no"));
1848                   QString name = _e.readElementText();
1849                   _parts[id].setAbbr(name);
1850                   }
1851             else if (_e.name() == "part-abbreviation-display")
1852                   _e.skipCurrentElement();  // skip but don't log
1853             else if (_e.name() == "score-instrument")
1854                   scoreInstrument(id);
1855             else if (_e.name() == "midi-device") {
1856                   if (!_e.attributes().hasAttribute("port")) {
1857                         _e.readElementText(); // empty string
1858                         continue;
1859                         }
1860                   QString instrId = _e.attributes().value("id").toString();
1861                   QString port = _e.attributes().value("port").toString();
1862                   // If instrId is missing, the device assignment affects all
1863                   // score-instrument elements in the score-part
1864                   if (instrId.isEmpty()) {
1865                         for (auto it = _instruments[id].cbegin(); it != _instruments[id].cend(); ++it)
1866                               _instruments[id][it.key()].midiPort = port.toInt() - 1;
1867                         }
1868                   else if (_instruments[id].contains(instrId))
1869                         _instruments[id][instrId].midiPort = port.toInt() - 1;
1870 
1871                   _e.readElementText(); // empty string
1872                   }
1873             else if (_e.name() == "midi-instrument")
1874                   midiInstrument(id);
1875             else
1876                   skipLogCurrElem();
1877             }
1878 
1879       Q_ASSERT(_e.isEndElement() && _e.name() == "score-part");
1880       }
1881 
1882 //---------------------------------------------------------
1883 //   scoreInstrument
1884 //---------------------------------------------------------
1885 
1886 /**
1887  Parse the /score-partwise/part-list/score-part/score-instrument node.
1888  */
1889 
scoreInstrument(const QString & partId)1890 void MusicXMLParserPass1::scoreInstrument(const QString& partId)
1891       {
1892       Q_ASSERT(_e.isStartElement() && _e.name() == "score-instrument");
1893       _logger->logDebugTrace("MusicXMLParserPass1::scoreInstrument", &_e);
1894       QString instrId = _e.attributes().value("id").toString();
1895 
1896       while (_e.readNextStartElement()) {
1897             if (_e.name() == "ensemble")
1898                   skipLogCurrElem();
1899             else if (_e.name() == "instrument-name") {
1900                   QString instrName = _e.readElementText();
1901                   /*
1902                   qDebug("partId '%s' instrId '%s' instrName '%s'",
1903                          qPrintable(partId),
1904                          qPrintable(instrId),
1905                          qPrintable(instrName)
1906                          );
1907                    */
1908                   _instruments[partId].insert(instrId, MusicXMLInstrument(instrName));
1909                   // Element instrument-name is typically not displayed in the score,
1910                   // but used only internally
1911                   if (_instruments[partId].contains(instrId))
1912                         _instruments[partId][instrId].name = instrName;
1913                   }
1914             else if (_e.name() == "instrument-sound") {
1915                   QString instrSound = _e.readElementText();
1916                   if (_instruments[partId].contains(instrId))
1917                         _instruments[partId][instrId].sound = instrSound;
1918                   }
1919             else if (_e.name() == "virtual-instrument") {
1920                   while (_e.readNextStartElement()) {
1921                         if (_e.name() == "virtual-library") {
1922                               QString virtualLibrary = _e.readElementText();
1923                               if (_instruments[partId].contains(instrId))
1924                                     _instruments[partId][instrId].virtLib = virtualLibrary;
1925                               }
1926                         else if (_e.name() == "virtual-name") {
1927                               QString virtualName = _e.readElementText();
1928                               if (_instruments[partId].contains(instrId))
1929                                     _instruments[partId][instrId].virtName = virtualName;
1930                               }
1931                         else
1932                               skipLogCurrElem();
1933                         }
1934                   }
1935             else
1936                   skipLogCurrElem();
1937             }
1938       Q_ASSERT(_e.isEndElement() && _e.name() == "score-instrument");
1939       }
1940 
1941 //---------------------------------------------------------
1942 //   midiInstrument
1943 //---------------------------------------------------------
1944 
1945 /**
1946  Parse the /score-partwise/part-list/score-part/midi-instrument node.
1947  */
1948 
midiInstrument(const QString & partId)1949 void MusicXMLParserPass1::midiInstrument(const QString& partId)
1950       {
1951       Q_ASSERT(_e.isStartElement() && _e.name() == "midi-instrument");
1952       _logger->logDebugTrace("MusicXMLParserPass1::midiInstrument", &_e);
1953       QString instrId = _e.attributes().value("id").toString();
1954 
1955       while (_e.readNextStartElement()) {
1956             if (_e.name() == "midi-bank")
1957                   skipLogCurrElem();
1958             else if (_e.name() == "midi-channel") {
1959                   int channel = _e.readElementText().toInt();
1960                   if (channel < 1) {
1961                         _logger->logError(QString("incorrect midi-channel: %1").arg(channel), &_e);
1962                         channel = 1;
1963                         }
1964                   else if (channel > 16) {
1965                         _logger->logError(QString("incorrect midi-channel: %1").arg(channel), &_e);
1966                         channel = 16;
1967                         }
1968                   if (_instruments[partId].contains(instrId))
1969                         _instruments[partId][instrId].midiChannel = channel - 1;
1970                   }
1971             else if (_e.name() == "midi-program") {
1972                   int program = _e.readElementText().toInt();
1973                   // Bug fix for Cubase 6.5.5 which generates <midi-program>0</midi-program>
1974                   // Check program number range
1975                   if (program < 1) {
1976                         _logger->logError(QString("incorrect midi-program: %1").arg(program), &_e);
1977                         program = 1;
1978                         }
1979                   else if (program > 128) {
1980                         _logger->logError(QString("incorrect midi-program: %1").arg(program), &_e);
1981                         program = 128;
1982                         }
1983                   if (_instruments[partId].contains(instrId))
1984                         _instruments[partId][instrId].midiProgram = program - 1;
1985                   }
1986             else if (_e.name() == "midi-unpitched") {
1987                   if (_instruments[partId].contains(instrId))
1988                         _instruments[partId][instrId].unpitched = _e.readElementText().toInt() - 1;
1989                   }
1990             else if (_e.name() == "volume") {
1991                   double vol = _e.readElementText().toDouble();
1992                   if (vol >= 0 && vol <= 100) {
1993                         if (_instruments[partId].contains(instrId))
1994                               _instruments[partId][instrId].midiVolume = static_cast<int>((vol / 100) * 127);
1995                         }
1996                   else
1997                         _logger->logError(QString("incorrect midi-volume: %1").arg(vol), &_e);
1998                   }
1999             else if (_e.name() == "pan") {
2000                   double pan = _e.readElementText().toDouble();
2001                   if (pan >= -90 && pan <= 90) {
2002                         if (_instruments[partId].contains(instrId))
2003                               _instruments[partId][instrId].midiPan = static_cast<int>(((pan + 90) / 180) * 127);
2004                         }
2005                   else
2006                         _logger->logError(QString("incorrect midi-volume: %g1").arg(pan), &_e);
2007                   }
2008             else
2009                   skipLogCurrElem();
2010             }
2011       Q_ASSERT(_e.isEndElement() && _e.name() == "midi-instrument");
2012       }
2013 
2014 //---------------------------------------------------------
2015 //   setNumberOfStavesForPart
2016 //---------------------------------------------------------
2017 
2018 /**
2019  Set number of staves for part \a partId to the max value
2020  of the current value \a staves.
2021  */
2022 
setNumberOfStavesForPart(Part * const part,const int staves)2023 static void setNumberOfStavesForPart(Part* const part, const int staves)
2024       {
2025       Q_ASSERT(part);
2026       if (staves > part->nstaves())
2027             part->setStaves(staves);
2028       }
2029 
2030 //---------------------------------------------------------
2031 //   part
2032 //---------------------------------------------------------
2033 
2034 /**
2035  Parse the /score-partwise/part node:
2036  read the parts data to determine measure timing and octave shifts.
2037  Assign voices and staves.
2038  */
2039 
part()2040 void MusicXMLParserPass1::part()
2041       {
2042       Q_ASSERT(_e.isStartElement() && _e.name() == "part");
2043       _logger->logDebugTrace("MusicXMLParserPass1::part", &_e);
2044       const QString id = _e.attributes().value("id").toString().trimmed();
2045 
2046       if (!_parts.contains(id)) {
2047             _logger->logError(QString("cannot find part '%1'").arg(id), &_e);
2048             skipLogCurrElem();
2049             }
2050 
2051       initPartState(id);
2052 
2053       VoiceOverlapDetector vod;
2054       Fraction time;  // current time within part
2055       Fraction mdur;  // measure duration
2056 
2057       int measureNr = 0;
2058       while (_e.readNextStartElement()) {
2059             if (_e.name() == "measure") {
2060                   measure(id, time, mdur, vod, measureNr);
2061                   time += mdur;
2062                   ++measureNr;
2063                   }
2064             else
2065                   skipLogCurrElem();
2066             }
2067 
2068       // Bug fix for Cubase 6.5.5..9.5.10 which generate <staff>2</staff> in a single staff part
2069       setNumberOfStavesForPart(_partMap.value(id), _parts[id].maxStaff());
2070       // allocate MuseScore staff to MusicXML voices
2071       allocateStaves(_parts[id].voicelist);
2072       // allocate MuseScore voice to MusicXML voices
2073       allocateVoices(_parts[id].voicelist);
2074       // calculate the octave shifts
2075       _parts[id].calcOctaveShifts();
2076       // determine the lyric numbers for this part
2077       _parts[id].lyricNumberHandler().determineLyricNos();
2078 
2079       // debug: print results
2080       //qDebug("%s", qPrintable(_parts[id].toString()));
2081 
2082       //qDebug("lyric numbers: %s", qPrintable(_parts[id].lyricNumberHandler().toString()));
2083 
2084 #if 0
2085       qDebug("instrument map:");
2086       for (auto& instr : _parts[id]._instrList) {
2087             qDebug("- %s '%s'", qPrintable(instr.first.print()), qPrintable(instr.second));
2088             }
2089       qDebug("transpose map:");
2090       for (auto& it : _parts[id]._intervals) {
2091             qDebug("- %s %d %d", qPrintable(it.first.print()), it.second.diatonic, it.second.chromatic);
2092             }
2093       qDebug("instrument transpositions:");
2094       if (_parts[id]._instrList.empty()) {
2095             const Fraction tick { 0, 1 };
2096             const QString name { "none" };
2097             const auto interval = _parts[id]._intervals.interval(tick);
2098             qDebug("- %s '%s' -> %d %d",
2099                    qPrintable(tick.print()), qPrintable(name), interval.diatonic, interval.chromatic);
2100             }
2101       else {
2102             for (auto& instr : _parts[id]._instrList) {
2103                   const auto& tick = instr.first;
2104                   const auto& name = instr.second;
2105                   const auto interval = _parts[id].interval(tick);
2106                   qDebug("- %s '%s' -> %d %d",
2107                          qPrintable(tick.print()), qPrintable(name), interval.diatonic, interval.chromatic);
2108                   }
2109             }
2110 #endif
2111 
2112       /*
2113       qDebug("voiceMapperStats: new staff");
2114       VoiceList& vl = _parts[id].voicelist;
2115       for (auto i = vl.constBegin(); i != vl.constEnd(); ++i) {
2116             qDebug("voiceMapperStats: voice %s staff data %s",
2117                    qPrintable(i.key()), qPrintable(i.value().toString()));
2118             }
2119       */
2120       }
2121 
2122 //---------------------------------------------------------
2123 //   measureDurationAsFraction
2124 //---------------------------------------------------------
2125 
2126 /**
2127  Determine a suitable measure duration value given the time signature
2128  by setting the duration denominator to be greater than or equal
2129  to the time signature denominator
2130  */
2131 
measureDurationAsFraction(const Fraction length,const int tsigtype)2132 static Fraction measureDurationAsFraction(const Fraction length, const int tsigtype)
2133       {
2134       if (tsigtype <= 0)
2135             // invalid tsigtype
2136             return length;
2137 
2138       Fraction res = length;
2139       while (res.denominator() < tsigtype) {
2140             res.setNumerator(res.numerator() * 2);
2141             res.setDenominator(res.denominator() * 2);
2142             }
2143       return res;
2144       }
2145 
2146 //---------------------------------------------------------
2147 //   measure
2148 //---------------------------------------------------------
2149 
2150 /**
2151  Parse the /score-partwise/part/measure node:
2152  read the measures data as required to determine measure timing, octave shifts
2153  and assign voices and staves.
2154  */
2155 
measure(const QString & partId,const Fraction cTime,Fraction & mdur,VoiceOverlapDetector & vod,const int measureNr)2156 void MusicXMLParserPass1::measure(const QString& partId,
2157                                   const Fraction cTime,
2158                                   Fraction& mdur,
2159                                   VoiceOverlapDetector& vod,
2160                                   const int measureNr)
2161       {
2162       Q_ASSERT(_e.isStartElement() && _e.name() == "measure");
2163       _logger->logDebugTrace("MusicXMLParserPass1::measure", &_e);
2164       QString number = _e.attributes().value("number").toString();
2165 
2166       Fraction mTime; // current time stamp within measure
2167       Fraction mDura; // current total measure duration
2168       vod.newMeasure();
2169       MxmlTupletStates tupletStates;
2170 
2171       while (_e.readNextStartElement()) {
2172             if (_e.name() == "attributes")
2173                   attributes(partId, cTime + mTime);
2174             else if (_e.name() == "barline")
2175                   _e.skipCurrentElement();  // skip but don't log
2176             else if (_e.name() == "note") {
2177                   Fraction missingPrev;
2178                   Fraction dura;
2179                   Fraction missingCurr;
2180                   // note: chord and grace note handling done in note()
2181                   note(partId, cTime + mTime, missingPrev, dura, missingCurr, vod, tupletStates);
2182                   if (missingPrev.isValid()) {
2183                         mTime += missingPrev;
2184                         }
2185                   if (dura.isValid()) {
2186                         mTime += dura;
2187                         }
2188                   if (missingCurr.isValid()) {
2189                         mTime += missingCurr;
2190                         }
2191                   if (mTime > mDura)
2192                         mDura = mTime;
2193                   }
2194             else if (_e.name() == "forward") {
2195                   Fraction dura;
2196                   forward(dura);
2197                   if (dura.isValid()) {
2198                         mTime += dura;
2199                         if (mTime > mDura)
2200                               mDura = mTime;
2201                         }
2202                   }
2203             else if (_e.name() == "backup") {
2204                   Fraction dura;
2205                   backup(dura);
2206                   if (dura.isValid()) {
2207                         if (dura <= mTime)
2208                               mTime -= dura;
2209                         else {
2210                               _logger->logError("backup beyond measure start", &_e);
2211                               mTime.set(0, 1);
2212                               }
2213                         }
2214                   }
2215             else if (_e.name() == "direction")
2216                   direction(partId, cTime + mTime);
2217             else if (_e.name() == "harmony")
2218                   _e.skipCurrentElement();  // skip but don't log
2219             else if (_e.name() == "print")
2220                   print(measureNr);
2221             else if (_e.name() == "sound")
2222                   _e.skipCurrentElement();  // skip but don't log
2223             else
2224                   skipLogCurrElem();
2225 
2226             /*
2227              qDebug("mTime %s (%s) mDura %s (%s)",
2228              qPrintable(mTime.print()),
2229              qPrintable(mTime.reduced().print()),
2230              qPrintable(mDura.print()),
2231              qPrintable(mDura.reduced().print()));
2232              */
2233             }
2234 
2235       // debug vod
2236       // vod.dump();
2237       // copy overlap data from vod to voicelist
2238       copyOverlapData(vod, _parts[partId].voicelist);
2239 
2240       // measure duration fixups
2241       mDura.reduce();
2242 
2243       // fix for PDFtoMusic Pro v1.3.0d Build BF4E and PlayScore / ReadScoreLib Version 3.11
2244       // which sometimes generate empty measures
2245       // if no valid length found and length according to time signature is known,
2246       // use length according to time signature
2247       if (mDura.isZero() && _timeSigDura.isValid() && _timeSigDura > Fraction(0, 1))
2248             mDura = _timeSigDura;
2249       // if no valid length found and time signature is unknown, use default
2250       if (mDura.isZero() && !_timeSigDura.isValid())
2251             mDura = Fraction(4, 4);
2252 
2253       // if necessary, round up to an integral number of 1/64s,
2254       // to comply with MuseScores actual measure length constraints
2255       Fraction length = mDura * Fraction(64,1);
2256       Fraction correctedLength = mDura;
2257       length.reduce();
2258       if (length.denominator() != 1) {
2259             Fraction roundDown = Fraction(length.numerator() / length.denominator(), 64);
2260             Fraction roundUp = Fraction(length.numerator() / length.denominator() + 1, 64);
2261             // mDura is not an integer multiple of 1/64;
2262             // first check if the duration is larger than an integer multiple of 1/64
2263             // by an amount smaller than the minimum division resolution
2264             // in that case, round down (rounding errors have possibly occurred),
2265             // otherwise, round up
2266             if ((_divs > 0) && ((mDura - roundDown) < Fraction(1, 4*_divs))) {
2267                   _logger->logError(QString("rounding down measure duration %1 to %2")
2268                                     .arg(qPrintable(mDura.print())).arg(qPrintable(roundDown.print())),
2269                                     &_e);
2270                   correctedLength = roundDown;
2271                   }
2272             else {
2273                   _logger->logError(QString("rounding up measure duration %1 to %2")
2274                                     .arg(qPrintable(mDura.print())).arg(qPrintable(roundUp.print())),
2275                                     &_e);
2276                   correctedLength = roundUp;
2277                   }
2278             mDura = correctedLength;
2279             }
2280 
2281       // set measure duration to a suitable value given the time signature
2282       if (_timeSigDura.isValid() && _timeSigDura > Fraction(0, 1)) {
2283             int btp = _timeSigDura.denominator();
2284             if (btp > 0)
2285                   mDura = measureDurationAsFraction(mDura, btp);
2286             }
2287 
2288       // set return value(s)
2289       mdur = mDura;
2290 
2291       // set measure number and duration
2292       /*
2293       qDebug("part %s measure %s dura %s (%d)",
2294              qPrintable(partId), qPrintable(number), qPrintable(mdur.print()), mdur.ticks());
2295        */
2296       _parts[partId].addMeasureNumberAndDuration(number, mdur);
2297       }
2298 
2299 //---------------------------------------------------------
2300 //   print
2301 //---------------------------------------------------------
2302 
print(const int measureNr)2303 void MusicXMLParserPass1::print(const int measureNr)
2304       {
2305       Q_ASSERT(_e.isStartElement() && _e.name() == "print");
2306       _logger->logDebugTrace("MusicXMLParserPass1::print", &_e);
2307 
2308       const QString newPage = _e.attributes().value("new-page").toString();
2309       const QString newSystem = _e.attributes().value("new-system").toString();
2310       if (newPage == "yes")
2311             _pageStartMeasureNrs.insert(measureNr);
2312       if (newSystem == "yes")
2313             _systemStartMeasureNrs.insert(measureNr);
2314 
2315       _e.skipCurrentElement();        // skip but don't log
2316       }
2317 
2318 //---------------------------------------------------------
2319 //   attributes
2320 //---------------------------------------------------------
2321 
2322 /**
2323  Parse the /score-partwise/part/measure/attributes node.
2324  */
2325 
attributes(const QString & partId,const Fraction cTime)2326 void MusicXMLParserPass1::attributes(const QString& partId, const Fraction cTime)
2327       {
2328       Q_ASSERT(_e.isStartElement() && _e.name() == "attributes");
2329       _logger->logDebugTrace("MusicXMLParserPass1::attributes", &_e);
2330 
2331       while (_e.readNextStartElement()) {
2332             if (_e.name() == "clef")
2333                   clef(partId);
2334             else if (_e.name() == "divisions")
2335                   divisions();
2336             else if (_e.name() == "key")
2337                   _e.skipCurrentElement();  // skip but don't log
2338             else if (_e.name() == "instruments")
2339                   _e.skipCurrentElement();  // skip but don't log
2340             else if (_e.name() == "staff-details")
2341                   _e.skipCurrentElement();  // skip but don't log
2342             else if (_e.name() == "staves")
2343                   staves(partId);
2344             else if (_e.name() == "time")
2345                   time(cTime);
2346             else if (_e.name() == "transpose")
2347                   transpose(partId, cTime);
2348             else
2349                   skipLogCurrElem();
2350             }
2351       }
2352 
2353 //---------------------------------------------------------
2354 //   clef
2355 //---------------------------------------------------------
2356 
2357 /**
2358  Parse the /score-partwise/part/measure/attributes/clef node.
2359  TODO: Store the clef type, to simplify staff type setting in pass 2.
2360  */
2361 
clef(const QString &)2362 void MusicXMLParserPass1::clef(const QString& /* partId */)
2363       {
2364       Q_ASSERT(_e.isStartElement() && _e.name() == "clef");
2365       _logger->logDebugTrace("MusicXMLParserPass1::clef", &_e);
2366 
2367       QString number = _e.attributes().value("number").toString();
2368       int n = 0;
2369       if (number != "") {
2370             n = number.toInt();
2371             if (n <= 0) {
2372                   _logger->logError(QString("invalid number %1").arg(number), &_e);
2373                   n = 0;
2374                   }
2375             else
2376                   n--;              // make zero-based
2377             }
2378 
2379       while (_e.readNextStartElement()) {
2380             if (_e.name() == "line")
2381                   _e.skipCurrentElement();  // skip but don't log
2382             else if (_e.name() == "sign")
2383                   QString sign = _e.readElementText();
2384             else
2385                   skipLogCurrElem();
2386             }
2387       }
2388 
2389 //---------------------------------------------------------
2390 //   determineTimeSig
2391 //---------------------------------------------------------
2392 
2393 /**
2394  Determine the time signature based on \a beats, \a beatType and \a timeSymbol.
2395  Sets return parameters \a st, \a bts, \a btp.
2396  Return true if OK, false on error.
2397  */
2398 
2399 // TODO: share between pass 1 and pass 2
2400 
determineTimeSig(MxmlLogger * logger,const QXmlStreamReader * const xmlreader,const QString beats,const QString beatType,const QString timeSymbol,TimeSigType & st,int & bts,int & btp)2401 static bool determineTimeSig(MxmlLogger* logger, const QXmlStreamReader* const xmlreader,
2402                              const QString beats, const QString beatType, const QString timeSymbol,
2403                              TimeSigType& st, int& bts, int& btp)
2404       {
2405       // initialize
2406       st  = TimeSigType::NORMAL;
2407       bts = 0;             // the beats (max 4 separated by "+") as integer
2408       btp = 0;             // beat-type as integer
2409       // determine if timesig is valid
2410       if (beats == "2" && beatType == "2" && timeSymbol == "cut") {
2411             st = TimeSigType::ALLA_BREVE;
2412             bts = 2;
2413             btp = 2;
2414             return true;
2415             }
2416       else if (beats == "4" && beatType == "4" && timeSymbol == "common") {
2417             st = TimeSigType::FOUR_FOUR;
2418             bts = 4;
2419             btp = 4;
2420             return true;
2421             }
2422       else if (beats == "2" && beatType == "2" && timeSymbol == "cut2") {
2423             st = TimeSigType::CUT_BACH;
2424             bts = 2;
2425             btp = 2;
2426             return true;
2427             }
2428       else if (beats == "9" && beatType == "8" && timeSymbol == "cut3") {
2429             st = TimeSigType::CUT_TRIPLE;
2430             bts = 9;
2431             btp = 8;
2432             return true;
2433             }
2434       else {
2435             if (!timeSymbol.isEmpty() && timeSymbol != "normal") {
2436                   logger->logError(QString("time symbol '%1' not recognized with beats=%2 and beat-type=%3")
2437                                    .arg(timeSymbol, beats, beatType), xmlreader);
2438                   return false;
2439                   }
2440 
2441             btp = beatType.toInt();
2442             QStringList list = beats.split("+");
2443             for (int i = 0; i < list.size(); i++)
2444                   bts += list.at(i).toInt();
2445             }
2446 
2447       // determine if bts and btp are valid
2448       if (bts <= 0 || btp <=0) {
2449             logger->logError(QString("beats=%1 and/or beat-type=%2 not recognized")
2450                              .arg(beats, beatType), xmlreader);
2451             return false;
2452             }
2453 
2454       return true;
2455       }
2456 
2457 //---------------------------------------------------------
2458 //   time
2459 //---------------------------------------------------------
2460 
2461 /**
2462  Parse the /score-partwise/part/measure/attributes/time node.
2463  */
2464 
time(const Fraction cTime)2465 void MusicXMLParserPass1::time(const Fraction cTime)
2466       {
2467       Q_ASSERT(_e.isStartElement() && _e.name() == "time");
2468 
2469       QString beats;
2470       QString beatType;
2471       QString timeSymbol = _e.attributes().value("symbol").toString();
2472 
2473       while (_e.readNextStartElement()) {
2474             if (_e.name() == "beats")
2475                   beats = _e.readElementText();
2476             else if (_e.name() == "beat-type")
2477                   beatType = _e.readElementText();
2478             else
2479                   skipLogCurrElem();
2480             }
2481 
2482       if (beats != "" && beatType != "") {
2483             // determine if timesig is valid
2484             TimeSigType st  = TimeSigType::NORMAL;
2485             int bts = 0;       // total beats as integer (beats may contain multiple numbers, separated by "+")
2486             int btp = 0;       // beat-type as integer
2487             if (determineTimeSig(_logger, &_e, beats, beatType, timeSymbol, st, bts, btp)) {
2488                   _timeSigDura = Fraction(bts, btp);
2489                   _score->sigmap()->add(cTime.ticks(), _timeSigDura);
2490                   }
2491             }
2492       }
2493 
2494 //---------------------------------------------------------
2495 //   transpose
2496 //---------------------------------------------------------
2497 
2498 /**
2499  Parse the /score-partwise/part/measure/attributes/transpose node.
2500  */
2501 
transpose(const QString & partId,const Fraction & tick)2502 void MusicXMLParserPass1::transpose(const QString& partId, const Fraction& tick)
2503       {
2504       Q_ASSERT(_e.isStartElement() && _e.name() == "transpose");
2505 
2506       Interval interval;
2507       while (_e.readNextStartElement()) {
2508             int i = _e.readElementText().toInt();
2509             if (_e.name() == "diatonic") {
2510                   interval.diatonic = i;
2511                   }
2512             else if (_e.name() == "chromatic") {
2513                   interval.chromatic = i;
2514                   }
2515             else if (_e.name() == "octave-change") {
2516                   interval.diatonic += i * 7;
2517                   interval.chromatic += i * 12;
2518                   }
2519             else
2520                   skipLogCurrElem();
2521             }
2522 
2523       if (_parts[partId]._intervals.count(tick) == 0)
2524             _parts[partId]._intervals[tick] = interval;
2525       else
2526             qDebug("duplicate transpose at tick %s", qPrintable(tick.print()));
2527       }
2528 
2529 //---------------------------------------------------------
2530 //   divisions
2531 //---------------------------------------------------------
2532 
2533 /**
2534  Parse the /score-partwise/part/measure/attributes/divisions node.
2535  */
2536 
divisions()2537 void MusicXMLParserPass1::divisions()
2538       {
2539       Q_ASSERT(_e.isStartElement() && _e.name() == "divisions");
2540 
2541       _divs = _e.readElementText().toInt();
2542       if (!(_divs > 0))
2543             _logger->logError("illegal divisions", &_e);
2544       }
2545 
2546 //---------------------------------------------------------
2547 //   staves
2548 //---------------------------------------------------------
2549 
2550 /**
2551  Parse the /score-partwise/part/measure/attributes/staves node.
2552  */
2553 
staves(const QString & partId)2554 void MusicXMLParserPass1::staves(const QString& partId)
2555       {
2556       Q_ASSERT(_e.isStartElement() && _e.name() == "staves");
2557       _logger->logDebugTrace("MusicXMLParserPass1::staves", &_e);
2558 
2559       int staves = _e.readElementText().toInt();
2560       if (!(staves > 0 && staves <= MAX_STAVES)) {
2561             _logger->logError("illegal staves", &_e);
2562             return;
2563             }
2564 
2565       setNumberOfStavesForPart(_partMap.value(partId), staves);
2566       }
2567 
2568 //---------------------------------------------------------
2569 //   direction
2570 //---------------------------------------------------------
2571 
2572 /**
2573  Parse the /score-partwise/part/measure/direction node
2574  to be able to handle octave-shifts, as these must be interpreted
2575  in musical order instead of in MusicXML file order.
2576  */
2577 
direction(const QString & partId,const Fraction cTime)2578 void MusicXMLParserPass1::direction(const QString& partId, const Fraction cTime)
2579       {
2580       Q_ASSERT(_e.isStartElement() && _e.name() == "direction");
2581 
2582       // note: file order is direction-type first, then staff
2583       // this means staff is still unknown when direction-type is handled
2584 
2585       QList<MxmlOctaveShiftDesc> starts;
2586       QList<MxmlOctaveShiftDesc> stops;
2587       int staff = 0;
2588 
2589       while (_e.readNextStartElement()) {
2590             if (_e.name() == "direction-type")
2591                   directionType(cTime, starts, stops);
2592             else if (_e.name() == "staff") {
2593                   int nstaves = getPart(partId)->nstaves();
2594                   QString strStaff = _e.readElementText();
2595                   staff = strStaff.toInt() - 1;
2596                   if (0 <= staff && staff < nstaves)
2597                         ;  //qDebug("direction staff %d", staff + 1);
2598                   else {
2599                         _logger->logError(QString("invalid staff %1").arg(strStaff), &_e);
2600                         staff = 0;
2601                         }
2602                   }
2603             else
2604                   _e.skipCurrentElement();
2605             }
2606 
2607       // handle the stops first
2608       foreach (auto desc, stops) {
2609             if (_octaveShifts.contains(desc.num)) {
2610                   MxmlOctaveShiftDesc prevDesc = _octaveShifts.value(desc.num);
2611                   if (prevDesc.tp == MxmlOctaveShiftDesc::Type::UP
2612                       || prevDesc.tp == MxmlOctaveShiftDesc::Type::DOWN) {
2613                         // a complete pair
2614                         _parts[partId].addOctaveShift(staff, prevDesc.size, prevDesc.time);
2615                         _parts[partId].addOctaveShift(staff, -prevDesc.size, desc.time);
2616                         }
2617                   else
2618                         _logger->logError("double octave-shift stop", &_e);
2619                   _octaveShifts.remove(desc.num);
2620                   }
2621             else
2622                   _octaveShifts.insert(desc.num, desc);
2623             }
2624 
2625       // then handle the starts
2626       foreach (auto desc, starts) {
2627             if (_octaveShifts.contains(desc.num)) {
2628                   MxmlOctaveShiftDesc prevDesc = _octaveShifts.value(desc.num);
2629                   if (prevDesc.tp == MxmlOctaveShiftDesc::Type::STOP) {
2630                         // a complete pair
2631                         _parts[partId].addOctaveShift(staff, desc.size, desc.time);
2632                         _parts[partId].addOctaveShift(staff, -desc.size, prevDesc.time);
2633                         }
2634                   else
2635                         _logger->logError("double octave-shift start", &_e);
2636                   _octaveShifts.remove(desc.num);
2637                   }
2638             else
2639                   _octaveShifts.insert(desc.num, desc);
2640             }
2641       }
2642 
2643 //---------------------------------------------------------
2644 //   directionType
2645 //---------------------------------------------------------
2646 
2647 /**
2648  Parse the /score-partwise/part/measure/direction/direction-type node.
2649  */
2650 
directionType(const Fraction cTime,QList<MxmlOctaveShiftDesc> & starts,QList<MxmlOctaveShiftDesc> & stops)2651 void MusicXMLParserPass1::directionType(const Fraction cTime,
2652                                         QList<MxmlOctaveShiftDesc>& starts,
2653                                         QList<MxmlOctaveShiftDesc>& stops)
2654       {
2655       Q_ASSERT(_e.isStartElement() && _e.name() == "direction-type");
2656 
2657       while (_e.readNextStartElement()) {
2658             if (_e.name() == "octave-shift") {
2659                   QString number = _e.attributes().value("number").toString();
2660                   int n = 0;
2661                   if (number != "") {
2662                         n = number.toInt();
2663                         if (n <= 0)
2664                               _logger->logError(QString("invalid number %1").arg(number), &_e);
2665                         else
2666                               n--;  // make zero-based
2667                         }
2668 
2669                   if (0 <= n && n < MAX_NUMBER_LEVEL) {
2670                         short size = _e.attributes().value("size").toShort();
2671                         QString type = _e.attributes().value("type").toString();
2672                         //qDebug("octave-shift type '%s' size %d number %d", qPrintable(type), size, n);
2673                         MxmlOctaveShiftDesc osDesc;
2674                         handleOctaveShift(cTime, type, size, osDesc);
2675                         osDesc.num = n;
2676                         if (osDesc.tp == MxmlOctaveShiftDesc::Type::UP
2677                             || osDesc.tp == MxmlOctaveShiftDesc::Type::DOWN)
2678                               starts.append(osDesc);
2679                         else if (osDesc.tp == MxmlOctaveShiftDesc::Type::STOP)
2680                               stops.append(osDesc);
2681                         }
2682                   else {
2683                         _logger->logError(QString("invalid octave-shift number %1").arg(number), &_e);
2684                         }
2685                   _e.skipCurrentElement();
2686                   }
2687             else
2688                   _e.skipCurrentElement();
2689             }
2690 
2691       Q_ASSERT(_e.isEndElement() && _e.name() == "direction-type");
2692       }
2693 
2694 //---------------------------------------------------------
2695 //   handleOctaveShift
2696 //---------------------------------------------------------
2697 
handleOctaveShift(const Fraction cTime,const QString & type,short size,MxmlOctaveShiftDesc & desc)2698 void MusicXMLParserPass1::handleOctaveShift(const Fraction cTime,
2699                                             const QString& type, short size,
2700                                             MxmlOctaveShiftDesc& desc)
2701       {
2702       MxmlOctaveShiftDesc::Type tp = MxmlOctaveShiftDesc::Type::NONE;
2703       short sz = 0;
2704 
2705       switch (size) {
2706             case   8: sz =  1; break;
2707             case  15: sz =  2; break;
2708             default:
2709                   _logger->logError(QString("invalid octave-shift size %1").arg(size), &_e);
2710                   return;
2711             }
2712 
2713       if (!cTime.isValid() || cTime < Fraction(0, 1))
2714             _logger->logError("invalid current time", &_e);
2715 
2716       if (type == "up")
2717             tp = MxmlOctaveShiftDesc::Type::UP;
2718       else if (type == "down") {
2719             tp = MxmlOctaveShiftDesc::Type::DOWN;
2720             sz *= -1;
2721             }
2722       else if (type == "stop")
2723             tp = MxmlOctaveShiftDesc::Type::STOP;
2724       else {
2725             _logger->logError(QString("invalid octave-shift type '%1'").arg(type), &_e);
2726             return;
2727             }
2728 
2729       desc = MxmlOctaveShiftDesc(tp, sz, cTime);
2730       }
2731 
2732 //---------------------------------------------------------
2733 //   notations
2734 //---------------------------------------------------------
2735 
2736 /**
2737  Parse the /score-partwise/part/measure/note/notations node.
2738  */
2739 
notations(MxmlStartStop & tupletStartStop)2740 void MusicXMLParserPass1::notations(MxmlStartStop& tupletStartStop)
2741       {
2742       Q_ASSERT(_e.isStartElement() && _e.name() == "notations");
2743       //_logger->logDebugTrace("MusicXMLParserPass1::note", &_e);
2744 
2745       while (_e.readNextStartElement()) {
2746             if (_e.name() == "tuplet") {
2747                   QString tupletType       = _e.attributes().value("type").toString();
2748 
2749                   // ignore possible children (currently not supported)
2750                   _e.skipCurrentElement();
2751 
2752                   if (tupletType == "start")
2753                         tupletStartStop = MxmlStartStop::START;
2754                   else if (tupletType == "stop")
2755                         tupletStartStop = MxmlStartStop::STOP;
2756                   else if (tupletType != "" && tupletType != "start" && tupletType != "stop") {
2757                         _logger->logError(QString("unknown tuplet type '%1'").arg(tupletType), &_e);
2758                         }
2759                   }
2760             else {
2761                   _e.skipCurrentElement();        // skip but don't log
2762                   }
2763             }
2764 
2765       Q_ASSERT(_e.isEndElement() && _e.name() == "notations");
2766       }
2767 
2768 //---------------------------------------------------------
2769 //   smallestTypeAndCount
2770 //---------------------------------------------------------
2771 
2772 /**
2773  Determine the smallest note type and the number of those
2774  present in a ChordRest.
2775  For a note without dots the type equals the note type
2776  and count is one.
2777  For a single dotted note the type equals half the note type
2778  and count is three.
2779  A double dotted note is similar.
2780  Note: code assumes when duration().type() is incremented,
2781  the note length is divided by two, checked by tupletAssert().
2782  */
2783 
smallestTypeAndCount(const TDuration durType,int & type,int & count)2784 static void smallestTypeAndCount(const TDuration durType, int& type, int& count)
2785       {
2786       type = int(durType.type());
2787       count = 1;
2788       switch (durType.dots()) {
2789             case 0:
2790                   // nothing to do
2791                   break;
2792             case 1:
2793                   type += 1;       // next-smaller type
2794                   count = 3;
2795                   break;
2796             case 2:
2797                   type += 2;       // next-next-smaller type
2798                   count = 7;
2799                   break;
2800             default:
2801                   qDebug("smallestTypeAndCount() does not support more than 2 dots");
2802             }
2803       }
2804 
2805 //---------------------------------------------------------
2806 //   matchTypeAndCount
2807 //---------------------------------------------------------
2808 
2809 /**
2810  Given two note types and counts, if the types are not equal,
2811  make them equal by successively doubling the count of the
2812  largest type.
2813  */
2814 
matchTypeAndCount(int & type1,int & count1,int & type2,int & count2)2815 static void matchTypeAndCount(int& type1, int& count1, int& type2, int& count2)
2816       {
2817       while (type1 < type2) {
2818             type1++;
2819             count1 *= 2;
2820             }
2821       while (type2 < type1) {
2822             type2++;
2823             count2 *= 2;
2824             }
2825       }
2826 
2827 //---------------------------------------------------------
2828 //   addDurationToTuplet
2829 //---------------------------------------------------------
2830 
2831 /**
2832  Add duration to tuplet duration
2833  Determine type and number of smallest notes in the tuplet
2834  */
2835 
addDurationToTuplet(const Fraction duration,const Fraction timeMod)2836 void MxmlTupletState::addDurationToTuplet(const Fraction duration, const Fraction timeMod)
2837       {
2838       /*
2839       qDebug("1 duration %s timeMod %s -> state.tupletType %d state.tupletCount %d state.actualNotes %d state.normalNotes %d",
2840              qPrintable(duration.print()),
2841              qPrintable(timeMod.print()),
2842              m_tupletType,
2843              m_tupletCount,
2844              m_actualNotes,
2845              m_normalNotes
2846              );
2847       */
2848       if (m_duration <= Fraction(0, 1)) {
2849             // first note: init variables
2850             m_actualNotes = timeMod.denominator();
2851             m_normalNotes = timeMod.numerator();
2852             smallestTypeAndCount(duration / timeMod, m_tupletType, m_tupletCount);
2853             }
2854       else {
2855             int noteType = 0;
2856             int noteCount = 0;
2857             smallestTypeAndCount(duration / timeMod, noteType, noteCount);
2858             // match the types
2859             matchTypeAndCount(m_tupletType, m_tupletCount, noteType, noteCount);
2860             m_tupletCount += noteCount;
2861             }
2862       m_duration += duration;
2863       /*
2864       qDebug("2 duration %s -> state.tupletType %d state.tupletCount %d state.actualNotes %d state.normalNotes %d",
2865              qPrintable(duration.print()),
2866              m_tupletType,
2867              m_tupletCount,
2868              m_actualNotes,
2869              m_normalNotes
2870              );
2871       */
2872       }
2873 
2874 //---------------------------------------------------------
2875 //   determineTupletFractionAndFullDuration
2876 //---------------------------------------------------------
2877 
2878 /**
2879  Split duration into two factors where fullDuration is note sized
2880  (i.e. the denominator is a power of 2), 1/2 < fraction <= 1/1
2881  and fraction * fullDuration equals duration.
2882  */
2883 
determineTupletFractionAndFullDuration(const Fraction duration,Fraction & fraction,Fraction & fullDuration)2884 void determineTupletFractionAndFullDuration(const Fraction duration, Fraction& fraction, Fraction& fullDuration)
2885       {
2886       fraction = duration;
2887       fullDuration = Fraction(1, 1);
2888       // move denominator's powers of 2 from fraction to fullDuration
2889       while (fraction.denominator() % 2 == 0) {
2890             fraction *= 2;
2891             fraction.reduce();
2892             fullDuration *= Fraction(1, 2);
2893             }
2894       // move numerator's powers of 2 from fraction to fullDuration
2895       while ( fraction.numerator() % 2 == 0) {
2896             fraction *= Fraction(1, 2);
2897             fraction.reduce();
2898             fullDuration *= 2;
2899             fullDuration.reduce();
2900             }
2901       // make sure 1/2 < fraction <= 1/1
2902       while (fraction <= Fraction(1, 2)) {
2903             fullDuration *= Fraction(1, 2);
2904             fraction *= 2;
2905             }
2906       fullDuration.reduce();
2907       fraction.reduce();
2908 
2909       /*
2910       Examples (note result when denominator is not a power of two):
2911       3:2 tuplet of 1/4 results in fraction 1/1 and fullDuration 1/2
2912       2:3 tuplet of 1/4 results in fraction 3/1 and fullDuration 1/4
2913       4:3 tuplet of 1/4 results in fraction 3/1 and fullDuration 1/4
2914       3:4 tuplet of 1/4 results in fraction 1/1 and fullDuration 1/1
2915 
2916        Bring back fraction in 1/2 .. 1/1 range.
2917        */
2918 
2919       if (fraction > Fraction(1, 1) && fraction.denominator() == 1) {
2920             fullDuration *= fraction;
2921             fullDuration.reduce();
2922             fraction = Fraction(1, 1);
2923             }
2924 
2925       /*
2926       qDebug("duration %s fraction %s fullDuration %s",
2927              qPrintable(duration.toString()),
2928              qPrintable(fraction.toString()),
2929              qPrintable(fullDuration.toString())
2930              );
2931       */
2932       }
2933 
2934 //---------------------------------------------------------
2935 //   isTupletFilled
2936 //---------------------------------------------------------
2937 
2938 /**
2939  Determine if the tuplet is completely filled,
2940  because either (1) it is at least the same duration
2941  as the specified number of the specified normal type notes
2942  or (2) the duration adds up to a normal note duration.
2943 
2944  Example (1): a 3:2 tuplet with a 1/4 and a 1/8 note
2945  is filled if normal type is 1/8,
2946  it is not filled if normal type is 1/4.
2947 
2948  Example (2): a 3:2 tuplet with a 1/4 and a 1/8 note is filled.
2949  */
2950 
isTupletFilled(const MxmlTupletState & state,const TDuration normalType,const Fraction timeMod)2951 static bool isTupletFilled(const MxmlTupletState& state, const TDuration normalType, const Fraction timeMod)
2952       {
2953       Q_UNUSED(timeMod);
2954       bool res { false };
2955       const auto actualNotes = state.m_actualNotes;
2956       /*
2957       const auto normalNotes = state.m_normalNotes;
2958       qDebug("duration %s normalType %s timeMod %s normalNotes %d actualNotes %d",
2959              qPrintable(state.m_duration.toString()),
2960              qPrintable(normalType.fraction().toString()),
2961              qPrintable(timeMod.toString()),
2962              normalNotes,
2963              actualNotes
2964              );
2965       */
2966 
2967       auto tupletType = state.m_tupletType;
2968       auto tupletCount = state.m_tupletCount;
2969 
2970       if (normalType.isValid()) {
2971             int matchedNormalType  = int(normalType.type());
2972             int matchedNormalCount = actualNotes;
2973             // match the types
2974             matchTypeAndCount(tupletType, tupletCount, matchedNormalType, matchedNormalCount);
2975             // ... result scenario (1)
2976             res = tupletCount >= matchedNormalCount;
2977             /*
2978             qDebug("normalType valid tupletType %d tupletCount %d matchedNormalType %d matchedNormalCount %d res %d",
2979                    tupletType,
2980                    tupletCount,
2981                    matchedNormalType,
2982                    matchedNormalCount,
2983                    res
2984                    );
2985              */
2986             }
2987       else {
2988             // ... result scenario (2)
2989             res = tupletCount >= actualNotes;
2990             /*
2991             qDebug("normalType not valid tupletCount %d actualNotes %d res %d",
2992                    tupletCount,
2993                    actualNotes,
2994                    res
2995                    );
2996              */
2997             }
2998       return res;
2999       }
3000 
3001 //---------------------------------------------------------
3002 //   missingTupletDuration
3003 //---------------------------------------------------------
3004 
missingTupletDuration(const Fraction duration)3005 Fraction missingTupletDuration(const Fraction duration)
3006       {
3007       Fraction tupletFraction;
3008       Fraction tupletFullDuration;
3009 
3010       determineTupletFractionAndFullDuration(duration, tupletFraction, tupletFullDuration);
3011       auto missing = (Fraction(1, 1) - tupletFraction) * tupletFullDuration;
3012 
3013       return missing;
3014       }
3015 
3016 //---------------------------------------------------------
3017 //   determineTupletAction
3018 //---------------------------------------------------------
3019 
3020 /**
3021  Update tuplet state using parse result tupletDesc.
3022  Tuplets with <actual-notes> and <normal-notes> but without <tuplet>
3023  are handled correctly.
3024  TODO Nested tuplets are not (yet) supported.
3025  */
3026 
determineTupletAction(const Fraction noteDuration,const Fraction timeMod,const MxmlStartStop tupletStartStop,const TDuration normalType,Fraction & missingPreviousDuration,Fraction & missingCurrentDuration)3027 MxmlTupletFlags MxmlTupletState::determineTupletAction(const Fraction noteDuration,
3028                                                        const Fraction timeMod,
3029                                                        const MxmlStartStop tupletStartStop,
3030                                                        const TDuration normalType,
3031                                                        Fraction& missingPreviousDuration,
3032                                                        Fraction& missingCurrentDuration
3033                                                        )
3034       {
3035       const auto actualNotes = timeMod.denominator();
3036       const auto normalNotes = timeMod.numerator();
3037       MxmlTupletFlags res = MxmlTupletFlag::NONE;
3038 
3039       // check for unexpected termination of previous tuplet
3040       if (m_inTuplet && timeMod == Fraction(1, 1)) {
3041             // recover by simply stopping the current tuplet first
3042             if (!isTupletFilled(*this, normalType, timeMod)) {
3043                   missingPreviousDuration = missingTupletDuration(m_duration);
3044                   //qDebug("tuplet incomplete, missing %s", qPrintable(missingPreviousDuration.print()));
3045                   }
3046             *this = {};
3047             res |= MxmlTupletFlag::STOP_PREVIOUS;
3048             }
3049 
3050       // check for obvious errors
3051       if (m_inTuplet && tupletStartStop == MxmlStartStop::START) {
3052             qDebug("tuplet already started");
3053             // recover by simply stopping the current tuplet first
3054             if (!isTupletFilled(*this, normalType, timeMod)) {
3055                   missingPreviousDuration = missingTupletDuration(m_duration);
3056                   //qDebug("tuplet incomplete, missing %s", qPrintable(missingPreviousDuration.print()));
3057                   }
3058             *this = {};
3059             res |= MxmlTupletFlag::STOP_PREVIOUS;
3060             }
3061       if (tupletStartStop == MxmlStartStop::STOP && !m_inTuplet) {
3062             qDebug("tuplet stop but no tuplet started");       // TODO
3063             // recovery handled later (automatically, no special case needed)
3064             }
3065 
3066       // Tuplet are either started by the tuplet start
3067       // or when the time modification is first found.
3068       if (!m_inTuplet) {
3069             if (tupletStartStop == MxmlStartStop::START
3070                 || (!m_inTuplet && (actualNotes != 1 || normalNotes != 1))) {
3071                   if (tupletStartStop != MxmlStartStop::START) {
3072                         m_implicit = true;
3073                         }
3074                   else {
3075                         m_implicit = false;
3076                         }
3077                   // create a new tuplet
3078                   m_inTuplet = true;
3079                   res |= MxmlTupletFlag::START_NEW;
3080                   }
3081             }
3082 
3083       // Add chord to the current tuplet.
3084       // Must also check for actual/normal notes to prevent
3085       // adding one chord too much if tuplet stop is missing.
3086       if (m_inTuplet && !(actualNotes == 1 && normalNotes == 1)) {
3087             addDurationToTuplet(noteDuration, timeMod);
3088             res |= MxmlTupletFlag::ADD_CHORD;
3089             }
3090 
3091       // Tuplets are stopped by the tuplet stop
3092       // or when the tuplet is filled completely
3093       // (either with knowledge of the normal type
3094       // or as a last resort calculated based on
3095       // actual and normal notes plus total duration)
3096       // or when the time-modification is not found.
3097 
3098       if (m_inTuplet) {
3099             if (tupletStartStop == MxmlStartStop::STOP
3100                 || (m_implicit && isTupletFilled(*this, normalType, timeMod))
3101                 || (actualNotes == 1 && normalNotes == 1)) {       // incorrect ??? check scenario incomplete tuplet w/o start
3102                   if (actualNotes > normalNotes && !isTupletFilled(*this, normalType, timeMod)) {
3103                         missingCurrentDuration = missingTupletDuration(m_duration);
3104                         qDebug("current tuplet incomplete, missing %s", qPrintable(missingCurrentDuration.print()));
3105                         }
3106 
3107                   *this = {};
3108                   res |= MxmlTupletFlag::STOP_CURRENT;
3109                   }
3110             }
3111 
3112       return res;
3113       }
3114 
3115 //---------------------------------------------------------
3116 //   note
3117 //---------------------------------------------------------
3118 
3119 /**
3120  Parse the /score-partwise/part/measure/note node.
3121  */
3122 
note(const QString & partId,const Fraction sTime,Fraction & missingPrev,Fraction & dura,Fraction & missingCurr,VoiceOverlapDetector & vod,MxmlTupletStates & tupletStates)3123 void MusicXMLParserPass1::note(const QString& partId,
3124                                const Fraction sTime,
3125                                Fraction& missingPrev,
3126                                Fraction& dura,
3127                                Fraction& missingCurr,
3128                                VoiceOverlapDetector& vod,
3129                                MxmlTupletStates& tupletStates)
3130       {
3131       Q_ASSERT(_e.isStartElement() && _e.name() == "note");
3132       //_logger->logDebugTrace("MusicXMLParserPass1::note", &_e);
3133 
3134       if (_e.attributes().value("print-spacing") == "no") {
3135             notePrintSpacingNo(dura);
3136             return;
3137             }
3138 
3139       //float alter = 0;
3140       bool chord = false;
3141       bool grace = false;
3142       //int octave = -1;
3143       bool bRest = false;
3144       int staff = 1;
3145       //int step = 0;
3146       QString type;
3147       QString voice = "1";
3148       QString instrId;
3149       MxmlStartStop tupletStartStop { MxmlStartStop::NONE };
3150 
3151       mxmlNoteDuration mnd(_divs, _logger);
3152 
3153       while (_e.readNextStartElement()) {
3154             if (mnd.readProperties(_e)) {
3155                   // element handled
3156                   }
3157             else if (_e.name() == "accidental")
3158                   _e.skipCurrentElement();  // skip but don't log
3159             else if (_e.name() == "beam") {
3160                   _hasBeamingInfo = true;
3161                   _e.skipCurrentElement();  // skip but don't log
3162                   }
3163             else if (_e.name() == "chord") {
3164                   chord = true;
3165                   _e.readNext();
3166                   }
3167             else if (_e.name() == "cue")
3168                   _e.skipCurrentElement();  // skip but don't log
3169             else if (_e.name() == "grace") {
3170                   grace = true;
3171                   _e.readNext();
3172                   }
3173             else if (_e.name() == "instrument") {
3174                   instrId = _e.attributes().value("id").toString();
3175                   _e.readNext();
3176                   }
3177             else if (_e.name() == "lyric") {
3178                   const auto number = _e.attributes().value("number").toString();
3179                   _parts[partId].lyricNumberHandler().addNumber(number);
3180                   _e.skipCurrentElement();
3181                   }
3182             else if (_e.name() == "notations")
3183                   notations(tupletStartStop);
3184             else if (_e.name() == "notehead")
3185                   _e.skipCurrentElement();  // skip but don't log
3186             else if (_e.name() == "pitch")
3187                   _e.skipCurrentElement();  // skip but don't log
3188             else if (_e.name() == "rest") {
3189                   bRest = true;
3190                   rest();
3191                   }
3192             else if (_e.name() == "staff") {
3193                   auto ok = false;
3194                   auto strStaff = _e.readElementText();
3195                   staff = strStaff.toInt(&ok);
3196                   _parts[partId].setMaxStaff(staff);
3197                   Part* part = _partMap.value(partId);
3198                   Q_ASSERT(part);
3199                   if (!ok || staff <= 0 || staff > part->nstaves())
3200                         _logger->logError(QString("illegal staff '%1'").arg(strStaff), &_e);
3201                   }
3202             else if (_e.name() == "stem")
3203                   _e.skipCurrentElement();  // skip but don't log
3204             else if (_e.name() == "tie")
3205                   _e.skipCurrentElement();  // skip but don't log
3206             else if (_e.name() == "type")
3207                   type = _e.readElementText();
3208             else if (_e.name() == "unpitched")
3209                   _e.skipCurrentElement();  // skip but don't log
3210             else if (_e.name() == "voice")
3211                   voice = _e.readElementText();
3212             else
3213                   skipLogCurrElem();
3214             }
3215 
3216       // convert staff to zero-based
3217       staff--;
3218 
3219       // multi-instrument handling
3220       QString prevInstrId = _parts[partId]._instrList.instrument(sTime);
3221       bool mustInsert = instrId != prevInstrId;
3222       /*
3223       qDebug("tick %s (%d) staff %d voice '%s' previnst='%s' instrument '%s' mustInsert %d",
3224              qPrintable(sTime.print()),
3225              sTime.ticks(),
3226              staff + 1,
3227              qPrintable(voice),
3228              qPrintable(prevInstrId),
3229              qPrintable(instrId),
3230              mustInsert
3231              );
3232       */
3233       if (mustInsert)
3234             _parts[partId]._instrList.setInstrument(instrId, sTime);
3235 
3236       // check for timing error(s) and set dura
3237       // keep in this order as checkTiming() might change dura
3238       auto errorStr = mnd.checkTiming(type, bRest, grace);
3239       dura = mnd.dura();
3240       if (errorStr != "")
3241             _logger->logError(errorStr, &_e);
3242 
3243       // don't count chord or grace note duration
3244       // note that this does not check the MusicXML requirement that notes in a chord
3245       // cannot have a duration longer than the first note in the chord
3246       missingPrev.set(0, 1);
3247       if (chord || grace)
3248             dura.set(0, 1);
3249 
3250       if (!chord && !grace) {
3251             // do tuplet
3252             auto timeMod = mnd.timeMod();
3253             auto& tupletState = tupletStates[voice];
3254             tupletState.determineTupletAction(mnd.dura(), timeMod, tupletStartStop, mnd.normalType(), missingPrev, missingCurr);
3255             }
3256 
3257       // store result
3258       if (dura.isValid() && dura > Fraction(0, 1)) {
3259             // count the chords
3260             if (!_parts.value(partId).voicelist.contains(voice)) {
3261                   VoiceDesc vs;
3262                   _parts[partId].voicelist.insert(voice, vs);
3263                   }
3264             _parts[partId].voicelist[voice].incrChordRests(staff);
3265             // determine note length for voice overlap detection
3266             vod.addNote((sTime + missingPrev).ticks(), (sTime + missingPrev + dura).ticks(), voice, staff);
3267             }
3268 
3269       if (!(_e.isEndElement() && _e.name() == "note"))
3270             qDebug("name %s line %lld", qPrintable(_e.name().toString()), _e.lineNumber());
3271       Q_ASSERT(_e.isEndElement() && _e.name() == "note");
3272       }
3273 
3274 //---------------------------------------------------------
3275 //   notePrintSpacingNo
3276 //---------------------------------------------------------
3277 
3278 /**
3279  Parse the /score-partwise/part/measure/note node for a note with print-spacing="no".
3280  These are handled like a forward: only moving the time forward.
3281  */
3282 
notePrintSpacingNo(Fraction & dura)3283 void MusicXMLParserPass1::notePrintSpacingNo(Fraction& dura)
3284       {
3285       Q_ASSERT(_e.isStartElement() && _e.name() == "note");
3286       //_logger->logDebugTrace("MusicXMLParserPass1::notePrintSpacingNo", &_e);
3287 
3288       bool chord = false;
3289       bool grace = false;
3290 
3291       while (_e.readNextStartElement()) {
3292             if (_e.name() == "chord") {
3293                   chord = true;
3294                   _e.readNext();
3295                   }
3296             else if (_e.name() == "duration")
3297                   duration(dura);
3298             else if (_e.name() == "grace") {
3299                   grace = true;
3300                   _e.readNext();
3301                   }
3302             else
3303                   _e.skipCurrentElement();        // skip but don't log
3304             }
3305 
3306       // don't count chord or grace note duration
3307       // note that this does not check the MusicXML requirement that notes in a chord
3308       // cannot have a duration longer than the first note in the chord
3309       if (chord || grace)
3310             dura.set(0, 1);
3311 
3312       Q_ASSERT(_e.isEndElement() && _e.name() == "note");
3313       }
3314 
3315 //---------------------------------------------------------
3316 //   duration
3317 //---------------------------------------------------------
3318 
3319 /**
3320  Parse the /score-partwise/part/measure/note/duration node.
3321  */
3322 
duration(Fraction & dura)3323 void MusicXMLParserPass1::duration(Fraction& dura)
3324       {
3325       Q_ASSERT(_e.isStartElement() && _e.name() == "duration");
3326       //_logger->logDebugTrace("MusicXMLParserPass1::duration", &_e);
3327 
3328       dura.set(0, 0);  // invalid unless set correctly
3329       int intDura = _e.readElementText().toInt();
3330       if (intDura > 0) {
3331             if (_divs > 0) {
3332                   dura.set(intDura, 4 * _divs);
3333                   dura.reduce(); // prevent overflow in later Fraction operations
3334                   }
3335             else
3336                   _logger->logError("illegal or uninitialized divisions", &_e);
3337             }
3338       else
3339             _logger->logError("illegal duration", &_e);
3340       //qDebug("duration %s valid %d", qPrintable(dura.print()), dura.isValid());
3341       }
3342 
3343 //---------------------------------------------------------
3344 //   forward
3345 //---------------------------------------------------------
3346 
3347 /**
3348  Parse the /score-partwise/part/measure/note/forward node.
3349  */
3350 
forward(Fraction & dura)3351 void MusicXMLParserPass1::forward(Fraction& dura)
3352       {
3353       Q_ASSERT(_e.isStartElement() && _e.name() == "forward");
3354       //_logger->logDebugTrace("MusicXMLParserPass1::forward", &_e);
3355 
3356       while (_e.readNextStartElement()) {
3357             if (_e.name() == "duration")
3358                   duration(dura);
3359             else if (_e.name() == "staff")
3360                   _e.skipCurrentElement();  // skip but don't log
3361             else if (_e.name() == "voice")
3362                   _e.skipCurrentElement();  // skip but don't log
3363             else
3364                   skipLogCurrElem();
3365             }
3366       }
3367 
3368 //---------------------------------------------------------
3369 //   backup
3370 //---------------------------------------------------------
3371 
3372 /**
3373  Parse the /score-partwise/part/measure/note/backup node.
3374  */
3375 
backup(Fraction & dura)3376 void MusicXMLParserPass1::backup(Fraction& dura)
3377       {
3378       Q_ASSERT(_e.isStartElement() && _e.name() == "backup");
3379       //_logger->logDebugTrace("MusicXMLParserPass1::backup", &_e);
3380 
3381       while (_e.readNextStartElement()) {
3382             if (_e.name() == "duration")
3383                   duration(dura);
3384             else
3385                   skipLogCurrElem();
3386             }
3387       }
3388 
3389 //---------------------------------------------------------
3390 //   timeModification
3391 //---------------------------------------------------------
3392 
3393 /**
3394  Parse the /score-partwise/part/measure/note/time-modification node.
3395  */
3396 
timeModification(Fraction & timeMod)3397 void MusicXMLParserPass1::timeModification(Fraction& timeMod)
3398       {
3399       Q_ASSERT(_e.isStartElement() && _e.name() == "time-modification");
3400       //_logger->logDebugTrace("MusicXMLParserPass1::timeModification", &_e);
3401 
3402       int intActual = 0;
3403       int intNormal = 0;
3404       QString strActual;
3405       QString strNormal;
3406 
3407       while (_e.readNextStartElement()) {
3408             if (_e.name() == "actual-notes")
3409                   strActual = _e.readElementText();
3410             else if (_e.name() == "normal-notes")
3411                   strNormal = _e.readElementText();
3412             else
3413                   skipLogCurrElem();
3414             }
3415 
3416       intActual = strActual.toInt();
3417       intNormal = strNormal.toInt();
3418       if (intActual > 0 && intNormal > 0)
3419             timeMod.set(intNormal, intActual);
3420       else {
3421             timeMod.set(1, 1);
3422             _logger->logError(QString("illegal time-modification: actual-notes %1 normal-notes %2")
3423                               .arg(strActual, strNormal), &_e);
3424             }
3425       }
3426 
3427 //---------------------------------------------------------
3428 //   rest
3429 //---------------------------------------------------------
3430 
3431 /**
3432  Parse the /score-partwise/part/measure/note/rest node.
3433  */
3434 
rest()3435 void MusicXMLParserPass1::rest()
3436       {
3437       Q_ASSERT(_e.isStartElement() && _e.name() == "rest");
3438       //_logger->logDebugTrace("MusicXMLParserPass1::rest", &_e);
3439 
3440       while (_e.readNextStartElement()) {
3441             if (_e.name() == "display-octave")
3442                   _e.skipCurrentElement();  // skip but don't log
3443             else if (_e.name() == "display-step")
3444                   _e.skipCurrentElement();  // skip but don't log
3445             else
3446                   skipLogCurrElem();
3447             }
3448       }
3449 
3450 } // namespace Ms
3451