LCOV - code coverage report
Current view: top level - src/importexport/guitarpro/internal/gtp - gp67dombuilder.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 89.8 % 1054 946
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : #include "gp67dombuilder.h"
       2              : 
       3              : #include <set>
       4              : 
       5              : #include "global/log.h"
       6              : 
       7              : #include "engraving/types/constants.h"
       8              : 
       9              : using namespace muse;
      10              : 
      11              : namespace mu::iex::guitarpro {
      12          221 : GP67DomBuilder::GP67DomBuilder()
      13              : {
      14          221 :     _gpDom = std::make_unique<GPDomModel>();
      15          221 : }
      16              : 
      17          221 : void GP67DomBuilder::buildGPDomModel(XmlDomElement* domElem)
      18              : {
      19          221 :     XmlDomNode revision;
      20              :     // Score node
      21          221 :     XmlDomNode scoreNode,        masterTrack,      audioTracks, eachTrack,
      22          221 :                masterBars,       bars,             voices,
      23          221 :                beats,            notes,            rhythms;
      24              : 
      25          221 :     XmlDomNode gpversion, encoding;
      26              : 
      27              :     std::map<String, XmlDomNode*> nodeMap =
      28              :     {
      29            0 :         { u"GPRevision",    &revision },
      30            0 :         { u"Score",         &scoreNode },
      31            0 :         { u"MasterTrack",   &masterTrack },
      32            0 :         { u"AudioTracks",   &audioTracks },
      33            0 :         { u"Tracks",        &eachTrack },
      34            0 :         { u"MasterBars",    &masterBars },
      35            0 :         { u"Bars",          &bars },
      36            0 :         { u"Voices",        &voices },
      37            0 :         { u"Beats",         &beats },
      38            0 :         { u"Notes",         &notes },
      39            0 :         { u"Rhythms",       &rhythms },
      40            0 :         { u"GPVersion",     &gpversion },
      41            0 :         { u"Encoding",      &encoding }
      42         3315 :     };
      43              : 
      44         2661 :     auto assignMap = [&nodeMap](XmlDomNode node)
      45              :     {
      46         2661 :         auto iter = nodeMap.find(node.nodeName());
      47         2661 :         if (iter != nodeMap.end()) {
      48         2661 :             *(iter->second) = node;
      49              :         }
      50         2661 :     };
      51              : 
      52         2882 :     for (XmlDomNode current = domElem->firstChild(); !current.isNull(); current = current.nextSibling()) {
      53         2661 :         assignMap(current);
      54          221 :     }
      55              : 
      56          221 :     buildGPRhythms(&rhythms);
      57          221 :     buildGPNotes(&notes);
      58          221 :     buildGPBeats(&beats);
      59          221 :     buildGPVoices(&voices);
      60          221 :     buildGPBars(&bars);
      61          221 :     buildGPMasterBars(&masterBars);
      62              : 
      63          221 :     buildGPScore(&scoreNode);
      64          221 :     buildGPMasterTracks(&masterTrack);
      65          221 :     buildGPAudioTracks(&audioTracks);
      66          221 :     buildGPTracks(&eachTrack, &gpversion);
      67          442 : }
      68              : 
      69          221 : std::unique_ptr<GPDomModel> GP67DomBuilder::getGPDomModel()
      70              : {
      71          221 :     return std::move(_gpDom);
      72              : }
      73              : 
      74          221 : void GP67DomBuilder::buildGPScore(XmlDomNode* scoreNode)
      75              : {
      76              :     // Contains list of unused info
      77              :     static const std::set<String> sUnusedNodes = {
      78              :         u"FirstPageFooter", u"FirstPageHeader",
      79              :         u"PageFooter", u"PageHeader",
      80              :         u"ScoreSystemsDefaultLayout", u"ScoreSystemsLayout", u"PageSetup"
      81          221 :     };
      82              : 
      83          221 :     std::unique_ptr<GPScore> score = std::make_unique<GPScore>();
      84          221 :     XmlDomNode currentNode = scoreNode->firstChild();
      85         4448 :     while (!currentNode.isNull()) {
      86         4227 :         String nodeName = currentNode.nodeName();
      87         4227 :         if (nodeName == u"Title") {
      88          221 :             score->setTitle(currentNode.toElement().text());
      89         4006 :         } else if (nodeName == u"Subtitle" || nodeName == u"SubTitle") {
      90          221 :             score->setSubTitle(currentNode.toElement().text());
      91         3785 :         } else if (nodeName == u"Artist") {
      92          221 :             score->setArtist(currentNode.toElement().text());
      93         3564 :         } else if (nodeName == u"Album") {
      94          221 :             score->setAlbum(currentNode.toElement().text());
      95         3343 :         } else if (nodeName == u"Words") {
      96          221 :             score->setPoet(currentNode.toElement().text());
      97         3122 :         } else if (nodeName == u"Music") {
      98          221 :             score->setComposer(currentNode.toElement().text());
      99         2901 :         } else if (nodeName == u"Copyright") {
     100              :             // Currently we ignore Copyright info
     101         2680 :         } else if (nodeName == u"Tabber") {
     102              :             // Currently we ignore Tabber info
     103         2459 :         } else if (nodeName == u"Instructions" || nodeName == u"Notices") {
     104              :             // Currently we ignore score unrelated texts
     105         2017 :         } else if (nodeName == u"MultiVoice") {
     106              :             /// gp saves the value "1>" instead of "1"
     107          172 :             String multiVoiceString = currentNode.toElement().text();
     108          172 :             if (!multiVoiceString.empty()) {
     109          172 :                 score->setMultiVoice(String(multiVoiceString[0]).toInt());
     110              :             }
     111         2017 :         } else if (sUnusedNodes.find(nodeName) != sUnusedNodes.end()) {
     112              :             // Ignored nodes, which specify unused specifics (e.g. default layout, footers e.t.c.)
     113              :         }
     114              : 
     115         4227 :         currentNode = currentNode.nextSibling();
     116         4227 :     }
     117          221 :     _gpDom->addGPScore(std::move(score));
     118          221 : }
     119              : 
     120          221 : void GP67DomBuilder::buildGPMasterTracks(XmlDomNode* masterTrack)
     121              : {
     122          221 :     std::unique_ptr<GPMasterTracks> masterTracks = std::make_unique<GPMasterTracks>();
     123              : 
     124          221 :     XmlDomNode currentNode = masterTrack->firstChild();
     125          883 :     while (!currentNode.isNull()) {
     126          662 :         String nodeName = currentNode.nodeName();
     127          662 :         if (nodeName == u"Automations") {
     128          221 :             masterTracks->setTempoMap(readTempoMap(&currentNode));
     129          441 :         } else if (nodeName == u"RSE") {
     130              :             //! TODO volume and pan(balance) of mixer are set here
     131          221 :         } else if (nodeName == u"Tracks") {
     132          221 :             String tracks = currentNode.toElement().text();
     133          221 :             size_t tracksCount = tracks.split(u' ').size();
     134          221 :             masterTracks->setTracksCount(tracksCount);
     135          221 :         }
     136              : 
     137          662 :         currentNode = currentNode.nextSibling();
     138          662 :     }
     139              : 
     140          221 :     _gpDom->addGPMasterTracks(std::move(masterTracks));
     141          221 : }
     142              : 
     143          221 : void GP67DomBuilder::buildGPAudioTracks(XmlDomNode* audioTrack)
     144              : {
     145          221 :     XmlDomNode currentNode = audioTrack->firstChild();
     146          221 :     while (!currentNode.isNull()) {
     147            0 :         currentNode = currentNode.nextSibling();
     148              :     }
     149          221 : }
     150              : 
     151          221 : void GP67DomBuilder::buildGPTracks(XmlDomNode* tracksNode, XmlDomNode* versionNode)
     152              : {
     153          221 :     std::map<int, std::unique_ptr<GPTrack> > tracks;
     154          221 :     XmlDomNode currentNode = tracksNode->firstChild();
     155          460 :     while (!currentNode.isNull()) {
     156          239 :         tracks.insert(createGPTrack(&currentNode, versionNode));
     157          239 :         currentNode = currentNode.nextSibling();
     158              :     }
     159              : 
     160          221 :     _gpDom->addGPTracks(std::move(tracks));
     161          221 : }
     162              : 
     163          221 : void GP67DomBuilder::buildGPMasterBars(XmlDomNode* masterBarsNode)
     164              : {
     165          221 :     std::vector<std::unique_ptr<GPMasterBar> > masterBars;
     166              : 
     167          221 :     XmlDomNode innerNode = masterBarsNode->firstChild();
     168          221 :     int masterBarIdx = 0;
     169          916 :     while (!innerNode.isNull()) {
     170          695 :         String nodeName = innerNode.nodeName();
     171              : 
     172          695 :         if (nodeName == u"MasterBar") {
     173          695 :             masterBars.push_back(createGPMasterBar(&innerNode));
     174          695 :             masterBars.back()->setId(masterBarIdx);
     175          695 :             masterBarIdx++;
     176              :         }
     177              : 
     178          695 :         innerNode = innerNode.nextSibling();
     179          695 :     }
     180              : 
     181          221 :     _gpDom->addGPMasterBars(std::move(masterBars));
     182          221 : }
     183              : 
     184          221 : void GP67DomBuilder::buildGPBars(XmlDomNode* barsNode)
     185              : {
     186          221 :     std::unordered_map<int, std::unique_ptr<GPBar> > bars;
     187              : 
     188          221 :     XmlDomNode innerNode = barsNode->firstChild();
     189          999 :     while (!innerNode.isNull()) {
     190          778 :         String nodeName = innerNode.nodeName();
     191          778 :         if (nodeName == u"Bar") {
     192          778 :             bars.insert(createGPBar(&innerNode));
     193              :         }
     194              : 
     195          778 :         innerNode = innerNode.nextSibling();
     196          778 :     }
     197              : 
     198          221 :     _bars.swap(bars);
     199          221 : }
     200              : 
     201          221 : void GP67DomBuilder::buildGPVoices(XmlDomNode* voicesNode)
     202              : {
     203          221 :     std::unordered_map<int, std::unique_ptr<GPVoice> > voices;
     204              : 
     205          221 :     XmlDomNode innerNode = voicesNode->firstChild();
     206          945 :     while (!innerNode.isNull()) {
     207          724 :         String nodeName = innerNode.nodeName();
     208          724 :         if (nodeName == u"Voice") {
     209          724 :             voices.insert(createGPVoice(&innerNode));
     210              :         }
     211              : 
     212          724 :         innerNode = innerNode.nextSibling();
     213          724 :     }
     214              : 
     215          221 :     _voices.swap(voices);
     216          221 : }
     217              : 
     218          221 : void GP67DomBuilder::buildGPBeats(XmlDomNode* beatsNode)
     219              : {
     220          221 :     std::unordered_map<int, std::shared_ptr<GPBeat> > beats;
     221              : 
     222          221 :     XmlDomNode innerNode = beatsNode->firstChild();
     223         1718 :     while (!innerNode.isNull()) {
     224         1497 :         String nodeName = innerNode.nodeName();
     225         1497 :         if (nodeName == u"Beat") {
     226         1497 :             beats.insert(createGPBeat(&innerNode));
     227              :         }
     228              : 
     229         1497 :         innerNode = innerNode.nextSibling();
     230         1497 :     }
     231              : 
     232          221 :     _beats.swap(beats);
     233          221 : }
     234              : 
     235          221 : void GP67DomBuilder::buildGPNotes(XmlDomNode* notesNode)
     236              : {
     237          221 :     std::unordered_map<int, std::shared_ptr<GPNote> > notes;
     238              : 
     239          221 :     XmlDomNode innerNode = notesNode->firstChild();
     240              : 
     241         1561 :     while (!innerNode.isNull()) {
     242         1340 :         String nodeName = innerNode.nodeName();
     243         1340 :         if (nodeName == u"Note") {
     244         1340 :             notes.insert(createGPNote(&innerNode));
     245              :         }
     246              : 
     247         1340 :         innerNode = innerNode.nextSibling();
     248         1340 :     }
     249              : 
     250          221 :     _notes.swap(notes);
     251          221 : }
     252              : 
     253          221 : void GP67DomBuilder::buildGPRhythms(XmlDomNode* rhythmsNode)
     254              : {
     255          221 :     std::unordered_map<int, std::shared_ptr<GPRhythm> > rhythms;
     256              : 
     257          221 :     XmlDomNode innerNode = rhythmsNode->firstChild();
     258          568 :     while (!innerNode.isNull()) {
     259          347 :         String nodeName = innerNode.nodeName();
     260          347 :         if (nodeName == u"Rhythm") {
     261          347 :             rhythms.insert(createGPRhythm(&innerNode));
     262              :         }
     263              : 
     264          347 :         innerNode = innerNode.nextSibling();
     265          347 :     }
     266              : 
     267          221 :     _rhythms.swap(rhythms);
     268          221 : }
     269              : 
     270          221 : std::vector<GPMasterTracks::Automation> GP67DomBuilder::readTempoMap(XmlDomNode* currentNode)
     271              : {
     272          221 :     std::vector<GPMasterTracks::Automation> tempoMap;
     273          221 :     XmlDomNode currentAutomation = currentNode->firstChild();
     274          444 :     while (!currentAutomation.isNull()) {
     275          223 :         if (currentAutomation.nodeName() == u"Automation") {
     276          223 :             XmlDomElement ln = currentAutomation.firstChildElement("Linear");
     277          223 :             String first_name = currentAutomation.firstChild().nodeName();
     278          223 :             if (first_name == u"Type") {
     279          223 :                 first_name = currentAutomation.firstChild().toElement().text();
     280              :             }
     281          223 :             if (first_name == u"Tempo") {
     282          223 :                 GPMasterTracks::Automation tempo;
     283          223 :                 tempo.type = GPMasterTracks::Automation::Type::tempo;
     284          223 :                 String str = currentAutomation.firstChildElement("Value").toElement().text();
     285          223 :                 StringList tempoValue = str.split(u' ');
     286          223 :                 tempo.value = tempoValue[0].toDouble();
     287          223 :                 tempo.tempoUnit = tempoValue.size() > 1 ? static_cast<int>(tempoValue[1].toDouble()) : 0;
     288          223 :                 tempo.bar = currentAutomation.firstChildElement("Bar").text().toInt();
     289          223 :                 tempo.position = currentAutomation.firstChildElement("Position").text().toFloat();
     290          223 :                 tempo.linear = (ln.toElement().text() == u"true");
     291          223 :                 XmlDomElement labelElem = currentAutomation.firstChildElement("Text");
     292          223 :                 if (labelElem.hasChildNodes()) {
     293           28 :                     tempo.text = labelElem.toElement().text();
     294              :                 }
     295              : 
     296          223 :                 tempoMap.push_back(tempo);
     297          223 :             }
     298          223 :         }
     299          223 :         currentAutomation = currentAutomation.nextSibling();
     300              :     }
     301              : 
     302          221 :     return tempoMap;
     303          221 : }
     304              : 
     305            0 : std::unique_ptr<GPMasterTracks> GP67DomBuilder::createGPMasterTrack(XmlDomNode* metadata)
     306              : {
     307              :     UNUSED(metadata);
     308            0 :     return std::make_unique<GPMasterTracks>();
     309              : }
     310              : 
     311          695 : std::unique_ptr<GPMasterBar> GP67DomBuilder::createGPMasterBar(XmlDomNode* masterBarNode)
     312              : {
     313              :     static const std::set<String> sUnused = {
     314              :         u"XProperties"
     315          695 :     };
     316              : 
     317            0 :     auto tripletFeelType = [](String&& str) {
     318            0 :         if (str == u"Triplet8th") {
     319            0 :             return GPMasterBar::TripletFeelType::Triplet8th;
     320              :         }
     321            0 :         if (str == u"Triplet16th") {
     322            0 :             return GPMasterBar::TripletFeelType::Triplet16th;
     323              :         }
     324            0 :         if (str == u"Dotted8th") {
     325            0 :             return GPMasterBar::TripletFeelType::Dotted8th;
     326              :         }
     327            0 :         if (str == u"Dotted16th") {
     328            0 :             return GPMasterBar::TripletFeelType::Dotted16th;
     329              :         }
     330            0 :         if (str == u"Scottish8th") {
     331            0 :             return GPMasterBar::TripletFeelType::Scottish8th;
     332              :         }
     333            0 :         if (str == u"Scottish16th") {
     334            0 :             return GPMasterBar::TripletFeelType::Scottish16th;
     335              :         }
     336            0 :         return GPMasterBar::TripletFeelType::None;
     337              :     };
     338              : 
     339          695 :     std::unique_ptr<GPMasterBar> masterBar = std::make_unique<GPMasterBar>();
     340              : 
     341          695 :     auto innerNode = masterBarNode->firstChild();
     342              : 
     343         3014 :     while (!innerNode.isNull()) {
     344         2319 :         String nodeName = innerNode.nodeName();
     345              : 
     346         2319 :         if (nodeName == u"Time") {
     347          695 :             masterBar->setTimeSig(readTimeSig(&innerNode));
     348         1624 :         } else if (nodeName == u"Repeat") {
     349            8 :             masterBar->setRepeat(readRepeat(&innerNode));
     350         1616 :         } else if (nodeName == u"AlternateEndings") {
     351            4 :             masterBar->setAlternativeEnding(readEnding(&innerNode));
     352         1612 :         } else if (nodeName == u"Key") {
     353          695 :             masterBar->setKeySig(readKeySig(&innerNode), readUseFlats(&innerNode));
     354          917 :         } else if (nodeName == u"Bars") {
     355          695 :             const String& barsElement = innerNode.toElement().text();
     356          695 :             const StringList& bars = barsElement.split(u' ');
     357         1473 :             for (const String& barIdx : bars) {
     358          778 :                 int idx = barIdx.toInt();
     359          778 :                 std::unique_ptr<GPBar> bar;
     360          778 :                 bar = std::move(_bars.at(idx));
     361          778 :                 _bars.erase(idx);
     362          778 :                 masterBar->addGPBar(std::move(bar));
     363          778 :             }
     364          917 :         } else if (nodeName == u"TripletFeel") {
     365            0 :             masterBar->setTripletFeel(tripletFeelType(innerNode.toElement().text()));
     366          222 :         } else if (nodeName == u"Fermatas") {
     367            6 :             masterBar->setFermatas(readFermatas(&innerNode));
     368          216 :         } else if (nodeName == u"Section") {
     369            3 :             masterBar->setSection(readMasterBarSection(innerNode));
     370          213 :         } else if (nodeName == u"Directions") {
     371           36 :             masterBar->setDirections(readRepeatsJumps(&innerNode));
     372          177 :         } else if (nodeName == u"DoubleBar") {
     373            3 :             masterBar->setBarlineType(GPMasterBar::BarlineType::DOUBLE);
     374          174 :         } else if (nodeName == u"FreeTime") {
     375            8 :             masterBar->setFreeTime(true);
     376          166 :         } else if (sUnused.find(nodeName) != sUnused.end()) {
     377              :             // Ignored
     378              :         }
     379              : 
     380         2319 :         innerNode = innerNode.nextSibling();
     381         2319 :     }
     382              : 
     383          695 :     return masterBar;
     384          695 : }
     385              : 
     386          778 : std::pair<int, std::unique_ptr<GPBar> > GP67DomBuilder::createGPBar(XmlDomNode* barNode)
     387              : {
     388          778 :     auto clefType = [](const String& clef) {
     389          778 :         if (clef == u"C4") {
     390            2 :             return GPBar::ClefType::C4;
     391          776 :         } else if (clef == u"C3") {
     392            2 :             return GPBar::ClefType::C3;
     393          774 :         } else if (clef == u"F4") {
     394           15 :             return GPBar::ClefType::F4;
     395          759 :         } else if (clef == u"G2") {
     396          727 :             return GPBar::ClefType::G2;
     397              :         } else {
     398           32 :             return GPBar::ClefType::Neutral;
     399              :         }
     400              :     };
     401              : 
     402            6 :     auto ottaviaType = [](const String& ott) {
     403            6 :         if (ott == u"8va") {
     404            2 :             return GPBar::OttaviaType::va8;
     405            4 :         } else if (ott == u"15ma") {
     406            2 :             return GPBar::OttaviaType::ma15;
     407            2 :         } else if (ott == u"8vb") {
     408            2 :             return GPBar::OttaviaType::vb8;
     409            0 :         } else if (ott == u"15mb") {
     410            0 :             return GPBar::OttaviaType::mb15;
     411              :         } else {
     412            0 :             return GPBar::OttaviaType::Regular;
     413              :         }
     414              :     };
     415            3 :     auto simileMarkType = [](const String& str) {
     416            3 :         if (str == u"Simple") {
     417            3 :             return GPBar::SimileMark::Simple;
     418            0 :         } else if (str == u"FirstOfDouble") {
     419            0 :             return GPBar::SimileMark::FirstOfDouble;
     420            0 :         } else if (str == u"SecondOfDouble") {
     421            0 :             return GPBar::SimileMark::SecondOfDouble;
     422              :         } else {
     423            0 :             return GPBar::SimileMark::None;
     424              :         }
     425              :     };
     426              : 
     427          778 :     std::unique_ptr<GPBar> bar = std::make_unique<GPBar>();
     428              : 
     429          778 :     auto innerNode = barNode->firstChild();
     430          778 :     int barIdx = barNode->toElement().attribute("id").value().toInt();
     431          778 :     bar->setId(barIdx);
     432              : 
     433         2357 :     while (!innerNode.isNull()) {
     434         1579 :         String nodeName = innerNode.nodeName();
     435              : 
     436         1579 :         if (nodeName == u"Clef") {
     437          778 :             bar->setClefType(clefType(innerNode.toElement().text()));
     438          801 :         } else if (nodeName == u"Ottavia") {
     439            6 :             bar->setOttaviaType(ottaviaType(innerNode.toElement().text()));
     440          795 :         } else if (nodeName == u"SimileMark") {
     441            3 :             bar->setSimileMark(simileMarkType(innerNode.toElement().text()));
     442          792 :         } else if (nodeName == u"Voices") {
     443          778 :             String voicesElement = innerNode.toElement().text();
     444          778 :             StringList voices = voicesElement.split(u' ');
     445          778 :             int currentPosition = -1;
     446         3890 :             for (const String& voiceIdx : voices) {
     447         3112 :                 currentPosition++;
     448         3112 :                 int idx = voiceIdx.toInt();
     449         3112 :                 if (idx == -1) {
     450         2388 :                     continue;
     451              :                 }
     452              : 
     453          724 :                 std::unique_ptr<GPVoice> voice;
     454          724 :                 voice = std::move(_voices.at(idx));
     455          724 :                 voice->setPosition(currentPosition);
     456          724 :                 _voices.erase(idx);
     457          724 :                 bar->addGPVoice(std::move(voice));
     458          724 :             }
     459          778 :         }
     460              : 
     461         1579 :         innerNode = innerNode.nextSibling();
     462         1579 :     }
     463              : 
     464         1556 :     return std::make_pair(barIdx, std::move(bar));
     465          778 : }
     466              : 
     467          724 : std::pair<int, std::unique_ptr<GPVoice> > GP67DomBuilder::createGPVoice(XmlDomNode* voiceNode)
     468              : {
     469          724 :     std::unique_ptr<GPVoice> voice = std::make_unique<GPVoice>();
     470              : 
     471          724 :     XmlDomNode innerNode = voiceNode->firstChild();
     472          724 :     int voiceIdx = voiceNode->toElement().attribute("id").value().toInt();
     473          724 :     voice->setId(voiceIdx);
     474              : 
     475         1448 :     while (!innerNode.isNull()) {
     476          724 :         auto nodeName = innerNode.nodeName();
     477          724 :         if (nodeName == u"Beats") {
     478          724 :             String beatsElement = innerNode.toElement().text();
     479          724 :             StringList beats = beatsElement.split(u' ');
     480         2857 :             for (const String& beatIdx : beats) {
     481         2133 :                 int idx = beatIdx.toInt();
     482         2133 :                 std::shared_ptr<GPBeat> beat;
     483         2133 :                 beat = _beats.at(idx);
     484         2133 :                 voice->addGPBeat(beat);
     485         2133 :             }
     486          724 :         }
     487              : 
     488          724 :         innerNode = innerNode.nextSibling();
     489          724 :     }
     490              : 
     491         1448 :     return std::make_pair(voiceIdx, std::move(voice));
     492          724 : }
     493              : 
     494         1497 : std::pair<int, std::shared_ptr<GPBeat> > GP67DomBuilder::createGPBeat(XmlDomNode* beatNode)
     495              : {
     496              :     static const std::set<String> sUnused = {
     497              :         u"Bank",
     498              :         u"StemOrientation", u"ConcertPitchStemOrientation",
     499              :         u"Ottavia"
     500         1497 :     };
     501              : 
     502         1497 :     auto dynamicType = [](String&& str) -> GPBeat::DynamicType {
     503         1497 :         if (str == u"FFF") {
     504            4 :             return GPBeat::DynamicType::FFF;
     505         1493 :         } else if (str == u"FF") {
     506           12 :             return GPBeat::DynamicType::FF;
     507         1481 :         } else if (str == u"F") {
     508          274 :             return GPBeat::DynamicType::F;
     509         1207 :         } else if (str == u"MF") {
     510         1174 :             return GPBeat::DynamicType::MF;
     511           33 :         } else if (str == u"MP") {
     512           19 :             return GPBeat::DynamicType::MP;
     513           14 :         } else if (str == u"P") {
     514            2 :             return GPBeat::DynamicType::P;
     515           12 :         } else if (str == u"PP") {
     516            4 :             return GPBeat::DynamicType::PP;
     517              :         } else {
     518            8 :             return GPBeat::DynamicType::PPP;
     519              :         }
     520              :     };
     521           59 :     auto legatoType = [](const String& origin, const String& destination) {
     522           59 :         if (origin == u"true" && destination == u"false") {
     523           20 :             return GPBeat::LegatoType::Start;
     524           39 :         } else if (origin == u"true" && destination == u"true") {
     525           27 :             return GPBeat::LegatoType::Mediate;
     526           12 :         } else if (origin == u"false" && destination == u"true") {
     527           12 :             return GPBeat::LegatoType::End;
     528              :         } else {
     529            0 :             return GPBeat::LegatoType::None;
     530              :         }
     531              :     };
     532            4 :     auto arpeggioType = [](const String& arp) {
     533            4 :         if (arp == u"Up") {
     534            2 :             return GPBeat::Arpeggio::Up;
     535            2 :         } else if (arp == u"Down") {
     536            2 :             return GPBeat::Arpeggio::Down;
     537              :         }
     538            0 :         return GPBeat::Arpeggio::None;
     539              :     };
     540           24 :     auto graceNotes = [](const String& gn) {
     541           24 :         if (gn == u"OnBeat") {
     542           10 :             return GPBeat::GraceNotes::OnBeat;
     543           14 :         } else if (gn == u"BeforeBeat") {
     544           14 :             return GPBeat::GraceNotes::BeforeBeat;
     545              :         }
     546            0 :         return GPBeat::GraceNotes::None;
     547              :     };
     548            7 :     auto faddingType = [](const String& str) {
     549            7 :         if (str == u"FadeIn") {
     550            3 :             return GPBeat::Fadding::FadeIn;
     551            4 :         } else if (str == u"FadeOut") {
     552            0 :             return GPBeat::Fadding::FadeOut;
     553            4 :         } else if (str == u"VolumeSwell") {
     554            4 :             return GPBeat::Fadding::VolumeSwell;
     555              :         }
     556            0 :         return GPBeat::Fadding::None;
     557              :     };
     558           14 :     auto hairpinType = [](const String& str) {
     559           14 :         if (str == u"Crescendo") {
     560            7 :             return GPBeat::Hairpin::Crescendo;
     561            7 :         } else if (str == u"Decrescendo") {
     562            7 :             return GPBeat::Hairpin::Decrescendo;
     563              :         }
     564            0 :         return GPBeat::Hairpin::None;
     565              :     };
     566            4 :     auto wahType = [](const String& str) {
     567            4 :         if (str == u"Open") {
     568            2 :             return GPBeat::Wah::Open;
     569            2 :         } else if (str == u"Closed") {
     570            2 :             return GPBeat::Wah::Closed;
     571              :         } else {
     572            0 :             return GPBeat::Wah::None;
     573              :         }
     574              :     };
     575              : 
     576            0 :     auto golpeType = [](const String& str) {
     577            0 :         if (str == u"Finger") {
     578            0 :             return GPBeat::Golpe::Finger;
     579            0 :         } else if (str == u"Thumb") {
     580            0 :             return GPBeat::Golpe::Thumb;
     581              :         }
     582              : 
     583            0 :         return GPBeat::Golpe::None;
     584              :     };
     585              : 
     586           51 :     auto ottavaType = [](const String& ott) {
     587           51 :         if (ott == u"8va") {
     588           30 :             return GPBeat::OttavaType::va8;
     589           21 :         } else if (ott == u"15ma") {
     590            3 :             return GPBeat::OttavaType::ma15;
     591           18 :         } else if (ott == u"8vb") {
     592           14 :             return GPBeat::OttavaType::vb8;
     593            4 :         } else if (ott == u"15mb") {
     594            4 :             return GPBeat::OttavaType::mb15;
     595              :         }
     596            0 :         LOGE() << "wrong ottava type: " << ott;
     597            0 :         return GPBeat::OttavaType::None;
     598              :     };
     599              : 
     600         1497 :     std::shared_ptr<GPBeat> beat = std::make_shared<GPBeat>();
     601              : 
     602         1497 :     auto innerNode = beatNode->firstChild();
     603         1497 :     int beatIdx = beatNode->toElement().attribute("id").value().toInt();
     604         1497 :     beat->setId(beatIdx);
     605              : 
     606         9669 :     while (!innerNode.isNull()) {
     607         8172 :         String nodeName = innerNode.nodeName();
     608              : 
     609         8172 :         if (nodeName == u"Dynamic") {
     610         1497 :             GPBeat::DynamicType dynamic = dynamicType(innerNode.toElement().text());
     611         1497 :             beat->setDynamic(dynamic);
     612         6675 :         } else if (nodeName == u"Legato") {
     613           59 :             String origin = innerNode.toElement().attribute("origin").value();
     614           59 :             String destination = innerNode.toElement().attribute("destination").value();
     615           59 :             GPBeat::LegatoType legato = legatoType(origin, destination);
     616           59 :             beat->setLegatoType(legato);
     617         6675 :         } else if (nodeName == u"Rhythm") {
     618         1497 :             int rIdx = innerNode.toElement().attribute("ref").value().toInt();
     619         1497 :             beat->addGPRhythm(_rhythms.at(rIdx));
     620         5119 :         } else if (nodeName == u"Notes") {
     621         1224 :             String notesStr = innerNode.toElement().text();
     622         1224 :             StringList strList = notesStr.split(u' ');
     623         2754 :             for (const auto& strIdx : strList) {
     624         1530 :                 int idx = strIdx.toInt();
     625         1530 :                 std::shared_ptr<GPNote> note;
     626         1530 :                 note = _notes.at(idx);
     627         1530 :                 beat->addGPNote(note);
     628         1530 :             }
     629         1224 :             beat->sortGPNotes();
     630         5119 :         } else if (nodeName == u"GraceNotes") {
     631           24 :             beat->setGraceNotes(graceNotes(innerNode.toElement().text()));
     632         3871 :         } else if (nodeName == u"Arpeggio") {
     633            4 :             beat->setArpeggio(arpeggioType(innerNode.toElement().text()));
     634         3867 :         } else if (nodeName == u"Properties") {
     635         1480 :             readBeatProperties(innerNode, beat.get());
     636         2387 :         } else if (nodeName == u"Chord") {
     637           20 :             beat->setDiagramIdx(innerNode.toElement().text().toInt());
     638         2367 :         } else if (nodeName == u"Timer") {
     639            6 :             beat->setTime(innerNode.toElement().text().toInt());
     640         2361 :         } else if (nodeName == u"FreeText") {
     641            7 :             beat->setFreeText(innerNode.toElement().text());
     642         2354 :         } else if (nodeName == u"Fadding") {
     643            7 :             beat->setFadding(faddingType(innerNode.toElement().text()));
     644         2347 :         } else if (nodeName == u"Hairpin") {
     645           14 :             beat->setHairpin(hairpinType(innerNode.toElement().text()));
     646         2333 :         } else if (nodeName == u"Tremolo") {
     647            6 :             GPBeat::Tremolo tr;
     648            6 :             String trStr = innerNode.toElement().text();
     649            6 :             StringList trList = trStr.split(u'/');
     650            6 :             tr.numerator = trList.at(0).toInt();
     651            6 :             tr.denominator = trList.at(1).toInt();
     652            6 :             beat->setTremolo(tr);
     653         2333 :         } else if (nodeName == u"Wah") {
     654            4 :             beat->setWah(wahType(innerNode.toElement().text()));
     655         2323 :         } else if (nodeName == u"Golpe") {
     656            0 :             beat->setGolpe(golpeType(innerNode.toElement().text()));
     657         2323 :         } else if (nodeName == u"Lyrics") {
     658              :             // this code reads lyrics for the beat (only one line).
     659              : 
     660            0 :             XmlDomElement lyrNode = innerNode.firstChildElement("Line");
     661            0 :             String str = lyrNode.toElement().text();
     662            0 :             beat->setLyrics(str.toStdString());
     663         2323 :         } else if (nodeName == u"Ottavia") {
     664           51 :             beat->setOttavaType(ottavaType(innerNode.toElement().text()));
     665         2272 :         } else if (nodeName == u"Whammy" || nodeName == u"WhammyExtend") {
     666           74 :             beat->setWhammy(readWhammy(innerNode.toElement()));
     667         2198 :         } else if (nodeName == u"DeadSlapped") {
     668            0 :             beat->setDeadSlapped(true);
     669         2198 :         } else if (nodeName == u"TransposedPitchStemOrientation") {
     670         1041 :             beat->setStemOrientationUp(innerNode.toElement().text() == u"Upward");
     671         1157 :         } else if (nodeName == u"TransposedPitchStemOrientationUserDefined") {
     672            0 :             beat->setStemOrientationUserDefined(true);
     673         1157 :         } else if (nodeName == u"XProperties") {
     674           42 :             readBeatXProperties(innerNode, beat.get());
     675         1115 :         } else if (sUnused.find(nodeName) != sUnused.end()) {
     676              :             // Ignored nodes
     677              :         }
     678              : 
     679         8172 :         innerNode = innerNode.nextSibling();
     680         8172 :     }
     681              : 
     682         2994 :     return std::make_pair(beatIdx, std::move(beat));
     683         1497 : }
     684              : 
     685         1340 : std::pair<int, std::shared_ptr<GPNote> > GP67DomBuilder::createGPNote(XmlDomNode* noteNode)
     686              : {
     687          131 :     auto tieType = [](const String& origin, const String& destination) ->GPNote::TieType {
     688          131 :         if (origin == u"true" && destination == u"false") {
     689           55 :             return GPNote::TieType::Start;
     690           76 :         } else if (origin == u"true" && destination == u"true") {
     691           18 :             return GPNote::TieType::Mediate;
     692           58 :         } else if (origin == u"false" && destination == u"true") {
     693           58 :             return GPNote::TieType::End;
     694              :         } else {
     695            0 :             return GPNote::TieType::None;
     696              :         }
     697              :     };
     698            9 :     auto vibratoType = [](const String& str) {
     699            9 :         if (str == u"Wide") {
     700            3 :             return GPNote::VibratoType::Wide;
     701            6 :         } else if (str == u"Slight") {
     702            6 :             return GPNote::VibratoType::Slight;
     703              :         } else {
     704            0 :             return GPNote::VibratoType::None;
     705              :         }
     706              :     };
     707            8 :     auto ornamentType = [](const String& str) {
     708            8 :         if (str == u"UpperMordent") {
     709            2 :             return GPNote::Ornament::UpperMordent;
     710            6 :         } else if (str == u"LowerMordent") {
     711            2 :             return GPNote::Ornament::LowerMordent;
     712            4 :         } else if (str == u"InvertedTurn") {
     713            2 :             return GPNote::Ornament::InvertedTurn;
     714            2 :         } else if (str == u"Turn") {
     715            2 :             return GPNote::Ornament::Turn;
     716              :         } else {
     717            0 :             return GPNote::Ornament::None;
     718              :         }
     719              :     };
     720              : 
     721         1340 :     auto note = std::make_shared<GPNote>();
     722         1340 :     int noteIdx = noteNode->toElement().attribute("id").value().toInt();
     723         1340 :     note->setId(noteIdx);
     724              : 
     725         1340 :     auto innerNode = noteNode->firstChild();
     726         3908 :     while (!innerNode.isNull()) {
     727         2568 :         String nodeName = innerNode.nodeName();
     728              : 
     729         2568 :         if (nodeName == u"Accidental") {
     730              :             std::map<String, int> accidentals = {
     731            0 :                 { u"DoubleFlat", -2 },
     732            0 :                 { u"Flat", -1 },
     733            0 :                 { u"Natural", 0 },
     734            0 :                 { u"Sharp", +1 },
     735            0 :                 { u"DoubleSharp", +2 }
     736          301 :             };
     737              : 
     738           43 :             String accidentalName = innerNode.toElement().text();
     739              : 
     740           43 :             if (accidentals.find(accidentalName) != accidentals.end()) {
     741           43 :                 note->setAccidental(accidentals[accidentalName]);
     742              :             }
     743           43 :         }
     744         2568 :         if (nodeName == u"InstrumentArticulation") {
     745              :         }
     746         2568 :         if (nodeName == u"Properties") {
     747         1340 :             readNoteProperties(&innerNode, note.get());
     748              :         }
     749         2568 :         if (nodeName == u"XProperties") {
     750            2 :             readNoteXProperties(innerNode, note.get());
     751              :         }
     752         2568 :         if (nodeName == u"Tie") {
     753          131 :             String origin = innerNode.toElement().attribute("origin").value();
     754          131 :             String destination = innerNode.toElement().attribute("destination").value();
     755          131 :             GPNote::TieType tie = tieType(origin, destination);
     756          131 :             note->setTieType(tie);
     757          131 :         }
     758         2568 :         if (nodeName == u"LetRing") {
     759            8 :             note->setLetRing(true);
     760         2560 :         } else if (nodeName == u"AntiAccent") {
     761            2 :             note->setGhostNote(innerNode.toElement().text() == u"Normal");
     762         2558 :         } else if (nodeName == u"Accent") {
     763            6 :             note->setAccent(innerNode.toElement().text().toUInt());
     764         2552 :         } else if (nodeName == u"LeftFingering") {
     765           12 :             String finger = innerNode.toElement().text();
     766           12 :             if (finger == u"Open") {
     767            2 :                 finger = u"0";
     768           10 :             } else if (finger == u"P") {
     769            2 :                 finger = u"t";
     770            8 :             } else if (finger == u"I") {
     771            2 :                 finger = u"1";
     772            6 :             } else if (finger == u"M") {
     773            2 :                 finger = u"2";
     774            4 :             } else if (finger == u"A") {
     775            2 :                 finger = u"3";
     776            2 :             } else if (finger == u"C") {
     777            2 :                 finger = u"4";
     778              :             }
     779           12 :             note->setLeftFingering(finger);
     780         2552 :         } else if (nodeName == "RightFingering") {
     781           10 :             note->setRightFingering(innerNode.toElement().text().toLower());
     782         2530 :         } else if (nodeName == "Vibrato") {
     783            9 :             note->setVibratoType(vibratoType(innerNode.toElement().text()));
     784         2521 :         } else if (nodeName == "Trill") {
     785            2 :             note->setTrillFret(innerNode.toElement().text().toInt());
     786         2519 :         } else if (nodeName == "Ornament") {
     787            8 :             note->setOrnament(ornamentType(innerNode.toElement().text()));
     788              :         }
     789              : 
     790         2568 :         innerNode = innerNode.nextSibling();
     791         2568 :     }
     792              : 
     793         2680 :     return std::make_pair(noteIdx, std::move(note));
     794         1383 : }
     795              : 
     796          347 : std::pair<int, std::shared_ptr<GPRhythm> > GP67DomBuilder::createGPRhythm(XmlDomNode* rhythmNode)
     797              : {
     798          347 :     auto rhythmType = [](const String& str) -> GPRhythm::RhytmType {
     799          347 :         if (str == u"Whole") {
     800           49 :             return GPRhythm::RhytmType::Whole;
     801          298 :         } else if (str == u"Half") {
     802           58 :             return GPRhythm::RhytmType::Half;
     803          240 :         } else if (str == u"Quarter") {
     804          173 :             return GPRhythm::RhytmType::Quarter;
     805           67 :         } else if (str == u"Eighth") {
     806           41 :             return GPRhythm::RhytmType::Eighth;
     807           26 :         } else if (str == u"16th") {
     808           17 :             return GPRhythm::RhytmType::Sixteenth;
     809            9 :         } else if (str == u"32nd") {
     810            9 :             return GPRhythm::RhytmType::ThirtySecond;
     811              :         } else {
     812            0 :             return GPRhythm::RhytmType::SixtyFourth;
     813              :         }
     814              :     };
     815              : 
     816          347 :     std::shared_ptr<GPRhythm> rhythm = std::make_shared<GPRhythm>();
     817              : 
     818          347 :     auto innerNode = rhythmNode->firstChild();
     819          347 :     int rhythmIdx = rhythmNode->toElement().attribute("id").value().toInt();
     820          719 :     while (!innerNode.isNull()) {
     821          372 :         String nodeName = innerNode.nodeName();
     822          372 :         if (nodeName == u"NoteValue") {
     823          347 :             auto rhType = rhythmType(innerNode.toElement().text());
     824          347 :             rhythm->setRhytm(rhType);
     825              :         }
     826          372 :         if (nodeName == u"AugmentationDot") {
     827           11 :             rhythm->setDotCount(innerNode.toElement().attribute("count").value().toInt());
     828          361 :         } else if (nodeName == u"PrimaryTuplet") {
     829           14 :             int num = innerNode.toElement().attribute("num").value().toInt();
     830           14 :             int denom = innerNode.toElement().attribute("den").value().toInt();
     831           14 :             rhythm->setTuplet({ num, denom });
     832              :         }
     833              : 
     834          372 :         innerNode = innerNode.nextSibling();
     835          372 :     }
     836              : 
     837          694 :     return std::make_pair(rhythmIdx, std::move(rhythm));
     838          347 : }
     839              : 
     840          238 : GPTrack::RSE GP67DomBuilder::readTrackRSE(XmlDomNode* trackChildNode) const
     841              : {
     842          238 :     XmlDomNode innerNode = trackChildNode->firstChild();
     843          238 :     String nodeName = innerNode.nodeName();
     844              : 
     845          238 :     if (!innerNode.isNull() && nodeName == u"ChannelStrip") {
     846          238 :         innerNode = innerNode.firstChildElement("Parameters");
     847          238 :         if (!innerNode.isNull()) {
     848          238 :             String str = innerNode.toElement().text();
     849          238 :             StringList strList = str.split(u' ');
     850          238 :             GPTrack::RSE rse;
     851          238 :             rse.pan = strList.at(11).toFloat();
     852          238 :             rse.volume = strList.at(12).toFloat();
     853          238 :             return rse;
     854          238 :         }
     855              :     }
     856              : 
     857            0 :     return GPTrack::RSE();
     858          238 : }
     859              : 
     860          695 : GPMasterBar::KeySig GP67DomBuilder::readKeySig(XmlDomNode* keyNode) const
     861              : {
     862          695 :     const auto& accidentalCount = keyNode->firstChildElement("AccidentalCount");
     863          695 :     const auto& modeNode = keyNode->firstChildElement("Mode");
     864              : 
     865          695 :     String modeName = modeNode.toElement().text();
     866              : 
     867          695 :     GPMasterBar::KeySig::Mode mode = GPMasterBar::KeySig::Mode::Major;
     868          695 :     if (modeName == "Minor") {
     869           56 :         mode = GPMasterBar::KeySig::Mode::Minor;
     870              :     }
     871              : 
     872          695 :     int keyCount = accidentalCount.toElement().text().toInt();
     873              : 
     874          695 :     return GPMasterBar::KeySig{ GPMasterBar::KeySig::Accidentals(keyCount), mode };
     875          695 : }
     876              : 
     877          695 : bool GP67DomBuilder::readUseFlats(XmlDomNode* keyNode) const
     878              : {
     879          695 :     const auto& transposeAs = keyNode->firstChildElement("TransposeAs");
     880          695 :     if (transposeAs.isNull()) {
     881          457 :         return false;
     882              :     }
     883          238 :     return transposeAs.toElement().text() == "Flats";
     884          695 : }
     885              : 
     886          695 : GPMasterBar::TimeSig GP67DomBuilder::readTimeSig(XmlDomNode* timeNode) const
     887              : {
     888          695 :     const String time = timeNode->toElement().text();
     889          695 :     const StringList timeSig = time.split(u'/');
     890          695 :     GPMasterBar::TimeSig sig { timeSig.at(0).toInt(), timeSig.at(1).toInt() };
     891          695 :     return sig;
     892          695 : }
     893              : 
     894            2 : void GP67DomBuilder::readNoteXProperties(const XmlDomNode& propertiesNode, GPNote* note)
     895              : {
     896            2 :     auto propertyNode = propertiesNode.firstChild();
     897              : 
     898            4 :     while (!propertyNode.isNull()) {
     899            2 :         int propertyId = propertyNode.toElement().attribute("id").value().toInt();
     900              : 
     901            2 :         if (propertyId == 688062467) {
     902            2 :             note->setTrillSpeed(propertyNode.firstChild().toElement().text().toInt());
     903              :         }
     904              : 
     905            2 :         propertyNode = propertyNode.nextSibling();
     906              :     }
     907            2 : }
     908              : 
     909         1340 : void GP67DomBuilder::readNoteProperties(XmlDomNode* propertiesNode, GPNote* note)
     910              : {
     911         1340 :     std::unordered_set<std::unique_ptr<INoteProperty> > properties;
     912              : 
     913         1340 :     auto propertyNode = propertiesNode->firstChild();
     914              : 
     915         7961 :     while (!propertyNode.isNull()) {
     916         6621 :         auto propertyName = propertyNode.toElement().attribute("name").value();
     917              : 
     918         6621 :         if (propertyName == u"Midi") {
     919          995 :             int midi = propertyNode.firstChild().toElement().text().toInt();
     920          995 :             note->setMidi(midi);
     921              :         }
     922         6621 :         if (propertyName == u"Variation") {
     923            0 :             note->setVariation(propertyNode.firstChild().toElement().text().toInt());
     924              :         }
     925         6621 :         if (propertyName == u"Element") {
     926            0 :             note->setElement(propertyNode.firstChild().toElement().text().toInt());
     927         6621 :         } else if (propertyName == u"String") {
     928         1329 :             int string = propertyNode.firstChild().toElement().text().toInt();
     929         1329 :             note->setString(string);
     930         5292 :         } else if (propertyName == u"Fret") {
     931         1329 :             int fret = propertyNode.firstChild().toElement().text().toInt();
     932         1329 :             note->setFret(fret);
     933         3963 :         } else if (propertyName == u"Octave") {
     934           11 :             int octave = propertyNode.firstChild().toElement().text().toInt();
     935           11 :             note->setOctave(octave);
     936         3952 :         } else if (propertyName == u"ConcertPitch") {
     937          995 :             auto pitchNode = propertyNode.firstChild();
     938          995 :             auto innerNode = pitchNode.firstChild();
     939         3980 :             while (!innerNode.isNull()) {
     940         2985 :                 String nodeName = innerNode.nodeName();
     941         2985 :                 if (nodeName == u"Accidental") {
     942              :                     std::map<String, int> accidentals = {
     943            0 :                         { u"bb", -2 },
     944            0 :                         { u"b",  -1 },
     945            0 :                         { u"#",  +1 },
     946            0 :                         { u"x",  +2 },
     947         5970 :                     };
     948              : 
     949          995 :                     String accidentalName = innerNode.toElement().text();
     950          995 :                     if (!accidentalName.isEmpty() && accidentals.find(accidentalName) != accidentals.end()) {
     951           96 :                         note->setAccidental(accidentals[accidentalName]);
     952              :                     } else {
     953          899 :                         note->setAccidental(0);
     954              :                     }
     955          995 :                 }
     956              : 
     957         2985 :                 innerNode = innerNode.nextSibling();
     958         2985 :             }
     959         3952 :         } else if (propertyName == u"Tone") {
     960           11 :             int tone = propertyNode.firstChild().toElement().text().toInt();
     961           11 :             note->setTone(tone);
     962         2946 :         } else if (propertyName == u"Bended") {
     963           94 :             if (propertyNode.firstChild().nodeName() == "Enable") {
     964           94 :                 note->setBend(createBend(&propertyNode));
     965              :             }
     966         2852 :         } else if (propertyName == u"Harmonic"
     967         2819 :                    || propertyName == u"HarmonicFret"
     968         5671 :                    || propertyName == u"HarmonicType") {
     969          115 :             readHarmonic(&propertyNode, note);
     970         2737 :         } else if (propertyName == u"PalmMuted") {
     971            3 :             if (propertyNode.firstChild().nodeName() == "Enable") {
     972            3 :                 note->setPalmMute(true);
     973              :             }
     974         2734 :         } else if (propertyName == u"Muted") {
     975              :             //! property muted in GP means dead note
     976            9 :             if (propertyNode.firstChild().nodeName() == "Enable") {
     977            9 :                 note->setMute(true);
     978              :             }
     979         2725 :         } else if (propertyName == u"Slide") {
     980           45 :             int slideInfo = propertyNode.firstChild().toElement().text().toUInt();
     981           45 :             switch (slideInfo) {
     982            1 :             case 64:
     983            1 :                 note->setPickScrape(GPNote::PickScrape::Down);
     984            1 :                 break;
     985            0 :             case 128:
     986            0 :                 note->setPickScrape(GPNote::PickScrape::Up);
     987            0 :                 break;
     988           44 :             default:
     989           44 :                 note->setSlides(slideInfo);
     990           44 :                 break;
     991              :             }
     992         2680 :         } else if (propertyName == u"HopoOrigin") {
     993           35 :             note->setHammerOn(GPNote::HammerOn::Start);
     994         2645 :         } else if (propertyName == u"Tapped") {
     995            2 :             if (propertyNode.firstChild().nodeName() == "Enable") {
     996            2 :                 note->setRightHandTapping(true);
     997              :             }
     998         2643 :         } else if (propertyName == u"LeftHandTapped") {
     999            0 :             if (propertyNode.firstChild().nodeName() == "Enable") {
    1000            0 :                 note->setLeftHandTapped(true);
    1001              :             }
    1002         2643 :         } else if (propertyName == "ShowStringNumber") {
    1003            0 :             note->setShowStringNumber(true);
    1004              :         }
    1005              : 
    1006         6621 :         propertyNode = propertyNode.nextSibling();
    1007         6621 :     }
    1008              : 
    1009         1340 :     note->addProperties(std::move(properties));
    1010         2680 :     return;
    1011         2335 : }
    1012              : 
    1013           42 : void GP67DomBuilder::readBeatXProperties(const XmlDomNode& propertiesNode, GPBeat* beat)
    1014              : {
    1015           42 :     auto propertyNode = propertiesNode.firstChild();
    1016              : 
    1017           42 :     bool brokenBeams = false;
    1018           42 :     bool brokenSecondaryBeams = false;
    1019           42 :     bool joinedBeams = false;
    1020              : 
    1021           92 :     while (!propertyNode.isNull()) {
    1022           50 :         int propertyId = propertyNode.toElement().attribute("id").value().toInt();
    1023              : 
    1024           50 :         if (propertyId == 687931393 || propertyId == 687935489) {
    1025              :             // arpeggio/brush ticks
    1026            8 :             beat->setArpeggioStretch(propertyNode.firstChild().toElement().text().toDouble() / mu::engraving::Constants::DIVISION);
    1027           42 :         } else if (propertyId == 1124204546) {
    1028           22 :             int beamData = propertyNode.firstChild().toElement().text().toInt();
    1029              : 
    1030           22 :             if (beamData == 1) {
    1031           11 :                 joinedBeams = true;
    1032           11 :             } else if (beamData == 2) {
    1033           11 :                 brokenBeams = true;
    1034              :             }
    1035           20 :         } else if (propertyId == 1124204552) {
    1036            7 :             int beamData = propertyNode.firstChild().toElement().text().toInt();
    1037            7 :             if (beamData == 1) {
    1038            7 :                 brokenSecondaryBeams = true;
    1039              :             }
    1040              :         }
    1041              : 
    1042           50 :         propertyNode = propertyNode.nextSibling();
    1043              :     }
    1044              : 
    1045           42 :     if (brokenBeams) {
    1046           11 :         beat->setBeamMode(GPBeat::BeamMode::BROKEN);
    1047           31 :     } else if (brokenSecondaryBeams) {
    1048            7 :         beat->setBeamMode(joinedBeams ? GPBeat::BeamMode::BROKEN2_JOINED : GPBeat::BeamMode::BROKEN2);
    1049           24 :     } else if (joinedBeams) {
    1050           11 :         beat->setBeamMode(GPBeat::BeamMode::JOINED);
    1051              :     }
    1052           42 : }
    1053              : 
    1054           74 : GPBeat::Whammy GP67DomBuilder::readWhammy(const XmlDomElement& elem) const
    1055              : {
    1056           74 :     GPBeat::Whammy whammy;
    1057           74 :     whammy.originValue = elem.attribute("originValue").value().toFloat();
    1058           74 :     whammy.destinationValue = elem.attribute("destinationValue").value().toFloat();
    1059           74 :     whammy.middleValue = elem.attribute("middleValue").value().toFloat();
    1060           74 :     whammy.originOffset = elem.attribute("originOffset").value().toFloat();
    1061           74 :     whammy.middleOffset1 = elem.attribute("middleOffset1").value().toFloat();
    1062           74 :     whammy.middleOffset2 = elem.attribute("middleOffset2").value().toFloat();
    1063           74 :     whammy.destinationOffset = elem.attribute("destinationOffset").value().toFloat();
    1064           74 :     return whammy;
    1065              : }
    1066              : 
    1067           94 : std::unique_ptr<GPNote::Bend> GP67DomBuilder::createBend(XmlDomNode* propertyNode)
    1068              : {
    1069           94 :     std::unique_ptr<GPNote::Bend> bend = std::make_unique<GPNote::Bend>();
    1070              : 
    1071           94 :     auto currentNode = propertyNode->nextSibling();
    1072         1146 :     while (!currentNode.isNull()) {
    1073         1052 :         String propertyName = currentNode.toElement().attribute("name").value();
    1074         1052 :         if (propertyName == u"BendDestinationOffset") {
    1075           86 :             bend->destinationOffset = currentNode.firstChild().toElement().text().toFloat();
    1076          966 :         } else if (propertyName == u"BendDestinationValue") {
    1077           94 :             bend->destinationValue = currentNode.firstChild().toElement().text().toFloat();
    1078          872 :         } else if (propertyName == u"BendMiddleOffset1") {
    1079           86 :             bend->middleOffset1 = currentNode.firstChild().toElement().text().toFloat();
    1080          786 :         } else if (propertyName == u"BendMiddleOffset2") {
    1081           86 :             bend->middleOffset2 = currentNode.firstChild().toElement().text().toFloat();
    1082          700 :         } else if (propertyName == u"BendMiddleValue") {
    1083           87 :             bend->middleValue = currentNode.firstChild().toElement().text().toFloat();
    1084          613 :         } else if (propertyName == u"BendOriginOffset") {
    1085           86 :             bend->originOffset = currentNode.firstChild().toElement().text().toFloat();
    1086          527 :         } else if (propertyName == u"BendOriginValue") {
    1087           94 :             bend->originValue = currentNode.firstChild().toElement().text().toFloat();
    1088              :         }
    1089              : 
    1090         1052 :         currentNode = currentNode.nextSibling();
    1091         1052 :     }
    1092              : 
    1093           94 :     return bend;
    1094           94 : }
    1095              : 
    1096          115 : void GP67DomBuilder::readHarmonic(XmlDomNode* propertyNode, GPNote* note) const
    1097              : {
    1098           41 :     auto harmonicType = [](const String& str) {
    1099           41 :         if (str == u"Artificial") {
    1100           21 :             return GPNote::Harmonic::Type::Artificial;
    1101           20 :         } else if (str == u"Semi") {
    1102            4 :             return GPNote::Harmonic::Type::Semi;
    1103           16 :         } else if (str == u"Pinch") {
    1104            2 :             return GPNote::Harmonic::Type::Pinch;
    1105           14 :         } else if (str == u"Feedback") {
    1106            8 :             return GPNote::Harmonic::Type::FeedBack;
    1107            6 :         } else if (str == u"Natural") {
    1108            3 :             return GPNote::Harmonic::Type::Natural;
    1109              :         } else {
    1110            3 :             return GPNote::Harmonic::Type::Tap;
    1111              :         }
    1112              :     };
    1113              : 
    1114          115 :     String propertyName = propertyNode->toElement().attribute("name").value();
    1115          115 :     if (propertyName == u"HarmonicFret") {
    1116           41 :         note->setHarmonicFret(propertyNode->firstChild().toElement().text().toFloat());
    1117           74 :     } else if (propertyName == u"HarmonicType") {
    1118           41 :         note->setHarmonicType(harmonicType(propertyNode->firstChild().toElement().text()));
    1119              :     }
    1120          115 : }
    1121              : 
    1122         1480 : void GP67DomBuilder::readBeatProperties(const XmlDomNode& propertiesNode, GPBeat* beat) const
    1123              : {
    1124            4 :     auto brushType = [](const String& brush) {
    1125            4 :         if (brush == u"Down") {
    1126            2 :             return GPBeat::Brush::Down;
    1127              :         }
    1128            2 :         return GPBeat::Brush::Up;
    1129              :     };
    1130            8 :     auto vibratoType = [](const String& str) {
    1131            8 :         if (str == u"Wide") {
    1132            3 :             return GPBeat::VibratoWTremBar::Wide;
    1133            5 :         } else if (str == u"Slight") {
    1134            5 :             return GPBeat::VibratoWTremBar::Slight;
    1135              :         } else {
    1136            0 :             return GPBeat::VibratoWTremBar::None;
    1137              :         }
    1138              :     };
    1139            5 :     auto rasgueadoType = [](const String& str) {
    1140            5 :         if (str == u"ii_1") {
    1141            0 :             return GPBeat::Rasgueado::II_1;
    1142            5 :         } else if (str == u"mi_1") {
    1143            0 :             return GPBeat::Rasgueado::MII_1;
    1144            5 :         } else if (str == u"mii_1") {
    1145            0 :             return GPBeat::Rasgueado::MII_1;
    1146            5 :         } else if (str == u"mii_2") {
    1147            0 :             return GPBeat::Rasgueado::MII_2;
    1148            5 :         } else if (str == u"ami_1") {
    1149            0 :             return GPBeat::Rasgueado::AMI_1;
    1150            5 :         } else if (str == u"ami_2") {
    1151            0 :             return GPBeat::Rasgueado::AMI_2;
    1152            5 :         } else if (str == u"pai_1") {
    1153            3 :             return GPBeat::Rasgueado::PAI_1;
    1154            2 :         } else if (str == u"pai_2") {
    1155            0 :             return GPBeat::Rasgueado::PAI_2;
    1156            2 :         } else if (str == u"pei_1") {
    1157            0 :             return GPBeat::Rasgueado::PEI_1;
    1158            2 :         } else if (str == u"pei_2") {
    1159            0 :             return GPBeat::Rasgueado::PEI_2;
    1160            2 :         } else if (str == u"pmp_1") {
    1161            0 :             return GPBeat::Rasgueado::PMP_1;
    1162            2 :         } else if (str == u"pmp_2") {
    1163            0 :             return GPBeat::Rasgueado::PMP_2;
    1164            2 :         } else if (str == u"ppp_1") {
    1165            0 :             return GPBeat::Rasgueado::PPP_1;
    1166            2 :         } else if (str == u"amii_1") {
    1167            0 :             return GPBeat::Rasgueado::AMII_1;
    1168            2 :         } else if (str == u"amip_1") {
    1169            0 :             return GPBeat::Rasgueado::AMIP_1;
    1170            2 :         } else if (str == u"eami_1") {
    1171            2 :             return GPBeat::Rasgueado::EAMI_1;
    1172            0 :         } else if (str == u"eamii_1") {
    1173            0 :             return GPBeat::Rasgueado::EAMII_1;
    1174            0 :         } else if (str == u"peami_1") {
    1175            0 :             return GPBeat::Rasgueado::PEAMI_1;
    1176              :         } else {
    1177            0 :             return GPBeat::Rasgueado::None;
    1178              :         }
    1179              :     };
    1180            4 :     auto pickStrokeType = [](const String& str) {
    1181            4 :         if (str == u"Up") {
    1182            2 :             return GPBeat::PickStroke::Up;
    1183            2 :         } else if (str == u"Down") {
    1184            2 :             return GPBeat::PickStroke::Down;
    1185              :         }
    1186            0 :         return GPBeat::PickStroke::None;
    1187              :     };
    1188              : 
    1189         1480 :     GPBeat::Whammy whammyData;
    1190         1480 :     bool hasWhammyMarker = false;
    1191         1480 :     auto propertyNode = propertiesNode.firstChild();
    1192              : 
    1193         3816 :     while (!propertyNode.isNull()) {
    1194         2336 :         String propertyName = propertyNode.toElement().attribute("name").value();
    1195              : 
    1196         2336 :         if (propertyName == u"Popped") {
    1197            6 :             if (propertyNode.firstChild().nodeName() == "Enable") {
    1198            6 :                 beat->setPopped(true);
    1199              :             }
    1200         2330 :         } else if (propertyName == u"Slapped") {
    1201            7 :             if (propertyNode.firstChild().nodeName() == "Enable") {
    1202            7 :                 beat->setSlapped(true);
    1203              :             }
    1204         2323 :         } else if (propertyName == u"Brush") {
    1205            4 :             beat->setBrush(brushType(propertyNode.firstChild().toElement().text()));
    1206         2319 :         } else if (propertyName == u"VibratoWTremBar") {
    1207            8 :             beat->setVibratoWTremBar(vibratoType(propertyNode.firstChild().toElement().text()));
    1208         2311 :         } else if (propertyName == u"Rasgueado") {
    1209            5 :             beat->setRasgueado(rasgueadoType(propertyNode.firstChild().toElement().text()));
    1210         2306 :         } else if (propertyName == u"PickStroke") {
    1211            4 :             beat->setPickStroke(pickStrokeType(propertyNode.firstChild().toElement().text()));
    1212         2302 :         } else if (propertyName == u"BarreFret") {
    1213            4 :             beat->setBarreFret(propertyNode.firstChild().toElement().text().toInt());
    1214         2298 :         } else if (propertyName == u"BarreString") {
    1215            3 :             beat->setBarreString(propertyNode.firstChild().toElement().text().toInt());
    1216         2295 :         } else if (propertyName == u"WhammyBar") {
    1217           12 :             hasWhammyMarker = true;
    1218         2283 :         } else if (propertyName == u"WhammyBarOriginOffset") {
    1219            2 :             whammyData.originOffset = propertyNode.firstChild().toElement().text().toFloat();
    1220         2281 :         } else if (propertyName == u"WhammyBarOriginValue") {
    1221           62 :             whammyData.originValue = propertyNode.firstChild().toElement().text().toFloat();
    1222         2219 :         } else if (propertyName == u"WhammyBarDestinationValue") {
    1223           62 :             whammyData.destinationValue = propertyNode.firstChild().toElement().text().toFloat();
    1224         2157 :         } else if (propertyName == u"WhammyBarMiddleValue") {
    1225           60 :             whammyData.middleValue = propertyNode.firstChild().toElement().text().toFloat();
    1226         2097 :         } else if (propertyName == u"WhammyBarDestinationOffset") {
    1227            5 :             whammyData.destinationOffset = propertyNode.firstChild().toElement().text().toFloat();
    1228         2092 :         } else if (propertyName == u"WhammyBarMiddleOffset1") {
    1229            5 :             whammyData.middleOffset1 = propertyNode.firstChild().toElement().text().toFloat();
    1230         2087 :         } else if (propertyName == u"WhammyBarMiddleOffset2") {
    1231            5 :             whammyData.middleOffset2 = propertyNode.firstChild().toElement().text().toFloat();
    1232              :         }
    1233              : 
    1234         2336 :         propertyNode = propertyNode.nextSibling();
    1235         2336 :     }
    1236              : 
    1237         1480 :     if (hasWhammyMarker && !whammyData.isEmpty() && !beat->hasWhammy()) {
    1238            6 :         beat->setWhammy(whammyData);
    1239              :     }
    1240         1480 : }
    1241              : 
    1242          241 : void GP67DomBuilder::readTrackProperties(XmlDomNode* propertiesNode, GPTrack* track, bool ignoreTuningFlats) const
    1243              : {
    1244          241 :     GPTrack::StaffProperties properties;
    1245          241 :     properties.ignoreFlats = ignoreTuningFlats;
    1246              : 
    1247          241 :     auto propertyNode = propertiesNode->firstChild();
    1248              : 
    1249         2075 :     while (!propertyNode.isNull()) {
    1250         1834 :         String propertyName = propertyNode.toElement().attribute("name").value();
    1251              : 
    1252         1834 :         if (propertyName == u"CapoFret") {
    1253          159 :             properties.capoFret = propertyNode.firstChild().toElement().text().toInt();
    1254         1675 :         } else if (propertyName == u"FretCount") {
    1255          159 :             properties.fretCount = propertyNode.firstChild().toElement().text().toInt();
    1256         1516 :         } else if (propertyName == "Tuning") {
    1257          239 :             String tuningStr = propertyNode.firstChildElement("Pitches").text();
    1258          239 :             std::vector<int> tunning;
    1259          239 :             tunning.reserve(6);
    1260         1668 :             for (const String& val : tuningStr.split(u' ')) {
    1261         1429 :                 tunning.push_back(val.toInt());
    1262          239 :             }
    1263          239 :             properties.tuning.swap(tunning);
    1264          239 :             properties.useFlats = !propertyNode.firstChildElement("Flat").isNull();
    1265         1516 :         } else if (propertyName == u"TuningFlat") {
    1266           57 :             properties.useFlats = !propertyNode.firstChildElement("Enable").isNull();
    1267         1220 :         } else if (propertyName == u"DiagramCollection" || propertyName == u"DiagramWorkingSet") {
    1268          476 :             readDiagram(propertyNode.firstChild(), track);
    1269              :         }
    1270              : 
    1271         1834 :         propertyNode = propertyNode.nextSibling();
    1272         1834 :     }
    1273              : 
    1274          241 :     track->setStaffProperties(properties);
    1275          241 : }
    1276              : 
    1277          476 : void GP67DomBuilder::readDiagram(const XmlDomNode& items, GPTrack* track) const
    1278              : {
    1279          476 :     auto item = items.firstChild();
    1280              : 
    1281          503 :     while (!item.isNull()) {
    1282           27 :         GPTrack::Diagram diagram;
    1283              : 
    1284           27 :         diagram.id = item.toElement().attribute("id").value().toInt();
    1285           27 :         diagram.name = item.toElement().attribute("name").value();
    1286              : 
    1287           27 :         auto diagramNode = item.firstChild();
    1288           27 :         diagram.stringCount = diagramNode.toElement().attribute("stringCount").value().toInt();
    1289           27 :         diagram.fretCount = diagramNode.toElement().attribute("fretCount").value().toInt();
    1290           27 :         diagram.baseFret = diagramNode.toElement().attribute("baseFret").value().toInt();
    1291              : 
    1292           27 :         auto fretNode = diagramNode.firstChild();
    1293          239 :         while (!fretNode.isNull()) {
    1294          212 :             if (fretNode.nodeName() == "Fret") {
    1295          132 :                 int string = fretNode.toElement().attribute("string").value().toInt();
    1296          132 :                 int fret = fretNode.toElement().attribute("fret").value().toInt();
    1297          132 :                 diagram.frets[string] = fret;
    1298              :             }
    1299              : 
    1300          212 :             fretNode = fretNode.nextSibling();
    1301              :         }
    1302              : 
    1303           27 :         track->addDiagram(std::make_pair(diagram.id, diagram));
    1304              : 
    1305           27 :         item = item.nextSibling();
    1306           27 :     }
    1307          476 : }
    1308              : 
    1309          238 : void GP67DomBuilder::readLyrics(const XmlDomNode& items, GPTrack* track) const
    1310              : {
    1311          238 :     XmlDomNode lineNode = items.firstChildElement("Line");
    1312              : 
    1313              :     // This code doesn't support multiple lines of lyrics.
    1314          238 :     if (!lineNode.isNull()) {
    1315          238 :         auto textNode = lineNode.firstChildElement("Text");
    1316          238 :         if (!textNode.isNull()) {
    1317          238 :             track->setLyrics(textNode.toElement().text().toStdString());
    1318              :         }
    1319              : 
    1320          238 :         auto offsetNode = lineNode.firstChildElement("Offset");
    1321          238 :         if (!offsetNode.isNull()) {
    1322          238 :             track->setLyricsOffset(offsetNode.toElement().text().toInt());
    1323              :         }
    1324          238 :     }
    1325          238 : }
    1326              : 
    1327            4 : std::vector<int> GP67DomBuilder::readEnding(XmlDomNode* endNode) const
    1328              : {
    1329            4 :     String str = endNode->toElement().text();
    1330            4 :     StringList strList = str.split(u' ');
    1331            4 :     std::vector<int> ending;
    1332            4 :     ending.reserve(strList.size());
    1333           10 :     for (const String& val : strList) {
    1334            6 :         ending.push_back(val.toInt());
    1335              :     }
    1336              : 
    1337            4 :     return ending;
    1338            4 : }
    1339              : 
    1340            8 : GPMasterBar::Repeat GP67DomBuilder::readRepeat(XmlDomNode* repeatNode) const
    1341              : {
    1342            8 :     auto repeatType = [](const String& start, const String& end) {
    1343            8 :         if (start == u"true" && end == u"false") {
    1344            2 :             return GPMasterBar::Repeat::Type::Start;
    1345            6 :         } else if (start == u"false" && end == u"true") {
    1346            6 :             return GPMasterBar::Repeat::Type::End;
    1347            0 :         } else if (start == u"true" && end == u"true") {
    1348            0 :             return GPMasterBar::Repeat::Type::StartEnd;
    1349              :         }
    1350            0 :         return GPMasterBar::Repeat::Type::None;
    1351              :     };
    1352              : 
    1353            8 :     String start = repeatNode->toElement().attribute("start").value();
    1354            8 :     String end = repeatNode->toElement().attribute("end").value();
    1355            8 :     int count = repeatNode->toElement().attribute("count").value().toInt();
    1356              : 
    1357            8 :     GPMasterBar::Repeat repeat{ repeatType(start, end), count };
    1358            8 :     return repeat;
    1359            8 : }
    1360              : 
    1361              : std::pair<String, String>
    1362            3 : GP67DomBuilder::readMasterBarSection(const XmlDomNode& sectionNode) const
    1363              : {
    1364            3 :     std::pair<String, String> section;
    1365              : 
    1366            3 :     auto node = sectionNode.firstChild();
    1367              : 
    1368            9 :     while (!node.isNull()) {
    1369            6 :         String nodeName = node.nodeName();
    1370              : 
    1371            6 :         if (nodeName == u"Letter") {
    1372            3 :             section.first = node.toElement().text();
    1373            3 :         } else if (nodeName == u"Text") {
    1374            3 :             section.second = node.toElement().text();
    1375              :         }
    1376              : 
    1377            6 :         node = node.nextSibling();
    1378            6 :     }
    1379              : 
    1380            3 :     return section;
    1381            3 : }
    1382              : 
    1383            6 : std::vector<GPMasterBar::Fermata> GP67DomBuilder::readFermatas(XmlDomNode* fermatasNode) const
    1384              : {
    1385           10 :     auto fermataType = [](const String& str) {
    1386           10 :         if (str == u"Short") {
    1387            0 :             return GPMasterBar::Fermata::Type::Short;
    1388           10 :         } else if (str == u"Medium") {
    1389           10 :             return GPMasterBar::Fermata::Type::Medium;
    1390              :         }
    1391            0 :         return GPMasterBar::Fermata::Type::Long;
    1392              :     };
    1393              : 
    1394            6 :     std::vector<GPMasterBar::Fermata> fermatas;
    1395              : 
    1396            6 :     auto fermataNode = fermatasNode->firstChild();
    1397              : 
    1398           16 :     while (!fermataNode.isNull()) {
    1399           10 :         GPMasterBar::Fermata fermata;
    1400              : 
    1401           10 :         auto fermataProperty = fermataNode.firstChild();
    1402           40 :         while (!fermataProperty.isNull()) {
    1403           30 :             String nodeName = fermataProperty.nodeName();
    1404           30 :             if (nodeName == u"Type") {
    1405           10 :                 fermata.type = fermataType(fermataProperty.toElement().text());
    1406           20 :             } else if (nodeName == u"Offset") {
    1407           10 :                 String str = fermataProperty.toElement().text();
    1408           10 :                 StringList numbers = str.split(u'/');
    1409           10 :                 fermata.offsetNum = numbers.at(0).toInt();
    1410           10 :                 fermata.offsetDenom = numbers.at(1).toInt();
    1411           20 :             } else if (nodeName == u"Length") {
    1412           10 :                 fermata.length = fermataProperty.toElement().text().toFloat();
    1413              :             }
    1414              : 
    1415           30 :             fermataProperty = fermataProperty.nextSibling();
    1416           30 :         }
    1417              : 
    1418           10 :         fermatas.push_back(fermata);
    1419           10 :         fermataNode = fermataNode.nextSibling();
    1420           10 :     }
    1421              : 
    1422            6 :     return fermatas;
    1423            6 : }
    1424              : 
    1425           36 : std::vector<GPMasterBar::Direction> GP67DomBuilder::readRepeatsJumps(XmlDomNode* repeatsJumpsNode) const
    1426              : {
    1427           36 :     std::vector<GPMasterBar::Direction> repeatsJumps;
    1428              : 
    1429           36 :     auto innerNode = repeatsJumpsNode->firstChild();
    1430              : 
    1431           72 :     while (!innerNode.isNull()) {
    1432           36 :         GPMasterBar::Direction repeatJump;
    1433           36 :         repeatJump.type = (innerNode.nodeName() == "Jump" ? GPMasterBar::Direction::Type::Jump : GPMasterBar::Direction::Type::Repeat);
    1434           36 :         repeatJump.name = innerNode.toElement().text();
    1435              : 
    1436              :         // GP encodes "To Coda" instructions as Jumps, but MuseScore uses Markers for that
    1437           36 :         if ((repeatJump.name == u"DaCoda") || (repeatJump.name == u"DaDoubleCoda")) {
    1438            4 :             repeatJump.type = GPMasterBar::Direction::Type::Marker;
    1439              :         }
    1440              : 
    1441           36 :         repeatsJumps.push_back(repeatJump);
    1442           36 :         innerNode = innerNode.nextSibling();
    1443           36 :     }
    1444              : 
    1445           36 :     return repeatsJumps;
    1446           36 : }
    1447              : } // namespace mu::iex::guitarpro
        

Generated by: LCOV version 2.0-1