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
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 "clonevoice.h"
24 :
25 : #include "../dom/beam.h"
26 : #include "../dom/chord.h"
27 : #include "../dom/factory.h"
28 : #include "../dom/interval.h"
29 : #include "../dom/linkedobjects.h"
30 : #include "../dom/measure.h"
31 : #include "../dom/note.h"
32 : #include "../dom/rest.h"
33 : #include "../dom/score.h"
34 : #include "../dom/segment.h"
35 : #include "../dom/spanner.h"
36 : #include "../dom/staff.h"
37 : #include "../dom/tiemap.h"
38 : #include "../dom/tremolotwochord.h"
39 : #include "../dom/tupletmap.h"
40 :
41 : #include "transpose.h"
42 :
43 : using namespace mu::engraving;
44 :
45 11 : static void doCloneVoice(Score* destScore, track_idx_t srcTrack, track_idx_t dstTrack, Segment* sourceSeg, const Fraction& lTick,
46 : bool link = true)
47 : {
48 11 : Score* sourceScore = sourceSeg->score();
49 11 : Fraction start = sourceSeg->tick();
50 11 : TieMap tieMap;
51 11 : TupletMap tupletMap; // tuplets cannot cross measure boundaries
52 11 : TremoloTwoChord* tremolo = nullptr;
53 :
54 166 : auto maybeLinkedClone = [link](EngravingItem* item) -> EngravingItem* {
55 : // If we want to maintain the link (exchange voice) create a linked clone
56 : // If we want new, unlinked elements (implode/explode) create a clone
57 166 : if (link) {
58 0 : return item->linkedClone();
59 : } else {
60 166 : return item->clone();
61 : }
62 11 : };
63 :
64 196 : for (Segment* oseg = sourceSeg; oseg && oseg->tick() < lTick; oseg = oseg->next1()) {
65 185 : Segment* ns = 0; //create segment later, on demand
66 185 : Measure* dm = destScore->tick2measure(oseg->tick());
67 :
68 185 : EngravingItem* oe = oseg->element(srcTrack);
69 :
70 185 : if (oe && !oe->generated() && oe->isChordRest()) {
71 132 : ChordRest* ocr = toChordRest(oe);
72 132 : ChordRest* ncr = toChordRest(maybeLinkedClone(ocr));
73 132 : ncr->setScore(destScore);
74 132 : ncr->setTrack(dstTrack);
75 :
76 : //Don't clone gaps to a first voice
77 132 : if (!(ncr->track() % VOICES) && ncr->isRest()) {
78 7 : toRest(ncr)->setGap(false);
79 : }
80 :
81 : //Handle beams
82 132 : if (ocr->beam() && !ocr->beam()->empty() && ocr->beam()->elements().front() == ocr) {
83 6 : Beam* nb = ocr->beam()->clone();
84 6 : nb->clear();
85 6 : nb->setTrack(dstTrack);
86 6 : nb->setScore(destScore);
87 6 : nb->add(ncr);
88 6 : ncr->setBeam(nb);
89 : }
90 :
91 : // clone Tuplets
92 132 : Tuplet* ot = ocr->tuplet();
93 132 : if (ot) {
94 21 : ot->setTrack(srcTrack);
95 21 : Tuplet* nt = tupletMap.findNew(ot);
96 21 : if (nt == 0) {
97 7 : nt = toTuplet(maybeLinkedClone(ot));
98 7 : nt->setTrack(dstTrack);
99 7 : nt->setOwnershipParent(dm);
100 7 : tupletMap.add(ot, nt);
101 :
102 7 : Tuplet* nt1 = nt;
103 7 : while (ot->tuplet()) {
104 0 : Tuplet* nt2 = tupletMap.findNew(ot->tuplet());
105 0 : if (nt2 == 0) {
106 0 : nt2 = toTuplet(maybeLinkedClone(ot->tuplet()));
107 0 : nt2->setTrack(dstTrack);
108 0 : nt2->setOwnershipParent(dm);
109 0 : tupletMap.add(ot->tuplet(), nt2);
110 : }
111 0 : nt1->setTuplet(nt2);
112 0 : ot = ot->tuplet();
113 0 : nt1 = nt2;
114 : }
115 : }
116 21 : ncr->setTuplet(nt);
117 : }
118 :
119 : // clone additional settings
120 132 : if (oe->isRest()) {
121 21 : Rest* ore = toRest(ocr);
122 : // If we would clone a full measure rest just don't clone this rest
123 21 : if (ore->isFullMeasureRest() && (dstTrack % VOICES)) {
124 0 : continue;
125 : }
126 : }
127 :
128 111 : auto cloneChord = [&](Chord* oldChord, Chord* newChord) {
129 111 : size_t n = oldChord->notes().size();
130 222 : for (size_t i = 0; i < n; ++i) {
131 111 : Note* on = oldChord->notes().at(i);
132 111 : Note* nn = newChord->notes().at(i);
133 111 : staff_idx_t idx = track2staff(dstTrack);
134 111 : Fraction tick = oseg->tick();
135 111 : Interval v = destScore->staff(idx) ? destScore->staff(idx)->transpose(tick) : Interval();
136 111 : nn->setTpc1(on->tpc1());
137 111 : if (v.isZero()) {
138 80 : nn->setTpc2(on->tpc1());
139 : } else {
140 31 : v.flip();
141 31 : nn->setTpc2(Transpose::transposeTpc(nn->tpc1(), v, true));
142 : }
143 :
144 111 : if (on->tieFor()) {
145 9 : Tie* tie = toTie(maybeLinkedClone(on->tieFor()));
146 9 : tie->setScore(destScore);
147 9 : nn->setTieFor(tie);
148 9 : tie->setStartNote(nn);
149 9 : tie->setTrack(nn->track());
150 9 : tie->setEndNote(nn);
151 9 : tieMap.add(on->tieFor(), tie);
152 : }
153 111 : if (on->tieBack()) {
154 9 : Tie* tie = tieMap.findNew(on->tieBack());
155 9 : if (tie) {
156 9 : nn->setTieBack(tie);
157 9 : tie->setEndNote(nn);
158 : } else {
159 0 : LOGD("cloneVoices: cannot find tie");
160 : }
161 : }
162 : // add back spanners (going back from end to start spanner element
163 : // makes sure the 'other' spanner anchor element is already set up)
164 : // 'on' is the old spanner end note and 'nn' is the new spanner end note
165 111 : for (Spanner* oldSp : on->spannerBack()) {
166 0 : Note* newStart = Spanner::startElementFromSpanner(oldSp, nn);
167 0 : if (newStart) {
168 0 : Spanner* newSp = toSpanner(maybeLinkedClone(oldSp));
169 0 : newSp->setNoteSpan(newStart, nn);
170 0 : destScore->doUndoAddElement(newSp);
171 : } else {
172 0 : LOGD("cloneVoices: cannot find spanner start note");
173 : }
174 : }
175 : }
176 : // two note tremolo
177 111 : if (oldChord->tremoloTwoChord()) {
178 0 : if (oldChord == oldChord->tremoloTwoChord()->chord1()) {
179 0 : if (tremolo) {
180 0 : LOGD("unconnected two note tremolo");
181 : }
182 0 : tremolo = item_cast<TremoloTwoChord*>(maybeLinkedClone(oldChord->tremoloTwoChord()));
183 0 : tremolo->setScore(newChord->score());
184 0 : tremolo->setOwnershipParent(newChord);
185 0 : tremolo->setTrack(newChord->track());
186 0 : tremolo->setChords(newChord, nullptr);
187 0 : newChord->setTremoloTwoChord(tremolo);
188 0 : } else if (oldChord == oldChord->tremoloTwoChord()->chord2()) {
189 0 : if (!tremolo) {
190 0 : LOGD("first note for two note tremolo missing");
191 : } else {
192 0 : tremolo->setChords(tremolo->chord1(), newChord);
193 0 : newChord->setTremoloTwoChord(tremolo);
194 : }
195 : } else {
196 0 : LOGD("inconsistent two note tremolo");
197 : }
198 : }
199 111 : };
200 132 : if (oe->isChord()) {
201 111 : cloneChord(toChord(ocr), toChord(ncr));
202 111 : for (size_t i = 0; i < toChord(ocr)->graceNotes().size(); ++i) {
203 0 : Chord* ogc = toChord(ocr)->graceNotes().at(i);
204 0 : Chord* ngc = toChord(ncr)->graceNotes().at(i);
205 0 : cloneChord(ogc, ngc);
206 : }
207 : }
208 :
209 : // Add element
210 132 : if (link) {
211 : // To current staff only (exchange voice)
212 0 : if (!ns) {
213 0 : ns = dm->undoGetSegment(oseg->segmentType(), oseg->tick());
214 : }
215 0 : ncr->setOwnershipParent(ns);
216 0 : destScore->doUndoAddElement(ncr);
217 : } else {
218 : // To all linked staves (implode/explode)
219 132 : destScore->undoAddCR(ncr, dm, oseg->tick());
220 : }
221 : }
222 :
223 185 : Segment* tst = dm->segments().firstCRSegment();
224 185 : if (srcTrack % VOICES && !(dstTrack % VOICES) && (!tst || (!tst->element(dstTrack)))) {
225 9 : Rest* rest = Factory::createRest(destScore->dummy());
226 9 : rest->setTicks(dm->ticks());
227 9 : rest->setDurationType(DurationType::V_MEASURE);
228 9 : rest->setTrack(dstTrack);
229 9 : if (link) {
230 0 : Segment* segment = dm->undoGetSegment(SegmentType::ChordRest, dm->tick());
231 0 : rest->setOwnershipParent(segment);
232 0 : destScore->doUndoAddElement(rest);
233 : } else {
234 9 : destScore->undoAddCR(rest, dm, dm->tick());
235 : }
236 : }
237 :
238 185 : const std::vector<EngravingItem*> annotations = oseg->annotations();
239 241 : for (EngravingItem* annotation : annotations) {
240 56 : if (!annotation->elementAppliesToTrack(srcTrack)) {
241 40 : continue;
242 : }
243 :
244 16 : EngravingItem* newAnnotation = maybeLinkedClone(annotation);
245 16 : newAnnotation->setTrack(dstTrack);
246 :
247 16 : if (!ns) {
248 13 : ns = dm->undoGetSegment(oseg->segmentType(), oseg->tick());
249 : }
250 16 : newAnnotation->setOwnershipParent(ns);
251 :
252 : // Add element
253 16 : if (link) {
254 : // To current staff only (exchange voice)
255 0 : destScore->doUndoAddElement(newAnnotation);
256 : } else {
257 : // To all linked staves (implode/explode)
258 16 : destScore->undoAddElement(newAnnotation);
259 : }
260 : }
261 185 : }
262 :
263 : // Find and add corresponding slurs and hairpins
264 13 : static const std::set<ElementType> SPANNERS_TO_COPY { ElementType::SLUR, ElementType::HAMMER_ON_PULL_OFF, ElementType::HAIRPIN };
265 11 : auto spanners = sourceScore->spannerMap().findOverlapping(start.ticks(), lTick.ticks());
266 24 : for (auto i = spanners.begin(); i < spanners.end(); i++) {
267 13 : Spanner* sp = i->value;
268 13 : Fraction spStart = sp->tick();
269 13 : Fraction spEnd = spStart + sp->ticks();
270 :
271 13 : if (muse::contains(SPANNERS_TO_COPY, sp->type()) && (spStart >= start && spEnd < lTick)) {
272 13 : if (!sp->elementAppliesToTrack(srcTrack)) {
273 11 : continue;
274 : }
275 2 : Spanner* ns = toSpanner(maybeLinkedClone(sp));
276 :
277 2 : ns->setScore(destScore);
278 2 : ns->setOwnershipParent(0);
279 2 : ns->setTrack(dstTrack);
280 2 : ns->setTrack2(dstTrack);
281 :
282 : // set start/end element for slur
283 2 : ChordRest* cr1 = sp->startCR();
284 2 : ChordRest* cr2 = sp->endCR();
285 :
286 2 : ns->setStartElement(0);
287 2 : ns->setEndElement(0);
288 2 : if (cr1 && cr1->links()) {
289 0 : for (EngravingObject* e : *cr1->links()) {
290 0 : ChordRest* cr = toChordRest(e);
291 0 : if (cr == cr1) {
292 0 : continue;
293 : }
294 0 : if ((cr->score() == destScore) && (cr->tick() == ns->tick()) && cr->track() == dstTrack) {
295 0 : ns->setStartElement(cr);
296 0 : break;
297 : }
298 : }
299 : }
300 2 : if (cr2 && cr2->links()) {
301 0 : for (EngravingObject* e : *cr2->links()) {
302 0 : ChordRest* cr = toChordRest(e);
303 0 : if (cr == cr2) {
304 0 : continue;
305 : }
306 0 : if ((cr->score() == destScore) && (cr->tick() == ns->tick2()) && cr->track() == dstTrack) {
307 0 : ns->setEndElement(cr);
308 0 : break;
309 : }
310 : }
311 : }
312 2 : if (link) {
313 0 : destScore->doUndoAddElement(ns);
314 : } else {
315 2 : destScore->undoAddElement(ns);
316 : }
317 : }
318 : }
319 11 : }
320 :
321 11 : void CloneVoice::cloneVoice(
322 : Segment* sourceSeg, // first source segment
323 : const Fraction& lTick, // last tick to clone
324 : Segment* destSeg, // first destination segment
325 : track_idx_t strack,
326 : track_idx_t dtrack,
327 : bool deleteSource,
328 : bool link // if true add elements in destination segment only
329 : // if false add elements in every linked staff
330 : )
331 : {
332 11 : Score* s = destSeg->score();
333 11 : Fraction ticks = destSeg->tick() + lTick - sourceSeg->tick();
334 :
335 : // Clear destination voice (in case of not linked and otrack = -1 we would delete our source
336 196 : for (Segment* seg = destSeg; seg && seg->tick() < ticks; seg = seg->next1()) {
337 185 : EngravingItem* el = seg->element(dtrack);
338 185 : if (el && el->isChordRest()) {
339 16 : link ? s->doUndoRemoveElement(el) : s->undoRemoveElement(el);
340 : }
341 : }
342 :
343 11 : doCloneVoice(s, strack, dtrack, sourceSeg, ticks, link);
344 :
345 11 : if (deleteSource) {
346 3 : auto spanners = s->spannerMap().findOverlapping(sourceSeg->tick().ticks(), lTick.ticks());
347 3 : for (auto i = spanners.begin(); i < spanners.end(); i++) {
348 0 : Spanner* sp = i->value;
349 0 : if (sp->isSlur() && (sp->track() == strack || sp->track2() == strack)) {
350 0 : link ? s->doUndoRemoveElement(sp) : s->undoRemoveElement(sp);
351 : }
352 : }
353 48 : for (Segment* seg = destSeg; seg && seg->tick() < ticks; seg = seg->next1()) {
354 45 : EngravingItem* el = seg->element(strack);
355 45 : if (el && el->isChordRest()) {
356 36 : link ? s->doUndoRemoveElement(el) : s->undoRemoveElement(el);
357 : }
358 :
359 45 : const std::vector<EngravingItem*> annotations = seg->annotations();
360 93 : for (EngravingItem* annotation : annotations) {
361 48 : if (annotation && annotation->track() == strack) {
362 6 : if (annotation->hasVoiceAssignmentProperties()) {
363 3 : VoiceAssignment voiceAssignment = annotation->getProperty(Pid::VOICE_ASSIGNMENT).value<VoiceAssignment>();
364 3 : if (voiceAssignment != VoiceAssignment::CURRENT_VOICE_ONLY) {
365 0 : continue;
366 : }
367 : }
368 6 : link ? s->doUndoRemoveElement(annotation) : s->undoRemoveElement(annotation);
369 : }
370 : }
371 45 : }
372 : // Set rests if first voice in a staff
373 3 : if (!(strack % VOICES)) {
374 : // TODO: take `link` into account?
375 0 : s->setRest(destSeg->tick(), strack, ticks, false, nullptr);
376 : }
377 3 : }
378 11 : }
|