LCOV - code coverage report
Current view: top level - src/engraving/dom - chordlist.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 72.1 % 1521 1097
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-Studio-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2021 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "chordlist.h"
      24              : 
      25              : #include "global/containers.h"
      26              : #include "global/io/buffer.h"
      27              : #include "global/io/file.h"
      28              : #include "global/io/fileinfo.h"
      29              : 
      30              : #include "types/constants.h"
      31              : 
      32              : #include "rw/compat/compatutils.h"
      33              : #include "rw/xmlreader.h"
      34              : #include "rw/xmlwriter.h"
      35              : 
      36              : #include "style/style.h"
      37              : 
      38              : #include "mscore.h"
      39              : #include "pitchspelling.h"
      40              : 
      41              : #include "log.h"
      42              : 
      43              : using namespace mu;
      44              : using namespace muse;
      45              : using namespace muse::io;
      46              : 
      47              : namespace mu::engraving {
      48              : //---------------------------------------------------------
      49              : //   HChord
      50              : //---------------------------------------------------------
      51              : 
      52        22077 : HChord::HChord(const String& str)
      53              : {
      54              :     static const char* const scaleNames[2][12] = {
      55              :         { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" },
      56              :         { "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B" }
      57              :     };
      58        22077 :     m_keys = 0;
      59        22077 :     StringList sl = str.split(u' ', muse::SkipEmptyParts);
      60       100142 :     for (const String& s : sl) {
      61       398428 :         for (int i = 0; i < 12; ++i) {
      62       398370 :             if (s == scaleNames[0][i] || s == scaleNames[1][i]) {
      63        78007 :                 operator+=(i);
      64        78007 :                 break;
      65              :             }
      66              :         }
      67              :     }
      68        22077 : }
      69              : 
      70              : //---------------------------------------------------------
      71              : //   HChord
      72              : //---------------------------------------------------------
      73              : 
      74            0 : HChord::HChord(int a, int b, int c, int d, int e, int f, int g, int h, int i, int k, int l)
      75              : {
      76            0 :     m_keys = 0;
      77            0 :     if (a >= 0) {
      78            0 :         operator+=(a);
      79              :     }
      80            0 :     if (b >= 0) {
      81            0 :         operator+=(b);
      82              :     }
      83            0 :     if (c >= 0) {
      84            0 :         operator+=(c);
      85              :     }
      86            0 :     if (d >= 0) {
      87            0 :         operator+=(d);
      88              :     }
      89            0 :     if (e >= 0) {
      90            0 :         operator+=(e);
      91              :     }
      92            0 :     if (f >= 0) {
      93            0 :         operator+=(f);
      94              :     }
      95            0 :     if (g >= 0) {
      96            0 :         operator+=(g);
      97              :     }
      98            0 :     if (h >= 0) {
      99            0 :         operator+=(h);
     100              :     }
     101            0 :     if (i >= 0) {
     102            0 :         operator+=(i);
     103              :     }
     104            0 :     if (k >= 0) {
     105            0 :         operator+=(k);
     106              :     }
     107            0 :     if (l >= 0) {
     108            0 :         operator+=(l);
     109              :     }
     110            0 : }
     111              : 
     112              : //---------------------------------------------------------
     113              : //   rotate
     114              : //    rotate 12 Bits
     115              : //---------------------------------------------------------
     116              : 
     117            0 : void HChord::rotate(int semiTones)
     118              : {
     119            0 :     while (semiTones > 0) {
     120            0 :         if (m_keys & 0x800) {
     121            0 :             m_keys = ((m_keys & ~0x800) << 1) + 1;
     122              :         } else {
     123            0 :             m_keys <<= 1;
     124              :         }
     125            0 :         --semiTones;
     126              :     }
     127            0 :     while (semiTones < 0) {
     128            0 :         if (m_keys & 1) {
     129            0 :             m_keys = (m_keys >> 1) | 0x800;
     130              :         } else {
     131            0 :             m_keys >>= 1;
     132              :         }
     133            0 :         ++semiTones;
     134              :     }
     135            0 : }
     136              : 
     137              : //---------------------------------------------------------
     138              : //   name
     139              : //---------------------------------------------------------
     140              : 
     141            0 : String HChord::name(int tpc) const
     142              : {
     143            0 :     static const HChord C0(0, 3, 6, 9);
     144            0 :     static const HChord C1(0, 3);
     145              : 
     146            0 :     String buf = tpc2name(tpc, NoteSpellingType::STANDARD, NoteCaseType::AUTO, false);
     147            0 :     HChord c(*this);
     148              : 
     149            0 :     int key = tpc2pitch(tpc);
     150              : 
     151            0 :     c.rotate(-key);          // transpose to C
     152              : 
     153              :     // special cases
     154            0 :     if (c == C0) {
     155            0 :         buf += u"dim";
     156            0 :         return buf;
     157              :     }
     158            0 :     if (c == C1) {
     159            0 :         buf += u"no5";
     160            0 :         return buf;
     161              :     }
     162              : 
     163            0 :     bool seven   = false;
     164            0 :     bool sharp9  = false;
     165            0 :     bool nat11   = false;
     166            0 :     bool sharp11 = false;
     167            0 :     bool nat13   = false;
     168            0 :     bool flat13  = false;
     169              : 
     170              :     // minor?
     171            0 :     if (c.contains(3)) {
     172            0 :         if (!c.contains(4)) {
     173            0 :             buf += u"m";
     174              :         } else {
     175            0 :             sharp9 = true;
     176              :         }
     177              :     }
     178              : 
     179              :     // 7
     180            0 :     if (c.contains(11)) {
     181            0 :         buf += u"Maj7";
     182            0 :         seven = true;
     183            0 :     } else if (c.contains(10)) {
     184            0 :         buf += u"7";
     185            0 :         seven = true;
     186              :     }
     187              : 
     188              :     // 4
     189            0 :     if (c.contains(5)) {
     190            0 :         if (!c.contains(4)) {
     191            0 :             buf += u"sus4";
     192              :         } else {
     193            0 :             nat11 = true;
     194              :         }
     195              :     }
     196              : 
     197              :     // 5
     198            0 :     if (c.contains(7)) {
     199            0 :         if (c.contains(6)) {
     200            0 :             sharp11 = true;
     201              :         }
     202            0 :         if (c.contains(8)) {
     203            0 :             flat13 = true;
     204              :         }
     205              :     } else {
     206            0 :         if (c.contains(6)) {
     207            0 :             buf += u"b5";
     208              :         }
     209            0 :         if (c.contains(8)) {
     210            0 :             buf += u"#5";
     211              :         }
     212              :     }
     213              : 
     214              :     // 6
     215            0 :     if (c.contains(9)) {
     216            0 :         if (!seven) {
     217            0 :             buf += u"6";
     218              :         } else {
     219            0 :             nat13 = true;
     220              :         }
     221              :     }
     222              : 
     223              :     // 9
     224            0 :     if (c.contains(1)) {
     225            0 :         buf += u"b9";
     226              :     }
     227            0 :     if (c.contains(2)) {
     228            0 :         buf += u"9";
     229              :     }
     230            0 :     if (sharp9) {
     231            0 :         buf += u"#9";
     232              :     }
     233              : 
     234              :     // 11
     235            0 :     if (nat11) {
     236            0 :         buf += u"11 ";
     237              :     }
     238            0 :     if (sharp11) {
     239            0 :         buf += u"#11";
     240              :     }
     241              : 
     242              :     // 13
     243            0 :     if (flat13) {
     244            0 :         buf += u"b13";
     245              :     }
     246            0 :     if (nat13) {
     247            0 :         if (c.contains(1) || c.contains(2) || sharp9 || nat11 || sharp11) {
     248            0 :             buf += u"13";
     249              :         } else {
     250            0 :             buf += u"add13";
     251              :         }
     252              :     }
     253            0 :     return buf;
     254            0 : }
     255              : 
     256              : //---------------------------------------------------------
     257              : //   voicing
     258              : //---------------------------------------------------------
     259              : 
     260           60 : String HChord::voicing() const
     261              : {
     262           60 :     String s = u"C";
     263           60 :     const char* names[] = { "C", " Db", " D", " Eb", " E", " F", " Gb", " G", " Ab", " A", " Bb", " B" };
     264              : 
     265          720 :     for (int i = 1; i < 12; i++) {
     266          660 :         if (contains(i)) {
     267          174 :             s += String::fromAscii(names[i]);
     268              :         }
     269              :     }
     270           60 :     return s;
     271            0 : }
     272              : 
     273              : //---------------------------------------------------------
     274              : //   print
     275              : //---------------------------------------------------------
     276              : 
     277            0 : void HChord::print() const
     278              : {
     279            0 :     const char* names[] = { "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B" };
     280              : 
     281            0 :     for (int i = 0; i < 12; i++) {
     282            0 :         if (contains(i)) {
     283            0 :             LOGD(" %s", names[i]);
     284              :         }
     285              :     }
     286            0 : }
     287              : 
     288              : //---------------------------------------------------------
     289              : //   add
     290              : //---------------------------------------------------------
     291              : 
     292        16758 : void HChord::add(const std::vector<HDegree>& degreeList)
     293              : {
     294              : // LOGD("HChord::add   ");print();
     295              :     // convert degrees to semitones
     296              :     static const int degreeTable[] = {
     297              :         // 1  2  3  4  5  6   7
     298              :         // C  D  E  F  G  A   B
     299              :         0, 2, 4, 5, 7, 9, 11
     300              :     };
     301              :     // factor in the degrees
     302        32694 :     for (const HDegree& d : degreeList) {
     303        15936 :         int i = (d.value() - 1) % 7;
     304        15936 :         if (i < 0) {
     305            2 :             continue; // ?
     306              :         }
     307        15934 :         int dv  = degreeTable[i] + d.alter();
     308        15934 :         int dv1 = degreeTable[i];
     309              : 
     310        15934 :         if (d.value() == 7 && d.alter() == 0) {
     311            0 :             dv -= 1;
     312              :         }
     313              : 
     314        15934 :         if (d.type() == HDegreeType::ADD) {
     315        11296 :             *this += dv;
     316         4638 :         } else if (d.type() == HDegreeType::ALTER) {
     317            0 :             if (contains(dv1)) {
     318            0 :                 *this -= dv1;
     319            0 :                 *this += dv;
     320              :             } else {
     321              : //                        LOGD("HDegreeType::ALTER: chord does not contain degree %d(%d):",
     322              : //                           d.value(), d.alter());
     323              : //                        print();
     324            0 :                 *this += dv;              // DEBUG: default to add
     325              :             }
     326         4638 :         } else if (d.type() == HDegreeType::SUBTRACT) {
     327         4638 :             if (contains(dv1)) {
     328         4621 :                 *this -= dv1;
     329              :             } else {
     330           17 :                 LOGD("SUB: chord does not contain degree %d(%d):",
     331              :                      d.value(), d.alter());
     332              :             }
     333              :         } else {
     334            0 :             LOGD("degree type %d not supported", static_cast<int>(d.type()));
     335              :         }
     336              : 
     337              : // LOGD("  HCHord::added  "); print();
     338              :     }
     339        16758 : }
     340              : 
     341              : //---------------------------------------------------------
     342              : //   readRenderList
     343              : //---------------------------------------------------------
     344              : 
     345       111012 : static void readRenderList(String val, std::vector<RenderActionPtr>& renderList, int mscVersion)
     346              : {
     347       111012 :     renderList.clear();
     348       111012 :     StringList sl = val.split(u' ', muse::SkipEmptyParts);
     349       322341 :     for (const String& s : sl) {
     350       211329 :         if (s.startsWith(u"m:")) {
     351        39614 :             StringList ssl = s.split(u':', muse::SkipEmptyParts);
     352        39614 :             if (ssl.size() == 3) {
     353              :                 // m:x:y
     354        39614 :                 double movex = ssl.at(1).toDouble();
     355        39614 :                 double movey = ssl.at(2).toDouble();
     356              : 
     357        39614 :                 if (mscVersion < 460) {
     358         5759 :                     movex = compat::CompatUtils::convertChordExtModUnits(movex);
     359         5759 :                     movey = compat::CompatUtils::convertChordExtModUnits(movey);
     360              :                 }
     361              : 
     362        39614 :                 renderList.emplace_back(new RenderActionMove(movex, movey));
     363              :             }
     364       211329 :         } else if (s.startsWith(u"ms:")) {
     365        20130 :             StringList ssl = s.split(u':', muse::SkipEmptyParts);
     366        20130 :             if (ssl.size() == 3) {
     367              :                 // ms:x:y
     368        20130 :                 double movex = ssl[1].toDouble();
     369        20130 :                 double movey = ssl[2].toDouble();
     370              : 
     371        20130 :                 renderList.emplace_back(new RenderActionMoveScaled(movex, movey));
     372              :             }
     373       171715 :         } else if (s == u":mx") {
     374         1830 :             renderList.emplace_back(new RenderActionMoveXHeight(true));
     375       149755 :         } else if (s == u":mxs") {
     376          915 :             renderList.emplace_back(new RenderActionMoveXHeightScaled(true));
     377       148840 :         } else if (s.startsWith(u"sc:")) {
     378         3660 :             StringList ssl = s.split(u':', muse::SkipEmptyParts);
     379         3660 :             if (ssl.size() == 2) {
     380         3660 :                 double scale = ssl.at(1).toDouble();
     381         3660 :                 renderList.emplace_back(new RenderActionScale(scale));
     382              :             }
     383       148840 :         } else if (s == u":push") {
     384         3279 :             renderList.emplace_back(new RenderActionPush());
     385       141901 :         } else if (s == u":pop") {
     386         1449 :             renderList.emplace_back(new RenderActionPop());
     387       140452 :         } else if (s == u":popx") {
     388            0 :             renderList.emplace_back(new RenderActionPopX());
     389       140452 :         } else if (s == u":popy") {
     390         1830 :             renderList.emplace_back(new RenderActionPopY());
     391       138622 :         } else if (s == u":n") {
     392         5314 :             renderList.emplace_back(new RenderActionNote());
     393       133308 :         } else if (s == u":a") {
     394         5314 :             renderList.emplace_back(new RenderActionAccidental());
     395       127994 :         } else if (s == u":pl") {
     396          915 :             renderList.emplace_back(new RenderActionParenLeft());
     397       127079 :         } else if (s == u":pr") {
     398          915 :             renderList.emplace_back(new RenderActionParenRight());
     399              :         } else {
     400       126164 :             renderList.emplace_back(new RenderActionSet(s));
     401              :         }
     402              :     }
     403       111012 : }
     404              : 
     405              : //---------------------------------------------------------
     406              : //   writeRenderList
     407              : //---------------------------------------------------------
     408              : 
     409            0 : static void writeRenderList(XmlWriter& xml, const std::vector<RenderActionPtr>& al, const AsciiStringView& name)
     410              : {
     411            0 :     String s;
     412              : 
     413            0 :     for (const RenderActionPtr& a : al) {
     414            0 :         if (!s.isEmpty()) {
     415            0 :             s += u" ";
     416              :         }
     417            0 :         switch (a->actionType()) {
     418            0 :         case RenderAction::RenderActionType::SET: {
     419            0 :             const RenderActionSetPtr set = std::static_pointer_cast<RenderActionSet>(a);
     420            0 :             s += set->text();
     421            0 :             break;
     422            0 :         }
     423            0 :         case RenderAction::RenderActionType::MOVE: {
     424            0 :             const RenderActionMovePtr move = std::static_pointer_cast<RenderActionMove>(a);
     425              : 
     426            0 :             if (!RealIsNull(move->x()) || !RealIsNull(move->y())) {
     427            0 :                 String scaled = move->scaled() ? u"s" : u"";
     428            0 :                 s += String(u"m%1:%2:%3").arg(scaled).arg(move->x()).arg(move->y());
     429            0 :             }
     430            0 :             break;
     431            0 :         }
     432            0 :         case RenderAction::RenderActionType::MOVEXHEIGHT: {
     433            0 :             const RenderActionMoveXHeightPtr movex = std::static_pointer_cast<RenderActionMoveXHeight>(a);
     434            0 :             String scaled = movex->scaled() ? u"s" : u"";
     435            0 :             s += String(u":mx%1").arg(scaled);
     436            0 :             break;
     437            0 :         }
     438            0 :         case RenderAction::RenderActionType::SCALE: {
     439            0 :             const RenderActionScalePtr scale = std::static_pointer_cast<RenderActionScale>(a);
     440            0 :             s+= String(u"sc:%1").arg(scale->scale());
     441            0 :             break;
     442            0 :         }
     443            0 :         case RenderAction::RenderActionType::PUSH:
     444            0 :             s += u":push";
     445            0 :             break;
     446            0 :         case RenderAction::RenderActionType::POP: {
     447            0 :             const RenderActionPopPtr pop = std::static_pointer_cast<RenderActionPop>(a);
     448            0 :             String coord = pop->popX() && pop->popY() ? u"" : (pop->popX() ? u"x" : u"y");
     449            0 :             s += String(u":pop%1").arg(coord);
     450            0 :             break;
     451            0 :         }
     452            0 :         case RenderAction::RenderActionType::NOTE:
     453            0 :             s += u":n";
     454            0 :             break;
     455            0 :         case RenderAction::RenderActionType::ACCIDENTAL:
     456            0 :             s += u":a";
     457            0 :             break;
     458            0 :         case RenderAction::RenderActionType::PAREN: {
     459            0 :             const RenderActionParenPtr paren = std::static_pointer_cast<RenderActionParen>(a);
     460            0 :             s += paren->direction() == DirectionH::LEFT ? u":pl" : u":pr";
     461            0 :             break;
     462            0 :         }
     463            0 :         case RenderAction::RenderActionType::STOPHALIGN:
     464              :             // Internal, skip
     465            0 :             break;
     466              :         }
     467              :     }
     468            0 :     xml.tag(name, s);
     469            0 : }
     470              : 
     471              : //---------------------------------------------------------
     472              : //  read
     473              : //---------------------------------------------------------
     474              : 
     475       101122 : void ChordToken::read(XmlReader& e, int mscVersion)
     476              : {
     477       101122 :     String c = e.attribute("class");
     478       101122 :     if (c == "quality") {
     479            0 :         tokenClass = ChordTokenClass::QUALITY;
     480       101122 :     } else if (c == "extension") {
     481        28980 :         tokenClass = ChordTokenClass::EXTENSION;
     482        72142 :     } else if (c == "modifier") {
     483        48961 :         tokenClass = ChordTokenClass::MODIFIER;
     484        23181 :     } else if (c == "type") {
     485        12810 :         tokenClass = ChordTokenClass::TYPE;
     486        10371 :     } else if (c == "accidental") {
     487         4575 :         tokenClass = ChordTokenClass::ACCIDENTAL;
     488              :     } else {
     489         5796 :         tokenClass = ChordTokenClass::ALL;
     490              :     }
     491       306264 :     while (e.readNextStartElement()) {
     492       205142 :         const AsciiStringView tag(e.name());
     493       205142 :         if (tag == "name") {
     494       104020 :             names << e.readText();
     495       101122 :         } else if (tag == "render") {
     496       101122 :             readRenderList(e.readText(), renderList, mscVersion);
     497              :         }
     498              :     }
     499       101122 : }
     500              : 
     501              : //---------------------------------------------------------
     502              : //  write
     503              : //---------------------------------------------------------
     504              : 
     505            0 : void ChordToken::write(XmlWriter& xml) const
     506              : {
     507            0 :     XmlWriter::Attributes attrs;
     508            0 :     switch (tokenClass) {
     509            0 :     case ChordTokenClass::QUALITY:
     510            0 :         attrs.push_back({ "class", "quality" });
     511            0 :         break;
     512            0 :     case ChordTokenClass::EXTENSION:
     513            0 :         attrs.push_back({ "class", "extension" });
     514            0 :         break;
     515            0 :     case ChordTokenClass::MODIFIER:
     516            0 :         attrs.push_back({ "class", "modifier" });
     517            0 :         break;
     518            0 :     case ChordTokenClass::TYPE:
     519            0 :         attrs.push_back({ "class", "type" });
     520            0 :         break;
     521            0 :     case ChordTokenClass::ACCIDENTAL:
     522            0 :         attrs.push_back({ "accidental", "type" });
     523            0 :         break;
     524            0 :     default:
     525            0 :         break;
     526              :     }
     527            0 :     xml.startElement("token", attrs);
     528            0 :     for (const String& s : names) {
     529            0 :         xml.tag("name", s);
     530              :     }
     531            0 :     writeRenderList(xml, renderList, "render");
     532            0 :     xml.endElement();
     533            0 : }
     534              : 
     535              : //---------------------------------------------------------
     536              : //  configure
     537              : //---------------------------------------------------------
     538              : 
     539        16837 : void ParsedChord::configure(const ChordList* cl)
     540              : {
     541        16837 :     if (!cl) {
     542            0 :         return;
     543              :     }
     544              :     // TODO: allow this to be parameterized via chord list
     545        16837 :     m_major << u"ma" << u"maj" << u"major" << u"t" << u"^";
     546        16837 :     m_minor << u"mi" << u"min" << u"minor" << u"-" << u"=";
     547        16837 :     m_diminished << u"dim" << u"dim." << u"o";
     548        16837 :     m_augmented << u"aug" << u"+";
     549        16837 :     m_lower << u"b" << u"-" << u"dim";
     550        16837 :     m_raise << u"#" << u"+" << u"aug";
     551        16837 :     m_mod << u"sus" << u"add" << u"no" << u"omit" << u"^" << u"type";
     552        16837 :     m_symbols << u"t" << u"^" << u"-" << u"+" << u"o" << u"0";
     553              : }
     554              : 
     555              : //---------------------------------------------------------
     556              : //  correctXmlText
     557              : //    remove digits from _xmlText, optionally replace with s
     558              : //    needed for m(Maj9) et al
     559              : //---------------------------------------------------------
     560              : 
     561          529 : void ParsedChord::correctXmlText(const String& s)
     562              : {
     563          529 :     String xmlText = m_xmlText;
     564          529 :     xmlText.remove(std::regex("[0-9]"));
     565          529 :     if (!s.empty()) {
     566          529 :         size_t pos = xmlText.lastIndexOf(u')');
     567          529 :         if (pos == muse::nidx) {
     568          529 :             pos = xmlText.size();
     569              :         }
     570          529 :         xmlText.insert(pos, s);
     571              :     }
     572          529 :     m_xmlText = xmlText;
     573          529 : }
     574              : 
     575              : //---------------------------------------------------------
     576              : //  parse
     577              : //    returns true if chord was parseable
     578              : //---------------------------------------------------------
     579              : 
     580        16837 : bool ParsedChord::parse(const String& s, const ChordList* cl, bool syntaxOnly, bool preferMinor)
     581              : {
     582        16837 :     String tok1, tok1L, tok2, tok2L;
     583        16837 :     String extensionDigits = u"123456789";
     584        16837 :     String special = u"()[],/\\ ";
     585        16837 :     String leading = u"([ ";
     586        16837 :     String trailing = u")],/\\ ";
     587        16837 :     String initial;
     588        16837 :     bool take6 = false, take7 = false, take9 = false, take11 = false, take13 = false;
     589        16837 :     size_t lastLeadingToken = 0;
     590        16837 :     size_t len = s.size();
     591        16837 :     size_t i = 0;
     592        16837 :     int thirdKey = 0, seventhKey = 0;
     593        16837 :     bool susChord = false;
     594        16837 :     std::vector<HDegree> hdl;
     595        16837 :     int key[] = { 0, 0, 2, 4, 5, 7, 9, 11, 0, 2, 4, 5, 7, 9, 11 };
     596              : 
     597        16837 :     configure(cl);
     598        16837 :     m_name = s;
     599        16837 :     m_parseable = true;
     600        16837 :     m_understandable = true;
     601              : 
     602        16837 :     lastLeadingToken = m_tokenList.size();
     603              :     // get quality
     604        33686 :     for (tok1 = u"", tok1L = u"", initial = u""; i < len; ++i) {
     605              :         // up to first (non-zero) digit, paren, or comma
     606        31558 :         if (extensionDigits.contains(s.at(i)) || special.contains(s.at(i))) {
     607        14709 :             break;
     608              :         }
     609        16849 :         tok1.append(s.at(i));
     610        16849 :         tok1L.append(s.at(i).toLower());
     611        29162 :         if (tok1L == u"m" || m_major.contains(tok1L) || m_minor.contains(tok1L) || m_diminished.contains(tok1L)
     612        29162 :             || m_augmented.contains(tok1L)) {
     613         9218 :             initial = tok1;
     614              :         }
     615              :     }
     616              :     // special case for "madd", which needs to parse as m,add rather than ma,dd
     617        16837 :     if (tok1L.startsWith(u"madd")) {
     618          162 :         initial = tok1.at(0);
     619              :     }
     620              :     // quality and first modifier ran together with no separation - eg, mima7, augadd
     621              :     // keep quality portion, reset index to read modifier portion later
     622              :     // Prevent greedy capture of first character as chord quality in the following cases
     623              :     static const std::array<String, 3> modifiersStartingWithQualityCharacters {
     624              :         u"tristan",
     625              :         u"omit",
     626              :         u"type"
     627        16837 :     };
     628        16837 :     if (!initial.empty() && initial != tok1 && !muse::contains(modifiersStartingWithQualityCharacters, tok1L)) {
     629          357 :         i -= (tok1.size() - initial.size());
     630          357 :         tok1 = initial;
     631          357 :         tok1L = initial.toLower();
     632              :     }
     633              :     // determine quality
     634        16837 :     if (tok1 == u"M" || m_major.contains(tok1L)) {
     635         1655 :         m_quality = u"major";
     636         1655 :         take6 = true;
     637         1655 :         take7 = true;
     638         1655 :         take9 = true;
     639         1655 :         take11 = true;
     640         1655 :         take13 = true;
     641         1655 :         if (!syntaxOnly) {
     642         1648 :             m_chord = HChord(u"C E G");
     643              :         }
     644        15182 :     } else if (tok1 == u"m" || m_minor.contains(tok1L)) {
     645         2889 :         m_quality = u"minor";
     646         2889 :         take6 = true;
     647         2889 :         take7 = true;
     648         2889 :         take9 = true;
     649         2889 :         take11 = true;
     650         2889 :         take13 = true;
     651         2889 :         if (!syntaxOnly) {
     652         2870 :             m_chord = HChord(u"C Eb G");
     653              :         }
     654        12293 :     } else if (m_diminished.contains(tok1L)) {
     655          581 :         m_quality = u"diminished";
     656          581 :         take7 = true;
     657          581 :         if (!syntaxOnly) {
     658          579 :             m_chord = HChord(u"C Eb Gb");
     659              :         }
     660        11712 :     } else if (m_augmented.contains(tok1L)) {
     661          515 :         m_quality = u"augmented";
     662          515 :         take7 = true;
     663          515 :         if (!syntaxOnly) {
     664          514 :             m_chord = HChord(u"C E G#");
     665              :         }
     666        11197 :     } else if (tok1L == "0") {
     667            6 :         m_quality = u"half-diminished";
     668            6 :         if (!syntaxOnly) {
     669            3 :             m_chord = HChord(u"C Eb Gb Bb");
     670              :         }
     671        11191 :     } else if (tok1L.empty()) {
     672              :         // empty quality - this will turn out to be major or dominant (or minor if preferMinor)
     673         9608 :         m_quality = u"";
     674         9608 :         if (!syntaxOnly) {
     675         9572 :             m_chord = preferMinor ? HChord(u"C Eb G") : HChord(u"C E G");
     676              :         }
     677         9608 :         if (preferMinor) {
     678            0 :             m_name = u"=" + m_name;
     679              :         }
     680              :     } else {
     681              :         // anything else is not a quality after all, but a modifier
     682              :         // reset to read again as modifier
     683         1583 :         m_quality = u"";
     684         1583 :         tok1 = u"";
     685         1583 :         tok1L = u"";
     686         1583 :         i = static_cast<int>(lastLeadingToken);
     687         1583 :         if (!syntaxOnly) {
     688         1572 :             m_chord = HChord(u"C E G");
     689              :         }
     690              :     }
     691        16837 :     if (tok1L == "=") {
     692            0 :         tok1 = u"";
     693            0 :         tok1L = u"";
     694              :     }
     695        16837 :     if (!tok1.empty()) {
     696         5646 :         addToken(tok1, ChordTokenClass::QUALITY);
     697              :     }
     698        16837 :     if (!syntaxOnly) {
     699        16758 :         m_xmlKind = m_quality;
     700        16758 :         m_xmlParens = u"no";
     701        16758 :         if (m_symbols.contains(tok1)) {
     702          302 :             m_xmlSymbols = u"yes";
     703          302 :             m_xmlText = u"";
     704              :         } else {
     705        16456 :             m_xmlSymbols = u"no";
     706        16456 :             m_xmlText = tok1;
     707              :         }
     708              :     }
     709              :     // eat trailing parens and commas
     710        16837 :     while (i < len && trailing.contains(s.at(i))) {
     711            0 :         addToken(String(s.at(i++)), ChordTokenClass::QUALITY);
     712              :     }
     713              : 
     714        16837 :     size_t prevIdx = i;
     715              : 
     716              :     // Get type - either "typen" or "III"
     717              :     // Eat up to first number
     718        25392 :     for (tok1 = u""; i < len; ++i) {
     719        23202 :         if (s.at(i).isDigit()) {
     720        14647 :             break;
     721              :         }
     722         8555 :         tok1.append(s.at(i));
     723              :     }
     724              : 
     725        16837 :     if (tok1 == "type") {
     726            0 :         addToken(tok1, ChordTokenClass::TYPE);
     727              :         // Read number
     728            0 :         for (tok1 = u""; i < len; ++i) {
     729            0 :             if (!s.at(i).isDigit()) {
     730            0 :                 break;
     731              :             }
     732            0 :             addToken(s.at(i), ChordTokenClass::TYPE);
     733              :         }
     734        16837 :     } else if (tok1.contains(std::wregex(L"[IVX]+"))) {
     735              :         // Get roman numerals
     736           39 :         for (i = 0; i < tok1.size(); i++) {
     737           27 :             addToken(tok1.at(i), ChordTokenClass::TYPE);
     738              :         }
     739              :     } else {
     740        16825 :         i = prevIdx;
     741              :     }
     742              : 
     743        16837 :     lastLeadingToken = m_tokenList.size();
     744              :     // get extension - up to first non-digit other than comma or slash
     745        32455 :     for (tok1 = u""; i < len; ++i) {
     746        27207 :         if (!s.at(i).isDigit() && s.at(i) != ',' && s.at(i) != '/') {
     747        11589 :             break;
     748              :         }
     749        15618 :         tok1.append(s.at(i));
     750              :     }
     751        16837 :     m_extension = tok1;
     752        16837 :     if (m_quality.empty()) {
     753        11191 :         if (m_extension == "7" || m_extension == "9" || m_extension == "11" || m_extension == "13") {
     754         7593 :             m_quality = preferMinor ? u"minor" : u"dominant";
     755         7593 :             if (!syntaxOnly) {
     756         7584 :                 m_xmlKind = preferMinor ? u"minor" : u"dominant";
     757              :             }
     758         7593 :             take7 = true;
     759         7593 :             take9 = true;
     760         7593 :             take11 = true;
     761         7593 :             take13 = true;
     762              :         } else {
     763         3598 :             m_quality = preferMinor ? u"minor" : u"major";
     764         3598 :             if (!syntaxOnly) {
     765         3560 :                 m_xmlKind = preferMinor ? u"minor" : u"major";
     766              :             }
     767         3598 :             take6 = true;
     768         3598 :             take7 = true;
     769         3598 :             take9 = true;
     770         3598 :             take11 = true;
     771         3598 :             take13 = true;
     772              :         }
     773              :     }
     774        16837 :     if (!tok1.empty()) {
     775        12611 :         addToken(tok1, ChordTokenClass::EXTENSION);
     776              :     }
     777        16837 :     if (!syntaxOnly) {
     778        16758 :         if (m_quality == "minor") {
     779         2870 :             thirdKey = 3;
     780              :         } else {
     781        13888 :             thirdKey = 4;
     782              :         }
     783        16758 :         if (m_quality == "major") {
     784         5208 :             seventhKey = 11;
     785        11550 :         } else if (m_quality == "diminished") {
     786          579 :             seventhKey = 9;
     787              :         } else {
     788        10971 :             seventhKey = 10;
     789              :         }
     790        16758 :         m_xmlText += m_extension;
     791        16758 :         StringList extl;
     792        16758 :         if (tok1 == "2") {
     793           55 :             String d = u"add" + tok1;
     794           55 :             m_xmlDegrees << d;
     795           55 :             m_xmlText.remove(tok1);
     796           55 :             m_chord += 2;
     797        16758 :         } else if (tok1 == "4") {
     798           55 :             String d = u"add" + tok1;
     799           55 :             m_xmlDegrees << d;
     800           55 :             m_xmlText.remove(tok1);
     801           55 :             m_chord += 5;
     802        16703 :         } else if (tok1 == "5") {
     803          434 :             m_xmlKind = u"power";
     804          434 :             m_chord -= thirdKey;
     805        16214 :         } else if (tok1 == "6") {
     806          569 :             if (take6) {
     807          569 :                 m_xmlKind += u"-sixth";
     808              :             } else {
     809            0 :                 extl << u"6";
     810              :             }
     811          569 :             m_chord += 9;
     812        15645 :         } else if (tok1 == "7") {
     813         6170 :             if (take7) {
     814         6170 :                 m_xmlKind += u"-seventh";
     815            0 :             } else if (m_xmlKind != "half-diminished") {
     816            0 :                 extl << u"7";
     817              :             }
     818         6170 :             m_chord += seventhKey;
     819         9475 :         } else if (tok1 == "9") {
     820         2301 :             if (take9) {
     821         2192 :                 m_xmlKind += u"-ninth";
     822          109 :             } else if (take7) {
     823          109 :                 m_xmlKind += u"-seventh";
     824          109 :                 extl << u"9";
     825          109 :                 correctXmlText(u"7");
     826            0 :             } else if (m_xmlKind == u"half-diminished") {
     827            0 :                 extl << u"9";
     828            0 :                 correctXmlText();
     829              :             } else {
     830            0 :                 extl << u"7" << u"9";
     831            0 :                 correctXmlText();
     832              :             }
     833         2301 :             m_chord += seventhKey;
     834         2301 :             m_chord += 2;
     835         7174 :         } else if (tok1 == u"11") {
     836          349 :             if (take11) {
     837          349 :                 m_xmlKind += u"-11th";
     838            0 :             } else if (take7) {
     839            0 :                 m_xmlKind += u"-seventh";
     840            0 :                 extl << u"9" << u"11";
     841            0 :                 correctXmlText(u"7");
     842            0 :             } else if (m_xmlKind == u"half-diminished") {
     843            0 :                 extl << u"9" << u"11";
     844            0 :                 correctXmlText();
     845              :             } else {
     846            0 :                 extl << u"7" << u"9" << u"11";
     847            0 :                 correctXmlText();
     848              :             }
     849          349 :             m_chord += seventhKey;
     850          349 :             m_chord += 2;
     851          349 :             m_chord += 5;
     852         6825 :         } else if (tok1 == u"13") {
     853         2401 :             if (take13) {
     854         2346 :                 m_xmlKind += u"-13th";
     855           55 :             } else if (take7) {
     856           55 :                 m_xmlKind += u"-seventh";
     857           55 :                 extl << u"9" << u"11" << u"13";
     858           55 :                 correctXmlText(u"7");
     859            0 :             } else if (m_xmlKind == u"half-diminished") {
     860            0 :                 extl << u"9" << u"11" << u"13";
     861            0 :                 correctXmlText();
     862              :             } else {
     863            0 :                 extl << u"7" << u"9" << u"11" << u"13";
     864            0 :                 correctXmlText();
     865              :             }
     866         2401 :             m_chord += seventhKey;
     867         2401 :             m_chord += 2;
     868         2401 :             m_chord += 5;
     869         2401 :             m_chord += 9;
     870         4424 :         } else if (tok1 == u"69" || tok1 == u"6,9" || tok1 == u"6/9") {
     871          239 :             if (take6) {
     872          239 :                 m_xmlKind += u"-sixth";
     873          239 :                 extl << u"9";
     874          239 :                 correctXmlText(u"6");
     875              :             } else {
     876            0 :                 extl << u"6" << u"9";
     877            0 :                 correctXmlText();
     878              :             }
     879          239 :             m_extension = u"69";
     880          239 :             m_chord += 9;
     881          239 :             m_chord += 2;
     882              :         }
     883        17271 :         for (const String& e : extl) {
     884          513 :             String d = u"add" + e;
     885          513 :             m_xmlDegrees << d;
     886          513 :         }
     887        16758 :         if (m_xmlKind == u"dominant-seventh") {
     888         3930 :             m_xmlKind = u"dominant";
     889              :         }
     890        16758 :     }
     891              :     // eat trailing parens and commas
     892        16837 :     while (i < len && trailing.contains(s.at(i))) {
     893            0 :         addToken(String(s.at(i++)), ChordTokenClass::EXTENSION);
     894              :     }
     895              : 
     896              :     // get modifiers
     897        16837 :     bool addPending = false;
     898        16837 :     bool susPending = false;
     899        16837 :     m_modifierList.clear();
     900        38195 :     while (i < len) {
     901              :         // eat leading parens
     902        23146 :         while (i < len && leading.contains(s.at(i))) {
     903         1788 :             addToken(String(s.at(i++)), ChordTokenClass::MODIFIER);
     904         1788 :             m_xmlParens = u"yes";
     905              :         }
     906              :         // get first token - up to first digit, paren, or comma
     907        56784 :         for (tok1 = u"", tok1L = u"", initial = u""; i < len; ++i) {
     908        55695 :             if (s.at(i).isDigit() || special.contains(s.at(i))) {
     909        20269 :                 break;
     910              :             }
     911        35426 :             tok1.append(s.at(i));
     912        35426 :             tok1L.append(s.at(i).toLower());
     913        35426 :             if (m_mod.contains(tok1L)) {
     914         5924 :                 initial = tok1;
     915              :             }
     916              :         }
     917              :         // if we reached the end of the string and never got a token,
     918              :         // then nothing to do, and no sense in looking for a second token
     919        21358 :         if (i == len && tok1.empty()) {
     920            0 :             break;
     921              :         }
     922        21358 :         if (!initial.empty() && initial != tok1) {
     923              :             // two modifiers ran together with no separation - eg, susb9
     924              :             // keep first, reset index to read second later
     925         2962 :             i -= (tok1.size() - initial.size());
     926         2962 :             tok1 = initial;
     927         2962 :             tok1L = initial.toLower();
     928              :         }
     929              :         // for "add" and "sus", just add the token and then read argument as a separate modifier
     930              :         // this allows the argument to itself be a two-part string
     931              :         // thus allowing addb9 -> add;b,9
     932        21358 :         if (tok1L == "add") {
     933         1681 :             addToken(tok1, ChordTokenClass::MODIFIER);
     934         1681 :             addPending = true;
     935         5059 :             continue;
     936              :         }
     937              :         // When there are no numbers after, default to sus4 further down
     938        19677 :         if (tok1L == "sus" && i != len) {
     939         3378 :             addToken(tok1, ChordTokenClass::MODIFIER);
     940         3378 :             susPending = true;
     941         3378 :             continue;
     942              :         }
     943              :         // eat spaces
     944        16355 :         while (i < len && s.at(i) == u' ') {
     945           56 :             ++i;
     946              :         }
     947              :         // get second token - a number <= 13
     948        36202 :         for (tok2 = u""; i < len; ++i) {
     949        26386 :             if (!s.at(i).isDigit()) {
     950         6462 :                 break;
     951              :             }
     952        19924 :             if (tok2.size() == 1 && (tok2.at(0) != u'1' || s.at(i) > u'3')) {
     953           21 :                 break;
     954              :             }
     955        19903 :             tok2.append(s.at(i));
     956              :         }
     957              :         // check suffix - 'st', 'nd', 'rd', 'th'
     958        16299 :         String suffix;
     959              :         static const int SUFFIX_LEN = 2;
     960        27502 :         for (int j = 0; j < SUFFIX_LEN && i + j < len; j++) {
     961        11203 :             suffix.append(s.at(i + j));
     962              :         }
     963              : 
     964        16299 :         if (suffix.contains(std::wregex(L"st|nd|rd|th"))) {
     965           17 :             i += SUFFIX_LEN;
     966              :         } else {
     967        16282 :             suffix.clear();
     968              :         }
     969              : 
     970        16299 :         tok2L = tok2.toLower();
     971              :         // re-attach "add"
     972        16299 :         if (addPending) {
     973         1681 :             if (m_raise.contains(tok1L)) {
     974          239 :                 tok1L = u"#";
     975         1442 :             } else if (m_lower.contains(tok1L)) {
     976           79 :                 tok1L = u"b";
     977         1363 :             } else if (tok1 == "M" || m_major.contains(tok1L)) {
     978            1 :                 tok1L = u"major";
     979              :             }
     980         1681 :             tok2L = tok1L + tok2L;
     981         1681 :             tok1L = u"add";
     982              :         }
     983              :         // re-attach "sus"
     984        16299 :         if (susPending) {
     985         3378 :             if (m_raise.contains(tok1L)) {
     986         1540 :                 tok1L = u"#";
     987         1838 :             } else if (m_lower.contains(tok1L)) {
     988         1100 :                 tok1L = u"b";
     989          738 :             } else if (tok1 == "M" || m_major.contains(tok1L)) {
     990            0 :                 tok1L = u"major";
     991              :             }
     992         3378 :             tok2L = tok1L + tok2L;
     993         3378 :             tok1L = u"sus";
     994              :         }
     995              :         // standardize spelling
     996        16299 :         if (tok1 == "M" || m_major.contains(tok1L)) {
     997          132 :             tok1L = u"major";
     998        16167 :         } else if (tok1L == "omit") {
     999           55 :             tok1L = u"no";
    1000        16112 :         } else if (tok1L == "sus" && tok2L.empty()) {
    1001          517 :             tok2L = u"4";
    1002        15595 :         } else if (m_augmented.contains(tok1L) && tok2L.empty()) {
    1003          222 :             if (m_quality == "dominant" && m_extension == "7") {
    1004           57 :                 m_quality = u"augmented";
    1005           57 :                 if (!syntaxOnly) {
    1006           56 :                     m_xmlKind = u"augmented-seventh";
    1007           56 :                     m_xmlText = m_extension + tok1;
    1008           56 :                     m_chord -= 7;
    1009           56 :                     m_chord += 8;
    1010              :                 }
    1011           57 :                 tok1L = u"";
    1012              :             } else {
    1013          165 :                 tok1L = u"#";
    1014          165 :                 tok2L = u"5";
    1015              :             }
    1016        15373 :         } else if (m_diminished.contains(tok1)) {
    1017            0 :             m_quality = u"diminished";
    1018            0 :             if (!syntaxOnly) {
    1019            0 :                 m_xmlKind = u"diminished";
    1020            0 :                 m_xmlText = m_extension + tok1;
    1021            0 :                 m_chord -= 4;
    1022            0 :                 m_chord += 3;
    1023            0 :                 m_chord -= 7;
    1024            0 :                 m_chord += 6;
    1025              :             }
    1026            0 :             tok1L = u"";
    1027        15373 :         } else if ((m_lower.contains(tok1L) || m_raise.contains(tok1L)) && tok2L.empty()) {
    1028              :             // trailing alteration - treat as applying to extension (and convert to modifier)
    1029              :             // this handles C5b, C9#, etc
    1030           54 :             tok2L = m_extension;
    1031           54 :             if (!syntaxOnly) {
    1032           54 :                 m_xmlKind = (m_quality == u"dominant") ? u"major" : m_quality;
    1033           54 :                 m_xmlText.remove(m_extension);
    1034           54 :                 if (m_extension == "5") {
    1035           54 :                     m_chord += thirdKey;
    1036              :                 } else {
    1037            0 :                     m_chord -= seventhKey;
    1038              :                 }
    1039              :             }
    1040           54 :             if (m_quality == "dominant") {
    1041            0 :                 m_quality = u"major";
    1042              :             }
    1043           54 :             m_extension = u"";
    1044           54 :             if (m_lower.contains(tok1L)) {
    1045           54 :                 tok1L = u"b";
    1046              :             } else {
    1047            0 :                 tok1L = u"#";
    1048              :             }
    1049        15319 :         } else if (m_lower.contains(tok1L)) {
    1050         4365 :             tok1L = u"b";
    1051        10954 :         } else if (m_raise.contains(tok1L)) {
    1052         5129 :             tok1L = u"#";
    1053              :         }
    1054        16299 :         String m = tok1L + tok2L + suffix;
    1055        16299 :         if (!m.empty()) {
    1056        16236 :             m_modifierList << m;
    1057              :         }
    1058        16299 :         if (!tok1.empty()) {
    1059        14151 :             addToken(tok1, ChordTokenClass::MODIFIER);
    1060              :         }
    1061        16299 :         if (!tok2.empty()) {
    1062        15081 :             addToken(tok2, ChordTokenClass::MODIFIER);
    1063              :         }
    1064        16299 :         if (!suffix.empty()) {
    1065           17 :             addToken(suffix, ChordTokenClass::MODIFIER);
    1066              :         }
    1067        16299 :         if (!syntaxOnly) {
    1068              :             int d;
    1069        16281 :             if (tok2L.empty()) {
    1070          475 :                 d = 0;
    1071              :             } else {
    1072        15806 :                 d = tok2L.toInt();
    1073              :             }
    1074        16281 :             if (d > 13) {
    1075            0 :                 d = 13;
    1076              :             }
    1077        16281 :             String degree;
    1078        16281 :             bool alter = false;
    1079        16281 :             if (tok1L == "add") {
    1080         1666 :                 if (d) {
    1081         1345 :                     hdl.push_back(HDegree(d, 0, HDegreeType::ADD));
    1082          321 :                 } else if (!tok2L.empty()) {
    1083              :                     // this was result of addPending
    1084              :                     // alteration; tok1 = alter, tok2 = value
    1085          321 :                     d = tok2.toInt();
    1086          321 :                     if (m_raise.contains(tok1) || tok1 == "M" || m_major.contains(tok1.toLower())) {
    1087          240 :                         if (d == 7) {
    1088           55 :                             m_chord += 11;
    1089           55 :                             tok2L = u"#7";
    1090          185 :                         } else if (m_raise.contains(tok1)) {
    1091          185 :                             hdl.push_back(HDegree(d, 1, HDegreeType::ADD));
    1092              :                         }
    1093           81 :                     } else if (m_lower.contains(tok1)) {
    1094           79 :                         if (d == 7) {
    1095            0 :                             m_chord += 10;
    1096              :                         } else {
    1097           79 :                             hdl.push_back(HDegree(d, -1, HDegreeType::ADD));
    1098              :                         }
    1099            2 :                     } else if (d) {
    1100            0 :                         hdl.push_back(HDegree(d, 0, HDegreeType::ADD));
    1101              :                     }
    1102              :                 }
    1103         1666 :                 degree = u"add" + tok2L;
    1104        14615 :             } else if (tok1L == u"no") {
    1105          349 :                 degree = u"sub" + tok2L;
    1106          349 :                 if (d) {
    1107          348 :                     hdl.push_back(HDegree(d, 0, HDegreeType::SUBTRACT));
    1108              :                 }
    1109        14266 :             } else if (tok1L == "sus") {
    1110              :                 // convert chords with sus into suspended "kind"
    1111              :                 // extension then becomes a series of degree adds
    1112         3886 :                 if (tok2L == "4") {
    1113          897 :                     m_xmlKind = u"suspended-fourth";
    1114         2989 :                 } else if (tok2L == "2") {
    1115          335 :                     m_xmlKind = u"suspended-second";
    1116              :                 }
    1117         3886 :                 m_xmlText = tok1L + tok2;
    1118         3886 :                 if (m_extension == "7" || m_extension == "9" || m_extension == "11" || m_extension == "13") {
    1119         3077 :                     m_xmlDegrees << ((m_quality == u"major") ? u"add#7" : u"add7");
    1120              :                     // hack for programs that cannot assemble names well
    1121              :                     // even though the kind is suspended, set text to also include the extension
    1122              :                     // in export, we will set the degree text to null
    1123         3077 :                     m_xmlText = m_extension + m_xmlText;
    1124         3077 :                     degree = u"";
    1125          809 :                 } else if (!m_extension.empty()) {
    1126           14 :                     degree = u"add" + m_extension;
    1127              :                 }
    1128         3886 :                 if (m_extension == u"13") {
    1129          957 :                     m_xmlDegrees << u"add9";
    1130          957 :                     m_xmlDegrees << u"add11";
    1131          957 :                     m_xmlDegrees << u"add13";
    1132         2929 :                 } else if (m_extension == u"11") {
    1133            2 :                     m_xmlDegrees << u"add9";
    1134            2 :                     m_xmlDegrees << u"add11";
    1135         2927 :                 } else if (m_extension == u"9") {
    1136          588 :                     m_xmlDegrees << u"add9";
    1137              :                 }
    1138         3886 :                 susChord = true;
    1139         3886 :                 m_chord -= thirdKey;
    1140         3886 :                 if (d) {
    1141         1234 :                     m_chord += key[d];
    1142              :                 }
    1143        10380 :             } else if (tok1L == u"major") {
    1144          131 :                 if (m_xmlKind.startsWith(u"minor")) {
    1145          126 :                     m_xmlKind = u"major-minor";
    1146          126 :                     if (m_extension == u"9" || tok2L == u"9") {
    1147           65 :                         m_xmlDegrees << u"add9";
    1148              :                     }
    1149          126 :                     if (m_extension == "11" || tok2L == u"11") {
    1150            0 :                         m_xmlDegrees << u"add9";
    1151            0 :                         m_xmlDegrees << u"add11";
    1152              :                     }
    1153          126 :                     if (m_extension == u"13" || tok2L == u"13") {
    1154            2 :                         m_xmlDegrees << u"add9";
    1155            2 :                         m_xmlDegrees << u"add11";
    1156            2 :                         m_xmlDegrees << u"add13";
    1157              :                     }
    1158          126 :                     m_xmlText += tok1 + tok2;
    1159          126 :                     correctXmlText(u"7");
    1160              :                 } else {
    1161            5 :                     tok1L = u"add";
    1162              :                 }
    1163          131 :                 m_chord -= 10;
    1164          131 :                 m_chord += 11;
    1165          131 :                 if (d && d != 7) {
    1166            9 :                     hdl.push_back(HDegree(d, 0, HDegreeType::ADD));
    1167              :                 }
    1168        10249 :             } else if (tok1L == u"alt") {
    1169           55 :                 m_xmlDegrees << u"altb5";
    1170           55 :                 m_xmlDegrees << u"add#5";
    1171           55 :                 m_xmlDegrees << u"addb9";
    1172           55 :                 m_xmlDegrees << u"add#9";
    1173           55 :                 m_chord -= 7;
    1174           55 :                 m_chord += 6;
    1175           55 :                 m_chord += 8;
    1176           55 :                 m_chord += 1;
    1177           55 :                 m_chord += 3;
    1178        10194 :             } else if (tok1L == u"blues") {
    1179              :                 // this isn't really well-defined, but it might as well mean something
    1180          110 :                 if (m_extension == u"11" || m_extension == u"13") {
    1181            0 :                     m_xmlDegrees << u"alt#9";
    1182              :                 } else {
    1183          110 :                     m_xmlDegrees << u"add#9";
    1184              :                 }
    1185          110 :                 m_chord += 3;
    1186        10084 :             } else if (tok1L == u"lyd") {
    1187          110 :                 if (m_extension == u"13") {
    1188            0 :                     m_xmlDegrees << u"alt#11";
    1189              :                 } else {
    1190          110 :                     m_xmlDegrees << u"add#11";
    1191              :                 }
    1192          110 :                 m_chord += 6;
    1193         9974 :             } else if (tok1L == "phryg") {
    1194           55 :                 if (!m_xmlKind.startsWith(u"minor")) {
    1195           55 :                     m_xmlKind = u"minor-seventh";
    1196              :                 }
    1197           55 :                 if (m_extension == "11" || m_extension == "13") {
    1198            0 :                     m_xmlDegrees << u"altb9";
    1199              :                 } else {
    1200           55 :                     m_xmlDegrees << u"addb9";
    1201              :                 }
    1202           55 :                 m_xmlText += tok1;
    1203           55 :                 m_chord = HChord(u"C Db Eb G Bb");
    1204         9919 :             } else if (tok1L == "tristan") {
    1205           56 :                 m_xmlKind = u"Tristan";
    1206           56 :                 m_xmlText = tok1;
    1207           56 :                 m_chord = HChord(u"C F# A# D#");
    1208         9863 :             } else if (addPending) {
    1209            0 :                 degree = u"add" + tok1L + tok2L;
    1210            0 :                 if (m_raise.contains(tok1L)) {
    1211            0 :                     hdl.push_back(HDegree(d, 1, HDegreeType::ADD));
    1212            0 :                 } else if (m_lower.contains(tok1L)) {
    1213            0 :                     hdl.push_back(HDegree(d, -1, HDegreeType::ADD));
    1214              :                 } else {
    1215            0 :                     hdl.push_back(HDegree(d, 0, HDegreeType::ADD));
    1216              :                 }
    1217         9863 :             } else if (susPending) {
    1218            0 :                 degree = u"sus" + tok1L + tok2L;
    1219            0 :                 if (m_raise.contains(tok1L)) {
    1220            0 :                     hdl.push_back(HDegree(d, 1, HDegreeType::ADD));
    1221            0 :                 } else if (m_lower.contains(tok1L)) {
    1222            0 :                     hdl.push_back(HDegree(d, -1, HDegreeType::ADD));
    1223              :                 } else {
    1224            0 :                     hdl.push_back(HDegree(d, 0, HDegreeType::ADD));
    1225              :                 }
    1226         9863 :             } else if (tok1L.empty() && !tok2L.empty()) {
    1227           56 :                 degree = u"add" + tok2L;
    1228           56 :                 hdl.push_back(HDegree(d, 0, HDegreeType::ADD));
    1229         9807 :             } else if (m_lower.contains(tok1L)) {
    1230         4417 :                 tok1L = u"b";
    1231         4417 :                 alter = true;
    1232         5390 :             } else if (m_raise.contains(tok1L)) {
    1233         5293 :                 tok1L = u"#";
    1234         5293 :                 alter = true;
    1235           97 :             } else if (tok1L.empty()) {
    1236              :                 // token was already handled fully
    1237              :             } else {
    1238           35 :                 m_understandable = false;
    1239           35 :                 if (s.startsWith(tok1)) {
    1240              :                     // unrecognized token right from very beginning
    1241           18 :                     m_xmlKind = u"other";
    1242           18 :                     m_xmlText = tok1;
    1243              :                 }
    1244              :             }
    1245        16281 :             if (alter) {
    1246         9710 :                 if (tok2L == "4" && m_xmlKind == "suspended-fourth") {
    1247            0 :                     degree = u"alt";
    1248         9710 :                 } else if (tok2L == "5") {
    1249         2818 :                     degree = u"alt";
    1250         6892 :                 } else if (tok2L == "9" && (m_extension == "11" || m_extension == "13")) {
    1251          932 :                     degree = u"alt";
    1252         5960 :                 } else if (tok2L == "11" && m_extension == "13") {
    1253          869 :                     degree = u"alt";
    1254              :                 } else {
    1255         5091 :                     degree = u"add";
    1256              :                 }
    1257         9710 :                 degree += tok1L + tok2L;
    1258         9710 :                 if (m_chord.contains(key[d]) && !(susChord && (d == 11))) {
    1259         4290 :                     hdl.push_back(HDegree(d, 0, HDegreeType::SUBTRACT));
    1260              :                 }
    1261         9710 :                 if (tok1L == "#") {
    1262         5293 :                     if (d == 7) {
    1263           86 :                         m_chord += 11;
    1264              :                     } else {
    1265         5207 :                         hdl.push_back(HDegree(d, 1, HDegreeType::ADD));
    1266              :                     }
    1267         4417 :                 } else if (tok1L == "b") {
    1268         4417 :                     if (d == 7) {
    1269            0 :                         m_chord += 10;
    1270              :                     } else {
    1271         4417 :                         hdl.push_back(HDegree(d, -1, HDegreeType::ADD));
    1272              :                     }
    1273              :                 }
    1274              :             }
    1275        16281 :             if (!degree.empty()) {
    1276        11795 :                 m_xmlDegrees << degree;
    1277              :             }
    1278        16281 :         }
    1279              :         // eat trailing parens and commas
    1280        18118 :         while (i < len && trailing.contains(s.at(i))) {
    1281         1819 :             addToken(String(s.at(i++)), ChordTokenClass::MODIFIER);
    1282              :         }
    1283        16299 :         addPending = false;
    1284        16299 :         susPending = false;
    1285        16299 :     }
    1286        16837 :     if (!syntaxOnly) {
    1287        16758 :         m_chord.add(hdl);
    1288              :         // fix "add" / "alt" conflicts
    1289              :         // so add9,altb9 -> addb9
    1290        16758 :         StringList altList = m_xmlDegrees.filter(u"alt");
    1291        21432 :         for (const String& d : altList) {
    1292         4674 :             String unalt(d);
    1293         4674 :             unalt.replace(std::regex("alt[b#]"), u"add");
    1294         4674 :             if (m_xmlDegrees.removeAll(unalt)) {
    1295          660 :                 String alt(d);
    1296          660 :                 alt.replace(u"alt", u"add");
    1297          660 :                 size_t i1 = m_xmlDegrees.indexOf(d);
    1298          660 :                 m_xmlDegrees.replace(i1, alt);
    1299          660 :             }
    1300         4674 :         }
    1301        16758 :     }
    1302              : 
    1303              :     // construct handle
    1304        16837 :     if (!m_modifierList.empty()) {
    1305        11526 :         StringList sortedModifiers = m_modifierList;
    1306        11526 :         std::sort(sortedModifiers.begin(), sortedModifiers.end());
    1307        11526 :         m_modifiers = u"<" + sortedModifiers.join(u"><") + u">";
    1308        11526 :     }
    1309        16837 :     m_handle = u"<" + m_quality + u"><" + m_extension + u">" + m_modifiers;
    1310              : 
    1311              :     // force <minor><7><b5> to export as half-diminished
    1312        16837 :     if (!syntaxOnly && m_handle == u"<minor><7><b5>") {
    1313          254 :         m_xmlKind = u"half-diminished";
    1314          254 :         m_xmlText = s;
    1315          254 :         m_xmlDegrees.clear();
    1316              :     }
    1317        16837 :     if (MScore::debugMode) {
    1318          135 :         LOGD("parse: source = <%s>, handle = %s", muPrintable(s), muPrintable(m_handle));
    1319          135 :         if (!syntaxOnly) {
    1320           60 :             LOGD("parse: HChord = <%s> (%d)", muPrintable(m_chord.voicing()), m_chord.getKeys());
    1321           60 :             LOGD("parse: xmlKind = <%s>, text = <%s>", muPrintable(m_xmlKind), muPrintable(m_xmlText));
    1322           60 :             LOGD("parse: xmlSymbols = %s, xmlParens = %s", muPrintable(m_xmlSymbols), muPrintable(m_xmlParens));
    1323           60 :             LOGD("parse: xmlDegrees = <%s>", muPrintable(m_xmlDegrees.join(u",")));
    1324              :         }
    1325              :     }
    1326        16837 :     return m_parseable;
    1327        16837 : }
    1328              : 
    1329              : //---------------------------------------------------------
    1330              : //   fromXml
    1331              : //---------------------------------------------------------
    1332              : 
    1333           79 : String ParsedChord::fromXml(const String& rawKind, const String& rawKindText, const String& useSymbols, const String& useParens,
    1334              :                             const std::vector<HDegree>& dl, const ChordList* cl)
    1335              : {
    1336           79 :     String kind = rawKind;
    1337           79 :     String kindText = rawKindText;
    1338           79 :     bool syms = (useSymbols == "yes");
    1339           79 :     bool parens = (useParens == "yes");
    1340           79 :     bool implied = false;
    1341           79 :     bool extend = false;
    1342           79 :     int extension = 0;
    1343           79 :     m_parseable = true;
    1344           79 :     m_understandable = true;
    1345              : 
    1346              :     // get quality info from kind
    1347           79 :     if (kind == "major-minor") {
    1348            1 :         m_quality = u"minor";
    1349            1 :         m_modifierList << u"major7";
    1350            1 :         extend = true;
    1351           78 :     } else if (kind.contains(u"major")) {
    1352           36 :         m_quality = u"major";
    1353           36 :         if (kind == "major" || kind == "major-sixth") {
    1354           29 :             implied = true;
    1355              :         }
    1356           42 :     } else if (kind.contains(u"minor")) {
    1357           17 :         m_quality = u"minor";
    1358           25 :     } else if (kind.contains(u"dominant")) {
    1359            6 :         m_quality = u"dominant";
    1360            6 :         implied = true;
    1361            6 :         extension = 7;
    1362           19 :     } else if (kind == "augmented-seventh") {
    1363            1 :         m_quality = u"augmented";
    1364            1 :         extension = 7;
    1365            1 :         extend = true;
    1366           18 :     } else if (kind == "augmented") {
    1367            1 :         m_quality = u"augmented";
    1368           17 :     } else if (kind == "half-diminished") {
    1369            4 :         m_quality = u"half-diminished";
    1370            4 :         if (syms) {
    1371            3 :             extension = 7;
    1372            3 :             extend = true;
    1373              :         }
    1374           13 :     } else if (kind == "diminished-seventh") {
    1375            1 :         m_quality = u"diminished";
    1376            1 :         extension = 7;
    1377           12 :     } else if (kind == "diminished") {
    1378            1 :         m_quality = u"diminished";
    1379           11 :     } else if (kind == "suspended-fourth") {
    1380            4 :         m_quality = u"major";
    1381            4 :         implied = true;
    1382            4 :         m_modifierList << u"sus4";
    1383            7 :     } else if (kind == "suspended-second") {
    1384            3 :         m_quality = u"major";
    1385            3 :         implied = true;
    1386            3 :         m_modifierList << u"sus2";
    1387            4 :     } else if (kind == "power") {
    1388            2 :         m_quality = u"major";
    1389            2 :         implied = true;
    1390            2 :         extension = 5;
    1391            2 :     } else if (kind == "pedal") {
    1392              :         // Ignore, assume major
    1393            0 :         m_quality = u"major";
    1394            0 :         implied = true;
    1395              :     } else {
    1396            2 :         m_quality = kind;
    1397              :     }
    1398              : 
    1399              :     // get extension info from kind
    1400           79 :     if (kind.contains(u"seventh")) {
    1401           10 :         extension = 7;
    1402           69 :     } else if (kind.contains(u"ninth")) {
    1403            3 :         extension = 9;
    1404           66 :     } else if (kind.contains(u"11th")) {
    1405            3 :         extension = 11;
    1406           63 :     } else if (kind.contains(u"13th")) {
    1407            3 :         extension = 13;
    1408           60 :     } else if (kind.contains(u"sixth")) {
    1409            6 :         extension = 6;
    1410              :     }
    1411              : 
    1412              :     // get modifier info from degree list
    1413           89 :     for (const HDegree& d : dl) {
    1414           10 :         String mod;
    1415           10 :         int v = d.value();
    1416           10 :         switch (d.type()) {
    1417            9 :         case HDegreeType::ADD:
    1418              :         case HDegreeType::ALTER:
    1419            9 :             switch (d.alter()) {
    1420            1 :             case -1:    mod = u"b";
    1421            1 :                 break;
    1422            1 :             case 1:     mod = u"#";
    1423            1 :                 break;
    1424            7 :             case 0:     mod = u"add";
    1425            7 :                 break;
    1426              :             }
    1427            9 :             break;
    1428            1 :         case HDegreeType::SUBTRACT:
    1429            1 :             mod = u"no";
    1430            1 :             break;
    1431            0 :         case HDegreeType::UNDEF:
    1432              :         default:
    1433            0 :             break;
    1434              :         }
    1435           10 :         mod += String::number(v);
    1436           10 :         if (mod == "add7" && kind.contains(u"suspended")) {
    1437            2 :             m_quality = u"dominant";
    1438            2 :             implied = true;
    1439            2 :             extension = 7;
    1440            2 :             extend = true;
    1441            2 :             mod = u"";
    1442            8 :         } else if (mod == "add#7" && kind.contains(u"suspended")) {
    1443            0 :             m_quality = u"major";
    1444            0 :             implied = false;
    1445            0 :             extension = 7;
    1446            0 :             extend = true;
    1447            0 :             mod = u"";
    1448            8 :         } else if (mod == "add9" && extend) {
    1449            2 :             if (extension < 9) {
    1450            2 :                 extension = 9;
    1451              :             }
    1452            2 :             mod = u"";
    1453            6 :         } else if (mod == "add11" && extend) {
    1454            0 :             if (extension < 11) {
    1455            0 :                 extension = 11;
    1456              :             }
    1457            0 :             mod = u"";
    1458            6 :         } else if (mod == "add13" && extend) {
    1459            0 :             extension = 13;
    1460            0 :             mod = u"";
    1461            6 :         } else if (mod == "add9" && kind.contains(u"sixth")) {
    1462            0 :             extension = 69;
    1463            0 :             mod = u"";
    1464              :         }
    1465           10 :         if (!mod.empty()) {
    1466            6 :             m_modifierList << mod;
    1467              :         }
    1468           10 :     }
    1469              :     // convert no3,add[42] into sus[42]
    1470           79 :     size_t no3 = m_modifierList.indexOf(u"no3");
    1471           79 :     if (no3 != muse::nidx) {
    1472            0 :         size_t addn = m_modifierList.indexOf(u"add4");
    1473            0 :         if (addn == muse::nidx) {
    1474            0 :             addn = m_modifierList.indexOf(u"add2");
    1475              :         }
    1476            0 :         if (addn != muse::nidx) {
    1477            0 :             String& s = m_modifierList[addn];
    1478            0 :             s.replace(u"add", u"sus");
    1479            0 :             m_modifierList.removeAt(no3);
    1480              :         }
    1481              :     }
    1482              :     // convert kind=minor-seventh, degree=altb5 to kind=half-diminished (suppression of degree=altb comes later)
    1483           79 :     if (kind == "minor-seventh" && m_modifierList.size() == 1 && m_modifierList.front() == "b5") {
    1484            0 :         kind = u"half-diminished";
    1485              :     }
    1486              :     // force parens where necessary)
    1487           79 :     if (!parens && extension == 0 && !m_modifierList.empty()) {
    1488           10 :         String firstMod = m_modifierList.front();
    1489           10 :         if (!firstMod.empty() && (firstMod.startsWith(u'#') || firstMod.startsWith(u'b'))) {
    1490            0 :             parens = true;
    1491              :         }
    1492           10 :     }
    1493              : 
    1494              :     // record extension
    1495           79 :     if (extension) {
    1496           35 :         m_extension = String::number(extension);
    1497              :     }
    1498              : 
    1499              :     // validate kindText
    1500           79 :     if (!kindText.empty() && kind != "none" && kind != "other") {
    1501           42 :         ParsedChord validate;
    1502           42 :         validate.parse(kindText, cl, false);
    1503              :         // kindText should parse to produce same kind, no degrees
    1504           42 :         if (validate.m_xmlKind != kind || !validate.m_xmlDegrees.empty()) {
    1505            0 :             kindText = u"";
    1506              :         }
    1507           42 :     }
    1508              : 
    1509              :     // construct name & handle
    1510           79 :     m_name = u"";
    1511           79 :     if (!kindText.empty()) {
    1512           43 :         if (!m_extension.empty() && kind.contains(u"suspended")) {
    1513            0 :             m_name += m_extension;
    1514              :         }
    1515           43 :         m_name += kindText;
    1516           43 :         if (extension == 69) {
    1517            0 :             m_name += u"9";
    1518              :         }
    1519           36 :     } else if (implied) {
    1520           30 :         m_name = m_extension;
    1521              :     } else {
    1522            6 :         if (m_quality == "major") {
    1523            0 :             m_name = syms ? u"^" : u"maj";
    1524            6 :         } else if (m_quality == "minor") {
    1525            2 :             m_name = syms ? u"-" : u"m";
    1526            4 :         } else if (m_quality == "augmented") {
    1527            1 :             m_name = syms ? u"+" : u"aug";
    1528            3 :         } else if (m_quality == "diminished") {
    1529            0 :             m_name = syms ? u"o" : u"dim";
    1530            3 :         } else if (m_quality == "half-diminished") {
    1531            3 :             m_name = syms ? u"0" : u"m7b5";
    1532              :         } else {
    1533            0 :             m_name = m_quality;
    1534              :         }
    1535            6 :         m_name += m_extension;
    1536              :     }
    1537           79 :     if (parens) {
    1538            0 :         m_name += u"(";
    1539              :     }
    1540           93 :     for (String mod : m_modifierList) {
    1541           14 :         mod.replace(u"major", u"maj");
    1542           14 :         if (!kindText.empty() && kind.contains(u"suspended") && mod.startsWith(u"sus")) {
    1543            2 :             continue;
    1544           12 :         } else if (!kindText.empty() && kind == "major-minor" && mod.startsWith(u"maj")) {
    1545            1 :             continue;
    1546              :         }
    1547           11 :         m_name += mod;
    1548           14 :     }
    1549           79 :     if (parens) {
    1550            0 :         m_name += u")";
    1551              :     }
    1552              : 
    1553              :     // parse name to construct handle & tokenList
    1554           79 :     parse(m_name, cl, true);
    1555              : 
    1556              :     // record original MusicXML
    1557           79 :     m_xmlKind = kind;
    1558           79 :     m_xmlText = kindText;
    1559           79 :     m_xmlSymbols = useSymbols;
    1560           79 :     m_xmlParens = useParens;
    1561           89 :     for (const HDegree& d : dl) {
    1562           10 :         if (kind == "half-diminished" && d.type() == HDegreeType::ALTER && d.alter() == -1 && d.value() == 5) {
    1563            0 :             continue;
    1564              :         }
    1565           10 :         m_xmlDegrees << d.text();
    1566              :     }
    1567              : 
    1568          158 :     return m_name;
    1569           79 : }
    1570              : 
    1571              : //---------------------------------------------------------
    1572              : //   position
    1573              : //---------------------------------------------------------
    1574              : 
    1575          436 : double ChordList::position(const StringList& names, bool stackModifiers, bool superScript, ChordTokenClass ctc, size_t modifierIdx,
    1576              :                            size_t nmodifiers) const
    1577              : {
    1578          436 :     String name = names.empty() ? u"" : names.front();
    1579          436 :     switch (ctc) {
    1580          170 :     case ChordTokenClass::EXTENSION:
    1581          170 :         return m_eadjust;
    1582          168 :     case ChordTokenClass::MODIFIER: {
    1583          168 :         double yAdj = 0.0;
    1584          168 :         if (superScript) {
    1585              :             // Fake superscript by aligning with capheight
    1586            0 :             yAdj += 0.15;
    1587          168 :         } else if (stackModifiers && nmodifiers > 1) {
    1588              :             static constexpr double LINE_SPACING = 0.4;             // Space between modifiers in units of modiferHeight
    1589           72 :             const double modifierHeight = m_mmag * m_stackedmmag;   // Modifier height in units of root capheight
    1590           72 :             const double stackHeight = (nmodifiers * modifierHeight) + ((nmodifiers - 1) * modifierHeight * LINE_SPACING); // Height of total modifier stack (bottom baseline to top capheight)
    1591           72 :             const double base = stackHeight / 2;                            // Baseline of bottom modifier in the stack
    1592           72 :             yAdj += base - modifierIdx * modifierHeight * (1 + LINE_SPACING);
    1593              :         }
    1594              : 
    1595          168 :         if (!name.isEmpty()) {
    1596          168 :             yAdj += m_madjust;
    1597              :         }
    1598          168 :         return yAdj;
    1599              :     }
    1600           98 :     default:
    1601           98 :         return 0.0;
    1602              :     }
    1603          436 : }
    1604              : 
    1605              : //---------------------------------------------------------
    1606              : //   renderList
    1607              : //---------------------------------------------------------
    1608              : 
    1609        11134 : const std::vector<RenderActionPtr >& ParsedChord::renderList(const ChordList* cl, bool stacked)
    1610              : {
    1611              :     // generate anew on each call,
    1612              :     // in case chord list has changed since last time
    1613        11134 :     if (!m_renderList.empty()) {
    1614         5451 :         m_renderList.clear();
    1615              :     }
    1616              : 
    1617        11134 :     if (m_tokenList.empty()) {
    1618          232 :         return m_renderList;
    1619              :     }
    1620              : 
    1621        10902 :     size_t modIdx = 0;
    1622        10902 :     const double modListSize = m_modifierList.size();
    1623        10902 :     const size_t finalModIdx = modListSize - 1;
    1624        10902 :     const bool stackModifiersEnabled = m_modifierList.size() > 1 && stacked;
    1625        10902 :     const bool superScriptEnabled = !stackModifiersEnabled && cl->chordPreset() == ChordStylePreset::JAZZ;
    1626              : 
    1627              :     // Special stack layout if there is a single 'add' or 'sus' with multiple degrees
    1628              :     // Standard stack layout if there is only one
    1629        10902 :     static const std::wregex SUS_ADD_REGEX = std::wregex(L"sus|add");
    1630        10902 :     bool stackSusOrAdd = false;
    1631        24358 :     for (const String& mod : m_modifierList) {
    1632        13462 :         if (!(stackModifiersEnabled || superScriptEnabled) || !mod.contains(SUS_ADD_REGEX)) {
    1633        12574 :             continue;
    1634              :         }
    1635          888 :         if (!stackSusOrAdd) {
    1636          882 :             stackSusOrAdd = true;
    1637              :         } else {
    1638            6 :             stackSusOrAdd = false;
    1639            6 :             break;
    1640              :         }
    1641              :     }
    1642              : 
    1643        10902 :     bool adjust = cl ? cl->autoAdjust() : false;
    1644        10902 :     bool firstModifierToken = true;
    1645        10902 :     bool closingParenPending = false;
    1646        52850 :     for (auto tokIt = m_tokenList.begin(); tokIt != m_tokenList.end(); tokIt++) {
    1647        41948 :         const ChordToken& tok = *tokIt;
    1648        41948 :         const String n = tok.names.front();
    1649        83878 :         if ((n == u"/" || n == u"," || n == u"\\" || n == u" ")
    1650        83878 :             && (stackSusOrAdd || (stackModifiersEnabled && tok.tokenClass == ChordTokenClass::MODIFIER))) {
    1651            9 :             continue;
    1652              :         }
    1653              : 
    1654        41939 :         const bool tokIsSusOrAdd = std::regex_match(n.toStdWString(), SUS_ADD_REGEX);
    1655        41939 :         const bool stackModifier = stackModifiersEnabled && !(stackSusOrAdd && tokIsSusOrAdd);
    1656        41939 :         const bool superScriptModifier = superScriptEnabled && !(stackSusOrAdd && tokIsSusOrAdd);
    1657        41939 :         String curMod = m_modifierList.size() > 1 ? m_modifierList.at(modIdx) : u"";
    1658        41939 :         if (stackSusOrAdd && stackModifier && modIdx == 0) {
    1659         2595 :             curMod.remove(SUS_ADD_REGEX);
    1660              :         }
    1661        41939 :         const bool modifierEnd = curMod.endsWith(n) && modIdx != finalModIdx;
    1662              : 
    1663        41939 :         std::vector<RenderActionPtr > rl;
    1664        41939 :         std::vector<ChordToken> definedTokens;
    1665        41939 :         bool found = false;
    1666              :         // potential definitions for token
    1667        41939 :         if (cl) {
    1668        85522 :             for (const ChordToken& ct : cl->chordTokenList) {
    1669        88520 :                 for (const String& ctn : ct.names) {
    1670        44937 :                     if (ctn == n) {
    1671         1232 :                         definedTokens.push_back(ct);
    1672              :                     }
    1673              :                 }
    1674              :             }
    1675              :         }
    1676              :         // find matching class, fallback on ChordTokenClass::ALL
    1677        41939 :         ChordTokenClass ctc = ChordTokenClass::ALL;
    1678        42282 :         for (const ChordToken& matchingTok : definedTokens) {
    1679          922 :             if (tok.tokenClass == matchingTok.tokenClass) {
    1680          579 :                 rl = matchingTok.renderList;
    1681          579 :                 ctc = tok.tokenClass;
    1682          579 :                 found = true;
    1683          579 :                 break;
    1684          343 :             } else if (matchingTok.tokenClass == ChordTokenClass::ALL) {
    1685           10 :                 rl = matchingTok.renderList;
    1686           10 :                 found = true;
    1687              :             }
    1688              :         }
    1689              : 
    1690              :         // build render list
    1691              :         // check for adjustments
    1692        41939 :         double yAdjust = adjust ? cl->position(tok.names, stackModifier, superScriptModifier, ctc, finalModIdx - modIdx, modListSize) : 0.0;
    1693              : 
    1694              :         // Modifier behaviour
    1695        41939 :         if (tok.tokenClass == ChordTokenClass::MODIFIER) {
    1696              :             // Stop adjusting when first non-adjusted modifier found
    1697        29627 :             if (RealIsNull(yAdjust) && !(stackSusOrAdd && tokIsSusOrAdd)) {
    1698        28679 :                 adjust = false;
    1699              :             }
    1700              : 
    1701              :             // This is the first modifier. Discount subsequent items from horizontal alignment
    1702        29627 :             if (cl->excludeModsHAlign() && modIdx == 0) {
    1703            0 :                 m_renderList.emplace_back(new RenderActionStopHAlign());
    1704              :             }
    1705              : 
    1706              :             // Jazz superscript
    1707        29627 :             if (superScriptModifier) {
    1708              :                 // Set scale
    1709            0 :                 m_renderList.emplace_back(new RenderActionScale(cl->stackedModifierMag()));
    1710              :                 // Move to x-height
    1711            0 :                 m_renderList.emplace_back(new RenderActionPush());
    1712            0 :                 m_renderList.emplace_back(new RenderActionMoveXHeight(true));
    1713              :             }
    1714              : 
    1715              :             // Stacked modifiers
    1716        29627 :             if (stackModifier) {
    1717              :                 // Pad modifier stack before
    1718         8173 :                 if (firstModifierToken) {
    1719         1769 :                     m_renderList.emplace_back(new RenderActionMove(0.15, 0));
    1720         1769 :                     firstModifierToken = false;
    1721              :                 }
    1722              : 
    1723              :                 // Check if the next token is a closing paren
    1724              :                 // Delay starting a new line until after the paren
    1725              :                 // (parens are excluded from the modifier string)
    1726         8173 :                 const auto& nextTokIt = std::next(tokIt);
    1727         8173 :                 if (nextTokIt != m_tokenList.end() && modifierEnd) {
    1728         2310 :                     const ChordToken& nextTok = *nextTokIt;
    1729         2310 :                     if (nextTok.names.front() == ")") {
    1730            3 :                         closingParenPending = true;
    1731              :                     }
    1732              :                 }
    1733              : 
    1734        21001 :                 auto startsWithAcc = [](const String& s) -> bool {
    1735        21001 :                     return s.startsWith(u"b") || s.startsWith(u"#");
    1736              :                 };
    1737        16346 :                 auto startsWithSusOrAdd = [](const String& s) -> bool {
    1738        16346 :                     return s.startsWith(u"sus") || s.startsWith(u"add");
    1739              :                 };
    1740              :                 // Align vertically stacked modifier's x position by pushing it at the start of every modifier and popping at the end
    1741              :                 // Make sure degrees are aligned when there are accidentals
    1742              :                 // -- nextMod now searches to the end of modifierList to support stacks of 3+ modifiers
    1743        16346 :                 auto it = std::find_if(
    1744         8173 :                     m_modifierList.begin() + (modIdx + 1),
    1745              :                     m_modifierList.end(),
    1746         4655 :                     [&startsWithAcc](const String& s) { return startsWithAcc(s); }
    1747              :                     );
    1748         8173 :                 const String& nextMod = (it != m_modifierList.end()) ? *it : u"";
    1749         8173 :                 bool nextModStartsWithAcc = (it != m_modifierList.end());
    1750         8173 :                 bool curModStartsWithAcc = startsWithAcc(curMod);
    1751         8173 :                 bool nIsAcc = startsWithAcc(n);
    1752         8173 :                 bool curModStartsWithSusOrAdd = startsWithSusOrAdd(curMod);
    1753         8173 :                 bool nIsSusOrAdd = startsWithSusOrAdd(n);
    1754         8173 :                 bool nIsParen = (n == u"(") || (n == u")");
    1755              : 
    1756         8173 :                 if (curModStartsWithSusOrAdd) {                                         // Align sus or add left
    1757           30 :                     if (nIsSusOrAdd) {
    1758           12 :                         m_renderList.emplace_back(new RenderActionPush());
    1759              :                     }
    1760         8143 :                 } else if (nIsAcc && nextModStartsWithAcc) {                            // current line has accidental, next line has accidental -> push before accidental
    1761         2282 :                     m_renderList.emplace_back(new RenderActionPush());
    1762         5861 :                 } else if (!curModStartsWithAcc && !nextModStartsWithAcc) {             // current line has no accidental, next line has no accidental -> push before degree
    1763           65 :                     m_renderList.emplace_back(new RenderActionPush());
    1764         5796 :                 } else if (curModStartsWithAcc && !nextModStartsWithAcc && !nIsAcc) {   // current line has accidental, next line has no accidental ->  push before degree
    1765         1754 :                     m_renderList.emplace_back(new RenderActionPush());
    1766         4042 :                 } else if (!curModStartsWithAcc && nextModStartsWithAcc && !nIsParen) { // current line has no accidental, next line has accidental -> push before degree
    1767            9 :                     m_renderList.emplace_back(new RenderActionPush());                  // and move by next line's accidental's width to align
    1768            9 :                     String nextAcc = nextMod;
    1769            9 :                     static const std::wregex DEGREE_REGEX = std::wregex(L"[0-9]+");
    1770            9 :                     nextAcc.replace(DEGREE_REGEX, u"");
    1771              :                     // Apply scaling and use renderList to match the behavior of standard accidental rendering
    1772            9 :                     m_renderList.emplace_back(new RenderActionScale(cl->stackedModifierMag()));
    1773            9 :                     const ChordToken accTok = cl->token(nextAcc, ChordTokenClass::MODIFIER);
    1774            9 :                     if (accTok.isValid()) {
    1775            0 :                         for (const RenderActionPtr& a : accTok.renderList) {
    1776            0 :                             if (a->actionType() == RenderAction::RenderActionType::SET) {
    1777            0 :                                 m_renderList.emplace_back(new RenderActionMoveTextWidth(u"s" + nextAcc));
    1778              :                             } else {
    1779            0 :                                 m_renderList.emplace_back(a);
    1780              :                             }
    1781              :                         }
    1782              :                     }
    1783            9 :                     m_renderList.emplace_back(new RenderActionScale(1 / cl->stackedModifierMag()));
    1784            9 :                 }
    1785              : 
    1786              :                 // Set scale
    1787         8173 :                 m_renderList.emplace_back(new RenderActionScale(cl->stackedModifierMag()));
    1788              :                 // Move to x-height
    1789         8173 :                 m_renderList.emplace_back(new RenderActionPush());
    1790         8173 :                 m_renderList.emplace_back(new RenderActionMoveXHeight(true));
    1791         8173 :             }
    1792              :         }
    1793              : 
    1794        41939 :         if (!RealIsNull(yAdjust)) {
    1795           72 :             m_renderList.emplace_back(new RenderActionMove(0.0, yAdjust));
    1796              :         }
    1797        41939 :         if (found) {
    1798          589 :             m_renderList.insert(m_renderList.end(), rl.begin(), rl.end());
    1799              :         } else {
    1800              :             // no definition for token, so render as literal
    1801        41350 :             m_renderList.emplace_back(new RenderActionSet(tok.names.front()));
    1802              :         }
    1803              :         // Reset adjust
    1804        41939 :         if (!RealIsNull(yAdjust)) {
    1805           72 :             m_renderList.emplace_back(new RenderActionMove(0.0, -yAdjust));
    1806              :         }
    1807              : 
    1808        41939 :         if (tok.tokenClass == ChordTokenClass::MODIFIER && !n.empty() && superScriptModifier) {
    1809            0 :             m_renderList.emplace_back(new RenderActionPopY());
    1810            0 :             m_renderList.emplace_back(new RenderActionScale(1 / cl->stackedModifierMag()));
    1811              :         }
    1812              : 
    1813              :         // Stacked modifiers
    1814        41939 :         if (tok.tokenClass == ChordTokenClass::MODIFIER && !n.empty() && stackModifier) {
    1815              :             // Reset move to x-height
    1816         8173 :             m_renderList.emplace_back(new RenderActionPopY());
    1817              :             // Reset scale
    1818         8173 :             m_renderList.emplace_back(new RenderActionScale(1 / cl->stackedModifierMag()));
    1819         8173 :             if (modifierEnd != closingParenPending) {
    1820         2310 :                 modIdx++;
    1821         2310 :                 closingParenPending = false;
    1822              : 
    1823              :                 // Restore x position
    1824         2310 :                 m_renderList.emplace_back(new RenderActionPopX());
    1825              :             }
    1826              :         }
    1827        41948 :     }
    1828              : 
    1829        10902 :     return m_renderList;
    1830              : }
    1831              : 
    1832              : //---------------------------------------------------------
    1833              : //   addToken
    1834              : //---------------------------------------------------------
    1835              : 
    1836        56199 : void ParsedChord::addToken(String s, ChordTokenClass tc)
    1837              : {
    1838        56199 :     if (s.empty()) {
    1839            0 :         return;
    1840              :     }
    1841        56199 :     ChordToken tok;
    1842        56199 :     tok.names << s;
    1843        56199 :     tok.tokenClass = tc;
    1844        56199 :     m_tokenList.push_back(tok);
    1845        56199 : }
    1846              : 
    1847              : //---------------------------------------------------------
    1848              : //   ChordDescription
    1849              : //    this form is used when reading from file
    1850              : //    a private id is assigned for id = 0
    1851              : //---------------------------------------------------------
    1852              : 
    1853         5280 : ChordDescription::ChordDescription(int i)
    1854              : {
    1855         5280 :     if (!i) {
    1856              : //            i = --(cl->privateID);
    1857            0 :         i = --ChordList::privateID;
    1858              :     }
    1859         5280 :     id = i;
    1860         5280 :     generated = false;
    1861         5280 :     renderListGenerated = false;
    1862         5280 :     exportOk = true;
    1863         5280 : }
    1864              : 
    1865              : //---------------------------------------------------------
    1866              : //   ChordDescription
    1867              : //    this form is used when generating from name
    1868              : //    a private id is always assigned
    1869              : //---------------------------------------------------------
    1870              : 
    1871          262 : ChordDescription::ChordDescription(const String& name)
    1872              : {
    1873          262 :     id = --ChordList::privateID;
    1874          262 :     generated = true;
    1875          262 :     names.push_back(name);
    1876          262 :     renderListGenerated = false;
    1877          262 :     exportOk = false;
    1878          262 : }
    1879              : 
    1880              : //---------------------------------------------------------
    1881              : //   complete
    1882              : //    generate missing renderList and semantic (Xml) info
    1883              : //---------------------------------------------------------
    1884              : 
    1885         9942 : void ChordDescription::complete(ParsedChord* pc, const ChordList* cl)
    1886              : {
    1887         9942 :     ParsedChord tempPc;
    1888         9942 :     if (!pc) {
    1889              :         // generate parsed chord for its rendering & semantic (xml) info
    1890         9680 :         pc = &tempPc;
    1891         9680 :         String n;
    1892         9680 :         if (!names.empty()) {
    1893         9680 :             n = names.front();
    1894              :         }
    1895         9680 :         pc->parse(n, cl);
    1896         9680 :     }
    1897         9942 :     parsedChords.push_back(*pc);
    1898         9942 :     if (renderList.empty() || renderListStacked.empty() || renderListGenerated) {
    1899         5567 :         renderList = pc->renderList(cl, false);
    1900         5567 :         renderListStacked = pc->renderList(cl, true);
    1901         5567 :         renderListGenerated = true;
    1902              :     }
    1903         9942 :     if (xmlKind.empty()) {
    1904          436 :         xmlKind = pc->xmlKind();
    1905          436 :         xmlDegrees = pc->xmlDegrees();
    1906              :     }
    1907              :     // these fields are not read from chord description files
    1908              :     // so get them from the parsed representation in all cases
    1909         9942 :     xmlText = pc->xmlText();
    1910         9942 :     xmlSymbols = pc->xmlSymbols();
    1911         9942 :     xmlParens = pc->xmlParens();
    1912         9942 :     if (chord.getKeys() == 0) {
    1913          349 :         chord = HChord(pc->keys());
    1914              :     }
    1915         9942 :     m_quality = pc->quality();
    1916         9942 : }
    1917              : 
    1918              : //---------------------------------------------------------
    1919              : //   read
    1920              : //---------------------------------------------------------
    1921              : 
    1922         9680 : void ChordDescription::read(XmlReader& e, int mscVersion)
    1923              : {
    1924         9680 :     int ni = 0;
    1925         9680 :     id = e.attribute("id").toInt();
    1926        38898 :     while (e.readNextStartElement()) {
    1927        29218 :         const AsciiStringView tag(e.name());
    1928        29218 :         if (tag == "name") {
    1929         5408 :             String n = e.readText();
    1930              :             // stack names for this file on top of the list
    1931         5408 :             names.insert(ni++, n);
    1932        29218 :         } else if (tag == "xml") {
    1933         5121 :             xmlKind = e.readText();
    1934        18689 :         } else if (tag == "degree") {
    1935         8905 :             xmlDegrees.push_back(e.readText());
    1936         9784 :         } else if (tag == "voicing") {
    1937         5208 :             chord = HChord(e.readText());
    1938         4576 :         } else if (tag == "render") {
    1939         4576 :             readRenderList(e.readText(), renderList, mscVersion);
    1940         4576 :             renderListGenerated = false;
    1941              :         } else {
    1942            0 :             e.unknown();
    1943              :         }
    1944              :     }
    1945         9680 : }
    1946              : 
    1947              : //---------------------------------------------------------
    1948              : //   write
    1949              : //---------------------------------------------------------
    1950              : 
    1951            0 : void ChordDescription::write(XmlWriter& xml) const
    1952              : {
    1953            0 :     if (generated && !exportOk) {
    1954            0 :         return;
    1955              :     }
    1956            0 :     if (id > 0) {
    1957            0 :         xml.startElement("chord", { { "id", id } });
    1958              :     } else {
    1959            0 :         xml.startElement("chord");
    1960              :     }
    1961            0 :     for (const String& s : names) {
    1962            0 :         xml.tag("name", s);
    1963              :     }
    1964            0 :     xml.tag("xml", xmlKind);
    1965            0 :     xml.tag("voicing", chord.voicing());
    1966            0 :     for (const String& s : xmlDegrees) {
    1967            0 :         xml.tag("degree", s);
    1968              :     }
    1969            0 :     writeRenderList(xml, renderList, "render");
    1970            0 :     xml.endElement();
    1971            0 : }
    1972              : 
    1973              : //---------------------------------------------------------
    1974              : //   ChordList
    1975              : //---------------------------------------------------------
    1976              : 
    1977              : int ChordList::privateID = -1000;
    1978              : 
    1979              : //---------------------------------------------------------
    1980              : //   configureAutoAdjust
    1981              : //---------------------------------------------------------
    1982              : 
    1983         1474 : void ChordList::configureAutoAdjust(double emag, double eadjust, double mmag, double madjust, double stackedmmag, bool stackModifiers,
    1984              :                                     bool excludeModsHAlign, String symbolFont, const ChordStylePreset& preset)
    1985              : {
    1986         1474 :     m_stackModifiers = stackModifiers;
    1987         1474 :     m_excludeModsHAlign = excludeModsHAlign;
    1988         1474 :     m_emag = emag;
    1989         1474 :     m_eadjust = eadjust;
    1990         1474 :     m_mmag = mmag;
    1991         1474 :     m_stackedmmag = stackedmmag;
    1992         1474 :     m_madjust = madjust;
    1993         1474 :     m_symbolTextFont = symbolFont;
    1994         1474 :     m_chordPreset = preset;
    1995         1474 : }
    1996              : 
    1997              : //---------------------------------------------------------
    1998              : //   read
    1999              : //---------------------------------------------------------
    2000              : 
    2001         1504 : void ChordList::read(XmlReader& e, int mscVersion)
    2002              : {
    2003         1504 :     int fontIdx = static_cast<int>(fonts.size());
    2004         1504 :     m_autoAdjust = false;
    2005       128579 :     while (e.readNextStartElement()) {
    2006       127075 :         const AsciiStringView tag(e.name());
    2007       127075 :         if (tag == "font") {
    2008         9510 :             ChordFont f;
    2009         9510 :             f.family = e.attribute("family", u"default");
    2010         9510 :             if (f.family == u"MuseJazz") {
    2011            0 :                 f.family = u"MuseJazz Text";
    2012         9510 :             } else if (f.family == u"ScoreText") {
    2013         2742 :                 f.family = m_symbolTextFont;
    2014         2742 :                 f.musicSymbolText = true;
    2015              :             }
    2016         9510 :             f.mag = 1.0;
    2017         9510 :             f.fontClass = e.attribute("class");
    2018       122502 :             while (e.readNextStartElement()) {
    2019       112992 :                 if (e.name() == "sym") {
    2020       110599 :                     ChordSymbol cs;
    2021       110599 :                     cs.fontIdx = fontIdx;
    2022       110599 :                     cs.name    = e.attribute("name");
    2023       110599 :                     cs.value   = e.attribute("value");
    2024       110599 :                     String code = e.attribute("code");
    2025       110599 :                     String symClass = e.attribute("class");
    2026       110599 :                     if (!code.empty()) {
    2027        34059 :                         bool ok = true;
    2028        34059 :                         char32_t val = code.toUInt(&ok, 0);
    2029        34059 :                         if (!ok && mscVersion >= 400 && mscVersion < 440) {
    2030            0 :                             val = code.toUInt(&ok, 16);
    2031              :                         }
    2032        34059 :                         if (!ok) {
    2033         8233 :                             cs.code = 0;
    2034         8233 :                             cs.value = code;
    2035        25826 :                         } else if (Char::requiresSurrogates(val)) {
    2036         1070 :                             cs.code = 0;
    2037         1070 :                             cs.value = String::fromUcs4(val);
    2038              :                         } else {
    2039        24756 :                             cs.code = val;
    2040        24756 :                             cs.value = String(cs.code);
    2041              :                         }
    2042              :                     } else {
    2043        76540 :                         cs.code = 0;
    2044              :                     }
    2045       110599 :                     if (cs.value.empty()) {
    2046         4549 :                         cs.value = cs.name;
    2047              :                     }
    2048       110599 :                     cs.name = symClass + cs.name;
    2049       110599 :                     m_symbols.insert({ cs.name, cs });
    2050       110599 :                     e.readNext();
    2051       112992 :                 } else if (e.name() == "mag") {
    2052         2393 :                     f.mag = e.readDouble();
    2053              :                 } else {
    2054            0 :                     e.unknown();
    2055              :                 }
    2056              :             }
    2057         9510 :             if (m_autoAdjust) {
    2058         9456 :                 if (f.fontClass == "extension") {
    2059         2364 :                     f.mag *= m_emag;
    2060         7092 :                 } else if (f.fontClass == "modifier") {
    2061         2364 :                     f.mag *= m_mmag;
    2062              :                 }
    2063              :             }
    2064         9510 :             fonts.push_back(f);
    2065         9510 :             ++fontIdx;
    2066       127075 :         } else if (tag == "autoAdjust") {
    2067         1449 :             String nmag = e.attribute("mag");
    2068         1449 :             if (!nmag.empty()) {
    2069          534 :                 m_nmag = nmag.toDouble();
    2070              :             }
    2071         1449 :             String nadjust = e.attribute("adjust");
    2072         1449 :             if (!nadjust.empty()) {
    2073          534 :                 m_nadjust = nadjust.toDouble();
    2074              :             }
    2075         1449 :             m_autoAdjust = e.readBool();
    2076       117565 :         } else if (tag == "token") {
    2077       101122 :             ChordToken t;
    2078       101122 :             t.read(e, mscVersion);
    2079       101122 :             chordTokenList.push_back(t);
    2080       116116 :         } else if (tag == "chord") {
    2081         9680 :             int id = e.intAttribute("id");
    2082              :             // if no id attribute (id == 0), then assign it a private id
    2083              :             // user chords that match these ChordDescriptions will be treated as normal recognized chords
    2084              :             // except that the id will not be written to the score file
    2085         9680 :             ChordDescription cd = (id && muse::contains(*this, id))
    2086         9680 :                                   ? muse::take(*this, id)
    2087         9680 :                                   : ChordDescription(id);
    2088              : 
    2089              :             // record updated id
    2090         9680 :             id = cd.id;
    2091              :             // read rest of description
    2092         9680 :             cd.read(e, mscVersion);
    2093              :             // restore updated id
    2094         9680 :             cd.id = id;
    2095              :             // throw away previously parsed chords
    2096         9680 :             cd.parsedChords.clear();
    2097              :             // generate any missing info (including new parsed chords)
    2098         9680 :             cd.complete(0, this);
    2099              :             // add to list
    2100         9680 :             insert({ id, cd });
    2101        14994 :         } else if (tag == "renderRoot") {
    2102         1475 :             readRenderList(e.readText(), renderListRoot, mscVersion);
    2103         3839 :         } else if (tag == "renderFunction") {
    2104         1449 :             readRenderList(e.readText(), renderListFunction, mscVersion);
    2105         2390 :         } else if ((tag == "renderBase" && mscVersion < 460) || tag == "renderBass") {
    2106         1475 :             readRenderList(e.readText(), renderListBass, mscVersion);
    2107          915 :         } else if (tag == "renderBassOffset") {
    2108          915 :             readRenderList(e.readText(), renderListBassOffset, mscVersion);
    2109              :         } else {
    2110            0 :             e.unknown();
    2111              :         }
    2112              :     }
    2113         1504 : }
    2114              : 
    2115              : //---------------------------------------------------------
    2116              : //   write
    2117              : //---------------------------------------------------------
    2118              : 
    2119            0 : void ChordList::write(XmlWriter& xml) const
    2120              : {
    2121            0 :     int fontIdx = 0;
    2122            0 :     for (const ChordFont& f : fonts) {
    2123            0 :         xml.startElement("font", { { "id", fontIdx }, { "family", f.family } });
    2124            0 :         xml.tag("mag", f.mag);
    2125            0 :         for (const auto& p : m_symbols) {
    2126            0 :             const ChordSymbol& s = p.second;
    2127            0 :             if (s.fontIdx == fontIdx) {
    2128            0 :                 if (s.code.isNull()) {
    2129            0 :                     xml.tag("sym", { { "name", s.name }, { "value", s.value } });
    2130              :                 } else {
    2131              :                     // write hex numbers with a "0x" prefix, so they can convert back properly on read
    2132            0 :                     xml.tag("sym", { { "name", s.name }, { "code", u"0x" + String::number(s.code.unicode(), 16) } });
    2133              :                 }
    2134              :             }
    2135              :         }
    2136            0 :         xml.endElement();
    2137            0 :         ++fontIdx;
    2138              :     }
    2139            0 :     if (m_autoAdjust) {
    2140            0 :         xml.tag("autoAdjust", { { "mag", m_nmag }, { "adjust", m_nadjust } });
    2141              :     }
    2142            0 :     for (const ChordToken& t : chordTokenList) {
    2143            0 :         t.write(xml);
    2144              :     }
    2145            0 :     if (!renderListRoot.empty()) {
    2146            0 :         writeRenderList(xml, renderListRoot, "renderRoot");
    2147              :     }
    2148            0 :     if (!renderListFunction.empty()) {
    2149            0 :         writeRenderList(xml, renderListFunction, "renderFunction");
    2150              :     }
    2151            0 :     if (!renderListBass.empty()) {
    2152            0 :         writeRenderList(xml, renderListBass, "renderBass");
    2153              :     }
    2154            0 :     if (!renderListBassOffset.empty()) {
    2155            0 :         writeRenderList(xml, renderListBassOffset, "renderBassOffset");
    2156              :     }
    2157            0 :     for (const auto& p : *this) {
    2158            0 :         const ChordDescription& cd = p.second;
    2159            0 :         cd.write(xml);
    2160              :     }
    2161            0 : }
    2162              : 
    2163              : //---------------------------------------------------------
    2164              : //   read
    2165              : //    read Chord List, return false on error
    2166              : //---------------------------------------------------------
    2167              : 
    2168         1503 : bool ChordList::read(const String& name)
    2169              : {
    2170              : //      LOGD("ChordList::read <%s>", muPrintable(name));
    2171         1503 :     muse::io::path_t path;
    2172         1503 :     FileInfo ftest(name);
    2173         1503 :     if (ftest.isAbsolute()) {
    2174            0 :         path = name;
    2175              :     } else {
    2176         1503 :         path = u":/engraving/styles/" + name;
    2177              :     }
    2178              : 
    2179              :     // default to chords_std.xml
    2180         1503 :     if (!FileInfo::exists(path)) {
    2181            0 :         path = u":/engraving/styles/chords_std.xml";
    2182              :     }
    2183              : 
    2184         1503 :     if (name.isEmpty()) {
    2185            0 :         return false;
    2186              :     }
    2187         1503 :     File f(path);
    2188         1503 :     if (!f.open(IODevice::ReadOnly)) {
    2189            0 :         LOGE() << "Cannot open chord description: " << f.filePath();
    2190            0 :         return false;
    2191              :     }
    2192              : 
    2193         1503 :     return read(&f);
    2194         1503 : }
    2195              : 
    2196         1503 : bool ChordList::read(IODevice* device)
    2197              : {
    2198         1503 :     XmlReader e(device);
    2199              : 
    2200         1503 :     while (e.readNextStartElement()) {
    2201         1503 :         if (e.name() == "museScore") {
    2202         1503 :             const String version = e.attribute("version");
    2203         1503 :             const StringList sl = version.split(u'.');
    2204         1503 :             const int mscVersion = sl.size() == 2 ? sl[0].toInt() * 100 + sl[1].toInt() : 0;
    2205         1503 :             read(e, mscVersion);
    2206         1503 :             return true;
    2207         1503 :         }
    2208              :     }
    2209            0 :     return false;
    2210         1503 : }
    2211              : 
    2212              : //---------------------------------------------------------
    2213              : //   writeChordList
    2214              : //---------------------------------------------------------
    2215              : 
    2216            0 : bool ChordList::write(const String& name) const
    2217              : {
    2218            0 :     FileInfo info(name);
    2219              : 
    2220            0 :     if (info.suffix().isEmpty()) {
    2221            0 :         String path = info.filePath();
    2222            0 :         path += u".xml";
    2223            0 :         info = FileInfo(path);
    2224            0 :     }
    2225              : 
    2226            0 :     auto outBuf = Buffer::opened(IODevice::WriteOnly);
    2227            0 :     write(&outBuf);
    2228            0 :     outBuf.close();
    2229              : 
    2230            0 :     Ret ret = File::writeFile(info.filePath(), outBuf.data());
    2231            0 :     if (!ret) {
    2232            0 :         LOGE() << "failed to write chord list: " << ret.toString();
    2233            0 :         return false;
    2234              :     }
    2235              : 
    2236            0 :     return true;
    2237            0 : }
    2238              : 
    2239            0 : bool ChordList::write(IODevice* device) const
    2240              : {
    2241            0 :     XmlWriter xml(device);
    2242            0 :     xml.startDocument();
    2243            0 :     xml.startElement("museScore", { { "version", Constants::MSC_VERSION_STR } });
    2244              : 
    2245            0 :     write(xml);
    2246              : 
    2247            0 :     xml.endElement();
    2248              : 
    2249            0 :     return true;
    2250            0 : }
    2251              : 
    2252              : //---------------------------------------------------------
    2253              : //   loaded
    2254              : //---------------------------------------------------------
    2255              : 
    2256         1813 : bool ChordList::loaded() const
    2257              : {
    2258              :     // track whether a description file has been loaded
    2259              :     // since chords.xml really doesn't load enough to stand alone,
    2260              :     // we need a way to track when a "real" chord list has been loaded
    2261              :     // for lack of anything better, key off renderListRoot
    2262         1813 :     return !renderListRoot.empty();
    2263              : }
    2264              : 
    2265              : //---------------------------------------------------------
    2266              : //   unload
    2267              : //---------------------------------------------------------
    2268              : 
    2269           66 : void ChordList::unload()
    2270              : {
    2271           66 :     clear();
    2272           66 :     m_symbols.clear();
    2273           66 :     fonts.clear();
    2274           66 :     renderListRoot.clear();
    2275           66 :     renderListBass.clear();
    2276           66 :     chordTokenList.clear();
    2277           66 :     m_autoAdjust = false;
    2278           66 : }
    2279              : 
    2280         6141 : const ChordDescription* ChordList::description(int id) const
    2281              : {
    2282         6141 :     auto it = this->find(id);
    2283         6141 :     if (it == this->end()) {
    2284           25 :         return nullptr;
    2285              :     }
    2286         6116 :     return &it->second;
    2287              : }
    2288              : 
    2289        14093 : ChordSymbol ChordList::symbol(const String& s) const
    2290              : {
    2291        14093 :     return muse::value(m_symbols, s);
    2292              : }
    2293              : 
    2294          747 : ChordToken ChordList::token(const String& s, ChordTokenClass type) const
    2295              : {
    2296        33319 :     for (const ChordToken& tok : chordTokenList) {
    2297        32710 :         if (tok.tokenClass != type || !tok.names.contains(s)) {
    2298        32572 :             continue;
    2299              :         }
    2300              : 
    2301          138 :         return tok;
    2302              :     }
    2303              : 
    2304          609 :     return ChordToken();
    2305              : }
    2306              : 
    2307         1813 : void ChordList::checkChordList(const MStyle& style)
    2308              : {
    2309              :     // make sure we have a chordlist
    2310         1813 :     if (!loaded()) {
    2311         1472 :         double emag = style.styleD(Sid::chordExtensionMag);
    2312         1472 :         double eadjust = style.styleD(Sid::chordExtensionAdjust);
    2313         1472 :         double mmag = style.styleD(Sid::chordModifierMag);
    2314         1472 :         double madjust = style.styleD(Sid::chordModifierAdjust);
    2315         1472 :         double stackedmmag = style.styleD(Sid::chordStackedModifierMag);
    2316         1472 :         bool stackModifiers = style.styleB(Sid::verticallyStackModifiers);
    2317         1472 :         bool excludeModsHAlign = style.styleB(Sid::chordAlignmentExcludeModifiers);
    2318         1472 :         String symbolFont = style.styleSt(Sid::musicalTextFont);
    2319         1472 :         ChordStylePreset preset = style.styleV(Sid::chordStyle).value<ChordStylePreset>();
    2320         1472 :         configureAutoAdjust(emag, eadjust, mmag, madjust, stackedmmag, stackModifiers, excludeModsHAlign, symbolFont, preset);
    2321              : 
    2322         1472 :         if (style.value(Sid::chordsXmlFile).toBool()) {
    2323           25 :             read(u"chords.xml");
    2324              :         }
    2325              : 
    2326         1472 :         read(style.value(Sid::chordDescriptionFile).value<String>());
    2327         1472 :     }
    2328         1813 : }
    2329              : 
    2330              : //---------------------------------------------------------
    2331              : //   print
    2332              : //    only for debugging
    2333              : //---------------------------------------------------------
    2334              : 
    2335            0 : void RenderAction::print(RenderActionType type, const String& info) const
    2336              : {
    2337              :     static const char* names[] = {
    2338              :         "SET", "MOVE", "MOVEXHEIGHT", "PUSH", "POP",
    2339              :         "NOTE", "ACCIDENTAL", "STOPHALIGN", "SCALE", "PAREN"
    2340              :     };
    2341            0 :     LOGD("%10s %s", names[int(type)], muPrintable(info));
    2342            0 : }
    2343              : 
    2344            0 : void RenderActionMove::print() const
    2345              : {
    2346            0 :     String info = String(u"%1 %2").arg(m_vec.x(), m_vec.y());
    2347            0 :     RenderAction::print(actionType(), info);
    2348            0 : }
    2349              : 
    2350            0 : void RenderActionSet::print() const
    2351              : {
    2352            0 :     String info = String(u"<%1>").arg(m_text);
    2353            0 :     RenderAction::print(actionType(), info);
    2354            0 : }
    2355              : 
    2356            0 : void RenderActionPop::print() const
    2357              : {
    2358            0 :     String info = String(u"pop x: %1 pop y: %2").arg(m_popx, m_popy);
    2359            0 :     RenderAction::print(actionType(), info);
    2360            0 : }
    2361              : 
    2362            0 : void RenderActionScale::print() const
    2363              : {
    2364            0 :     String info = String(u"%1").arg(m_scale);
    2365            0 :     RenderAction::print(actionType(), info);
    2366            0 : }
    2367              : 
    2368            0 : void RenderActionMoveScaled::print() const
    2369              : {
    2370            0 :     String info = String(u"SCALED %1 %2").arg(x(), y());
    2371            0 :     RenderAction::print(actionType(), info);
    2372            0 : }
    2373              : 
    2374            0 : void RenderActionMoveXHeight::print() const
    2375              : {
    2376            0 :     String info = String(u"up: %1").arg(m_up);
    2377            0 :     RenderAction::print(actionType(), info);
    2378            0 : }
    2379              : 
    2380            0 : void RenderActionParen::print() const
    2381              : {
    2382            0 :     String info = String(u"direction: %1").arg(direction() == DirectionH::LEFT ? u"left" : u"right");
    2383            0 :     RenderAction::print(actionType(), info);
    2384            0 : }
    2385              : }
        

Generated by: LCOV version 2.0-1