LCOV - code coverage report
Current view: top level - src/importexport/guitarpro/internal - continiouselementsbuilder.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 86.3 % 211 182
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              : #include "continiouselementsbuilder.h"
      23              : 
      24              : #include "engraving/dom/trill.h"
      25              : #include "engraving/dom/score.h"
      26              : #include "engraving/dom/ottava.h"
      27              : #include "engraving/dom/chordrest.h"
      28              : #include "engraving/dom/hairpin.h"
      29              : #include "engraving/dom/textline.h"
      30              : #include "engraving/dom/vibrato.h"
      31              : #include "engraving/dom/factory.h"
      32              : 
      33              : using namespace mu::engraving;
      34              : 
      35              : namespace mu::iex::guitarpro {
      36          295 : ContiniousElementsBuilder::ContiniousElementsBuilder(Score* score)
      37          295 :     : m_score(score)
      38              : {
      39          295 : }
      40              : 
      41        33447 : static mu::engraving::ElementType muTypeFromImportType(ContiniousElementsBuilder::ImportType importType)
      42              : {
      43              :     using import_t = ContiniousElementsBuilder::ImportType;
      44              : 
      45        33447 :     switch (importType) {
      46         2392 :     case import_t::LET_RING:
      47         2392 :         return ElementType::LET_RING;
      48         2392 :     case import_t::PALM_MUTE:
      49         2392 :         return ElementType::PALM_MUTE;
      50         2050 :     case import_t::RASGUEADO:
      51         2050 :         return ElementType::RASGUEADO;
      52         1595 :     case import_t::PICK_SCRAPE:
      53         1595 :         return ElementType::PICK_SCRAPE;
      54              : 
      55        10831 :     case import_t::HARMONIC_ARTIFICIAL:
      56              :     case import_t::HARMONIC_PINCH:
      57              :     case import_t::HARMONIC_TAP:
      58              :     case import_t::HARMONIC_SEMI:
      59              :     case import_t::HARMONIC_FEEDBACK:
      60        10831 :         return ElementType::HARMONIC_MARK;
      61              : 
      62           60 :     case import_t::OTTAVA_MA15:
      63              :     case import_t::OTTAVA_VA8:
      64              :     case import_t::OTTAVA_VB8:
      65              :     case import_t::OTTAVA_MB15:
      66           60 :         return ElementType::OTTAVA;
      67              : 
      68         2309 :     case import_t::TRILL:
      69         2309 :         return ElementType::TRILL;
      70          714 :     case import_t::HAMMER_ON_PULL_OFF:
      71          714 :         return ElementType::HAMMER_ON_PULL_OFF;
      72              : 
      73           13 :     case import_t::HAIRPIN_CRESCENDO:
      74              :     case import_t::HAIRPIN_DIMINUENDO:
      75           13 :         return ElementType::HAIRPIN;
      76              : 
      77         7974 :     case import_t::VIBRATO_LEFT_HAND_SLIGHT:
      78              :     case import_t::VIBRATO_LEFT_HAND_WIDE:
      79              :     case import_t::VIBRATO_W_TREM_BAR_SLIGHT:
      80              :     case import_t::VIBRATO_W_TREM_BAR_WIDE:
      81         7974 :         return ElementType::VIBRATO;
      82              : 
      83         3117 :     case import_t::NONE:
      84         3117 :         return ElementType::INVALID;
      85              :     }
      86              : 
      87            0 :     return ElementType::INVALID;
      88              : }
      89              : 
      90           51 : static String harmonicText(ContiniousElementsBuilder::ImportType type)
      91              : {
      92              :     static const std::unordered_map<ContiniousElementsBuilder::ImportType, String> names {
      93            0 :         { ContiniousElementsBuilder::ImportType::HARMONIC_ARTIFICIAL, u"AH" },
      94            0 :         { ContiniousElementsBuilder::ImportType::HARMONIC_PINCH, u"PH" },
      95            0 :         { ContiniousElementsBuilder::ImportType::HARMONIC_TAP, u"TH" },
      96            0 :         { ContiniousElementsBuilder::ImportType::HARMONIC_SEMI, u"SH" },
      97            0 :         { ContiniousElementsBuilder::ImportType::HARMONIC_FEEDBACK, u"Fdbk" },
      98           58 :     };
      99              : 
     100           51 :     auto it = names.find(type);
     101           51 :     if (it != names.end()) {
     102           51 :         return it->second;
     103              :     }
     104              : 
     105            0 :     LOGE() << "wrong harmonic type";
     106            0 :     return String();
     107            1 : }
     108              : 
     109           30 : static mu::engraving::VibratoType vibratoTypeFromImportType(ContiniousElementsBuilder::ImportType type)
     110              : {
     111              :     static const std::unordered_map<ContiniousElementsBuilder::ImportType, VibratoType> names {
     112              :         { ContiniousElementsBuilder::ImportType::VIBRATO_LEFT_HAND_SLIGHT, VibratoType::GUITAR_VIBRATO },
     113              :         { ContiniousElementsBuilder::ImportType::VIBRATO_LEFT_HAND_WIDE, VibratoType::GUITAR_VIBRATO_WIDE },
     114              :         { ContiniousElementsBuilder::ImportType::VIBRATO_W_TREM_BAR_SLIGHT, VibratoType::VIBRATO_SAWTOOTH },
     115              :         { ContiniousElementsBuilder::ImportType::VIBRATO_W_TREM_BAR_WIDE, VibratoType::VIBRATO_SAWTOOTH_WIDE }
     116           32 :     };
     117              : 
     118           30 :     auto it = names.find(type);
     119           30 :     if (it != names.end()) {
     120           30 :         return it->second;
     121              :     }
     122              : 
     123            0 :     LOGE() << "wrong vibrato type";
     124            0 :     return VibratoType::GUITAR_VIBRATO;
     125              : }
     126              : 
     127           60 : static std::pair<bool, mu::engraving::OttavaType> ottavaType(ContiniousElementsBuilder::ImportType type)
     128              : {
     129              :     static const std::unordered_map<ContiniousElementsBuilder::ImportType, mu::engraving::OttavaType> types {
     130              :         { ContiniousElementsBuilder::ImportType::OTTAVA_VA8,  mu::engraving::OttavaType::OTTAVA_8VA },
     131              :         { ContiniousElementsBuilder::ImportType::OTTAVA_VB8,  mu::engraving::OttavaType::OTTAVA_8VB },
     132              :         { ContiniousElementsBuilder::ImportType::OTTAVA_MA15, mu::engraving::OttavaType::OTTAVA_15MA },
     133              :         { ContiniousElementsBuilder::ImportType::OTTAVA_MB15, mu::engraving::OttavaType::OTTAVA_15MB }
     134           62 :     };
     135              : 
     136           60 :     auto it = types.find(type);
     137           60 :     if (it != types.end()) {
     138           60 :         return { true, it->second };
     139              :     }
     140              : 
     141            0 :     return { false, mu::engraving::OttavaType::OTTAVA_8VA };
     142              : }
     143              : 
     144              : /// whenever the continious line elements should end on rest or rest should be included in it
     145        42819 : static bool shouldSplitByRests(mu::engraving::ElementType muType)
     146              : {
     147        42819 :     switch (muType) {
     148        29939 :     case ElementType::PICK_SCRAPE:
     149              :     case ElementType::TRILL:
     150              :     case ElementType::HARMONIC_MARK:
     151              :     case ElementType::VIBRATO:
     152              :     case ElementType::HAMMER_ON_PULL_OFF:
     153        29939 :         return true;
     154              : 
     155        12880 :     default:
     156        12880 :         return false;
     157              :     }
     158              : }
     159              : 
     160        42819 : void ContiniousElementsBuilder::buildContiniousElement(ChordRest* cr, ElementType muType, ImportType importType, bool elemExists,
     161              :                                                        sub_type_t subType)
     162              : {
     163          124 :     auto setStartCR = [](Spanner* elem, ChordRest* cr) {
     164          124 :         elem->setTick(cr->tick());
     165          124 :         elem->setStartElement(cr);
     166          124 :     };
     167              : 
     168          208 :     auto setEndCR = [](Spanner* elem, ChordRest* cr) {
     169          208 :         elem->setTick2(cr->endTick());
     170          208 :         elem->setEndElement(cr);
     171          208 :     };
     172              : 
     173        42819 :     bool splitByRests = shouldSplitByRests(muType);
     174              : 
     175        42819 :     track_idx_t track = cr->track();
     176              : 
     177        42819 :     auto& elements = m_elementsByType[importType];
     178        48881 :     while (elements.size() < track + 1) {
     179         6062 :         elements.push_back(nullptr);
     180              :     }
     181              : 
     182        42819 :     auto& elem = elements[track];
     183        42819 :     auto& lastTypeForTrack = m_lastImportTypes[muType][track][subType];
     184              : 
     185        42819 :     ContiniousElementState state = calculateState(cr->isRest(), elemExists, splitByRests, lastTypeForTrack != importType);
     186              : 
     187              :     /// handling the continious element according to its state
     188        42819 :     switch (state) {
     189        33239 :     case ContiniousElementState::CHORD_NO_ELEMENT:
     190        33239 :         lastTypeForTrack = ImportType::NONE;
     191        33239 :         elem = nullptr;
     192        33239 :         break;
     193              : 
     194         6516 :     case ContiniousElementState::REST_BREAK:
     195         6516 :         lastTypeForTrack = ImportType::NONE;
     196         6516 :         elem = nullptr;
     197         6516 :         break;
     198              : 
     199         2856 :     case ContiniousElementState::REST_CONTINUE:
     200              :     case ContiniousElementState::ELEMENT_ON_REST: // currently it's mistake: we cannot indicate Rest with any of continious elements in guitar pro
     201         2856 :         if (lastTypeForTrack != ImportType::NONE) {
     202           34 :             m_elementsToAddToScore[track][lastTypeForTrack].endedOnRest = true;
     203              :         }
     204              : 
     205         2856 :         break;
     206              : 
     207           84 :     case ContiniousElementState::CONTINUE_CURRENT_LINE:
     208              :     {
     209           84 :         ChordRest* lastCR = elem->endCR();
     210           84 :         if (lastCR == cr) {
     211            0 :             break;
     212              :         }
     213              : 
     214           84 :         if (elem->tick2() < cr->tick()) {
     215           14 :             if (lastTypeForTrack != ImportType::NONE) {
     216           14 :                 auto& lastTypeElementsToAdd = m_elementsToAddToScore[track][lastTypeForTrack];
     217              : 
     218              :                 /// removing info about the Rest and updating last element's ticks
     219           14 :                 if (lastTypeElementsToAdd.endedOnRest) {
     220           13 :                     lastTypeElementsToAdd.endedOnRest = false;
     221           13 :                     Spanner* prevElem = lastTypeElementsToAdd.elements.back();
     222           13 :                     if (!prevElem) {
     223            0 :                         LOGE() << "error while importing";
     224         9372 :                         return;
     225              :                     }
     226              : 
     227           13 :                     elem = prevElem;
     228           13 :                     setEndCR(elem, cr);
     229              :                 } else {
     230              :                     // Simile mark case
     231            1 :                     setEndCR(elem, cr);
     232              :                 }
     233              :             }
     234              :         } else {
     235           70 :             setEndCR(elem, cr);
     236              :         }
     237              : 
     238           84 :         break;
     239              :     }
     240              : 
     241          124 :     case ContiniousElementState::CREATE_NEW_ITEM:
     242              :     {
     243          124 :         EngravingItem* engItem = Factory::createItem(muType, m_score->dummy());
     244              : 
     245          124 :         Spanner* newElem = dynamic_cast<Spanner*>(engItem);
     246          124 :         IF_ASSERT_FAILED(newElem) {
     247            0 :             return;
     248              :         }
     249              : 
     250          124 :         elem = newElem;
     251              : 
     252          124 :         setStartCR(newElem, cr);
     253          124 :         setEndCR(newElem, cr);
     254              : 
     255          124 :         newElem->setTrack(track);
     256          124 :         newElem->setTrack2(track);
     257              : 
     258          124 :         m_elementsToAddToScore[track][importType].elements.push_back(newElem);
     259          124 :         m_orderedAddedElements.push_back(newElem);
     260          124 :         lastTypeForTrack = importType;
     261          124 :         break;
     262              :     }
     263              : 
     264            0 :     default:
     265            0 :         break;
     266              :     }
     267              : 
     268        42819 :     if (!cr->isChord()) {
     269         9372 :         return;
     270              :     }
     271              : 
     272        33447 :     setupAddedElement(cr->track(), importType);
     273              : }
     274              : 
     275          182 : void ContiniousElementsBuilder::notifyUncompletedMeasure()
     276              : {
     277          260 :     for (auto& trackMaps : m_elementsToAddToScore) {
     278          236 :         for (auto& typeMaps : trackMaps.second) {
     279          158 :             typeMaps.second.endedOnRest = true;
     280              :         }
     281              :     }
     282          182 : }
     283              : 
     284            3 : static Chord* searchEndChord(Chord* startChord)
     285              : {
     286            3 :     ChordRest* nextCr = nullptr;
     287            3 :     if (startChord->isGrace()) {
     288              :         //! this case when start note is a grace note so end note can be next note in grace notes
     289              :         //! or parent note of grace notes
     290            0 :         Chord* parentGrace = toChord(startChord->parent());
     291              : 
     292            0 :         auto it = parentGrace->graceNotes().begin();
     293            0 :         for (; it != parentGrace->graceNotes().end(); ++it) {
     294            0 :             if (*it == startChord) {
     295            0 :                 break;
     296              :             }
     297              :         }
     298              : 
     299            0 :         if (it == parentGrace->graceNotes().end()) {
     300            0 :             nextCr = nullptr;
     301            0 :         } else if (std::next(it) == parentGrace->graceNotes().end()) {
     302            0 :             nextCr = parentGrace;
     303              :         } else {
     304            0 :             nextCr = *(++it);
     305              :         }
     306              :     } else {
     307            3 :         nextCr = startChord->segment()->next1()->nextChordRest(startChord->track());
     308            3 :         if (!nextCr) {
     309            0 :             return nullptr;
     310              :         }
     311              : 
     312            3 :         if (nextCr->isChord() && !toChord(nextCr)->graceNotes().empty()) {
     313            0 :             nextCr = toChord(nextCr)->graceNotes().front();
     314              :         }
     315              :     }
     316              : 
     317            3 :     return (nextCr && nextCr->isChord()) ? toChord(nextCr) : nullptr;
     318              : }
     319              : 
     320          294 : void ContiniousElementsBuilder::addElementsToScore()
     321              : {
     322          296 :     for (auto& trackMaps: m_spannersWithoutEndElement) {
     323            4 :         for (auto& typeMaps : trackMaps.second) {
     324            5 :             for (Spanner* elem : typeMaps.second) {
     325            3 :                 elem->setEndElement(searchEndChord(elem->endChord()));
     326              :             }
     327              :         }
     328              :     }
     329              : 
     330          418 :     for (Spanner* elem : m_orderedAddedElements) {
     331          124 :         m_score->addElement(elem);
     332              :     }
     333          294 : }
     334              : 
     335              : /// indicating the type of behaviour for continious element
     336        42819 : ContiniousElementsBuilder::ContiniousElementState ContiniousElementsBuilder::calculateState(bool isRest, bool elemExists, bool splitByRests,
     337              :                                                                                             bool importTypeChanged) const
     338              : {
     339        42819 :     ContiniousElementState state = ContiniousElementState::UNDEFINED;
     340              : 
     341        42819 :     if (isRest && splitByRests) {
     342         6516 :         state = ContiniousElementState::REST_BREAK;
     343              :     } else {
     344        36303 :         if (!elemExists) {
     345        36092 :             if (!isRest) {
     346        33239 :                 state = ContiniousElementState::CHORD_NO_ELEMENT;
     347              :             } else {
     348         2853 :                 state = ContiniousElementState::REST_CONTINUE;
     349              :             }
     350              :         } else {
     351              :             // element exists on current beat
     352          211 :             if (isRest) {
     353            3 :                 state = ContiniousElementState::ELEMENT_ON_REST;
     354          208 :             } else if (importTypeChanged) {
     355          124 :                 state = ContiniousElementState::CREATE_NEW_ITEM;
     356              :             } else {
     357           84 :                 state = ContiniousElementState::CONTINUE_CURRENT_LINE;
     358              :             }
     359              :         }
     360              :     }
     361              : 
     362        42819 :     return state;
     363              : }
     364              : 
     365        33447 : void ContiniousElementsBuilder::setupAddedElement(track_idx_t trackIdx, ImportType importType)
     366              : {
     367        33447 :     ElementType muType = muTypeFromImportType(importType);
     368              : 
     369        33447 :     Spanner* lineElem = m_elementsByType[importType][trackIdx];
     370        33447 :     if (!lineElem) {
     371        33239 :         return;
     372              :     }
     373              : 
     374          208 :     switch (muType) {
     375            3 :     case ElementType::TRILL:
     376              :     {
     377            3 :         if (lineElem->isTrill()) {
     378            3 :             toTrill(lineElem)->setTrillType(TrillType::TRILL_LINE);
     379              :         }
     380              : 
     381            3 :         break;
     382              :     }
     383           13 :     case ElementType::HAIRPIN:
     384              :     {
     385           13 :         if (lineElem->isHairpin()) {
     386           13 :             toHairpin(lineElem)->setHairpinType(
     387           13 :                 importType == ImportType::HAIRPIN_CRESCENDO ? HairpinType::CRESC_HAIRPIN : HairpinType::DIM_HAIRPIN);
     388              :         }
     389              : 
     390           13 :         break;
     391              :     }
     392           51 :     case ElementType::HARMONIC_MARK:
     393              :     {
     394           51 :         if (lineElem->isTextLineBase()) {
     395           51 :             const String& text = harmonicText(importType);
     396           51 :             toTextLineBase(lineElem)->setBeginText(text);
     397           51 :             toTextLineBase(lineElem)->setContinueText(text);
     398           51 :         }
     399              : 
     400           51 :         break;
     401              :     }
     402           60 :     case ElementType::OTTAVA:
     403              :     {
     404           60 :         const auto& [foundOttava, muOttavaType] = ottavaType(importType);
     405           60 :         if (!foundOttava) {
     406            0 :             return;
     407              :         }
     408              : 
     409           60 :         if (lineElem->isOttava()) {
     410           60 :             toOttava(lineElem)->setOttavaType(muOttavaType);
     411              :         }
     412              : 
     413           60 :         break;
     414              :     }
     415           30 :     case ElementType::VIBRATO:
     416           30 :         if (lineElem->isVibrato()) {
     417           30 :             toVibrato(lineElem)->setVibratoType(vibratoTypeFromImportType(importType));
     418              :         }
     419              : 
     420           30 :         break;
     421            4 :     case ElementType::HAMMER_ON_PULL_OFF:
     422            4 :         m_spannersWithoutEndElement[trackIdx][importType].insert(lineElem);
     423            4 :         break;
     424           47 :     default:
     425           47 :         break;
     426              :     }
     427              : }
     428              : } // namespace mu::iex::guitarpro
        

Generated by: LCOV version 2.0-1