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) 2025 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 "edittie.h"
24 :
25 : #include "../dom/chord.h"
26 : #include "../dom/factory.h"
27 : #include "../dom/input.h"
28 : #include "../dom/laissezvib.h"
29 : #include "../dom/note.h"
30 : #include "../dom/partialtie.h"
31 : #include "../dom/score.h"
32 : #include "../dom/select.h"
33 : #include "../dom/tie.h"
34 : #include "../dom/tiejumppointlist.h"
35 : #include "../dom/utils.h"
36 :
37 : #include "editchord.h"
38 : #include "noteinput.h"
39 : #include "transaction/transaction.h"
40 :
41 : #include "log.h"
42 :
43 : using namespace mu::engraving;
44 :
45 46 : void ChangeTieJumpPointActive::flip()
46 : {
47 46 : TieJumpPoint* jumpPoint = m_jumpPointList->findJumpPoint(m_id);
48 46 : if (!jumpPoint) {
49 0 : return;
50 : }
51 46 : bool oldActive = jumpPoint->active();
52 :
53 46 : if (m_active && jumpPoint->endTie() && jumpPoint->endTie()->jumpPoint() != jumpPoint) {
54 12 : jumpPoint->endTie()->setJumpPoint(jumpPoint);
55 : }
56 :
57 46 : jumpPoint->setActive(m_active);
58 46 : m_active = oldActive;
59 : }
60 :
61 11 : std::vector<Note*> EditTie::cmdTieNoteList(const Selection& selection, bool noteEntryMode)
62 : {
63 11 : EngravingItem* el = selection.element();
64 11 : if (Note* n = InputState::note(el)) {
65 10 : if (noteEntryMode) {
66 2 : return n->chord()->notes();
67 : } else {
68 24 : return { n };
69 : }
70 : } else {
71 1 : ChordRest* cr = InputState::chordRest(el);
72 1 : if (cr && cr->isChord()) {
73 0 : return toChord(cr)->notes();
74 : }
75 : }
76 1 : return selection.noteList();
77 : }
78 :
79 11 : static Tie* createAndAddTie(Note* startNote, Note* endNote)
80 : {
81 11 : Score* score = startNote->score();
82 11 : Tie* tie = endNote ? Factory::createTie(startNote) : Factory::createPartialTie(startNote);
83 11 : tie->setStartNote(startNote);
84 11 : tie->setTrack(startNote->track());
85 11 : tie->setTick(startNote->chord()->segment()->tick());
86 11 : if (endNote) {
87 9 : if (endNote->tieBack()) {
88 1 : score->undoRemoveElement(endNote->tieBack());
89 : }
90 9 : tie->setEndNote(endNote);
91 9 : tie->setTicks(endNote->chord()->segment()->tick() - startNote->chord()->segment()->tick());
92 : }
93 11 : score->undoAddElement(tie);
94 :
95 11 : tie->addTiesToJumpPoints();
96 11 : if (!tie->endNote() && tie->tieJumpPoints() && tie->tieJumpPoints()->empty()) {
97 0 : score->undoRemoveElement(tie);
98 0 : tie = nullptr;
99 : }
100 :
101 11 : return tie;
102 : }
103 :
104 1 : void EditTie::cmdAddTie(Score* score, bool addToChord)
105 : {
106 1 : std::vector<Note*> noteList = cmdTieNoteList(score->selection(), score->noteEntryMode());
107 1 : if (noteList.empty()) {
108 0 : LOGD("no notes selected");
109 0 : return;
110 : }
111 :
112 1 : std::sort(noteList.begin(), noteList.end(), [](const Note* a, const Note* b) { return a->track() < b->track(); });
113 1 : track_idx_t track = noteList.at(0)->track();
114 :
115 1 : std::vector<EngravingItem*> toSelect;
116 :
117 1 : score->startCmd(TranslatableString("undoableAction", "Add tie"));
118 1 : Chord* lastAddedChord = nullptr;
119 :
120 1 : Transaction& tx = score->transactionManager()->currentOrDummyTransaction();
121 :
122 1 : InputState& is = score->inputState();
123 :
124 2 : for (Note* note : noteList) {
125 1 : if (note->tieFor()) {
126 0 : LOGD("cmdAddTie: note %p has already tie? noteFor: %p", note, note->tieFor());
127 0 : if (addToChord) {
128 0 : continue;
129 : } else {
130 0 : score->undoRemoveElement(note->tieFor());
131 : }
132 : }
133 :
134 1 : ChordRest* cr = nullptr;
135 1 : Chord* c = note->chord();
136 1 : int staffMove = c->staffMove();
137 :
138 : // set cursor at position after note
139 1 : if (c->isGraceBefore()) {
140 : // tie grace note before to main note
141 1 : cr = toChord(c->ownershipParent());
142 1 : addToChord = true;
143 : } else {
144 0 : is.setTrack(note->chord()->track());
145 0 : is.setSegment(note->chord()->segment());
146 0 : is.moveToNextInputPos();
147 0 : if (is.beyondScore()) {
148 0 : score->appendMeasures(1);
149 0 : is.moveToNextInputPos();
150 : }
151 0 : is.setLastSegment(is.segment());
152 :
153 0 : if (!is.cr()) {
154 0 : score->expandVoice();
155 : }
156 0 : cr = is.cr();
157 : }
158 1 : if (!cr) {
159 0 : break;
160 : }
161 :
162 1 : bool addFlag = lastAddedChord != nullptr;
163 1 : if (c->track() != track) {
164 0 : addFlag = false;
165 0 : track = c->track();
166 : }
167 : // try to re-use existing note or chord
168 1 : Note* n = nullptr;
169 1 : if (addToChord && cr->isChord()) {
170 1 : Chord* chord = toChord(cr);
171 1 : Note* nn = chord->findNote(note->pitch());
172 1 : if (nn && nn->tpc() == note->tpc()) {
173 1 : n = nn; // re-use note
174 : } else {
175 0 : addFlag = true; // re-use chord
176 : }
177 : }
178 :
179 : // if no note to re-use, create one
180 1 : NoteVal nval(note->noteVal());
181 1 : if (!n) {
182 0 : n = NoteInput::addPitch(tx, score, nval, addFlag);
183 0 : if (staffMove != 0) {
184 0 : score->undo(new ChangeChordStaffMove(n->chord(), staffMove));
185 : }
186 : } else {
187 1 : score->select(n);
188 : }
189 :
190 1 : if (n) {
191 1 : if (!lastAddedChord) {
192 1 : lastAddedChord = n->chord();
193 : }
194 : // n is not necessarily next note if duration span over measure
195 1 : Note* nnote = searchTieNote(note);
196 1 : while (nnote) {
197 : // DEBUG: if duration spans over measure
198 : // this does not set line for intermediate notes
199 : // tpc was set correctly already
200 : //n->setLine(note->line());
201 : //n->setTpc(note->tpc());
202 1 : createAndAddTie(note, nnote);
203 :
204 1 : if (!addFlag || nnote->chord()->tick() >= lastAddedChord->tick() || nnote->chord()->isGrace()) {
205 1 : break;
206 : } else {
207 0 : note = nnote;
208 0 : is.setLastSegment(is.segment());
209 0 : nnote = NoteInput::addPitch(tx, score, nval, true);
210 : }
211 : }
212 1 : if (staffMove != 0) {
213 0 : for (Note* tiedNote : n->tiedNotes()) {
214 0 : score->undo(new ChangeChordStaffMove(tiedNote->chord(), staffMove));
215 0 : }
216 : }
217 : }
218 1 : toSelect.push_back(n);
219 : }
220 1 : if (lastAddedChord) {
221 1 : NoteInput::nextInputPos(tx, score, lastAddedChord, false);
222 : }
223 2 : for (EngravingItem* e : toSelect) {
224 1 : if (score->canReselectItem(e)) {
225 0 : score->select(e, SelectType::ADD);
226 : }
227 : }
228 1 : score->endCmd();
229 1 : }
230 :
231 10 : Tie* EditTie::cmdToggleTie(Score* score)
232 : {
233 10 : std::vector<Note*> noteList = cmdTieNoteList(score->selection(), score->noteEntryMode());
234 :
235 10 : if (noteList.empty()) {
236 0 : LOGD("no notes selected");
237 0 : return nullptr;
238 : }
239 :
240 10 : std::vector<Note*> tieNoteList(noteList.size());
241 10 : bool singleTick = true;
242 10 : bool someHaveExistingNextNoteToTieTo = false;
243 10 : bool allHaveExistingNextNoteToTieTo = true;
244 21 : for (size_t i = 0; i < noteList.size(); ++i) {
245 11 : Note* n = noteList[i];
246 11 : if (n->chord()->tick() != noteList.front()->tick()) {
247 1 : singleTick = false;
248 : }
249 11 : if (n->tieFor()) {
250 0 : tieNoteList[i] = nullptr;
251 : } else {
252 11 : Note* tieNote = searchTieNote(n);
253 11 : tieNoteList[i] = tieNote;
254 11 : if (tieNote || n->chord()->hasFollowingJumpItem()) {
255 10 : someHaveExistingNextNoteToTieTo = true;
256 : } else {
257 1 : allHaveExistingNextNoteToTieTo = false;
258 : }
259 : }
260 : }
261 :
262 10 : const bool shouldTieListSelection = noteList.size() >= 2 && !singleTick;
263 :
264 10 : if (singleTick /* i.e. all notes are in the same tick */ && !allHaveExistingNextNoteToTieTo) {
265 0 : cmdAddTie(score);
266 0 : return nullptr;
267 : }
268 :
269 : const TranslatableString actionName = someHaveExistingNextNoteToTieTo
270 10 : ? TranslatableString("undoableAction", "Add tie")
271 10 : : TranslatableString("undoableAction", "Remove tie");
272 :
273 10 : score->startCmd(actionName);
274 :
275 10 : Tie* tie = nullptr;
276 :
277 21 : for (size_t i = 0; i < noteList.size(); ++i) {
278 11 : Note* note = noteList[i];
279 11 : Note* tieToNote = tieNoteList[i];
280 :
281 11 : if (!note) {
282 1 : continue;
283 : }
284 :
285 : // Tie to adjacent unselected note
286 10 : if (someHaveExistingNextNoteToTieTo && tieToNote) {
287 7 : Note* startNote = note->tick() <= tieToNote->tick() ? note : tieToNote;
288 7 : Note* endNote = startNote == tieToNote ? note : tieToNote;
289 7 : tie = createAndAddTie(startNote, endNote);
290 7 : continue;
291 7 : }
292 :
293 3 : Tie* oldTie = note->tieFor();
294 3 : Chord* chord = note->chord();
295 3 : if (oldTie) {
296 : // Toggle existing tie off
297 0 : if (oldTie->tieJumpPoints()) {
298 0 : oldTie->undoRemoveTiesFromJumpPoints();
299 : }
300 0 : score->undoRemoveElement(oldTie);
301 0 : continue;
302 : }
303 :
304 3 : if (chord->hasFollowingJumpItem()) {
305 : // Create partial tie
306 2 : tie = createAndAddTie(note, nullptr);
307 2 : continue;
308 : }
309 :
310 1 : if (!shouldTieListSelection || i > noteList.size() - 2) {
311 0 : continue;
312 : }
313 :
314 : // Tie to next appropriate note in selection
315 1 : Note* note2 = nullptr;
316 :
317 1 : for (size_t j = i + 1; j < noteList.size(); ++j) {
318 1 : Note* candidateNote = noteList[j];
319 1 : if (!candidateNote) {
320 0 : continue;
321 : }
322 1 : const bool samePart = note->part() == candidateNote->part();
323 1 : const bool samePitch = note->pitch() == candidateNote->pitch();
324 1 : const bool sameUnisonIdx = note->unisonIndex() == candidateNote->unisonIndex();
325 1 : const bool diffTick = note->tick() != candidateNote->tick();
326 1 : if (samePart && samePitch && sameUnisonIdx && diffTick) {
327 1 : note2 = candidateNote;
328 1 : noteList[j] = nullptr;
329 1 : break;
330 : }
331 : }
332 :
333 1 : if (!(note && note2)) {
334 0 : continue;
335 : }
336 :
337 1 : Note* startNote = note->tick() <= note2->tick() ? note : note2;
338 1 : Note* endNote = startNote == note2 ? note : note2;
339 :
340 1 : tie = createAndAddTie(startNote, endNote);
341 : }
342 :
343 10 : score->endCmd();
344 :
345 10 : return tie;
346 10 : }
347 :
348 1 : void EditTie::cmdToggleLaissezVib(Score* score)
349 : {
350 1 : const std::vector<Note*> noteList = score->selection().noteList();
351 :
352 1 : if (noteList.empty()) {
353 0 : LOGD("no notes selected");
354 0 : return;
355 : }
356 :
357 1 : score->startCmd(TranslatableString("undoableAction", "Toggle laissez vibrer"));
358 :
359 2 : for (Note* note: noteList) {
360 1 : if (LaissezVib* lv = note->laissezVib()) {
361 0 : score->undoRemoveElement(lv);
362 1 : } else if (note->tieFor()) {
363 0 : continue;
364 : } else {
365 1 : LaissezVib* lvTie = Factory::createLaissezVib(note);
366 1 : score->undoAddElement(lvTie);
367 : }
368 : }
369 :
370 1 : score->endCmd();
371 1 : }
|