LCOV - code coverage report
Current view: top level - src/engraving/editing - editcapo.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 73.8 % 141 104
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-Studio-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2021 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "editcapo.h"
      24              : 
      25              : #include "dom/measure.h"
      26              : #include "dom/note.h"
      27              : #include "dom/part.h"
      28              : #include "dom/score.h"
      29              : #include "dom/staff.h"
      30              : #include "dom/stringdata.h"
      31              : 
      32              : namespace mu::engraving {
      33              : // static
      34           42 : void EditCapo::applyCapoTranspose(int startTick, int endTick, UpdateCtx& ctx)
      35              : {
      36           42 :     staff_idx_t staffIdx = ctx.staff->idx();
      37           42 :     track_idx_t startTrack = staffIdx * VOICES;
      38           42 :     track_idx_t endTrack = startTrack + VOICES;
      39              : 
      40          811 :     for (MeasureBase* mb = ctx.staff->score()->measures()->first(); mb; mb = mb->next()) {
      41          769 :         if (!mb->isMeasure()) {
      42           36 :             continue;
      43              :         }
      44         2554 :         for (Segment& seg: toMeasure(mb)->segments()) {
      45         1821 :             if (!seg.isChordRestType()) {
      46         1071 :                 continue;
      47              :             }
      48         3750 :             for (track_idx_t track = startTrack; track < endTrack; ++track) {
      49         3000 :                 EngravingItem* e = seg.element(track);
      50         3000 :                 if (!e || !e->isChord()) {
      51         2941 :                     continue;
      52              :                 }
      53           59 :                 if (e->tick().ticks() < startTick || (-1 != endTick && e->tick().ticks() >= endTick)) {
      54           12 :                     continue;
      55              :                 }
      56              : 
      57           47 :                 Chord* chord = toChord(e);
      58           47 :                 chord->sortNotes();
      59           47 :                 ctx.stringData = chord->originPart()->stringData(chord->tick(), staffIdx);
      60              :                 // Prefer not change strings for intervals and chords
      61              :                 ctx.possibleFretConflict
      62           47 :                     = chord->notes().size() > 1 || std::any_of(chord->notes().begin(), chord->notes().end(),
      63           46 :                                                                [&](const Note* n) {
      64           46 :                     return n->fret() < ctx.params.fretPosition;
      65              :                 });
      66              : 
      67           99 :                 for (auto note: chord->notes()) {
      68           52 :                     if (ctx.updateIgnoredStrings) {
      69            0 :                         updateString(note, ctx);
      70              :                     } else {
      71           52 :                         update(note, ctx);
      72              :                     }
      73           52 :                     if (const GuitarBend* bend = note->bendFor(); bend) {
      74            2 :                         if (GuitarBendType::BEND == bend->bendType()) {
      75            2 :                             Note* startNote = bend->startNote();
      76            2 :                             Note* endNote = bend->endNote();
      77            2 :                             const StringData* stringData = ctx.stringData;
      78            2 :                             const int startString = startNote->string();
      79            2 :                             const int startFret = stringData->fret(startNote->pitch(), startString, startNote->staff(), startNote->tick());
      80            2 :                             if (startFret != startNote->fret()) {
      81            2 :                                 startNote->setFret(startFret);
      82              :                             }
      83              : 
      84            2 :                             if (endNote->fret() != startFret) {
      85            2 :                                 endNote->setString(startString);
      86            2 :                                 endNote->setFret(startFret);
      87              :                             }
      88            2 :                             GuitarBend::fixNotesFrettingForStandardBend(bend->startNote(), bend->endNote());
      89              :                         }
      90              :                     }
      91              :                 }
      92              : 
      93           47 :                 for (Chord* g: chord->graceNotes()) {
      94            0 :                     for (Note* n: g->notes()) {
      95            0 :                         if (ctx.updateIgnoredStrings) {
      96            0 :                             updateString(n, ctx);
      97              :                         } else {
      98            0 :                             update(n, ctx);
      99              :                         }
     100              :                     }
     101              :                 }
     102              :             }
     103              :         }
     104              :     }
     105           42 : }
     106              : 
     107              : // static
     108           46 : void EditCapo::updateNotationForCapoChange(const CapoParams& oldParams, const CapoParams& newParams, const Staff* staff, int startTick,
     109              :                                            int endTick)
     110              : {
     111           46 :     UpdateCtx ctx;
     112           46 :     ctx.staff = staff;
     113           46 :     ctx.params = newParams;
     114              :     // These two have to be set in applyCapoTranspose for every chord!
     115              :     // ctx.stringData = nullptr;
     116              :     // ctx.possibleFretConflict = false;
     117              : 
     118           46 :     Score* score = staff->score();
     119           46 :     Fraction layoutEndTick = endTick != -1 ? Fraction::fromTicks(endTick) : Fraction::max();
     120           46 :     score->setLayout(layoutEndTick, staff->idx());
     121              : 
     122           46 :     const bool modeChanged = oldParams.transposeMode != newParams.transposeMode;
     123           46 :     const bool fretChanged = oldParams.fretPosition != newParams.fretPosition;
     124           46 :     const bool stringsChanged = oldParams.ignoredStrings != newParams.ignoredStrings;
     125           46 :     const bool activeChanged = oldParams.active != newParams.active;
     126              : 
     127           46 :     if (modeChanged) {
     128           30 :         handleModeChange(oldParams, newParams, startTick, endTick, ctx);
     129           30 :         return;
     130              :     }
     131           16 :     if (fretChanged) {
     132            0 :         handleFretChange(oldParams, newParams, startTick, endTick, ctx);
     133            0 :         return;
     134              :     }
     135           16 :     if (stringsChanged) {
     136            2 :         handleStringChanged(newParams, startTick, endTick, ctx);
     137            2 :         return;
     138              :     }
     139           14 :     if (activeChanged) {
     140           12 :         handleActiveChanged(newParams, startTick, endTick, ctx);
     141           12 :         return;
     142              :     }
     143           46 : }
     144              : 
     145              : // static
     146           52 : void EditCapo::update(Note* note, const UpdateCtx& ctx)
     147              : {
     148           52 :     if (muse::contains(ctx.params.ignoredStrings, (string_idx_t)note->string())) {
     149            4 :         note->setPitch(ctx.stringData->getPitch(note->string(), note->fret(), ctx.staff));
     150              :     } else {
     151           48 :         const int pitch = note->pitch() + ctx.noteOffset;
     152           48 :         note->setPitch(pitch);
     153              :     }
     154           52 :     note->setTpcFromPitch();
     155           52 : }
     156              : 
     157              : // static
     158            0 : void EditCapo::updateString(Note* note, const UpdateCtx& ctx)
     159              : {
     160            0 :     const int pitch = note->pitch();
     161            0 :     const int string = note->string();
     162            0 :     const int fret = note->fret();
     163            0 :     if (CapoParams::TransposeMode::PLAYBACK_ONLY != ctx.params.transposeMode) {
     164            0 :         if (muse::contains(ctx.params.ignoredStrings, (string_idx_t)note->string())) {
     165              :             // Already handled
     166            0 :             if (pitch == ctx.stringData->getPitch(string, fret, 0)) {
     167            0 :                 return;
     168              :             }
     169            0 :             if (CapoParams::TransposeMode::STANDARD_ONLY == ctx.params.transposeMode) {
     170            0 :                 note->setPitch(ctx.stringData->getPitch(note->string(), note->fret(), ctx.staff));
     171              :             }
     172            0 :             note->setTpcFromPitch();
     173            0 :             return;
     174              :         }
     175            0 :         if (pitch != ctx.stringData->getPitch(string, fret, -ctx.params.fretPosition)) {
     176            0 :             if (CapoParams::TransposeMode::STANDARD_ONLY == ctx.params.transposeMode) {
     177            0 :                 note->setPitch(ctx.stringData->getPitch(note->string(), note->fret(), -ctx.params.fretPosition));
     178              :             }
     179              :         }
     180              :     }
     181            0 :     note->setTpcFromPitch();
     182              : }
     183              : 
     184              : // static
     185           30 : void EditCapo::handleModeChange(const CapoParams& oldParams, const CapoParams& newParams, int startTick, int endTick, UpdateCtx& ctx)
     186              : {
     187           30 :     switch (oldParams.transposeMode) {
     188           20 :     case CapoParams::TransposeMode::PLAYBACK_ONLY:
     189           20 :         if (newParams.transposeMode == CapoParams::TransposeMode::STANDARD_ONLY) {
     190            8 :             ctx.noteOffset = newParams.fretPosition;
     191              :         }
     192           20 :         break;
     193            6 :     case CapoParams::TransposeMode::STANDARD_ONLY:
     194            6 :         if (newParams.transposeMode == CapoParams::TransposeMode::PLAYBACK_ONLY) {
     195            0 :             ctx.noteOffset = -oldParams.fretPosition;
     196            6 :         } else if (newParams.transposeMode == CapoParams::TransposeMode::TAB_ONLY) {
     197            6 :             ctx.noteOffset = -oldParams.fretPosition;
     198              :         }
     199            6 :         break;
     200            4 :     case CapoParams::TransposeMode::TAB_ONLY:
     201            4 :         if (newParams.transposeMode == CapoParams::TransposeMode::STANDARD_ONLY) {
     202            0 :             ctx.noteOffset = newParams.fretPosition;
     203              :         }
     204            4 :         break;
     205              :     }
     206           30 :     applyCapoTranspose(startTick, endTick, ctx);
     207           30 : }
     208              : 
     209              : // static
     210            0 : void EditCapo::handleFretChange(const CapoParams& oldParams, const CapoParams& newParams, int startTick, int endTick, UpdateCtx& ctx)
     211              : {
     212            0 :     if (CapoParams::TransposeMode::STANDARD_ONLY == newParams.transposeMode) {
     213            0 :         ctx.noteOffset = newParams.fretPosition - oldParams.fretPosition;
     214            0 :         applyCapoTranspose(startTick, endTick, ctx);
     215              :     }
     216            0 : }
     217              : 
     218              : // static
     219            2 : void EditCapo::handleStringChanged(const CapoParams& newParams, int startTick, int endTick, UpdateCtx& ctx)
     220              : {
     221            2 :     ctx.updateIgnoredStrings = true;
     222              : 
     223            2 :     switch (newParams.transposeMode) {
     224            0 :     case CapoParams::TransposeMode::STANDARD_ONLY:
     225            0 :         ctx.noteOffset = newParams.fretPosition;
     226            0 :         applyCapoTranspose(startTick, endTick, ctx);
     227            0 :         break;
     228            2 :     case CapoParams::TransposeMode::TAB_ONLY:
     229              :     case CapoParams::TransposeMode::PLAYBACK_ONLY:
     230            2 :         break;
     231              :     }
     232            2 : }
     233              : 
     234              : // static
     235           12 : void EditCapo::handleActiveChanged(const CapoParams& newParams, int startTick, int endTick, UpdateCtx& ctx)
     236              : {
     237           12 :     if (!newParams.active) {
     238              :         // Treat as whatever mode to playback-only transition
     239           10 :         switch (newParams.transposeMode) {
     240            4 :         case CapoParams::TransposeMode::STANDARD_ONLY:
     241            4 :             ctx.noteOffset = -newParams.fretPosition;
     242            4 :             break;
     243            6 :         case CapoParams::TransposeMode::TAB_ONLY:
     244              :         case CapoParams::TransposeMode::PLAYBACK_ONLY:
     245            6 :             break;
     246              :         }
     247              :     } else {
     248              :         // Otherwise, as playback-only mode to previous one
     249            2 :         switch (newParams.transposeMode) {
     250            0 :         case CapoParams::TransposeMode::STANDARD_ONLY:
     251            0 :             ctx.noteOffset = newParams.fretPosition;
     252            0 :             break;
     253            2 :         case CapoParams::TransposeMode::TAB_ONLY:
     254              :         case CapoParams::TransposeMode::PLAYBACK_ONLY:
     255            2 :             break;
     256              :         }
     257              :     }
     258           12 :     applyCapoTranspose(startTick, endTick, ctx);
     259           12 : }
     260              : } // namespace mu::engraving
        

Generated by: LCOV version 2.0-1