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