LCOV - code coverage report
Current view: top level - src/importexport/guitarpro/internal/guitarbendimport - bendbuilder.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 86.6 % 224 194
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) 2024 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 "bendbuilder.h"
      23              : 
      24              : #include <algorithm>
      25              : 
      26              : #include <engraving/dom/chord.h>
      27              : #include <engraving/dom/guitarbend.h>
      28              : #include <engraving/dom/factory.h>
      29              : #include <engraving/dom/measure.h>
      30              : #include <engraving/dom/note.h>
      31              : #include <engraving/dom/score.h>
      32              : #include <engraving/dom/part.h>
      33              : #include <engraving/dom/staff.h>
      34              : #include <engraving/dom/stringdata.h>
      35              : #include <engraving/dom/tie.h>
      36              : #include <engraving/dom/tuplet.h>
      37              : 
      38              : #include "../utils.h"
      39              : 
      40              : #include "global/log.h"
      41              : 
      42              : using namespace mu::engraving;
      43              : 
      44              : namespace mu::iex::guitarpro {
      45              : static std::vector<Chord*> createGraceChords(Chord* chord, const grace_segment_map_t& bendInfo);
      46              : static void reindexGraceNotes(Chord* chord);
      47              : static void createSlightBends(const BendDataContext& bendDataCtx, Score* score);
      48              : static void createPreBends(const BendDataContext& bendDataCtx, Score* score);
      49              : static void createPreDives(const DiveDataContext& diveDataCtx, Score* score);
      50              : static void createGraceAfterNotes(const GraceAfterTrackMap& data, GuitarBendType type, Score* score);
      51              : static void createTiedSpans(const TiedNotesTrackMap& data, GuitarBendType type, Score* score);
      52              : 
      53          294 : void BendBuilder::addElementsToScore(Score* score, const BendDataContext& bendCtx, const DiveDataContext& diveCtx)
      54              : {
      55          294 :     createPreBends(bendCtx, score);
      56          294 :     createSlightBends(bendCtx, score);
      57          294 :     createGraceAfterNotes(bendCtx.graceAfterBendData, GuitarBendType::BEND, score);
      58          294 :     createTiedSpans(bendCtx.tiedNotesBendsData, GuitarBendType::BEND, score);
      59          294 :     createPreDives(diveCtx, score);
      60          294 :     createTiedSpans(diveCtx.tiedNotesDivesData, GuitarBendType::DIVE, score);
      61          294 :     createGraceAfterNotes(diveCtx.graceAfterDiveData, GuitarBendType::DIVE, score);
      62              : 
      63          294 :     if (bendCtx.splitChordCtx) {
      64            0 :         m_bendDataProcessorSplitChord = std::make_unique<BendDataProcessorSplitChord>(score);
      65            0 :         m_bendDataProcessorSplitChord->processBends(*bendCtx.splitChordCtx);
      66              :     }
      67          294 : }
      68              : 
      69          294 : static void createSlightBends(const BendDataContext& bendDataCtx, mu::engraving::Score* score)
      70              : {
      71          298 :     for (const auto& [track, trackInfo] : bendDataCtx.slightBendData) {
      72           10 :         for (const auto& [tick, tickInfo] : trackInfo) {
      73            6 :             Chord* chord = utils::getLocatedChord(score, tick, track);
      74           14 :             for (size_t noteIndex = 0; noteIndex < chord->notes().size(); noteIndex++) {
      75            8 :                 if (!muse::contains(tickInfo, noteIndex)) {
      76            0 :                     continue;
      77              :                 }
      78              : 
      79            8 :                 Note* note = chord->notes()[noteIndex];
      80            8 :                 const auto& noteBendData = tickInfo.at(noteIndex);
      81            8 :                 GuitarBend* bend = chord->score()->addGuitarBend(GuitarBendType::SLIGHT_BEND, note);
      82            8 :                 IF_ASSERT_FAILED(bend) {
      83            0 :                     LOGE() << "bend wasn't created for track " << chord->track() << ", tick " << chord->tick().ticks();
      84            0 :                     continue;
      85              :                 }
      86              : 
      87            8 :                 bend->setStartTimeFactor(noteBendData.startFactor);
      88            8 :                 bend->setEndTimeFactor(noteBendData.endFactor);
      89              :             }
      90              :         }
      91              :     }
      92          294 : }
      93              : 
      94          294 : static void createPreBends(const BendDataContext& bendDataCtx, mu::engraving::Score* score)
      95              : {
      96          307 :     for (const auto& [track, trackInfo] : bendDataCtx.prebendData) {
      97           37 :         for (const auto& [tick, tickInfo] : trackInfo) {
      98           24 :             Chord* chord = utils::getLocatedChord(score, tick, track);
      99           55 :             for (size_t noteIndex = 0; noteIndex < chord->notes().size(); noteIndex++) {
     100           31 :                 if (!muse::contains(tickInfo, noteIndex)) {
     101            7 :                     continue;
     102              :                 }
     103              : 
     104           24 :                 Note* note = chord->notes()[noteIndex];
     105              :                 // NOTE: addGuitarBend will create grace note and transpose it down. We need the pitch of current note instead to properly fret the note.
     106           24 :                 note->transposeDiatonic(1, true, false);
     107           24 :                 const auto& noteBendData = tickInfo.at(noteIndex);
     108           24 :                 GuitarBend* bend = chord->score()->addGuitarBend(GuitarBendType::PRE_BEND, note);
     109           24 :                 IF_ASSERT_FAILED(bend) {
     110            0 :                     LOGE() << "prebend wasn't created for track " << chord->track() << ", tick " << chord->tick().ticks();
     111            0 :                     continue;
     112              :                 }
     113              : 
     114              :                 // NOTE: returning pitch.
     115           24 :                 note->transposeDiatonic(-1, true, false);
     116           24 :                 bend->setStartTimeFactor(noteBendData.startFactor);
     117           24 :                 bend->setEndTimeFactor(noteBendData.endFactor);
     118              : 
     119           24 :                 const int pitch = noteBendData.quarterTones / 2;
     120           24 :                 note->setPitch(note->pitch() + pitch);
     121           24 :                 note->setTpcFromPitch();
     122           24 :                 int quarterOff = noteBendData.quarterTones % 2;
     123           24 :                 bend->setEndNotePitch(note->pitch(), quarterOff);
     124           24 :                 Note* startNote = bend->startNote();
     125           24 :                 if (!startNote) {
     126            0 :                     return;
     127              :                 }
     128              : 
     129           24 :                 startNote->setPitch(note->pitch() - pitch);
     130           24 :                 startNote->setTpcFromPitch();
     131           24 :                 if (note->displayFret() == Note::DisplayFretOption::Hide) {
     132            0 :                     startNote->setDisplayFret(Note::DisplayFretOption::Hide);
     133              :                 }
     134              : 
     135           24 :                 int newPitch = note->pitch();
     136           24 :                 Note* tiedNote = nullptr;
     137              : 
     138           24 :                 Tie* tieFor = startNote->tieFor();
     139           24 :                 if (tieFor) {
     140            0 :                     tiedNote = tieFor->endNote();
     141            0 :                     startNote->remove(tieFor);
     142              :                 }
     143              : 
     144           24 :                 while (tiedNote) {
     145            0 :                     tiedNote->setPitch(newPitch);
     146            0 :                     tiedNote->setTpcFromPitch();
     147            0 :                     Tie* tie = tiedNote->tieFor();
     148            0 :                     if (!tie) {
     149            0 :                         break;
     150              :                     }
     151              : 
     152            0 :                     tiedNote = tie->endNote();
     153              :                 }
     154              :             }
     155              :         }
     156              :     }
     157              : }
     158              : 
     159          124 : static void reindexGraceNotes(Chord* chord)
     160              : {
     161          124 :     int gi = 0;
     162          324 :     for (Chord* c : chord->graceNotes()) {
     163          200 :         c->setGraceIndex(gi++);
     164              :     }
     165          124 : }
     166              : 
     167          137 : static Chord* createGraceAfterChord(Chord* parent)
     168              : {
     169          137 :     Chord* graceChord = Factory::createChord(parent->score()->dummy());
     170          137 :     graceChord->setTrack(parent->track());
     171          137 :     graceChord->setNoteType(NoteType::GRACE8_AFTER);
     172          137 :     graceChord->setNoStem(true);
     173          137 :     graceChord->setBeamMode(BeamMode::NONE);
     174              : 
     175          137 :     TDuration dur;
     176          137 :     dur.setVal(mu::engraving::Constants::DIVISION / 2);
     177          137 :     graceChord->setDurationType(dur);
     178          137 :     graceChord->setTicks(dur.fraction());
     179          137 :     parent->add(graceChord);
     180              : 
     181          137 :     return graceChord;
     182              : }
     183              : 
     184          110 : static std::vector<Chord*> createGraceChords(Chord* chord, const grace_segment_map_t& bendInfo)
     185              : {
     186          110 :     std::vector<Chord*> graceChords;
     187              : 
     188          110 :     size_t maxGraceAmount = 0;
     189          236 :     for (size_t noteIndex = 0; noteIndex < chord->notes().size(); noteIndex++) {
     190          126 :         if (!muse::contains(bendInfo, noteIndex)) {
     191            2 :             continue;
     192              :         }
     193          124 :         maxGraceAmount = std::max(maxGraceAmount, bendInfo.at(noteIndex).data.size());
     194              :     }
     195              : 
     196          247 :     for (size_t i = 0; i < maxGraceAmount; i++) {
     197          137 :         graceChords.push_back(createGraceAfterChord(chord));
     198              :     }
     199              : 
     200          110 :     return graceChords;
     201            0 : }
     202              : 
     203          588 : static void createGraceAfterNotes(const GraceAfterTrackMap& data, GuitarBendType type, Score* score)
     204              : {
     205          647 :     for (const auto& [track, trackInfo] : data) {
     206          169 :         for (const auto& [tick, tickEntry] : trackInfo) {
     207          110 :             Chord* chord = utils::getLocatedChord(score, tick, track);
     208          110 :             std::vector<Chord*> graceChords = createGraceChords(chord, tickEntry);
     209              : 
     210          236 :             for (size_t noteIndex = 0; noteIndex < chord->notes().size(); noteIndex++) {
     211          126 :                 if (!muse::contains(tickEntry, noteIndex)) {
     212            2 :                     continue;
     213              :                 }
     214              : 
     215          124 :                 Note* mainNote = chord->notes()[noteIndex];
     216          124 :                 const auto& graceData = tickEntry.at(noteIndex);
     217          124 :                 const auto& lastGraceData = graceData.lastNoteData;
     218              : 
     219          124 :                 const bool isDive = (type == GuitarBendType::DIVE);
     220              : 
     221          124 :                 Note* currentNote = mainNote;
     222          278 :                 for (size_t graceIndex = 0; graceIndex < graceData.data.size(); graceIndex++) {
     223          154 :                     const auto& noteData = graceData.data[graceIndex];
     224          154 :                     Chord* graceChord = graceChords[graceIndex];
     225              : 
     226          154 :                     Note* graceNote = Factory::createNote(graceChord);
     227          154 :                     const int gracePitch = currentNote->pitch() + noteData.quarterTones / 2;
     228          154 :                     graceNote->setPitch(gracePitch);
     229          154 :                     graceNote->setTpcFromPitch();
     230          154 :                     graceChord->add(graceNote);
     231              : 
     232          154 :                     if (isDive) {
     233           81 :                         graceNote->setString(mainNote->string());
     234           81 :                         const Staff* staff = mainNote->staff();
     235           81 :                         const StringData* sd = mainNote->part()->stringData(mainNote->tick(), staff->idx());
     236           81 :                         const int offset = staff->pitchOffset(mainNote->tick());
     237           81 :                         const int fret = sd->fret(gracePitch + offset, mainNote->string(), staff);
     238           81 :                         if (fret >= 0) {
     239           67 :                             graceNote->setFret(fret);
     240              :                         }
     241              :                     }
     242              : 
     243          154 :                     GuitarBend* bend = score->addGuitarBend(type, currentNote, graceNote);
     244          154 :                     IF_ASSERT_FAILED(bend) {
     245            0 :                         LOGE() << "grace-after spanner not created for track " << track
     246            0 :                                << ", tick " << tick.ticks();
     247            0 :                         break;
     248              :                     }
     249              : 
     250              :                     // Bend quarterTones are absolute from chain start, dive quarterTones are deltas
     251              :                     const int endPitch = isDive
     252          154 :                                          ? currentNote->pitch() + noteData.quarterTones / 2
     253           73 :                                          : bend->startNoteOfChain()->pitch() + noteData.quarterTones / 2;
     254              : 
     255          154 :                     bend->setEndNotePitch(endPitch, noteData.quarterTones % 2);
     256          154 :                     bend->setStartTimeFactor(noteData.startFactor);
     257          154 :                     bend->setEndTimeFactor(noteData.endFactor);
     258              : 
     259          154 :                     currentNote = graceNote;
     260              :                 }
     261              : 
     262          124 :                 reindexGraceNotes(mainNote->chord());
     263              : 
     264          124 :                 if (lastGraceData.shouldMoveTie) {
     265            5 :                     Tie* tieFor = mainNote->tieFor();
     266            5 :                     if (tieFor && tieFor->endNote()) {
     267            5 :                         Note* tiedNote = tieFor->endNote();
     268            5 :                         mainNote->remove(tieFor);
     269            5 :                         GuitarBend* tieBend = score->addGuitarBend(type, currentNote, tiedNote);
     270            5 :                         if (tieBend) {
     271            5 :                             tieBend->setEndTimeFactor(lastGraceData.endFactor);
     272              :                         }
     273              :                     }
     274              :                 }
     275              :             }
     276          110 :         }
     277              :     }
     278          588 : }
     279              : 
     280          294 : static void createPreDives(const DiveDataContext& diveDataCtx, Score* score)
     281              : {
     282          322 :     for (const auto& [track, trackInfo] : diveDataCtx.preDiveData) {
     283           67 :         for (const auto& [tick, tickInfo] : trackInfo) {
     284           39 :             Chord* chord = utils::getLocatedChord(score, tick, track);
     285           39 :             if (!chord) {
     286            0 :                 continue;
     287              :             }
     288              : 
     289           81 :             for (size_t noteIndex = 0; noteIndex < chord->notes().size(); noteIndex++) {
     290           42 :                 if (!muse::contains(tickInfo, noteIndex)) {
     291            0 :                     continue;
     292              :                 }
     293              : 
     294           42 :                 Note* note = chord->notes()[noteIndex];
     295           42 :                 const SegmentData& pd = tickInfo.at(noteIndex);
     296              : 
     297           42 :                 const int writtenPitch = note->pitch();
     298           42 :                 const int originalString = note->string();
     299           42 :                 const int originalFret = note->fret();
     300           42 :                 const int attackPitch = writtenPitch - pd.quarterTones / 2;
     301              : 
     302           42 :                 note->transposeDiatonic(-1, true, false);
     303           42 :                 GuitarBend* preDive = score->addGuitarBend(GuitarBendType::PRE_DIVE, note);
     304           42 :                 note->transposeDiatonic(1, true, false);
     305              : 
     306           42 :                 IF_ASSERT_FAILED(preDive) {
     307            0 :                     LOGE() << "pre-dive not created for track " << track << ", tick " << tick.ticks();
     308            0 :                     continue;
     309              :                 }
     310              : 
     311           42 :                 Note* ghostNote = preDive->startNote();
     312           42 :                 if (ghostNote) {
     313           42 :                     ghostNote->setPitch(writtenPitch);
     314           42 :                     ghostNote->setTpcFromPitch();
     315           42 :                     ghostNote->setString(originalString);
     316           42 :                     ghostNote->setFret(originalFret);
     317              :                 }
     318              : 
     319           42 :                 note->setPitch(attackPitch);
     320           42 :                 note->setTpcFromPitch();
     321           42 :                 note->setString(originalString);
     322           42 :                 note->setFret(originalFret);
     323              :             }
     324              :         }
     325              :     }
     326          294 : }
     327              : 
     328          588 : static void createTiedSpans(const TiedNotesTrackMap& data, GuitarBendType type, Score* score)
     329              : {
     330          588 :     const bool isDive = (type == GuitarBendType::DIVE);
     331              : 
     332          620 :     for (const auto& [track, trackInfo] : data) {
     333           85 :         for (const auto& [tick, tickEntry] : trackInfo) {
     334           53 :             Chord* chord = utils::getLocatedChord(score, tick, track);
     335           53 :             if (!chord) {
     336            0 :                 continue;
     337              :             }
     338              : 
     339          118 :             for (size_t noteIndex = 0; noteIndex < chord->notes().size(); noteIndex++) {
     340           65 :                 if (!muse::contains(tickEntry, noteIndex)) {
     341            4 :                     continue;
     342              :                 }
     343              : 
     344           61 :                 const auto& noteInfo = tickEntry.at(noteIndex);
     345           61 :                 Note* startNote = chord->notes()[noteIndex];
     346           61 :                 Note* endNote = startNote->tieFor() ? startNote->tieFor()->endNote() : nullptr;
     347           61 :                 if (!endNote) {
     348            2 :                     LOGE() << "bend/dive import error: not found tied note for track " << track << ", tick " << tick.ticks();
     349            2 :                     continue;
     350              :                 }
     351              : 
     352           59 :                 Tie* tie = startNote->tieFor();
     353           59 :                 if (tie) {
     354           59 :                     startNote->remove(tie);
     355              :                 }
     356              : 
     357           59 :                 GuitarBend* bend = score->addGuitarBend(type, startNote, endNote);
     358           59 :                 IF_ASSERT_FAILED(bend) {
     359            0 :                     LOGE() << "bend/dive wasn't created for track " << chord->track() << ", tick " << chord->tick().ticks();
     360            0 :                     continue;
     361              :                 }
     362              : 
     363           59 :                 if (isDive && startNote->tieBack()) {
     364            1 :                     startNote->setPitch(startNote->tieBack()->startNote()->pitch());
     365            1 :                     startNote->setTpcFromPitch();
     366              :                 }
     367              : 
     368              :                 int rootPitch = isDive
     369           59 :                                 ? startNote->pitch()
     370           43 :                                 : bend->startNoteOfChain()->pitch();
     371              : 
     372           59 :                 bend->setEndNotePitch(rootPitch + noteInfo.quarterTones / 2, noteInfo.quarterTones % 2);
     373           59 :                 bend->setStartTimeFactor(noteInfo.startFactor);
     374           59 :                 bend->setEndTimeFactor(noteInfo.endFactor);
     375           59 :                 endNote->setParenthesesMode(ParenthesesMode::BOTH);
     376              : 
     377           59 :                 Tie* endTie = endNote->tieFor();
     378           81 :                 while (endTie) {
     379           22 :                     Note* nextNote = endTie->endNote();
     380           22 :                     IF_ASSERT_FAILED(nextNote) {
     381            0 :                         LOGE() << "bend/dive import error: not found tied note for track " << track << ", tick " << tick.ticks();
     382            0 :                         break;
     383              :                     }
     384           22 :                     nextNote->setPitch(endNote->pitch());
     385           22 :                     nextNote->setTpcFromPitch();
     386           22 :                     nextNote->setParenthesesMode(ParenthesesMode::BOTH);
     387           22 :                     endTie = nextNote->tieFor();
     388              :                 }
     389              :             }
     390              :         }
     391              :     }
     392          588 : }
     393              : } // namespace mu::iex::guitarpro
        

Generated by: LCOV version 2.0-1