1 //=============================================================================
2 //  MusE Score
3 //  Linux Music Score Editor
4 //
5 //  Copyright (C) 2012 Werner Schweer and others
6 //
7 //  This program is free software; you can redistribute it and/or modify
8 //  it under the terms of the GNU General Public License version 2.
9 //
10 //  This program is distributed in the hope that it will be useful,
11 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //  GNU General Public License for more details.
14 //
15 //  You should have received a copy of the GNU General Public License
16 //  along with this program; if not, write to the Free Software
17 //  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 //=============================================================================
19 
20 /**
21  MusicXML support.
22  */
23 
24 #include "libmscore/accidental.h"
25 #include "libmscore/articulation.h"
26 #include "libmscore/chord.h"
27 #include "libmscore/sym.h"
28 
29 #include "musicxmlsupport.h"
30 
31 
32 namespace Ms {
33 
NoteList()34 NoteList::NoteList()
35       {
36       for (int i = 0; i < MAX_STAVES; ++i)
37             _staffNoteLists << StartStopList();
38       }
39 
addNote(const int startTick,const int endTick,const int staff)40 void NoteList::addNote(const int startTick, const int endTick, const int staff)
41       {
42       if (staff >= 0 && staff < _staffNoteLists.size())
43             _staffNoteLists[staff] << StartStop(startTick, endTick);
44       }
45 
dump(const QString & voice) const46 void NoteList::dump(const QString& voice) const
47       {
48       // dump contents
49       for (int i = 0; i < MAX_STAVES; ++i) {
50             printf("voice %s staff %d:", qPrintable(voice), i);
51             for (int j = 0; j < _staffNoteLists.at(i).size(); ++j)
52                   printf(" %d-%d", _staffNoteLists.at(i).at(j).first, _staffNoteLists.at(i).at(j).second);
53             printf("\n");
54             }
55       // show overlap
56       printf("overlap voice %s:", qPrintable(voice));
57       for (int i = 0; i < MAX_STAVES - 1; ++i)
58             for (int j = i + 1; j < MAX_STAVES; ++j)
59                   stavesOverlap(i, j);
60       printf("\n");
61       }
62 
63 /**
64  Determine if notes n1 and n2 overlap.
65  This is NOT the case if
66  - n1 starts when or after n2 stops
67  - or n2 starts when or after n1 stops
68  */
69 
notesOverlap(const StartStop & n1,const StartStop & n2)70 static bool notesOverlap(const StartStop& n1, const StartStop& n2)
71       {
72       return !(n1.first >= n2.second || n1.second <= n2.first);
73       }
74 
75 /**
76  Determine if any note in staff1 and staff2 overlaps.
77  */
78 
stavesOverlap(const int staff1,const int staff2) const79 bool NoteList::stavesOverlap(const int staff1, const int staff2) const
80       {
81       for (int i = 0; i < _staffNoteLists.at(staff1).size(); ++i)
82             for (int j = 0; j < _staffNoteLists.at(staff2).size(); ++j)
83                   if (notesOverlap(_staffNoteLists.at(staff1).at(i), _staffNoteLists.at(staff2).at(j))) {
84                         //printf(" %d-%d", staff1, staff2);
85                         return true;
86                         }
87       return false;
88       }
89 
90 /**
91  Determine if any note in any staff overlaps.
92  */
93 
anyStaffOverlaps() const94 bool NoteList::anyStaffOverlaps() const
95       {
96       for (int i = 0; i < MAX_STAVES - 1; ++i)
97             for (int j = i + 1; j < MAX_STAVES; ++j)
98                   if (stavesOverlap(i, j))
99                         return true;
100       return false;
101       }
102 
VoiceOverlapDetector()103 VoiceOverlapDetector::VoiceOverlapDetector()
104       {
105       // qDebug("VoiceOverlapDetector::VoiceOverlapDetector(staves %d)", MAX_STAVES);
106       }
107 
addNote(const int startTick,const int endTick,const QString & voice,const int staff)108 void VoiceOverlapDetector::addNote(const int startTick, const int endTick, const QString& voice, const int staff)
109       {
110       // if necessary, create the note list for voice
111       if (!_noteLists.contains(voice))
112             _noteLists.insert(voice, NoteList());
113       _noteLists[voice].addNote(startTick, endTick, staff);
114       }
115 
dump() const116 void VoiceOverlapDetector::dump() const
117       {
118       // qDebug("VoiceOverlapDetector::dump()");
119       QMapIterator<QString, NoteList> i(_noteLists);
120       while (i.hasNext()) {
121             i.next();
122             i.value().dump(i.key());
123             }
124       }
125 
newMeasure()126 void VoiceOverlapDetector::newMeasure()
127       {
128       // qDebug("VoiceOverlapDetector::newMeasure()");
129       _noteLists.clear();
130       }
131 
stavesOverlap(const QString & voice) const132 bool VoiceOverlapDetector::stavesOverlap(const QString& voice) const
133       {
134       if (_noteLists.contains(voice))
135             return _noteLists.value(voice).anyStaffOverlaps();
136       else
137             return false;
138       }
139 
toString() const140 QString MusicXMLInstrument::toString() const
141       {
142       return QString("chan %1 prog %2 vol %3 pan %4 unpitched %5 name '%6' sound '%7' head %8 line %9 stemDir %10")
143              .arg(midiChannel)
144              .arg(midiProgram)
145              .arg(midiVolume)
146              .arg(midiPan)
147              .arg(unpitched)
148              .arg(name, sound)
149              .arg(int(notehead))
150              .arg(line)
151              .arg(int(stemDirection));
152       }
153 
handleMessage(QtMsgType type,const QString & description,const QUrl &,const QSourceLocation & sourceLocation)154 void ValidatorMessageHandler::handleMessage(QtMsgType type, const QString& description,
155                                             const QUrl& /* identifier */, const QSourceLocation& sourceLocation)
156       {
157       // convert description from html to text
158       QDomDocument desc;
159       QString contentError;
160       int contentLine;
161       int contentColumn;
162       if (!desc.setContent(description, false, &contentError, &contentLine,
163                            &contentColumn)) {
164             qDebug("ValidatorMessageHandler: could not parse validation error line %d column %d: %s",
165                    contentLine, contentColumn, qPrintable(contentError));
166             return;
167             }
168       QDomElement e = desc.documentElement();
169       if (e.tagName() != "html") {
170             qDebug("ValidatorMessageHandler: description is not html");
171             return;
172             }
173       QString descText = e.text();
174 
175       QString strType;
176       switch (type) {
177             case 0:  strType = tr("Debug"); break;
178             case 1:  strType = tr("Warning"); break;
179             case 2:  strType = tr("Critical"); break;
180             case 3:  strType = tr("Fatal"); break;
181             default: strType = tr("Unknown"); break;
182             }
183 
184       QString errorStr = QString(tr("%1 error: line %2 column %3 %4"))
185             .arg(strType)
186             .arg(sourceLocation.line())
187             .arg(sourceLocation.column())
188             .arg(descText);
189 
190       // append error, separated by newline if necessary
191       if (errors != "")
192             errors += "\n";
193       errors += errorStr;
194       }
195 
196 //---------------------------------------------------------
197 //   printDomElementPath
198 //---------------------------------------------------------
199 
domElementPath(const QDomElement & e)200 static QString domElementPath(const QDomElement& e)
201       {
202       QString s;
203       QDomNode dn(e);
204       while (!dn.parentNode().isNull()) {
205             dn = dn.parentNode();
206             const QDomElement& de = dn.toElement();
207             const QString k(de.tagName());
208             if (!s.isEmpty())
209                   s += ":";
210             s += k;
211             }
212       return s;
213       }
214 
215 //---------------------------------------------------------
216 //   domError
217 //---------------------------------------------------------
218 
domError(const QDomElement & e)219 void domError(const QDomElement& e)
220       {
221       QString m;
222       QString s = domElementPath(e);
223 //      if (!docName.isEmpty())
224 //            m = QString("<%1>:").arg(docName);
225       int ln = e.lineNumber();
226       if (ln != -1)
227             m += QString("line:%1 ").arg(ln);
228       int col = e.columnNumber();
229       if (col != -1)
230             m += QString("col:%1 ").arg(col);
231       m += QString("%1: Unknown Node <%2>, type %3").arg(s, e.tagName()).arg(e.nodeType());
232       if (e.isText())
233             m += QString("  text node <%1>").arg(e.toText().data());
234       qDebug("%s", qPrintable(m));
235       }
236 
237 //---------------------------------------------------------
238 //   domNotImplemented
239 //---------------------------------------------------------
240 
domNotImplemented(const QDomElement & e)241 void domNotImplemented(const QDomElement& e)
242       {
243       if (!MScore::debugMode)
244             return;
245       QString s = domElementPath(e);
246 //      if (!docName.isEmpty())
247 //            qDebug("<%s>:", qPrintable(docName));
248       qDebug("%s: Node not implemented: <%s>, type %d",
249              qPrintable(s), qPrintable(e.tagName()), e.nodeType());
250       if (e.isText())
251             qDebug("  text node <%s>", qPrintable(e.toText().data()));
252       }
253 
254 
255 //---------------------------------------------------------
256 //   stringToInt
257 //---------------------------------------------------------
258 
259 /**
260  Convert a string in \a s into an int. Set *ok to true iff conversion was
261  successful. \a s may end with ".0", as is generated by Audiveris 3.2 and up,
262  in elements <divisions>, <duration>, <alter> and <sound> attributes
263  dynamics and tempo.
264  In case of error val return a default value of 0.
265  Note that non-integer values cannot be handled by mscore.
266  */
267 
stringToInt(const QString & s,bool * ok)268 int MxmlSupport::stringToInt(const QString& s, bool* ok)
269       {
270       int res = 0;
271       QString str = s;
272       if (s.endsWith(".0"))
273             str = s.left(s.size() - 2);
274       res = str.toInt(ok);
275       return res;
276       }
277 
278 //---------------------------------------------------------
279 //   durationAsFraction
280 //---------------------------------------------------------
281 
282 /**
283  Return duration specified in the element e as Fraction.
284  Caller must ensure divisions is valid.
285  */
286 
durationAsFraction(const int divisions,const QDomElement e)287 Fraction MxmlSupport::durationAsFraction(const int divisions, const QDomElement e)
288       {
289       Fraction f;
290       if (e.tagName() == "duration") {
291             bool ok;
292             int val = MxmlSupport::stringToInt(e.text(), &ok);
293             f = Fraction(val, 4 * divisions); // note divisions = ticks / quarter note
294             f.reduce();
295             }
296       else {
297             qDebug() << "durationAsFraction tagname error" << f.print();
298             }
299       return f;
300       }
301 
302 //---------------------------------------------------------
303 //   noteTypeToFraction
304 //---------------------------------------------------------
305 
306 /**
307  Convert MusicXML note type to fraction.
308  */
309 
noteTypeToFraction(QString type)310 Fraction MxmlSupport::noteTypeToFraction(QString type)
311       {
312       if (type == "1024th")
313             return Fraction(1, 1024);
314       else if (type == "512th")
315             return Fraction(1, 512);
316       else if (type == "256th")
317             return Fraction(1, 256);
318       else if (type == "128th")
319             return Fraction(1, 128);
320       else if (type == "64th")
321             return Fraction(1, 64);
322       else if (type == "32nd")
323             return Fraction(1, 32);
324       else if (type == "16th")
325             return Fraction(1, 16);
326       else if (type == "eighth")
327             return Fraction(1, 8);
328       else if (type == "quarter")
329             return Fraction(1, 4);
330       else if (type == "half")
331             return Fraction(1, 2);
332       else if (type == "whole")
333             return Fraction(1, 1);
334       else if (type == "breve")
335             return Fraction(2, 1);
336       else if (type == "long")
337             return Fraction(4, 1);
338       else if (type == "maxima")
339             return Fraction(8, 1);
340       else
341             return Fraction(0, 0);
342       }
343 
344 //---------------------------------------------------------
345 //   calculateFraction
346 //---------------------------------------------------------
347 
348 /**
349  Convert note type, number of dots and actual and normal notes into a duration
350  */
351 
calculateFraction(QString type,int dots,int normalNotes,int actualNotes)352 Fraction MxmlSupport::calculateFraction(QString type, int dots, int normalNotes, int actualNotes)
353       {
354       // type
355       Fraction f = MxmlSupport::noteTypeToFraction(type);
356       if (f.isValid()) {
357             // dot(s)
358             Fraction f_no_dots = f;
359             for (int i = 0; i < dots; ++i)
360                   f += (f_no_dots / Fraction(2 << i, 1));
361             // tuplet
362             if (actualNotes > 0 && normalNotes > 0) {
363                   f *= normalNotes;
364                   f /= Fraction(actualNotes,1);
365                   }
366             // clean up (just in case)
367             f.reduce();
368             }
369       return f;
370       }
371 
372 //---------------------------------------------------------
373 //   accSymId2MxmlString
374 //---------------------------------------------------------
375 
accSymId2MxmlString(const SymId id)376 QString accSymId2MxmlString(const SymId id)
377       {
378       QString s;
379       switch (id) {
380             case SymId::accidentalSharp:                 s = "sharp";                break;
381             case SymId::accidentalNatural:               s = "natural";              break;
382             case SymId::accidentalFlat:                  s = "flat";                 break;
383             case SymId::accidentalDoubleSharp:           s = "double-sharp";         break;
384             //case SymId::accidentalDoubleSharp:           s = "sharp-sharp";          break; // see above
385             //case SymId::accidentalDoubleFlat:            s = "double-flat";          break; // doesn't exist in MusicXML, but see below
386             case SymId::accidentalDoubleFlat:            s = "flat-flat";            break;
387             case SymId::accidentalNaturalSharp:          s = "natural-sharp";        break;
388             case SymId::accidentalNaturalFlat:           s = "natural-flat";         break;
389 
390             case SymId::accidentalQuarterToneFlatStein:  s = "quarter-flat";         break;
391             case SymId::accidentalQuarterToneSharpStein: s = "quarter-sharp";        break;
392             case SymId::accidentalThreeQuarterTonesFlatZimmermann: s = "three-quarters-flat";  break;
393             //case SymId::noSym:                                     s = "three-quarters-flat";  break; // AccidentalType::FLAT_FLAT_SLASH, MuseScore 1?
394             case SymId::accidentalThreeQuarterTonesSharpStein:     s = "three-quarters-sharp"; break;
395             case SymId::accidentalQuarterToneSharpArrowDown:       s = "sharp-down";           break;
396             case SymId::accidentalThreeQuarterTonesSharpArrowUp:   s = "sharp-up";             break;
397             case SymId::accidentalQuarterToneFlatNaturalArrowDown: s = "natural-down";         break;
398             case SymId::accidentalQuarterToneSharpNaturalArrowUp:  s = "natural-up";           break;
399             case SymId::accidentalThreeQuarterTonesFlatArrowDown:  s = "flat-down";            break;
400             case SymId::accidentalQuarterToneFlatArrowUp:          s = "flat-up";              break;
401             case SymId::accidentalThreeQuarterTonesSharpArrowDown: s = "double-sharp-down";    break;
402             case SymId::accidentalFiveQuarterTonesSharpArrowUp:    s = "double-sharp-up";      break;
403             case SymId::accidentalFiveQuarterTonesFlatArrowDown:   s = "flat-flat-down";       break;
404             case SymId::accidentalThreeQuarterTonesFlatArrowUp:    s = "flat-flat-up";         break;
405 
406             case SymId::accidentalArrowDown:             s = "arrow-down";           break;
407             case SymId::accidentalArrowUp:               s = "arrow-up";             break;
408 
409             case SymId::accidentalTripleSharp:           s = "triple-sharp";         break;
410             case SymId::accidentalTripleFlat:            s = "triple-flat";          break;
411 
412             case SymId::accidentalKucukMucennebSharp:    s = "slash-quarter-sharp";  break;
413             case SymId::accidentalBuyukMucennebSharp:    s = "slash-sharp";          break;
414             case SymId::accidentalBakiyeFlat:            s = "slash-flat";           break;
415             case SymId::accidentalBuyukMucennebFlat:     s = "double-slash-flat";    break;
416 
417             //case SymId::noSym:                           s = "sharp1";               break;
418             //case SymId::noSym:                           s = "sharp2";               break;
419             //case SymId::noSym:                           s = "sharp3";               break;
420             //case SymId::noSym:                           s = "sharp4";               break;
421             //case SymId::noSym:                           s = "flat1";                break;
422             //case SymId::noSym:                           s = "flat2";                break;
423             //case SymId::noSym:                           s = "flat3";                break;
424             //case SymId::noSym:                           s = "flat4";                break;
425 
426             case SymId::accidentalSori:                  s = "sori";                 break;
427             case SymId::accidentalKoron:                 s = "koron";                break;
428             default:
429                   //s = "other"; // actually pick up the SMuFL name or SymId
430                   qDebug("accSymId2MxmlString: unknown accidental %d", static_cast<int>(id));
431             }
432       return s;
433       }
434 
435 //---------------------------------------------------------
436 //   mxmlString2accSymId
437 // see https://github.com/w3c/musicxml/blob/6e3a667b85855b04d7e4548ea508b537bc29fc52/schema/musicxml.xsd#L1392-L1439
438 //---------------------------------------------------------
439 
mxmlString2accSymId(const QString mxmlName)440 SymId mxmlString2accSymId(const QString mxmlName)
441       {
442       QMap<QString, SymId> map; // map MusicXML accidental name to MuseScore enum SymId
443       map["sharp"] = SymId::accidentalSharp;
444       map["natural"] = SymId::accidentalNatural;
445       map["flat"] = SymId::accidentalFlat;
446       map["double-sharp"] = SymId::accidentalDoubleSharp;
447       map["sharp-sharp"] = SymId::accidentalDoubleSharp;
448       //map["double-flat"] = SymId::accidentalDoubleFlat; // shouldn't harm, but doesn't exist in MusicXML
449       map["flat-flat"] = SymId::accidentalDoubleFlat;
450       map["natural-sharp"] = SymId::accidentalNaturalSharp;
451       map["natural-flat"] = SymId::accidentalNaturalFlat;
452 
453       map["quarter-flat"] = SymId::accidentalQuarterToneFlatStein;
454       map["quarter-sharp"] = SymId::accidentalQuarterToneSharpStein;
455       map["three-quarters-flat"] = SymId::accidentalThreeQuarterTonesFlatZimmermann;
456       map["three-quarters-sharp"] = SymId::accidentalThreeQuarterTonesSharpStein;
457 
458       map["sharp-down"] = SymId::accidentalQuarterToneSharpArrowDown;
459       map["sharp-up"] = SymId::accidentalThreeQuarterTonesSharpArrowUp;
460       map["natural-down"] = SymId::accidentalQuarterToneFlatNaturalArrowDown;
461       map["natural-up"] = SymId::accidentalQuarterToneSharpNaturalArrowUp;
462       map["flat-down"] = SymId::accidentalThreeQuarterTonesFlatArrowDown;
463       map["flat-up"] = SymId::accidentalQuarterToneFlatArrowUp;
464       map["double-sharp-down"] = SymId::accidentalThreeQuarterTonesSharpArrowDown;
465       map["double-sharp-up"] = SymId::accidentalFiveQuarterTonesSharpArrowUp;
466       map["flat-flat-down"] = SymId::accidentalFiveQuarterTonesFlatArrowDown;
467       map["flat-flat-up"] = SymId::accidentalThreeQuarterTonesFlatArrowUp;
468 
469       map["arrow-down"] = SymId::accidentalArrowDown;
470       map["arrow-up"] = SymId::accidentalArrowUp;
471 
472       map["triple-sharp"] = SymId::accidentalTripleSharp;
473       map["triple-flat"] = SymId::accidentalTripleFlat;
474 
475       map["slash-quarter-sharp"] = SymId::accidentalKucukMucennebSharp;
476       map["slash-sharp"] = SymId::accidentalBuyukMucennebSharp;
477       map["slash-flat"] = SymId::accidentalBakiyeFlat;
478       map["double-slash-flat"] = SymId::accidentalBuyukMucennebFlat;
479 
480       //map["sharp1"] = SymId::noSym;
481       //map["sharp2"] = SymId::noSym;
482       //map["sharp3"] = SymId::noSym;
483       //map["sharp4"] = SymId::noSym;
484       //map["flat1"] = SymId::noSym;
485       //map["flat2"] = SymId::noSym;
486       //map["flat3"] = SymId::noSym;
487       //map["flat3"] = SymId::noSym;
488 
489       map["sori"] = SymId::accidentalSori;
490       map["koron"] = SymId::accidentalKoron;
491 
492       //map["other"] = SymId::noSym; // actually pick up the SMuFL name or SymId
493 
494       if (map.contains(mxmlName))
495             return map.value(mxmlName);
496       else
497             qDebug("mxmlString2accSymId: unknown accidental '%s'", qPrintable(mxmlName));
498 
499       // default
500       return SymId::noSym;
501       }
502 
503 //---------------------------------------------------------
504 //   accidentalType2MxmlString
505 //---------------------------------------------------------
506 
accidentalType2MxmlString(const AccidentalType type)507 QString accidentalType2MxmlString(const AccidentalType type)
508       {
509       QString s;
510       switch (type) {
511             case AccidentalType::SHARP:              s = "sharp";                break;
512             case AccidentalType::NATURAL:            s = "natural";              break;
513             case AccidentalType::FLAT:               s = "flat";                 break;
514             case AccidentalType::SHARP2:             s = "double-sharp";         break;
515             //case AccidentalType::SHARP2:             s = "sharp-sharp";          break; // see above
516             //case AccidentalType::FLAT2:              s = "double-flat";          break; // doesn't exist in MusicXML, but see below
517             case AccidentalType::FLAT2:              s = "flat-flat";            break;
518             case AccidentalType::NATURAL_SHARP:      s = "natural-sharp";        break;
519             case AccidentalType::NATURAL_FLAT:       s = "natural-flat";         break;
520             case AccidentalType::SHARP_ARROW_UP:     s = "sharp-up";             break;
521 
522             case AccidentalType::MIRRORED_FLAT:      s = "quarter-flat";         break;
523             case AccidentalType::SHARP_SLASH:        s = "quarter-sharp";        break;
524             case AccidentalType::MIRRORED_FLAT2:     s = "three-quarters-flat";  break;
525             //case AccidentalType::FLAT_FLAT_SLASH:    s = "three-quarters-flat";  break; // MuseScore 1?
526             case AccidentalType::SHARP_SLASH4:       s = "three-quarters-sharp"; break;
527             case AccidentalType::SHARP_ARROW_DOWN:   s = "sharp-down";           break;
528             case AccidentalType::NATURAL_ARROW_UP:   s = "natural-up";           break;
529             case AccidentalType::NATURAL_ARROW_DOWN: s = "natural-down";         break;
530             case AccidentalType::FLAT_ARROW_DOWN:    s = "flat-down";            break;
531             case AccidentalType::FLAT_ARROW_UP:      s = "flat-up";              break;
532             case AccidentalType::SHARP2_ARROW_DOWN:  s = "double-sharp-down";    break;
533             case AccidentalType::SHARP2_ARROW_UP:    s = "double-sharp-up";      break;
534             case AccidentalType::FLAT2_ARROW_DOWN:   s = "flat-flat-down";       break;
535             case AccidentalType::FLAT2_ARROW_UP:     s = "flat-flat-up";         break;
536 
537             case AccidentalType::ARROW_DOWN:         s = "arrow-down";           break;
538             case AccidentalType::ARROW_UP:           s = "arrow-up";             break;
539 
540             case AccidentalType::SHARP3:             s = "triple-sharp";         break;
541             case AccidentalType::FLAT3:              s = "triple-flat";          break;
542 
543             case AccidentalType::SHARP_SLASH3:       s = "slash-quarter-sharp";  break;
544             case AccidentalType::SHARP_SLASH2:       s = "slash-sharp";          break;
545             case AccidentalType::FLAT_SLASH:         s = "slash-flat";           break;
546             case AccidentalType::FLAT_SLASH2:        s = "double-slash-flat";    break;
547 
548             //case AccidentalType::NONE:               s = "sharp1";               break;
549             //case AccidentalType::NONE:               s = "sharp2";               break;
550             //case AccidentalType::NONE:               s = "sharp3";               break;
551             //case AccidentalType::NONE:               s = "sharp4";               break;
552             //case AccidentalType::NONE:               s = "flat1";                break;
553             //case AccidentalType::NONE:               s = "flat2";                break;
554             //case AccidentalType::NONE:               s = "flat3";                break;
555             //case AccidentalType::NONE:               s = "flat3";                break;
556 
557             case AccidentalType::SORI:               s = "sori";                 break;
558             case AccidentalType::KORON:              s = "koron";                break;
559             default:
560                   //s = "other"; // actually pick up the SMuFL name or SymId
561                   qDebug("accidentalType2MxmlString: unknown accidental %d", static_cast<int>(type));
562             }
563       return s;
564       }
565 
566 //---------------------------------------------------------
567 //   mxmlString2accidentalType
568 //---------------------------------------------------------
569 
570 /**
571  Convert a MusicXML accidental name to a MuseScore enum AccidentalType.
572  see https://github.com/w3c/musicxml/blob/6e3a667b85855b04d7e4548ea508b537bc29fc52/schema/musicxml.xsd#L1392-L1439
573  */
574 
mxmlString2accidentalType(const QString mxmlName)575 AccidentalType mxmlString2accidentalType(const QString mxmlName)
576       {
577       QMap<QString, AccidentalType> map; // map MusicXML accidental name to MuseScore enum AccidentalType
578       map["sharp"] = AccidentalType::SHARP;
579       map["natural"] = AccidentalType::NATURAL;
580       map["flat"] = AccidentalType::FLAT;
581       map["double-sharp"] = AccidentalType::SHARP2;
582       map["sharp-sharp"] = AccidentalType::SHARP2;
583       //map["double-flat"] = AccidentalType::FLAT2; // shouldn't harm, but doesn't exist in MusicXML
584       map["flat-flat"] = AccidentalType::FLAT2;
585       map["natural-sharp"] = AccidentalType::SHARP;
586       map["natural-flat"] = AccidentalType::FLAT;
587 
588       map["quarter-flat"] = AccidentalType::MIRRORED_FLAT;
589       map["quarter-sharp"] = AccidentalType::SHARP_SLASH;
590       map["three-quarters-flat"] = AccidentalType::MIRRORED_FLAT2;
591       map["three-quarters-sharp"] = AccidentalType::SHARP_SLASH4;
592 
593       map["sharp-up"] = AccidentalType::SHARP_ARROW_UP;
594       map["natural-down"] = AccidentalType::NATURAL_ARROW_DOWN;
595       map["natural-up"] = AccidentalType::NATURAL_ARROW_UP;
596       map["sharp-down"] = AccidentalType::SHARP_ARROW_DOWN;
597       map["flat-down"] = AccidentalType::FLAT_ARROW_DOWN;
598       map["flat-up"] = AccidentalType::FLAT_ARROW_UP;
599       map["double-sharp-down"] = AccidentalType::SHARP2_ARROW_DOWN;
600       map["double-sharp-up"] = AccidentalType::SHARP2_ARROW_UP;
601       map["flat-flat-down"] = AccidentalType::FLAT2_ARROW_DOWN;
602       map["flat-flat-up"] = AccidentalType::FLAT2_ARROW_UP;
603 
604       map["arrow-down"] = AccidentalType::ARROW_DOWN;
605       map["arrow-up"] = AccidentalType::ARROW_UP;
606 
607       map["triple-sharp"] = AccidentalType::SHARP3;
608       map["triple-flat"] = AccidentalType::FLAT3;
609 
610       map["slash-quarter-sharp"] = AccidentalType::SHARP_SLASH3; // MIRRORED_FLAT_SLASH; ?
611       map["slash-sharp"] = AccidentalType::SHARP_SLASH2; // SHARP_SLASH; ?
612       map["slash-flat"] = AccidentalType::FLAT_SLASH;
613       map["double-slash-flat"] = AccidentalType::FLAT_SLASH2;
614 
615       //map["sharp1"] = AccidentalType::NONE;
616       //map["sharp2"] = AccidentalType::NONE;
617       //map["sharp3"] = AccidentalType::NONE;
618       //map["sharp4"] = AccidentalType::NONE;
619       //map["flat1"] = AccidentalType::NONE;
620       //map["flat2"] = AccidentalType::NONE;
621       //map["flat3"] = AccidentalType::NONE;
622       //map["flat4"] = AccidentalType::NONE;
623 
624       map["sori"] = AccidentalType::SORI;
625       map["koron"] = AccidentalType::KORON;
626 
627       //map["other"] = AccidentalType::NONE; // actually pick up the SMuFL name or SymId
628 
629       if (map.contains(mxmlName))
630             return map.value(mxmlName);
631       else
632             qDebug("mxmlString2accidentalType: unknown accidental '%s'", qPrintable(mxmlName));
633       return AccidentalType::NONE;
634       }
635 
636 //---------------------------------------------------------
637 //   isAppr
638 //---------------------------------------------------------
639 
640 /**
641  Check if v approximately equals ref.
642  Used to prevent floating point comparison for equality from failing
643  */
644 
isAppr(const double v,const double ref,const double epsilon)645 static bool isAppr(const double v, const double ref, const double epsilon)
646       {
647       return v > ref - epsilon && v < ref + epsilon;
648       }
649 
650 //---------------------------------------------------------
651 //   microtonalGuess
652 //---------------------------------------------------------
653 
654 /**
655  Convert a MusicXML alter tag into a microtonal accidental in MuseScore enum AccidentalType.
656  Works only for quarter tone, half tone, three-quarters tone and whole tone accidentals.
657  */
658 
microtonalGuess(double val)659 AccidentalType microtonalGuess(double val)
660       {
661       const double eps = 0.001;
662       if (isAppr(val, -2, eps))
663             return AccidentalType::FLAT2;
664       else if (isAppr(val, -1.5, eps))
665             return AccidentalType::MIRRORED_FLAT2;
666       else if (isAppr(val, -1, eps))
667             return AccidentalType::FLAT;
668       else if (isAppr(val, -0.5, eps))
669             return AccidentalType::MIRRORED_FLAT;
670       else if (isAppr(val, 0, eps))
671             return AccidentalType::NATURAL;
672       else if (isAppr(val, 0.5, eps))
673             return AccidentalType::SHARP_SLASH;
674       else if (isAppr(val, 1, eps))
675             return AccidentalType::SHARP;
676       else if (isAppr(val, 1.5, eps))
677             return AccidentalType::SHARP_SLASH4;
678       else if (isAppr(val, 2, eps))
679             return AccidentalType::SHARP2;
680       else
681             qDebug("Guess for microtonal accidental corresponding to value %f failed.", val);        // TODO
682 
683       // default
684       return AccidentalType::NONE;
685       }
686 
687 //---------------------------------------------------------
688 //   isLaissezVibrer
689 //---------------------------------------------------------
690 
isLaissezVibrer(const SymId id)691 bool isLaissezVibrer(const SymId id)
692       {
693       return id == SymId::articLaissezVibrerAbove || id == SymId::articLaissezVibrerBelow;
694       }
695 
696 //---------------------------------------------------------
697 //   hasLaissezVibrer
698 //---------------------------------------------------------
699 
700 // TODO: there should be a lambda hiding somewhere ...
701 
hasLaissezVibrer(const Chord * const chord)702 bool hasLaissezVibrer(const Chord* const chord)
703       {
704       for (const Articulation* a : chord->articulations()) {
705             if (isLaissezVibrer(a->symId()))
706                   return true;
707             }
708       return false;
709       }
710 
711 }
712