1 //=============================================================================
2 // MuseScore
3 // Music Composition & Notation
4 //
5 // Copyright (C) 2011 Werner Schweer
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 // as published by the Free Software Foundation and appearing in
10 // the file LICENCE.GPL
11 //=============================================================================
12
13 #include "config.h"
14 #include "chordlist.h"
15 #include "score.h"
16 #include "xml.h"
17 #include "pitchspelling.h"
18 #include "mscore.h"
19
20 namespace Ms {
21
22 //---------------------------------------------------------
23 // HChord
24 //---------------------------------------------------------
25
HChord(const QString & str)26 HChord::HChord(const QString& str)
27 {
28 static const char* const scaleNames[2][12] = {
29 { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" },
30 { "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B" }
31 };
32 keys = 0;
33 QStringList sl = str.split(" ", QString::SkipEmptyParts);
34 for (const QString& s : qAsConst(sl)) {
35 for (int i = 0; i < 12; ++i) {
36 if (s == scaleNames[0][i] || s == scaleNames[1][i]) {
37 operator+=(i);
38 break;
39 }
40 }
41 }
42 }
43
44 //---------------------------------------------------------
45 // HChord
46 //---------------------------------------------------------
47
HChord(int a,int b,int c,int d,int e,int f,int g,int h,int i,int k,int l)48 HChord::HChord(int a, int b, int c, int d, int e, int f, int g, int h, int i, int k, int l)
49 {
50 keys = 0;
51 if (a >= 0)
52 operator+=(a);
53 if (b >= 0)
54 operator+=(b);
55 if (c >= 0)
56 operator+=(c);
57 if (d >= 0)
58 operator+=(d);
59 if (e >= 0)
60 operator+=(e);
61 if (f >= 0)
62 operator+=(f);
63 if (g >= 0)
64 operator+=(g);
65 if (h >= 0)
66 operator+=(h);
67 if (i >= 0)
68 operator+=(i);
69 if (k >= 0)
70 operator+=(k);
71 if (l >= 0)
72 operator+=(l);
73 }
74
75 //---------------------------------------------------------
76 // rotate
77 // rotate 12 Bits
78 //---------------------------------------------------------
79
rotate(int semiTones)80 void HChord::rotate(int semiTones)
81 {
82 while (semiTones > 0) {
83 if (keys & 0x800)
84 keys = ((keys & ~0x800) << 1) + 1;
85 else
86 keys <<= 1;
87 --semiTones;
88 }
89 while (semiTones < 0) {
90 if (keys & 1)
91 keys = (keys >> 1) | 0x800;
92 else
93 keys >>= 1;
94 ++semiTones;
95 }
96 }
97
98 //---------------------------------------------------------
99 // name
100 //---------------------------------------------------------
101
name(int tpc) const102 QString HChord::name(int tpc) const
103 {
104 static const HChord C0(0,3,6,9);
105 static const HChord C1(0,3);
106
107 QString buf = tpc2name(tpc, NoteSpellingType::STANDARD, NoteCaseType::AUTO, false);
108 HChord c(*this);
109
110 int key = tpc2pitch(tpc);
111
112 c.rotate(-key); // transpose to C
113
114 // special cases
115 if (c == C0) {
116 buf += "dim";
117 return buf;
118 }
119 if (c == C1) {
120 buf += "no5";
121 return buf;
122 }
123
124 bool seven = false;
125 bool sharp9 = false;
126 bool nat11 = false;
127 bool sharp11 = false;
128 bool nat13 = false;
129 bool flat13 = false;
130
131 // minor?
132 if (c.contains(3)) {
133 if (!c.contains(4))
134 buf += "m";
135 else
136 sharp9 = true;
137 }
138
139 // 7
140 if (c.contains(11)) {
141 buf += "Maj7";
142 seven = true;
143 }
144 else if (c.contains(10)) {
145 buf += "7";
146 seven = true;
147 }
148
149 // 4
150 if (c.contains(5)) {
151 if (!c.contains(4)) {
152 buf += "sus4";
153 }
154 else
155 nat11 = true;
156 }
157
158 // 5
159 if (c.contains(7)) {
160 if (c.contains(6))
161 sharp11 = true;
162 if (c.contains(8))
163 flat13 = true;
164 }
165 else {
166 if (c.contains(6))
167 buf += "b5";
168 if (c.contains(8))
169 buf += "#5";
170 }
171
172 // 6
173 if (c.contains(9)) {
174 if (!seven)
175 buf += "6";
176 else
177 nat13 = true;
178 }
179
180 // 9
181 if (c.contains(1))
182 buf += "b9";
183 if (c.contains(2))
184 buf += "9";
185 if (sharp9)
186 buf += "#9";
187
188 // 11
189 if (nat11)
190 buf += "11 ";
191 if (sharp11)
192 buf += "#11";
193
194 // 13
195 if (flat13)
196 buf += "b13";
197 if (nat13) {
198 if (c.contains(1) || c.contains(2) || sharp9 || nat11 || sharp11)
199 buf += "13";
200 else
201 buf += "add13";
202 }
203 return buf;
204 }
205
206 //---------------------------------------------------------
207 // voicing
208 //---------------------------------------------------------
209
voicing() const210 QString HChord::voicing() const
211 {
212 QString s = "C";
213 const char* names[] = { "C", " Db", " D", " Eb", " E", " F", " Gb", " G", " Ab", " A", " Bb", " B" };
214
215 for (int i = 1; i < 12; i++) {
216 if (contains(i))
217 s += names[i];
218 }
219 return s;
220 }
221
222
223 //---------------------------------------------------------
224 // print
225 //---------------------------------------------------------
226
print() const227 void HChord::print() const
228 {
229 const char* names[] = { "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B" };
230
231 for (int i = 0; i < 12; i++) {
232 if (contains(i))
233 qDebug(" %s", names[i]);
234 }
235 }
236
237 //---------------------------------------------------------
238 // add
239 //---------------------------------------------------------
240
add(const QList<HDegree> & degreeList)241 void HChord::add(const QList<HDegree>& degreeList)
242 {
243 // qDebug("HChord::add ");print();
244 // convert degrees to semitones
245 static const int degreeTable[] = {
246 // 1 2 3 4 5 6 7
247 // C D E F G A B
248 0, 2, 4, 5, 7, 9, 11
249 };
250 // factor in the degrees
251 for (const HDegree& d : degreeList) {
252 int dv = degreeTable[(d.value() - 1) % 7] + d.alter();
253 int dv1 = degreeTable[(d.value() - 1) % 7];
254
255 if (d.value() == 7 && d.alter() == 0) {
256 // DEBUG: seventh degree is Bb, not B
257 // except Maj (TODO)
258 dv -= 1;
259 }
260
261 if (d.type() == HDegreeType::ADD)
262 *this += dv;
263 else if (d.type() == HDegreeType::ALTER) {
264 if (contains(dv1)) {
265 *this -= dv1;
266 *this += dv;
267 }
268 else {
269 // qDebug("HDegreeType::ALTER: chord does not contain degree %d(%d):",
270 // d.value(), d.alter());
271 // print();
272 *this += dv; // DEBUG: default to add
273 }
274 }
275 else if (d.type() == HDegreeType::SUBTRACT) {
276 if (contains(dv1))
277 *this -= dv1;
278 else {
279 qDebug("SUB: chord does not contain degree %d(%d):",
280 d.value(), d.alter());
281 }
282 }
283 else
284 qDebug("degree type %d not supported", static_cast<int>(d.type()));
285
286 // qDebug(" HCHord::added "); print();
287 }
288 }
289
290 //---------------------------------------------------------
291 // readRenderList
292 //---------------------------------------------------------
293
readRenderList(QString val,QList<RenderAction> & renderList)294 static void readRenderList(QString val, QList<RenderAction>& renderList)
295 {
296 renderList.clear();
297 QStringList sl = val.split(" ", QString::SkipEmptyParts);
298 for (const QString& s : qAsConst(sl)) {
299 if (s.startsWith("m:")) {
300 QStringList ssl = s.split(":", QString::SkipEmptyParts);
301 if (ssl.size() == 3) {
302 // m:x:y
303 RenderAction a;
304 a.type = RenderAction::RenderActionType::MOVE;
305 a.movex = ssl[1].toDouble();
306 a.movey = ssl[2].toDouble();
307 renderList.append(a);
308 }
309 #if 0
310 else if (ssl.size() == 2) {
311 // m:keyword
312 RenderAction a;
313 a.type = RenderAction::RenderActionType::MOVE;
314 // TODO: derive offset from keyword
315 }
316 #endif
317 }
318 else if (s == ":push")
319 renderList.append(RenderAction(RenderAction::RenderActionType::PUSH));
320 else if (s == ":pop")
321 renderList.append(RenderAction(RenderAction::RenderActionType::POP));
322 else if (s == ":n")
323 renderList.append(RenderAction(RenderAction::RenderActionType::NOTE));
324 else if (s == ":a")
325 renderList.append(RenderAction(RenderAction::RenderActionType::ACCIDENTAL));
326 else {
327 RenderAction a(RenderAction::RenderActionType::SET);
328 a.text = s;
329 renderList.append(a);
330 }
331 }
332 }
333
334 //---------------------------------------------------------
335 // writeRenderList
336 //---------------------------------------------------------
337
writeRenderList(XmlWriter & xml,const QList<RenderAction> * al,const QString & name)338 static void writeRenderList(XmlWriter& xml, const QList<RenderAction>* al, const QString& name)
339 {
340 QString s;
341
342 int n = al->size();
343 for (int i = 0; i < n; ++i) {
344 if (!s.isEmpty())
345 s += " ";
346 const RenderAction& a = (*al)[i];
347 switch(a.type) {
348 case RenderAction::RenderActionType::SET:
349 s += a.text;
350 break;
351 case RenderAction::RenderActionType::MOVE:
352 if (a.movex != 0.0 || a.movey != 0.0)
353 s += QString("m:%1:%2").arg(a.movex).arg(a.movey);
354 break;
355 case RenderAction::RenderActionType::PUSH:
356 s += ":push";
357 break;
358 case RenderAction::RenderActionType::POP:
359 s += ":pop";
360 break;
361 case RenderAction::RenderActionType::NOTE:
362 s += ":n";
363 break;
364 case RenderAction::RenderActionType::ACCIDENTAL:
365 s += ":a";
366 break;
367 }
368 }
369 xml.tag(name, s);
370 }
371
372 //---------------------------------------------------------
373 // read
374 //---------------------------------------------------------
375
read(XmlReader & e)376 void ChordToken::read(XmlReader& e)
377 {
378 QString c = e.attribute("class");
379 if (c == "quality")
380 tokenClass = ChordTokenClass::QUALITY;
381 else if (c == "extension")
382 tokenClass = ChordTokenClass::EXTENSION;
383 else if (c == "modifier")
384 tokenClass = ChordTokenClass::MODIFIER;
385 else
386 tokenClass = ChordTokenClass::ALL;
387 while (e.readNextStartElement()) {
388 const QStringRef& tag(e.name());
389 if (tag == "name")
390 names += e.readElementText();
391 else if (tag == "render")
392 readRenderList(e.readElementText(), renderList);
393 }
394 }
395
396 //---------------------------------------------------------
397 // write
398 //---------------------------------------------------------
399
write(XmlWriter & xml) const400 void ChordToken::write(XmlWriter& xml) const
401 {
402 QString t = "token";
403 switch (tokenClass) {
404 case ChordTokenClass::QUALITY:
405 t += " class=\"quality\"";
406 break;
407 case ChordTokenClass::EXTENSION:
408 t += " class=\"extension\"";
409 break;
410 case ChordTokenClass::MODIFIER:
411 t += " class=\"modifier\"";
412 break;
413 default:
414 break;
415 }
416 xml.stag(t);
417 for (const QString& s : names)
418 xml.tag("name", s);
419 writeRenderList(xml, &renderList, "render");
420 xml.etag();
421 }
422
423 //---------------------------------------------------------
424 // ParsedChord
425 //---------------------------------------------------------
426
ParsedChord()427 ParsedChord::ParsedChord()
428 {
429 _parseable = false;
430 _understandable = false;
431 }
432
433 //---------------------------------------------------------
434 // configure
435 //---------------------------------------------------------
436
configure(const ChordList * cl)437 void ParsedChord::configure(const ChordList* cl)
438 {
439 if (!cl)
440 return;
441 // TODO: allow this to be parameterized via chord list
442 major << "ma" << "maj" << "major" << "t" << "^";
443 minor << "mi" << "min" << "minor" << "-" << "=";
444 diminished << "dim" << "o";
445 augmented << "aug" << "+";
446 lower << "b" << "-" << "dim";
447 raise << "#" << "+" << "aug";
448 mod1 << "sus" << "alt";
449 mod2 << "sus" << "add" << "no" << "omit" << "^";
450 symbols << "t" << "^" << "-" << "+" << "o" << "0";
451 }
452
453 //---------------------------------------------------------
454 // correctXmlText
455 // remove digits from _xmlText, optionally replace with s
456 // needed for m(Maj9) et al
457 //---------------------------------------------------------
458
correctXmlText(const QString & s)459 void ParsedChord::correctXmlText(const QString& s)
460 {
461 _xmlText.remove(QRegExp("[0-9]"));
462 if (s != "") {
463 int pos = _xmlText.lastIndexOf(')');
464 if (pos == -1)
465 pos = _xmlText.size();
466 _xmlText.insert(pos,s);
467 }
468 }
469
470 //---------------------------------------------------------
471 // parse
472 // returns true if chord was parseable
473 //---------------------------------------------------------
474
parse(const QString & s,const ChordList * cl,bool syntaxOnly,bool preferMinor)475 bool ParsedChord::parse(const QString& s, const ChordList* cl, bool syntaxOnly, bool preferMinor)
476 {
477 QString tok1, tok1L, tok2, tok2L;
478 QString extensionDigits = "123456789";
479 QString special = "()[],/\\ ";
480 QString leading = "([ ";
481 QString trailing = ")],/\\ ";
482 QString initial;
483 bool take6 = false, take7 = false, take9 = false, take11 = false, take13 = false;
484 #if 0
485 // enable this to allow quality or extension to be parenthesized
486 int firstLeadingToken;
487 #endif
488 int lastLeadingToken;
489 int len = s.size();
490 int i;
491 int thirdKey = 0, seventhKey = 0;
492 bool susChord = false;
493 QList<HDegree> hdl;
494 int key[] = { 0, 0, 2, 4, 5, 7, 9, 11, 0, 2, 4, 5, 7, 9, 11 };
495
496 configure(cl);
497 _name = s;
498 _parseable = true;
499 _understandable = true;
500 i = 0;
501
502 #if 0
503 // enable this code to allow quality to be parenthesized
504 // otherwise, parentheses will automatically cause contents to be interpreted as extension or modifier
505 // eat leading parens
506 firstLeadingToken = _tokenList.size();
507 while (i < len && leading.contains(s[i]))
508 addToken(QString(s[i++]),ChordTokenClass::QUALITY);
509 #endif
510 lastLeadingToken = _tokenList.size();
511 // get quality
512 for (tok1 = "", tok1L = "", initial = ""; i < len; ++i) {
513 // up to first (non-zero) digit, paren, or comma
514 if (extensionDigits.contains(s[i]) || special.contains(s[i]))
515 break;
516 tok1.push_back(s[i]);
517 tok1L.push_back(s[i].toLower());
518 if (tok1L == "m" || major.contains(tok1L) || minor.contains(tok1L) || diminished.contains(tok1L) || augmented.contains(tok1L))
519 initial = tok1;
520 }
521 // special case for "madd", which needs to parse as m,add rather than ma,dd
522 if (tok1L.startsWith("madd"))
523 initial = tok1[0];
524 // quality and first modifier ran together with no separation - eg, mima7, augadd
525 // keep quality portion, reset index to read modifier portion later
526 if (initial != "" && initial != tok1 && tok1L != "tristan" && tok1L != "omit") {
527 i -= (tok1.length() - initial.length());
528 tok1 = initial;
529 tok1L = initial.toLower();
530 }
531 // determine quality
532 if (tok1 == "M" || major.contains(tok1L)) {
533 _quality = "major";
534 take6 = true; take7 = true; take9 = true; take11 = true; take13 = true;
535 if (!syntaxOnly)
536 chord = HChord("C E G");
537 }
538 else if (tok1 == "m" || minor.contains(tok1L)) {
539 _quality = "minor";
540 take6 = true; take7 = true; take9 = true; take11 = true; take13 = true;
541 if (!syntaxOnly)
542 chord = HChord("C Eb G");
543 }
544 else if (diminished.contains(tok1L)) {
545 _quality = "diminished";
546 take7 = true;
547 if (!syntaxOnly)
548 chord = HChord("C Eb Gb");
549 }
550 else if (augmented.contains(tok1L)) {
551 _quality = "augmented";
552 take7 = true;
553 if (!syntaxOnly)
554 chord = HChord("C E G#");
555 }
556 else if (tok1L == "0") {
557 _quality = "half-diminished";
558 if (!syntaxOnly)
559 chord = HChord("C Eb Gb Bb");
560 }
561 else if (tok1L == "") {
562 // empty quality - this will turn out to be major or dominant (or minor if preferMinor)
563 _quality = "";
564 if (!syntaxOnly)
565 chord = preferMinor ? HChord("C Eb G") : HChord("C E G");
566 if (preferMinor)
567 _name = "=" + _name;
568 }
569 else {
570 // anything else is not a quality after all, but a modifier
571 // reset to read again as modifier
572 _quality = "";
573 tok1 = "";
574 tok1L = "";
575 i = lastLeadingToken;
576 if (!syntaxOnly)
577 chord = HChord("C E G");
578 }
579 if (tok1L == "=") {
580 tok1 = "";
581 tok1L = "";
582 }
583 if (tok1 != "")
584 addToken(tok1,ChordTokenClass::QUALITY);
585 #if 0
586 // enable this code to allow quality to be parenthesized
587 // otherwise, parentheses will automatically cause contents to be interpreted as extension or modifier
588 else {
589 // leading tokens were not really ChordTokenClass::QUALITY
590 for (int t = firstLeadingToken; t < lastLeadingToken; ++t)
591 _tokenList[t].tokenClass = ChordTokenClass::EXTENSION;
592 }
593 #endif
594 if (!syntaxOnly) {
595 _xmlKind = _quality;
596 _xmlParens = "no";
597 if (symbols.contains(tok1)) {
598 _xmlSymbols = "yes";
599 _xmlText = "";
600 }
601 else {
602 _xmlSymbols = "no";
603 _xmlText = tok1;
604 }
605 }
606 // eat trailing parens and commas
607 while (i < len && trailing.contains(s[i]))
608 addToken(QString(s[i++]),ChordTokenClass::QUALITY);
609
610 #if 0
611 // enable this code to allow extensions to be parenthesized
612 // otherwise, parentheses will automatically cause contents to be interpreted as modifier
613 // eat leading parens
614 firstLeadingToken = _tokenList.size();
615 while (i < len && leading.contains(s[i]))
616 addToken(QString(s[i++]),ChordTokenClass::EXTENSION);
617 #endif
618 lastLeadingToken = _tokenList.size();
619 // get extension - up to first non-digit other than comma or slash
620 for (tok1 = ""; i < len; ++i) {
621 if (!s[i].isDigit() && s[i] != ',' && s[i] != '/')
622 break;
623 tok1.push_back(s[i]);
624 }
625 _extension = tok1;
626 if (_quality == "") {
627 if (_extension == "7" || _extension == "9" || _extension == "11" || _extension == "13") {
628 _quality = preferMinor ? "minor" : "dominant";
629 if (!syntaxOnly)
630 _xmlKind = preferMinor ? "minor" : "dominant";
631 take7 = true; take9 = true; take11 = true; take13 = true;
632 }
633 else {
634 _quality = preferMinor ? "minor" : "major";
635 if (!syntaxOnly)
636 _xmlKind = preferMinor ? "minor" : "major";
637 take6 = true; take7 = true; take9 = true; take11 = true; take13 = true;
638 }
639 }
640 if (tok1 != "")
641 addToken(tok1,ChordTokenClass::EXTENSION);
642 #if 0
643 // enable this code to allow extensions to be parenthesized
644 // otherwise, parentheses will automatically cause contents to be interpreted as modifier
645 else {
646 // leading tokens were not really ChordTokenClass::EXTENSION
647 for (int t = firstLeadingToken; t < lastLeadingToken; ++t) {
648 _tokenList[t].tokenClass = ChordTokenClass::MODIFIER;
649 if (!syntaxOnly)
650 _xmlParens = "yes";
651 }
652 }
653 #endif
654 if (!syntaxOnly) {
655 if (_quality == "minor")
656 thirdKey = 3;
657 else
658 thirdKey = 4;
659 if (_quality == "major")
660 seventhKey = 11;
661 else if (_quality == "diminished")
662 seventhKey = 9;
663 else
664 seventhKey = 10;
665 _xmlText += _extension;
666 QStringList extl;
667 if (tok1 == "2") {
668 QString d = "add" + tok1;
669 _xmlDegrees += d;
670 _xmlText.remove(tok1);
671 chord += 2;
672 }
673 else if (tok1 == "4") {
674 QString d = "add" + tok1;
675 _xmlDegrees += d;
676 _xmlText.remove(tok1);
677 chord += 5;
678 }
679 else if (tok1 == "5") {
680 _xmlKind = "power";
681 chord -= thirdKey;
682 }
683 else if (tok1 == "6") {
684 if (take6)
685 _xmlKind += "-sixth";
686 else
687 extl << "6";
688 chord += 9;
689 }
690 else if (tok1 == "7") {
691 if (take7)
692 _xmlKind += "-seventh";
693 else if (_xmlKind != "half-diminished")
694 extl << "7";
695 chord += seventhKey;
696 }
697 else if (tok1 == "9") {
698 if (take9)
699 _xmlKind += "-ninth";
700 else if (take7) {
701 _xmlKind += "-seventh";
702 extl << "9";
703 correctXmlText("7");
704 }
705 else if (_xmlKind == "half-diminished") {
706 extl << "9";
707 correctXmlText();
708 }
709 else {
710 extl << "7" << "9";
711 correctXmlText();
712 }
713 chord += seventhKey;
714 chord += 2;
715 }
716 else if (tok1 == "11") {
717 if (take11)
718 _xmlKind += "-11th";
719 else if (take7) {
720 _xmlKind += "-seventh";
721 extl << "9" << "11";
722 correctXmlText("7");
723 }
724 else if (_xmlKind == "half-diminished") {
725 extl << "9" << "11";
726 correctXmlText();
727 }
728 else {
729 extl << "7" << "9" << "11";
730 correctXmlText();
731 }
732 chord += seventhKey;
733 chord += 2;
734 chord += 5;
735 }
736 else if (tok1 == "13") {
737 if (take13)
738 _xmlKind += "-13th";
739 else if (take7) {
740 _xmlKind += "-seventh";
741 extl << "9" << "11" << "13";
742 correctXmlText("7");
743 }
744 else if (_xmlKind == "half-diminished") {
745 extl << "9" << "11" << "13";
746 correctXmlText();
747 }
748 else {
749 extl << "7" << "9" << "11" << "13";
750 correctXmlText();
751 }
752 chord += seventhKey;
753 chord += 2;
754 chord += 5;
755 chord += 9;
756 }
757 else if (tok1 == "69" || tok1 == "6,9" || tok1 == "6/9") {
758 if (take6) {
759 _xmlKind += "-sixth";
760 extl << "9";
761 correctXmlText("6");
762 }
763 else {
764 extl << "6" << "9";
765 correctXmlText();
766 }
767 _extension = "69";
768 chord += 9;
769 chord += 2;
770 }
771 for (const QString &e : qAsConst(extl)) {
772 QString d = "add" + e;
773 _xmlDegrees += d;
774 }
775 if (_xmlKind == "dominant-seventh")
776 _xmlKind = "dominant";
777 }
778 // eat trailing parens and commas
779 while (i < len && trailing.contains(s[i]))
780 addToken(QString(s[i++]),ChordTokenClass::EXTENSION);
781
782 // get modifiers
783 bool addPending = false;
784 _modifierList.clear();
785 while (i < len) {
786 // eat leading parens
787 while (i < len && leading.contains(s[i])) {
788 addToken(QString(s[i++]),ChordTokenClass::MODIFIER);
789 _xmlParens = "yes";
790 }
791 // get first token - up to first digit, paren, or comma
792 for (tok1 = "", tok1L = "", initial = ""; i < len; ++i) {
793 if (s[i].isDigit() || special.contains(s[i]))
794 break;
795 tok1.push_back(s[i]);
796 tok1L.push_back(s[i].toLower());
797 if (mod2.contains(tok1L))
798 initial = tok1;
799 }
800 // if we reached the end of the string and never got a token,
801 // then nothing to do, and no sense in looking for a second token
802 if (i == len && tok1 == "")
803 break;
804 if (initial != "" && initial != tok1) {
805 // two modifiers ran together with no separation - eg, susb9
806 // keep first, reset index to read second later
807 i -= (tok1.length() - initial.length());
808 tok1 = initial;
809 tok1L = initial.toLower();
810 }
811 // for "add", just add the token and then read argument as a separate modifier
812 // this allows the argument to itself be a two-part string
813 // thus allowing addb9 -> add;b,9
814 if (tok1L == "add") {
815 addToken(tok1,ChordTokenClass::MODIFIER);
816 addPending = true;
817 continue;
818 }
819 // eat spaces
820 while (i < len && s[i] == ' ')
821 ++i;
822 // get second token - a number <= 13
823 for (tok2 = ""; i < len; ++i) {
824 if (!s[i].isDigit())
825 break;
826 if (tok2.size() == 1 && (tok2[0] != '1' || s[i] > '3'))
827 break;
828 tok2.push_back(s[i]);
829 }
830 tok2L = tok2.toLower();
831 // re-attach "add"
832 if (addPending) {
833 if (raise.contains(tok1L))
834 tok1L = "#";
835 else if (lower.contains(tok1L))
836 tok1L = "b";
837 else if (tok1 == "M" || major.contains(tok1L))
838 tok1L = "major";
839 tok2L = tok1L + tok2L;
840 tok1L = "add";
841 }
842 // standardize spelling
843 if (tok1 == "M" || major.contains(tok1L))
844 tok1L = "major";
845 else if (tok1L == "omit")
846 tok1L = "no";
847 else if (tok1L == "sus" && tok2L == "")
848 tok2L = "4";
849 else if (augmented.contains(tok1L) && tok2L == "") {
850 if (_quality == "dominant" && _extension == "7") {
851 _quality = "augmented";
852 if (!syntaxOnly) {
853 _xmlKind = "augmented-seventh";
854 _xmlText = _extension + tok1;
855 chord -= 7;
856 chord += 8;
857 }
858 tok1L = "";
859 }
860 else {
861 tok1L = "#";
862 tok2L = "5";
863 }
864 }
865 else if (diminished.contains(tok1)) {
866 _quality = "diminished";
867 if (!syntaxOnly) {
868 _xmlKind = "diminished";
869 _xmlText = _extension + tok1;
870 chord -= 4;
871 chord += 3;
872 chord -= 7;
873 chord += 6;
874 }
875 tok1L = "";
876 }
877 else if ((lower.contains(tok1L) || raise.contains(tok1L)) && tok2L == "") {
878 // trailing alteration - treat as applying to extension (and convert to modifier)
879 // this handles C5b, C9#, etc
880 tok2L = _extension;
881 if (!syntaxOnly) {
882 _xmlKind = (_quality == "dominant") ? "major" : _quality;
883 _xmlText.remove(_extension);
884 if (_extension == "5")
885 chord += thirdKey;
886 else
887 chord -= seventhKey;
888 }
889 if (_quality == "dominant")
890 _quality = "major";
891 _extension = "";
892 if (lower.contains(tok1L))
893 tok1L = "b";
894 else
895 tok1L = "#";
896 }
897 else if (lower.contains(tok1L))
898 tok1L = "b";
899 else if (raise.contains(tok1L))
900 tok1L = "#";
901 QString m = tok1L + tok2L;
902 if (m != "")
903 _modifierList += m;
904 if (tok1 != "")
905 addToken(tok1,ChordTokenClass::MODIFIER);
906 if (tok2 != "")
907 addToken(tok2,ChordTokenClass::MODIFIER);
908 if (!syntaxOnly) {
909 int d;
910 if (tok2L == "")
911 d = 0;
912 else
913 d = tok2L.toInt();
914 if (d > 13)
915 d = 13;
916 QString degree;
917 bool alter = false;
918 if (tok1L == "add") {
919 if (d)
920 hdl += HDegree(d, 0, HDegreeType::ADD);
921 else if (tok2L != "") {
922 // this was result of addPending
923 // alteration; tok1 = alter, tok2 = value
924 d = tok2.toInt();
925 if (raise.contains(tok1) || tok1 == "M" || major.contains(tok1.toLower())) {
926 if (d == 7) {
927 chord += 11;
928 tok2L = "#7";
929 }
930 else if (raise.contains(tok1))
931 hdl += HDegree(d, 1, HDegreeType::ADD);
932 }
933 else if (lower.contains(tok1)) {
934 if (d == 7)
935 chord += 10;
936 else
937 hdl += HDegree(d, -1, HDegreeType::ADD);
938 }
939 else if (d)
940 hdl += HDegree(d, 0, HDegreeType::ADD);
941 }
942 degree = "add" + tok2L;
943 }
944 else if (tok1L == "no") {
945 degree = "sub" + tok2L;
946 if (d)
947 hdl += HDegree(d, 0, HDegreeType::SUBTRACT);
948 }
949 else if (tok1L == "sus") {
950 #if 0
951 // enable this code to export 7sus4 as dominant,sub3,add4 rather than suspended-fourth,add7 (similar for 9sus4, 13sus4)
952 // should basically work, but not fully tested
953 // probably needs corresponding special casing at end of loop
954 if (_extension == "") {
955 if (tok2L == "4")
956 _xmlKind = "suspended-fourth";
957 else if (tok2L == "2")
958 _xmlKind = "suspended-second";
959 _xmlText = tok1 + tok2;
960 }
961 else {
962 _xmlDegrees += "sub3";
963 tok1L = "add";
964 }
965 #else
966 // enable this code to export 7sus4 as suspended-fourth,add7 rather than dominant,sub3,add4 (similar for 9sus4, 13sus4)
967 // convert chords with sus into suspended "kind"
968 // extension then becomes a series of degree adds
969 if (tok2L == "4")
970 _xmlKind = "suspended-fourth";
971 else if (tok2L == "2")
972 _xmlKind = "suspended-second";
973 _xmlText = tok1 + tok2;
974 if (_extension == "7" || _extension == "9" || _extension == "11" || _extension == "13") {
975 _xmlDegrees += (_quality == "major") ? "add#7" : "add7";
976 // hack for programs that cannot assemble names well
977 // even though the kind is suspended, set text to also include the extension
978 // in export, we will set the degree text to null
979 _xmlText = _extension + _xmlText;
980 degree = "";
981 }
982 else if (_extension != "")
983 degree = "add" + _extension;
984 if (_extension == "13") {
985 _xmlDegrees += "add9";
986 _xmlDegrees += "add11";
987 _xmlDegrees += "add13";
988 }
989 else if (_extension == "11") {
990 _xmlDegrees += "add9";
991 _xmlDegrees += "add11";
992 }
993 else if (_extension == "9")
994 _xmlDegrees += "add9";
995 #endif
996 susChord = true;
997 chord -= thirdKey;
998 if (d)
999 chord += key[d];
1000 }
1001 else if (tok1L == "major") {
1002 if (_xmlKind.startsWith("minor")) {
1003 _xmlKind = "major-minor";
1004 if (_extension == "9" || tok2L == "9")
1005 _xmlDegrees += "add9";
1006 if (_extension == "11" || tok2L == "11") {
1007 _xmlDegrees += "add9";
1008 _xmlDegrees += "add11";
1009 }
1010 if (_extension == "13" || tok2L == "13") {
1011 _xmlDegrees += "add9";
1012 _xmlDegrees += "add11";
1013 _xmlDegrees += "add13";
1014 }
1015 _xmlText += tok1 + tok2;
1016 correctXmlText("7");
1017 }
1018 else
1019 tok1L = "add";
1020 chord -= 10;
1021 chord += 11;
1022 if (d && d != 7)
1023 hdl += HDegree(d, 0, HDegreeType::ADD);
1024 }
1025 else if (tok1L == "alt") {
1026 _xmlDegrees += "altb5";
1027 _xmlDegrees += "add#5";
1028 _xmlDegrees += "addb9";
1029 _xmlDegrees += "add#9";
1030 chord -= 7;
1031 chord += 6;
1032 chord += 8;
1033 chord += 1;
1034 chord += 3;
1035 }
1036 else if (tok1L == "blues") {
1037 // this isn't really well-defined, but it might as well mean something
1038 if (_extension == "11" || _extension == "13")
1039 _xmlDegrees += "alt#9";
1040 else
1041 _xmlDegrees += "add#9";
1042 chord += 3;
1043 }
1044 else if (tok1L == "lyd") {
1045 if (_extension == "13")
1046 _xmlDegrees += "alt#11";
1047 else
1048 _xmlDegrees += "add#11";
1049 chord += 6;
1050 }
1051 else if (tok1L == "phryg") {
1052 if (!_xmlKind.startsWith("minor"))
1053 _xmlKind = "minor-seventh";
1054 if (_extension == "11" || _extension == "13")
1055 _xmlDegrees += "altb9";
1056 else
1057 _xmlDegrees += "addb9";
1058 _xmlText += tok1;
1059 chord = HChord("C Db Eb G Bb");
1060 }
1061 else if (tok1L == "tristan") {
1062 _xmlKind = "Tristan";
1063 _xmlText = tok1;
1064 chord = HChord("C F# A# D#");
1065 }
1066 else if (addPending) {
1067 degree = "add" + tok1L + tok2L;
1068 if (raise.contains(tok1L))
1069 hdl += HDegree(d, 1, HDegreeType::ADD);
1070 else if (lower.contains(tok1L))
1071 hdl += HDegree(d, -1, HDegreeType::ADD);
1072 else
1073 hdl += HDegree(d, 0, HDegreeType::ADD);
1074 }
1075 else if (tok1L == "" && tok2L != "") {
1076 degree = "add" + tok2L;
1077 hdl += HDegree(d, 0, HDegreeType::ADD);
1078 }
1079 else if (lower.contains(tok1L)) {
1080 tok1L = "b";
1081 alter = true;
1082 }
1083 else if (raise.contains(tok1L)) {
1084 tok1L = "#";
1085 alter = true;
1086 }
1087 else if (tok1L == "") {
1088 // token was already handled fully
1089 }
1090 else {
1091 _understandable = false;
1092 if (s.startsWith(tok1)) {
1093 // unrecognized token right from very beginning
1094 _xmlKind = "other";
1095 _xmlText = tok1;
1096 }
1097 }
1098 if (alter) {
1099 if (tok2L == "4" && _xmlKind == "suspended-fourth")
1100 degree = "alt";
1101 else if (tok2L == "5")
1102 degree = "alt";
1103 else if (tok2L == "9" && (_extension == "11" || _extension == "13"))
1104 degree = "alt";
1105 else if (tok2L == "11" && _extension == "13")
1106 degree = "alt";
1107 else
1108 degree = "add";
1109 degree += tok1L + tok2L;
1110 if (chord.contains(key[d]) && !(susChord && (d == 11)))
1111 hdl += HDegree(d, 0, HDegreeType::SUBTRACT);
1112 if (tok1L == "#")
1113 hdl += HDegree(d, 1, HDegreeType::ADD);
1114 else if (tok1L == "b")
1115 hdl += HDegree(d, -1, HDegreeType::ADD);
1116 }
1117 if (degree != "")
1118 _xmlDegrees += degree;
1119 }
1120 // eat trailing parens and commas
1121 while (i < len && trailing.contains(s[i]))
1122 addToken(QString(s[i++]),ChordTokenClass::MODIFIER);
1123 addPending = false;
1124 }
1125 if (!syntaxOnly) {
1126 chord.add(hdl);
1127 // fix "add" / "alt" conflicts
1128 // so add9,altb9 -> addb9
1129 QStringList altList = _xmlDegrees.filter("alt");
1130 for (const QString& d : qAsConst(altList)) {
1131 QString unalt(d);
1132 unalt.replace(QRegExp("alt[b#]"),"add");
1133 if (_xmlDegrees.removeAll(unalt) > 0) {
1134 QString alt(d);
1135 alt.replace("alt","add");
1136 int i1 = _xmlDegrees.indexOf(d);
1137 _xmlDegrees.replace(i1, alt);
1138 }
1139 }
1140 }
1141
1142 // construct handle
1143 if (!_modifierList.empty()) {
1144 _modifierList.sort();
1145 _modifiers = "<" + _modifierList.join("><") + ">";
1146 }
1147 _handle = "<" + _quality + "><" + _extension + ">" + _modifiers;
1148
1149 // force <minor><7><b5> to export as half-diminished
1150 if (!syntaxOnly && _handle == "<minor><7><b5>") {
1151 _xmlKind = "half-diminished";
1152 _xmlText = s;
1153 _xmlDegrees.clear();
1154 }
1155 if (MScore::debugMode) {
1156 qDebug("parse: source = <%s>, handle = %s", qPrintable(s), qPrintable(_handle));
1157 if (!syntaxOnly) {
1158 qDebug("parse: HChord = <%s> (%d)", qPrintable(chord.voicing()), chord.getKeys());
1159 qDebug("parse: xmlKind = <%s>, text = <%s>", qPrintable(_xmlKind), qPrintable(_xmlText));
1160 qDebug("parse: xmlSymbols = %s, xmlParens = %s", qPrintable(_xmlSymbols), qPrintable(_xmlParens));
1161 qDebug("parse: xmlDegrees = <%s>", qPrintable(_xmlDegrees.join(",")));
1162 }
1163 }
1164 return _parseable;
1165 }
1166
1167 //---------------------------------------------------------
1168 // fromXml
1169 //---------------------------------------------------------
1170
fromXml(const QString & rawKind,const QString & rawKindText,const QString & useSymbols,const QString & useParens,const QList<HDegree> & dl,const ChordList * cl)1171 QString ParsedChord::fromXml(const QString& rawKind, const QString& rawKindText, const QString& useSymbols, const QString& useParens, const QList<HDegree>& dl, const ChordList* cl)
1172 {
1173 QString kind = rawKind;
1174 QString kindText = rawKindText;
1175 bool syms = (useSymbols == "yes");
1176 bool parens = (useParens == "yes");
1177 bool implied = false;
1178 bool extend = false;
1179 int extension = 0;
1180 _parseable = true;
1181 _understandable = true;
1182
1183 // get quality info from kind
1184 if (kind == "major-minor") {
1185 _quality = "minor";
1186 _modifierList += "major7";
1187 extend = true;
1188 }
1189 else if (kind.contains("major")) {
1190 _quality = "major";
1191 if (kind == "major" || kind == "major-sixth")
1192 implied = true;
1193 }
1194 else if (kind.contains("minor"))
1195 _quality = "minor";
1196 else if (kind.contains("dominant")) {
1197 _quality = "dominant";
1198 implied = true;
1199 extension = 7;
1200 }
1201 else if (kind == "augmented-seventh") {
1202 _quality = "augmented";
1203 extension = 7;
1204 extend = true;
1205 }
1206 else if (kind == "augmented")
1207 _quality = "augmented";
1208 else if (kind == "half-diminished") {
1209 _quality = "half-diminished";
1210 if (syms) {
1211 extension = 7;
1212 extend = true;
1213 }
1214 }
1215 else if (kind == "diminished-seventh") {
1216 _quality = "diminished";
1217 extension = 7;
1218 }
1219 else if (kind == "diminished")
1220 _quality = "diminished";
1221 else if (kind == "suspended-fourth") {
1222 _quality = "major";
1223 implied = true;
1224 _modifierList += "sus4";
1225 }
1226 else if (kind == "suspended-second") {
1227 _quality = "major";
1228 implied = true;
1229 _modifierList += "sus2";
1230 }
1231 else if (kind == "power") {
1232 _quality = "major";
1233 implied = true;
1234 extension = 5;
1235 }
1236 else
1237 _quality = kind;
1238
1239 // get extension info from kind
1240 if (kind.contains("seventh"))
1241 extension = 7;
1242 else if (kind.contains("ninth"))
1243 extension = 9;
1244 else if (kind.contains("11th"))
1245 extension = 11;
1246 else if (kind.contains("13th"))
1247 extension = 13;
1248 else if (kind.contains("sixth"))
1249 extension = 6;
1250
1251 // get modifier info from degree list
1252 for (const HDegree& d : dl) {
1253 QString mod;
1254 int v = d.value();
1255 switch (d.type()) {
1256 case HDegreeType::ADD:
1257 case HDegreeType::ALTER:
1258 switch (d.alter()) {
1259 case -1: mod = "b"; break;
1260 case 1: mod = "#"; break;
1261 case 0: mod = "add"; break;
1262 }
1263 break;
1264 case HDegreeType::SUBTRACT:
1265 mod = "no";
1266 break;
1267 case HDegreeType::UNDEF:
1268 default:
1269 break;
1270 }
1271 mod += QString("%1").arg(v);
1272 if (mod == "add7" && kind.contains("suspended")) {
1273 _quality = "dominant";
1274 implied = true;
1275 extension = 7;
1276 extend = true;
1277 mod = "";
1278 }
1279 else if (mod == "add#7" && kind.contains("suspended")) {
1280 _quality = "major";
1281 implied = false;
1282 extension = 7;
1283 extend = true;
1284 mod = "";
1285 }
1286 else if (mod == "add9" && extend) {
1287 if (extension < 9)
1288 extension = 9;
1289 mod = "";
1290 }
1291 else if (mod == "add11" && extend) {
1292 if (extension < 11)
1293 extension = 11;
1294 mod = "";
1295 }
1296 else if (mod == "add13" && extend) {
1297 extension = 13;
1298 mod = "";
1299 }
1300 else if (mod == "add9" && kind.contains("sixth")) {
1301 extension = 69;
1302 mod = "";
1303 }
1304 if (mod != "")
1305 _modifierList += mod;
1306 }
1307 // convert no3,add[42] into sus[42]
1308 int no3 = _modifierList.indexOf("no3");
1309 if (no3 >= 0) {
1310 int addn = _modifierList.indexOf("add4");
1311 if (addn == -1)
1312 addn = _modifierList.indexOf("add2");
1313 if (addn != -1) {
1314 QString& s = _modifierList[addn];
1315 s.replace("add","sus");
1316 _modifierList.removeAt(no3);
1317 }
1318 }
1319 // convert kind=minor-seventh, degree=altb5 to kind=half-diminished (suppression of degree=altb comes later)
1320 if (kind == "minor-seventh" && _modifierList.size() == 1 && _modifierList.front() == "b5")
1321 kind = "half-diminished";
1322 // force parens where necessary)
1323 if (!parens && extension == 0 && !_modifierList.empty()) {
1324 QString firstMod = _modifierList.front();
1325 if (firstMod != "" && (firstMod.startsWith('#') || firstMod.startsWith('b')))
1326 parens = true;
1327 }
1328
1329 // record extension
1330 if (extension)
1331 _extension = QString("%1").arg(extension);
1332
1333 // validate kindText
1334 if (kindText != "" && kind != "none" && kind != "other") {
1335 ParsedChord validate;
1336 validate.parse(kindText, cl, false);
1337 // kindText should parse to produce same kind, no degrees
1338 if (validate._xmlKind != kind || !validate._xmlDegrees.empty())
1339 kindText = "";
1340 }
1341
1342 // construct name & handle
1343 _name = "";
1344 if (kindText != "") {
1345 if (_extension != "" && kind.contains("suspended"))
1346 _name += _extension;
1347 _name += kindText;
1348 if (extension == 69)
1349 _name += "9";
1350 }
1351 else if (implied)
1352 _name = _extension;
1353 else {
1354 if (_quality == "major")
1355 _name = syms ? "^" : "maj";
1356 else if (_quality == "minor")
1357 _name = syms ? "-" : "m";
1358 else if (_quality == "augmented")
1359 _name = syms ? "+" : "aug";
1360 else if (_quality == "diminished")
1361 _name = syms ? "o" : "dim";
1362 else if (_quality == "half-diminished")
1363 _name = syms ? "0" : "m7b5";
1364 else
1365 _name = _quality;
1366 _name += _extension;
1367 }
1368 if (parens)
1369 _name += "(";
1370 for (QString mod : qAsConst(_modifierList)) {
1371 mod.replace("major","maj");
1372 if (kindText != "" && kind.contains("suspended") && mod.startsWith("sus"))
1373 continue;
1374 else if (kindText != "" && kind == "major-minor" && mod.startsWith("maj"))
1375 continue;
1376 _name += mod;
1377 }
1378 if (parens)
1379 _name += ")";
1380
1381 // parse name to construct handle & tokenList
1382 parse(_name, cl, true);
1383
1384 // record original MusicXML
1385 _xmlKind = kind;
1386 _xmlText = kindText;
1387 _xmlSymbols = useSymbols;
1388 _xmlParens = useParens;
1389 for (const HDegree& d : dl) {
1390 if (kind == "half-diminished" && d.type() == HDegreeType::ALTER && d.alter() == -1 && d.value() == 5)
1391 continue;
1392 _xmlDegrees += d.text();
1393 }
1394
1395 return _name;
1396 }
1397
1398
1399 //---------------------------------------------------------
1400 // position
1401 //---------------------------------------------------------
1402
position(const QStringList & names,ChordTokenClass ctc) const1403 qreal ChordList::position(const QStringList& names, ChordTokenClass ctc) const
1404 {
1405 QString name = names.empty() ? "" : names.first();
1406 switch (ctc) {
1407 case ChordTokenClass::EXTENSION:
1408 return _eadjust;
1409 case ChordTokenClass::MODIFIER: {
1410 QChar c = name.isEmpty() ? name.at(0) : '0';
1411 if (c.isDigit() || c.isPunct())
1412 return _madjust;
1413 else
1414 return 0.0;
1415 }
1416 default:
1417 if (name == "o" || name == "0")
1418 return _eadjust;
1419 else
1420 return 0.0;
1421 }
1422 }
1423
1424 //---------------------------------------------------------
1425 // renderList
1426 //---------------------------------------------------------
1427
renderList(const ChordList * cl)1428 const QList<RenderAction>& ParsedChord::renderList(const ChordList* cl)
1429 {
1430 // generate anew on each call,
1431 // in case chord list has changed since last time
1432 if (!_renderList.empty())
1433 _renderList.clear();
1434 bool adjust = cl ? cl->autoAdjust() : false;
1435 for (const ChordToken &tok : qAsConst(_tokenList)) {
1436 QString n = tok.names.first();
1437 QList<RenderAction> rl;
1438 QList<ChordToken> definedTokens;
1439 bool found = false;
1440 // potential definitions for token
1441 if (cl) {
1442 for (const ChordToken &ct : cl->chordTokenList) {
1443 for (const QString &ctn : qAsConst(ct.names)) {
1444 if (ctn == n)
1445 definedTokens += ct;
1446 }
1447 }
1448 }
1449 // find matching class, fallback on ChordTokenClass::ALL
1450 ChordTokenClass ctc = ChordTokenClass::ALL;
1451 for (const ChordToken &matchingTok : qAsConst(definedTokens)) {
1452 if (tok.tokenClass == matchingTok.tokenClass) {
1453 rl = matchingTok.renderList;
1454 ctc = tok.tokenClass;
1455 found = true;
1456 break;
1457 }
1458 else if (matchingTok.tokenClass == ChordTokenClass::ALL) {
1459 rl = matchingTok.renderList;
1460 found = true;
1461 }
1462 }
1463 // check for adjustments
1464 // stop adjusting when first non-adjusted modifier found
1465 qreal p = adjust ? cl->position(tok.names, ctc) : 0.0;
1466 if (tok.tokenClass == ChordTokenClass::MODIFIER && p == 0.0)
1467 adjust = false;
1468 // build render list
1469 if (p != 0.0) {
1470 RenderAction m1 = RenderAction(RenderAction::RenderActionType::MOVE);
1471 m1.movex = 0.0;
1472 m1.movey = p;
1473 _renderList.append(m1);
1474 }
1475 if (found) {
1476 _renderList.append(rl);
1477 }
1478 else {
1479 // no definition for token, so render as literal
1480 RenderAction a(RenderAction::RenderActionType::SET);
1481 a.text = tok.names.first();
1482 _renderList.append(a);
1483 }
1484 if (p != 0.0) {
1485 RenderAction m2 = RenderAction(RenderAction::RenderActionType::MOVE);
1486 m2.movex = 0.0;
1487 m2.movey = -p;
1488 _renderList.append(m2);
1489 }
1490 }
1491 return _renderList;
1492 }
1493
1494 //---------------------------------------------------------
1495 // addToken
1496 //---------------------------------------------------------
1497
addToken(QString s,ChordTokenClass tc)1498 void ParsedChord::addToken(QString s, ChordTokenClass tc)
1499 {
1500 if (s == "")
1501 return;
1502 ChordToken tok;
1503 tok.names += s;
1504 tok.tokenClass = tc;
1505 _tokenList += tok;
1506 }
1507
1508 //---------------------------------------------------------
1509 // ChordDescription
1510 // this form is used when reading from file
1511 // a private id is assigned for id = 0
1512 //---------------------------------------------------------
1513
ChordDescription(int i)1514 ChordDescription::ChordDescription(int i)
1515 {
1516 if (!i)
1517 // i = --(cl->privateID);
1518 i = -- ChordList::privateID;
1519 id = i;
1520 generated = false;
1521 renderListGenerated = false;
1522 exportOk = true;
1523 }
1524
1525 //---------------------------------------------------------
1526 // ChordDescription
1527 // this form is used when generating from name
1528 // a private id is always assigned
1529 //---------------------------------------------------------
1530
ChordDescription(const QString & name)1531 ChordDescription::ChordDescription(const QString& name)
1532 {
1533 id = -- ChordList::privateID;
1534 generated = true;
1535 names.append(name);
1536 renderListGenerated = false;
1537 exportOk = false;
1538 }
1539
1540 //---------------------------------------------------------
1541 // complete
1542 // generate missing renderList and semantic (Xml) info
1543 //---------------------------------------------------------
1544
complete(ParsedChord * pc,const ChordList * cl)1545 void ChordDescription::complete(ParsedChord* pc, const ChordList* cl)
1546 {
1547 ParsedChord tempPc;
1548 if (!pc) {
1549 // generate parsed chord for its rendering & semantic (xml) info
1550 pc = &tempPc;
1551 QString n;
1552 if (!names.empty())
1553 n = names.front();
1554 pc->parse(n, cl);
1555 }
1556 parsedChords.append(*pc);
1557 if (renderList.empty() || renderListGenerated) {
1558 renderList = pc->renderList(cl);
1559 renderListGenerated = true;
1560 }
1561 if (xmlKind == "") {
1562 xmlKind = pc->xmlKind();
1563 xmlDegrees = pc->xmlDegrees();
1564 }
1565 // these fields are not read from chord description files
1566 // so get them from the parsed representation in all cases
1567 xmlText = pc->xmlText();
1568 xmlSymbols = pc->xmlSymbols();
1569 xmlParens = pc->xmlParens();
1570 if (chord.getKeys() == 0) {
1571 chord = HChord(pc->keys());
1572 }
1573 _quality = pc->quality();
1574 }
1575
1576 //---------------------------------------------------------
1577 // read
1578 //---------------------------------------------------------
1579
read(XmlReader & e)1580 void ChordDescription::read(XmlReader& e)
1581 {
1582 int ni = 0;
1583 id = e.attribute("id").toInt();
1584 while (e.readNextStartElement()) {
1585 const QStringRef& tag(e.name());
1586 if (tag == "name") {
1587 QString n = e.readElementText();
1588 // stack names for this file on top of the list
1589 names.insert(ni++,n);
1590 }
1591 else if (tag == "xml")
1592 xmlKind = e.readElementText();
1593 else if (tag == "degree")
1594 xmlDegrees.append(e.readElementText());
1595 else if (tag == "voicing")
1596 chord = HChord(e.readElementText());
1597 else if (tag == "render") {
1598 readRenderList(e.readElementText(), renderList);
1599 renderListGenerated = false;
1600 }
1601 else
1602 e.unknown();
1603 }
1604 }
1605
1606 //---------------------------------------------------------
1607 // write
1608 //---------------------------------------------------------
1609
write(XmlWriter & xml) const1610 void ChordDescription::write(XmlWriter& xml) const
1611 {
1612 if (generated && !exportOk)
1613 return;
1614 if (id > 0)
1615 xml.stag(QString("chord id=\"%1\"").arg(id));
1616 else
1617 xml.stag(QString("chord"));
1618 for (const QString& s : names)
1619 xml.tag("name", s);
1620 xml.tag("xml", xmlKind);
1621 xml.tag("voicing", chord.voicing());
1622 for (const QString& s : xmlDegrees)
1623 xml.tag("degree", s);
1624 writeRenderList(xml, &renderList, "render");
1625 xml.etag();
1626 }
1627
1628
1629 //---------------------------------------------------------
1630 // ChordList
1631 //---------------------------------------------------------
1632
1633 int ChordList::privateID = -1000;
1634
1635 //---------------------------------------------------------
1636 // configureAutoAdjust
1637 //---------------------------------------------------------
1638
configureAutoAdjust(qreal emag,qreal eadjust,qreal mmag,qreal madjust)1639 void ChordList::configureAutoAdjust(qreal emag, qreal eadjust, qreal mmag, qreal madjust)
1640 {
1641 _emag = emag;
1642 _eadjust = eadjust;
1643 _mmag = mmag;
1644 _madjust = madjust;
1645 #if 0
1646 // TODO: regenerate all chord descriptions
1647 // currently we always reload the entire chordlist
1648 if (_autoAdjust) {
1649 for (ChordFont cf : fonts) {
1650 if (cf.fontClass == "extension")
1651 cf.mag = _emag;
1652 else if (cf.fontClass == "modifier")
1653 cf.mag = _mmag;
1654 }
1655 }
1656 #endif
1657 }
1658
1659 //---------------------------------------------------------
1660 // read
1661 //---------------------------------------------------------
1662
read(XmlReader & e)1663 void ChordList::read(XmlReader& e)
1664 {
1665 int fontIdx = 0;
1666 _autoAdjust = false;
1667 while (e.readNextStartElement()) {
1668 const QStringRef& tag(e.name());
1669 if (tag == "font") {
1670 ChordFont f;
1671 f.family = e.attribute("family", "default");
1672 if (f.family == "MuseJazz")
1673 f.family = "MuseJazz Text";
1674 f.mag = 1.0;
1675 f.fontClass = e.attribute("class");
1676 while (e.readNextStartElement()) {
1677 if (e.name() == "sym") {
1678 ChordSymbol cs;
1679 QString code;
1680 QString symClass;
1681 cs.fontIdx = fontIdx;
1682 cs.name = e.attribute("name");
1683 cs.value = e.attribute("value");
1684 code = e.attribute("code");
1685 symClass = e.attribute("class");
1686 if (code != "") {
1687 bool ok = true;
1688 int val = code.toInt(&ok, 0);
1689 if (!ok) {
1690 cs.code = 0;
1691 cs.value = code;
1692 }
1693 else if (val & 0xffff0000) {
1694 cs.code = 0;
1695 QChar high = QChar(QChar::highSurrogate(val));
1696 QChar low = QChar(QChar::lowSurrogate(val));
1697 cs.value = QString("%1%2").arg(high).arg(low);
1698 }
1699 else {
1700 cs.code = val;
1701 cs.value = QString(cs.code);
1702 }
1703 }
1704 else
1705 cs.code = 0;
1706 if (cs.value == "")
1707 cs.value = cs.name;
1708 cs.name = symClass + cs.name;
1709 symbols.insert(cs.name, cs);
1710 e.readNext();
1711 }
1712 else if (e.name() == "mag")
1713 f.mag = e.readDouble();
1714 else
1715 e.unknown();
1716 }
1717 if (_autoAdjust) {
1718 if (f.fontClass == "extension")
1719 f.mag *= _emag;
1720 else if (f.fontClass == "modifier")
1721 f.mag *= _mmag;
1722 }
1723 fonts.append(f);
1724 ++fontIdx;
1725 }
1726 else if (tag == "autoAdjust") {
1727 QString nmag = e.attribute("mag");
1728 _nmag = nmag.toDouble();
1729 QString nadjust = e.attribute("adjust");
1730 _nadjust = nadjust.toDouble();
1731 _autoAdjust = e.readBool();
1732 }
1733 else if (tag == "token") {
1734 ChordToken t;
1735 t.read(e);
1736 chordTokenList.append(t);
1737 }
1738 else if (tag == "chord") {
1739 int id = e.intAttribute("id");
1740 // if no id attribute (id == 0), then assign it a private id
1741 // user chords that match these ChordDescriptions will be treated as normal recognized chords
1742 // except that the id will not be written to the score file
1743 ChordDescription cd = (id && contains(id)) ? take(id) : ChordDescription(id);
1744
1745 // record updated id
1746 id = cd.id;
1747 // read rest of description
1748 cd.read(e);
1749 // restore updated id
1750 cd.id = id;
1751 // throw away previously parsed chords
1752 cd.parsedChords.clear();
1753 // generate any missing info (including new parsed chords)
1754 cd.complete(0,this);
1755 // add to list
1756 insert(id, cd);
1757 }
1758 else if (tag == "renderRoot")
1759 readRenderList(e.readElementText(), renderListRoot);
1760 else if (tag == "renderFunction")
1761 readRenderList(e.readElementText(), renderListFunction);
1762 else if (tag == "renderBase")
1763 readRenderList(e.readElementText(), renderListBase);
1764 else
1765 e.unknown();
1766 }
1767 }
1768
1769 //---------------------------------------------------------
1770 // write
1771 //---------------------------------------------------------
1772
write(XmlWriter & xml) const1773 void ChordList::write(XmlWriter& xml) const
1774 {
1775 int fontIdx = 0;
1776 for (const ChordFont &f : fonts) {
1777 xml.stag(QString("font id=\"%1\" family=\"%2\"").arg(fontIdx).arg(f.family));
1778 xml.tag("mag", f.mag);
1779 for (const ChordSymbol &s : symbols) {
1780 if (s.fontIdx == fontIdx) {
1781 if (s.code.isNull())
1782 xml.tagE(QString("sym name=\"%1\" value=\"%2\"").arg(s.name, s.value));
1783 else
1784 xml.tagE(QString("sym name=\"%1\" code=\"0x%2\"").arg(s.name).arg(s.code.unicode(), 0, 16));
1785 }
1786 }
1787 xml.etag();
1788 ++fontIdx;
1789 }
1790 if (_autoAdjust)
1791 xml.tagE(QString("autoAdjust mag=\"%1\" adjust=\"%2\"").arg(_nmag).arg(_nadjust));
1792 for (const ChordToken &t : chordTokenList)
1793 t.write(xml);
1794 if (!renderListRoot.empty())
1795 writeRenderList(xml, &renderListRoot, "renderRoot");
1796 if (!renderListFunction.empty())
1797 writeRenderList(xml, &renderListFunction, "renderFunction");
1798 if (!renderListBase.empty())
1799 writeRenderList(xml, &renderListBase, "renderBase");
1800 for (const ChordDescription& cd : *this)
1801 cd.write(xml);
1802 }
1803
1804 //---------------------------------------------------------
1805 // read
1806 // read Chord List, return false on error
1807 //---------------------------------------------------------
1808
read(const QString & name)1809 bool ChordList::read(const QString& name)
1810 {
1811 // qDebug("ChordList::read <%s>", qPrintable(name));
1812 QString path;
1813 QFileInfo ftest(name);
1814 if (ftest.isAbsolute())
1815 path = name;
1816 else {
1817 #if defined(Q_OS_IOS)
1818 path = QString("%1/%2").arg(MScore::globalShare()).arg(name);
1819 #elif defined(Q_OS_ANDROID)
1820 path = QString(":/styles/%1").arg(name);
1821 #else
1822 path = QString("%1styles/%2").arg(MScore::globalShare(), name);
1823 #endif
1824 }
1825 // default to chords_std.xml
1826 QFileInfo fi(path);
1827 if (!fi.exists())
1828 #if defined(Q_OS_IOS)
1829 path = QString("%1/%2").arg(MScore::globalShare()).arg("chords_std.xml");
1830 #elif defined(Q_OS_ANDROID)
1831 path = QString(":/styles/chords_std.xml");
1832 #else
1833 path = QString("%1styles/%2").arg(MScore::globalShare(), "chords_std.xml");
1834 #endif
1835
1836 if (name.isEmpty())
1837 return false;
1838 QFile f(path);
1839 if (!f.open(QIODevice::ReadOnly)) {
1840 MScore::lastError = QObject::tr("Cannot open chord description:\n%1\n%2").arg(f.fileName(), f.errorString());
1841 qDebug("ChordList::read failed: <%s>", qPrintable(path));
1842 return false;
1843 }
1844 XmlReader e(&f);
1845
1846 while (e.readNextStartElement()) {
1847 if (e.name() == "museScore") {
1848 // QString version = e.attribute(QString("version"));
1849 // QStringList sl = version.split('.');
1850 // int _mscVersion = sl[0].toInt() * 100 + sl[1].toInt();
1851 read(e);
1852 return true;
1853 }
1854 }
1855 return false;
1856 }
1857
1858 //---------------------------------------------------------
1859 // writeChordList
1860 //---------------------------------------------------------
1861
write(const QString & name) const1862 bool ChordList::write(const QString& name) const
1863 {
1864 QFileInfo info(name);
1865
1866 if (info.suffix().isEmpty()) {
1867 QString path = info.filePath();
1868 path += QString(".xml");
1869 info.setFile(path);
1870 }
1871
1872 QFile f(info.filePath());
1873
1874 if (!f.open(QIODevice::WriteOnly)) {
1875 MScore::lastError = QObject::tr("Open chord description\n%1\nfailed: %2").arg(f.fileName(), f.errorString());
1876 return false;
1877 }
1878
1879 XmlWriter xml(0, &f);
1880 xml.header();
1881 xml.stag("museScore version=\"" MSC_VERSION "\"");
1882
1883 write(xml);
1884 xml.etag();
1885 if (f.error() != QFile::NoError) {
1886 MScore::lastError = QObject::tr("Write chord description failed: %1").arg(f.errorString());
1887 }
1888 return true;
1889 }
1890
1891 //---------------------------------------------------------
1892 // loaded
1893 //---------------------------------------------------------
1894
loaded() const1895 bool ChordList::loaded() const
1896 {
1897 // track whether a description file has been loaded
1898 // since chords.xml really doesn't load enough to stand alone,
1899 // we need a way to track when a "real" chord list has been loaded
1900 // for lack of anything better, key off renderListRoot
1901 return !renderListRoot.empty();
1902 }
1903
1904 //---------------------------------------------------------
1905 // unload
1906 //---------------------------------------------------------
1907
unload()1908 void ChordList::unload()
1909 {
1910 clear();
1911 symbols.clear();
1912 fonts.clear();
1913 renderListRoot.clear();
1914 renderListBase.clear();
1915 chordTokenList.clear();
1916 _autoAdjust = false;
1917 }
1918
1919 //---------------------------------------------------------
1920 // print
1921 // only for debugging
1922 //---------------------------------------------------------
1923
print() const1924 void RenderAction::print() const
1925 {
1926 static const char* names[] = {
1927 "SET", "MOVE", "PUSH", "POP",
1928 "NOTE", "ACCIDENTAL"
1929 };
1930 qDebug("%10s <%s> %f %f", names[int(type)], qPrintable(text), movex, movey);
1931 }
1932
1933
1934 }
1935