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