LCOV - code coverage report
Current view: top level - src/engraving/editing - editduration.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 26.3 % 137 36
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) 2026 MuseScore Limited
       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 "editduration.h"
      24              : 
      25              : #include <algorithm>
      26              : 
      27              : #include "rw/xmlreader.h"
      28              : 
      29              : #include "../dom/chord.h"
      30              : #include "../dom/chordrest.h"
      31              : #include "../dom/durationtype.h"
      32              : #include "../dom/factory.h"
      33              : #include "../dom/input.h"
      34              : #include "../dom/laissezvib.h"
      35              : #include "../dom/measure.h"
      36              : #include "../dom/note.h"
      37              : #include "../dom/score.h"
      38              : #include "../dom/segment.h"
      39              : #include "../dom/select.h"
      40              : #include "../dom/tie.h"
      41              : #include "../dom/utils.h"
      42              : 
      43              : #include "navigation.h"
      44              : #include "noteinput.h"
      45              : #include "paste.h"
      46              : #include "transaction/transaction.h"
      47              : 
      48              : #include "log.h"
      49              : 
      50              : using namespace mu::engraving;
      51              : 
      52            7 : void EditDuration::doubleDuration(Transaction& tx, Score* score)
      53              : {
      54            7 :     incDecDuration(tx, score, -1, false);
      55            7 : }
      56              : 
      57            7 : void EditDuration::halfDuration(Transaction& tx, Score* score)
      58              : {
      59            7 :     incDecDuration(tx, score, 1, false);
      60            7 : }
      61              : 
      62           14 : void EditDuration::incDurationDotted(Transaction& tx, Score* score)
      63              : {
      64           14 :     incDecDuration(tx, score, -1, true);
      65           14 : }
      66              : 
      67           14 : void EditDuration::decDurationDotted(Transaction& tx, Score* score)
      68              : {
      69           14 :     incDecDuration(tx, score, 1, true);
      70           14 : }
      71              : 
      72              : //---------------------------------------------------------
      73              : //   incDecDuration
      74              : //     When stepDotted is false and nSteps is 1 or -1, will halve or double the duration
      75              : //     When stepDotted is true, will step by nearest dotted or undotted note
      76              : //---------------------------------------------------------
      77              : 
      78           42 : void EditDuration::incDecDuration(Transaction& tx, Score* score, int nSteps, bool stepDotted)
      79              : {
      80           42 :     Selection& selection = score->selection();
      81           42 :     InputState& is = score->inputState();
      82              : 
      83           42 :     if (selection.isRange()) {
      84            0 :         if (!selection.canCopy()) {
      85            0 :             return;
      86              :         }
      87            0 :         ChordRest* firstCR = selection.firstChordRest();
      88            0 :         if (firstCR->isGrace()) {
      89            0 :             firstCR = toChordRest(firstCR->parent());
      90              :         }
      91            0 :         TDuration initialDuration = firstCR->ticks();
      92            0 :         TDuration d = initialDuration.shiftRetainDots(nSteps, stepDotted);
      93            0 :         if (!d.isValid()) {
      94            0 :             return;
      95              :         }
      96            0 :         Fraction scale = d.ticks() / initialDuration.ticks();
      97            0 :         for (ChordRest* cr : score->getSelectedChordRests()) {
      98            0 :             Fraction newTicks = cr->ticks() * scale;
      99            0 :             if (newTicks < Fraction(1, 1024)
     100            0 :                 || (stepDotted && cr->durationType().dots() != firstCR->durationType().dots()
     101            0 :                     && !cr->isGrace())) {
     102            0 :                 return;
     103              :             }
     104            0 :         }
     105            0 :         const muse::ByteArray mimeData(selection.mimeData());
     106            0 :         XmlReader e(mimeData);
     107            0 :         score->deleteRange(selection.startSegment(), selection.endSegment(), staff2track(selection.staffStart()),
     108            0 :                            staff2track(selection.staffEnd()), score->selectionFilter(), selection.rangeContainsMultiNoteChords());
     109            0 :         Paste::pasteStaff(tx, score, e, selection.startSegment(), selection.staffStart(), scale);
     110           42 :     } else if (selection.isList()) {
     111           42 :         const std::vector<Note*> notes = selection.noteList();
     112           42 :         const std::set<ChordRest*> crsSet = score->getSelectedChordRests();
     113           42 :         std::vector<ChordRest*> crs(crsSet.begin(), crsSet.end());
     114           42 :         std::sort(crs.begin(), crs.end(), [](const ChordRest* a, const ChordRest* b) { return a->tick() > b->tick(); });
     115              : 
     116           84 :         for (ChordRest* cr : crs) {
     117              :             // if measure rest is selected as input, then the correct initialDuration will be the
     118              :             // duration of the measure's time signature, else is just the ChordRest's duration
     119           42 :             TDuration initialDuration = cr->durationType();
     120           42 :             if (initialDuration == DurationType::V_MEASURE) {
     121            0 :                 initialDuration = TDuration(cr->measure()->timesig(), true);
     122              : 
     123            0 :                 if (initialDuration.fraction() < cr->measure()->timesig() && nSteps > 0) {
     124              :                     // Duration already shortened by truncation; shorten one step less
     125            0 :                     --nSteps;
     126              :                 }
     127              :             }
     128              : 
     129           42 :             TDuration newDuration { stepDotted ? initialDuration.shiftRetainDots(nSteps, stepDotted) : initialDuration.shift(nSteps) };
     130           42 :             if (!newDuration.isValid()) {
     131            0 :                 continue;
     132              :             }
     133              : 
     134           42 :             if (cr->isGrace()) {
     135            0 :                 score->undoChangeChordRestLen(cr, newDuration);
     136              :             } else {
     137           42 :                 score->changeCRlen(cr, newDuration);
     138              :             }
     139              :         }
     140           84 :         for (Note* n : notes) {
     141           42 :             if (score->canReselectItem(n)) {
     142            0 :                 score->select(toEngravingItem(n), SelectType::ADD);
     143              :             }
     144              :         }
     145           42 :         if (is.noteEntryMode()) {
     146           42 :             const ChordRest* cr = crs.size() == 1 ? crs.front() : nullptr;
     147           42 :             IF_ASSERT_FAILED(cr) {
     148              :                 // (At time of writing) it shouldn't be possible to have more than
     149              :                 // one CR selected during note entry...
     150            0 :                 return;
     151              :             }
     152           42 :             is.setDuration(cr->durationType());
     153           42 :             NoteInput::nextInputPos(tx, score, cr, false);
     154              :         }
     155           42 :     }
     156              : }
     157              : 
     158              : //---------------------------------------------------------
     159              : //   extendToNextNote
     160              : //---------------------------------------------------------
     161              : 
     162            0 : void EditDuration::extendToNextNote(Transaction& tx, Score* score)
     163              : {
     164            0 :     Selection& selection = score->selection();
     165            0 :     InputState& is = score->inputState();
     166              : 
     167            0 :     const Fraction startTick = selection.tickStart();
     168            0 :     Fraction endTick = selection.tickEnd();
     169            0 :     const staff_idx_t startStaff = selection.staffStart();
     170            0 :     const staff_idx_t endStaff = selection.staffEnd();
     171            0 :     Box* startBox = selection.startBox();
     172            0 :     Box* endBox = selection.endBox();
     173              : 
     174            0 :     std::vector<EngravingItem*> toSelect;
     175            0 :     const bool wasRangeSelection = selection.isRange();
     176            0 :     const std::vector<Note*> initialSelection = selection.noteList();
     177              : 
     178            0 :     for (ChordRest* cr : score->getSelectedChordRests()) {
     179            0 :         ChordRest* ncr = Navigation::nextChordRest(cr);
     180            0 :         if (cr->isRest() || cr->isGrace() || cr->endTick() == score->endTick()
     181            0 :             || (ncr && ncr->isChord() && cr->endTick() == ncr->tick())) {
     182            0 :             continue;
     183              :         }
     184            0 :         std::vector<Note*> chordNotes = toChord(cr)->notes();
     185            0 :         std::vector<Note*> selectedChordNotes;
     186            0 :         for (Note* n : chordNotes) {
     187            0 :             if (std::find(initialSelection.begin(), initialSelection.end(), n) != initialSelection.end()) {
     188            0 :                 selectedChordNotes.push_back(n);
     189            0 :                 toSelect.push_back(n);
     190            0 :                 if (LaissezVib* lv = n->laissezVib()) {
     191            0 :                     score->undoRemoveElement(lv);
     192              :                 }
     193              :             }
     194              :         }
     195            0 :         bool allNotesSelected = chordNotes.size() == selectedChordNotes.size() ? true : false;
     196              : 
     197            0 :         while (cr->endTick() != score->endTick()) { // not at end of score
     198            0 :             if (ncr && cr->endTick() == ncr->tick() && ncr->isChord()) {
     199            0 :                 break;
     200              :             }
     201            0 :             if (!ncr || cr->endTick() != ncr->tick()  // if voices>0 have empty measures till end OR have empty measures between cr and ncr
     202            0 :                 || cr->tuplet() != ncr->tuplet() || !allNotesSelected) {
     203            0 :                 std::vector<Note*> notesToExtend = !allNotesSelected ? (allNotesSelected = true, selectedChordNotes) : toChord(cr)->notes();
     204            0 :                 is.setTrack(cr->track());
     205            0 :                 is.setSegment(cr->segment());
     206            0 :                 is.moveToNextInputPos();
     207            0 :                 is.setLastSegment(is.segment());
     208              : 
     209            0 :                 if (!is.cr()) {
     210            0 :                     score->expandVoice();
     211              :                 }
     212            0 :                 is.setDuration(is.cr()->durationType());
     213              : 
     214            0 :                 Note* nn = nullptr;
     215            0 :                 for (size_t i = 0; i < notesToExtend.size(); i++) {
     216            0 :                     Note* note = notesToExtend[i];
     217            0 :                     NoteVal nval(note->noteVal());
     218            0 :                     nn = NoteInput::addPitch(tx, score, nval, i != 0);
     219              : 
     220            0 :                     Tie* tie =  Factory::createTie(note);
     221            0 :                     tie->setStartNote(note);
     222            0 :                     tie->setTrack(note->track());
     223            0 :                     tie->setTick(note->chord()->segment()->tick());
     224            0 :                     tie->setEndNote(nn);
     225            0 :                     tie->setTicks(nn->chord()->segment()->tick() - note->chord()->segment()->tick());
     226            0 :                     score->undoAddElement(tie);
     227              :                 }
     228            0 :                 cr = toChordRest(nn->chord());
     229            0 :                 toSelect.push_back(cr);
     230            0 :             } else {
     231            0 :                 Fraction newDur = cr->ticks() + ncr->ticks();
     232            0 :                 score->changeCRlen(cr, newDur);
     233            0 :                 while (toChord(cr)->notes()[0]->tieFor()) {
     234            0 :                     cr = Navigation::nextChordRest(cr);
     235            0 :                     toSelect.push_back(cr);
     236              :                 }
     237              :             }
     238            0 :             ncr = Navigation::nextChordRest(cr);
     239            0 :             endTick = cr->endTick() >= endTick ? cr->endTick() : endTick;
     240              :         }
     241            0 :     }
     242              : 
     243            0 :     if (wasRangeSelection) {
     244            0 :         selection.setRangeTicks(startTick, endTick, startStaff, endStaff, startBox, endBox);
     245            0 :         selection.updateSelectedElements();
     246              :     } else {
     247            0 :         for (EngravingItem* ei : toSelect) {
     248            0 :             if (ei->isChord()) {
     249            0 :                 std::vector<Note*> notes = toChord(ei)->notes();
     250            0 :                 score->select({ notes.begin(), notes.end() }, SelectType::ADD);
     251            0 :             } else {
     252            0 :                 score->select(ei, SelectType::ADD);
     253              :             }
     254              :         }
     255              :     }
     256            0 : }
        

Generated by: LCOV version 2.0-1