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 : }
|