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