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 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 "stavesharinglayout.h"
24 :
25 : #include "measurelayout.h"
26 : #include "systemheaderlayout.h"
27 :
28 : #include "dom/breath.h"
29 : #include "dom/chord.h"
30 : #include "dom/factory.h"
31 : #include "dom/instrchange.h"
32 : #include "dom/keysig.h"
33 : #include "dom/measure.h"
34 : #include "dom/note.h"
35 : #include "dom/part.h"
36 : #include "dom/rest.h"
37 : #include "dom/score.h"
38 : #include "dom/staff.h"
39 : #include "dom/system.h"
40 : #include "dom/timesig.h"
41 :
42 : using namespace mu::engraving;
43 :
44 : namespace mu::engraving::rendering::score {
45 79 : void StaveSharingLayout::updateStaveSharingForFullSystem(MeasureBase* firstMB, MeasureBase* lastMB, LayoutContext& ctx)
46 : {
47 79 : StaveSharingContext ssctx(firstMB, lastMB, ctx);
48 79 : if (ssctx.crSegments.empty()) {
49 0 : return;
50 : }
51 :
52 79 : ssctx.updateForLastAdded = false;
53 :
54 79 : updateStaveSharing(ssctx);
55 79 : }
56 :
57 34114 : bool StaveSharingLayout::updateStaveSharingForLastAddedMeasure(System* system, LayoutContext& ctx)
58 : {
59 34114 : if (!system->last()->isMeasure()) {
60 2432 : return false;
61 : }
62 :
63 31682 : StaveSharingContext ssctx(system->first(), system->last(), ctx);
64 31682 : if (ssctx.crSegments.empty()) {
65 64 : return false;
66 : }
67 :
68 31618 : ssctx.updateForLastAdded = true;
69 :
70 31618 : updateStaveSharing(ssctx);
71 :
72 31618 : if (ssctx.trackMapChanged) {
73 15 : SystemHeaderLayout::updateSystemHeaderWidth(system, ctx);
74 :
75 17 : for (MeasureBase* mb = system->first(); mb; mb = mb->next()) {
76 17 : if (mb->isMeasure()) {
77 17 : toMeasure(mb)->mutldata()->setNeedLayout(true);
78 : // TODO: only relayout shared staves
79 17 : MeasureLayout::layoutMeasure(mb, ctx);
80 : }
81 17 : if (mb == system->last()) {
82 15 : break;
83 : }
84 : }
85 : }
86 :
87 31618 : return ssctx.trackMapChanged;
88 31682 : }
89 :
90 0 : void StaveSharingLayout::updateNotationWithoutRecomputingTrackMap(Measure* measure, LayoutContext& ctx)
91 : {
92 0 : StaveSharingContext ssctx(measure, measure, ctx);
93 0 : if (ssctx.crSegments.empty()) {
94 0 : return;
95 : }
96 :
97 0 : for (Part* p : ssctx.layoutCtx.dom().parts()) {
98 0 : if (p->isSharedPart() && toSharedPart(p)->show()) {
99 0 : SharedPart* sharedPart = toSharedPart(p);
100 0 : ssctx.curSharedPart = sharedPart;
101 0 : updateNotation(ssctx);
102 : }
103 : }
104 :
105 0 : measure->mutldata()->setNeedLayout(true);
106 0 : MeasureLayout::layoutMeasure(measure, ctx);
107 0 : }
108 :
109 31697 : void StaveSharingLayout::updateStaveSharing(StaveSharingContext& ctx)
110 : {
111 73087 : for (Part* p : ctx.layoutCtx.dom().parts()) {
112 41390 : if (p->isSharedPart() && toSharedPart(p)->show()) {
113 351 : SharedPart* sharedPart = toSharedPart(p);
114 351 : ctx.curSharedPart = sharedPart;
115 351 : updateTrackMaps(ctx);
116 351 : updateNotation(ctx);
117 : }
118 : }
119 31697 : }
120 :
121 351 : void StaveSharingLayout::updateTrackMaps(StaveSharingContext& ctx)
122 : {
123 351 : const SharedTrackMap& oldTrackMap = ctx.curSharedPart->trackMapAtTick(ctx.sTick);
124 :
125 351 : ctx.curTrackMap = computeTrackMap(ctx);
126 351 : if (ctx.curTrackMap != oldTrackMap) {
127 15 : ctx.trackMapChanged = true;
128 : }
129 :
130 351 : ctx.curSharedPart->removeMapsBetweenTicks(ctx.sTick, ctx.eTick);
131 351 : ctx.curSharedPart->setTrackMapAtTick(ctx.curTrackMap, ctx.sTick);
132 351 : }
133 :
134 351 : SharedTrackMap StaveSharingLayout::computeTrackMap(StaveSharingContext& ctx)
135 : {
136 351 : SharedPart* p = ctx.curSharedPart;
137 :
138 351 : TrackGroup originTracks;
139 351 : originTracks.reserve(p->originParts().size());
140 1018 : for (Part* op : p->originParts()) {
141 667 : originTracks.push_back(op->trackRange().startTrack);
142 : }
143 :
144 351 : std::unordered_set<track_idx_t> processedTracks;
145 :
146 351 : std::map<track_idx_t, TrackGroup> unisonsMap;
147 1018 : for (size_t i = 0; i < originTracks.size(); ++i) {
148 667 : track_idx_t track1 = originTracks[i];
149 667 : if (muse::contains(processedTracks, track1)) {
150 302 : continue;
151 : }
152 :
153 681 : for (size_t j = i + 1; j < originTracks.size(); ++j) {
154 316 : track_idx_t track2 = originTracks[j];
155 316 : if (muse::contains(processedTracks, track2)) {
156 0 : continue;
157 : }
158 316 : if (isUnison(track1, track2, ctx)) {
159 302 : processedTracks.insert(track1);
160 302 : processedTracks.insert(track2);
161 :
162 302 : if (!muse::contains(unisonsMap, track1)) {
163 302 : unisonsMap[track1] = { track1, track2 };
164 : } else {
165 0 : unisonsMap.at(track1).push_back(track2);
166 : }
167 : }
168 : }
169 : }
170 :
171 351 : std::map<track_idx_t, TrackGroup> sameVoiceMap;
172 1018 : for (size_t i = 0; i < originTracks.size(); ++i) {
173 667 : track_idx_t track1 = originTracks[i];
174 667 : if (muse::contains(processedTracks, track1)) {
175 604 : continue;
176 : }
177 63 : processedTracks.insert(track1);
178 :
179 126 : TrackGroup curTrackGroup = { track1 };
180 63 : std::unordered_set<Note*> localUnisonNotes;
181 :
182 63 : track_idx_t lastInThisVoice = track1;
183 77 : for (size_t j = i + 1; j < originTracks.size(); ++j) {
184 14 : track_idx_t track2 = originTracks[j];
185 14 : if (muse::contains(processedTracks, track2)) {
186 0 : continue;
187 : }
188 14 : if (canGoToSameVoice(lastInThisVoice, track2, ctx, curTrackGroup, localUnisonNotes)) {
189 0 : processedTracks.insert(track2);
190 0 : curTrackGroup.push_back(track2);
191 0 : lastInThisVoice = track2;
192 14 : } else if (curTrackGroup.size() > 1) {
193 0 : break;
194 : }
195 : }
196 :
197 63 : sameVoiceMap.emplace(track1, curTrackGroup);
198 63 : }
199 :
200 351 : SharedTrackMap trackMap;
201 :
202 351 : std::unordered_set<track_idx_t> assignedTracks;
203 351 : track_idx_t lastAssignedTrack = muse::nidx;
204 351 : track_idx_t curSharedTrack = p->trackRange().startTrack;
205 :
206 1018 : for (track_idx_t track : originTracks) {
207 667 : if (muse::contains(assignedTracks, track)) {
208 302 : continue;
209 : }
210 :
211 365 : bool canGoOnThisStave = track2voice(curSharedTrack) == 0
212 14 : || lastAssignedTrack == muse::nidx
213 379 : || canGoToSameStave(lastAssignedTrack, track, ctx);
214 365 : if (!canGoOnThisStave) {
215 12 : curSharedTrack = trackZeroVoice(curSharedTrack + VOICES);
216 : }
217 :
218 365 : if (muse::contains(unisonsMap, track)) {
219 906 : for (track_idx_t unisonTrack : unisonsMap[track]) {
220 604 : trackMap[unisonTrack] = curSharedTrack;
221 604 : assignedTracks.insert(unisonTrack);
222 604 : lastAssignedTrack = unisonTrack;
223 : }
224 : } else {
225 126 : for (track_idx_t sharedVoiceTrack : sameVoiceMap[track]) {
226 63 : trackMap[sharedVoiceTrack] = curSharedTrack;
227 63 : assignedTracks.insert(sharedVoiceTrack);
228 63 : lastAssignedTrack = sharedVoiceTrack;
229 : }
230 : }
231 :
232 365 : track2voice(curSharedTrack) == 0 ? ++curSharedTrack : curSharedTrack = trackZeroVoice(curSharedTrack + VOICES);
233 : }
234 :
235 351 : return trackMap;
236 351 : }
237 :
238 0 : bool StaveSharingLayout::isEmpty(track_idx_t track, StaveSharingContext& ctx)
239 : {
240 0 : for (Segment* seg : ctx.crSegments) {
241 0 : ChordRest* cr = toChordRest(seg->element(track));
242 0 : if (cr && !(cr->isRest() && toRest(cr)->isFullMeasureRest())) {
243 0 : return false;
244 : }
245 : }
246 :
247 0 : return true;
248 : }
249 :
250 1750 : bool StaveSharingLayout::sameInstrument(track_idx_t prevTrack, track_idx_t nextTrack, const Fraction& tick, StaveSharingContext& ctx)
251 : {
252 1750 : const Staff* nextStaff = ctx.layoutCtx.dom().staff(track2staff(nextTrack));
253 1750 : const Staff* prevStaff = ctx.layoutCtx.dom().staff(track2staff(prevTrack));
254 :
255 1750 : const Part* nextPart = nextStaff ? nextStaff->part() : nullptr;
256 1750 : const Part* prevPart = prevStaff ? prevStaff->part() : nullptr;
257 :
258 1750 : const Instrument* nextInstrument = nextPart ? nextPart->instrument(tick) : nullptr;
259 1750 : const Instrument* prevInstrument = prevPart ? prevPart->instrument(tick) : nullptr;
260 1750 : if (!nextInstrument || !prevInstrument) {
261 0 : return false;
262 : }
263 :
264 1750 : return nextInstrument->id() == prevInstrument->id() && nextInstrument->transpose() == prevInstrument->transpose();
265 : }
266 :
267 316 : bool StaveSharingLayout::isUnison(track_idx_t prevTrack, track_idx_t nextTrack, StaveSharingContext& ctx)
268 : {
269 1912 : for (Segment* segment : ctx.crSegments) {
270 1610 : if (!sameInstrument(prevTrack, nextTrack, segment->tick(), ctx)) {
271 14 : return false;
272 : }
273 :
274 1604 : ChordRest* cr1 = toChordRest(segment->element(prevTrack));
275 1604 : ChordRest* cr2 = toChordRest(segment->element(nextTrack));
276 1604 : if (bool(cr1) != bool(cr2)) {
277 0 : return false;
278 : }
279 :
280 1604 : if (!cr1) {
281 1542 : continue;
282 : }
283 :
284 1604 : if (cr1->isChord() != cr2->isChord()) {
285 2 : return false;
286 : }
287 :
288 1602 : if (cr1->durationType() != cr2->durationType() || cr1->actualTicks() != cr2->actualTicks()) {
289 4 : return false;
290 : }
291 :
292 1598 : if (!cr1->isChord()) {
293 1542 : continue;
294 : }
295 :
296 56 : Chord* c1 = toChord(cr1);
297 56 : Chord* c2 = toChord(cr2);
298 :
299 56 : auto checkChordsForUnison = [&](Chord* c1, Chord* c2) {
300 56 : if (c1->durationType() != c2->durationType() || c1->noteType() != c2->noteType()) {
301 0 : return false;
302 : }
303 :
304 56 : const std::vector<Note*>& notes1 = c1->notes();
305 56 : const std::vector<Note*>& notes2 = c2->notes();
306 56 : if (notes1.size() != notes2.size()) {
307 0 : return false;
308 : }
309 :
310 110 : for (size_t i = 0; i < notes1.size(); ++i) {
311 56 : Note* n1 = notes1[i];
312 56 : Note* n2 = notes2[i];
313 :
314 56 : if (!n1->isExactUnison(n2)) {
315 2 : return false;
316 : }
317 :
318 54 : if (!checkNoteSpannersForUnison(n1, n2)) {
319 0 : return false;
320 : }
321 : }
322 :
323 54 : if (!checkArticulationsForSameVoice(c1, c2)) {
324 0 : return false;
325 : }
326 :
327 54 : return true;
328 : };
329 :
330 56 : if (!checkChordsForUnison(c1, c2)) {
331 2 : return false;
332 : }
333 :
334 54 : const std::vector<Chord*>& grace1 = c1->graceNotes();
335 54 : const std::vector<Chord*>& grace2 = c2->graceNotes();
336 54 : if (grace1.size() != grace2.size()) {
337 0 : return false;
338 : }
339 54 : for (size_t i = 0; i < grace1.size(); ++i) {
340 0 : if (!checkChordsForUnison(grace1[i], grace2[i])) {
341 0 : return false;
342 : }
343 : }
344 : }
345 :
346 4572 : for (Segment* segment : ctx.allSegments) {
347 4270 : if (!checkSegmentForSameVoice(segment, prevTrack, nextTrack)) {
348 0 : return false;
349 : }
350 : }
351 :
352 302 : if (!checkSpannersForSameVoice(prevTrack, nextTrack, ctx)) {
353 0 : return false;
354 : }
355 :
356 302 : return true;
357 : }
358 :
359 14 : bool StaveSharingLayout::canGoToSameVoice(track_idx_t prevTrack, track_idx_t nextTrack, StaveSharingContext& ctx,
360 : const TrackGroup& curTrackGroup, std::unordered_set<Note*>& localUnisonNotes)
361 : {
362 14 : std::vector<Note*> potentialUnisonNotes;
363 :
364 32 : for (Segment* segment : ctx.crSegments) {
365 30 : if (!sameInstrument(prevTrack, nextTrack, segment->tick(), ctx)) {
366 12 : return false;
367 : }
368 24 : ChordRest* cr1 = toChordRest(segment->element(prevTrack));
369 24 : ChordRest* cr2 = toChordRest(segment->element(nextTrack));
370 24 : if (bool(cr1) != bool(cr2)) {
371 0 : return false;
372 : }
373 :
374 24 : if (!cr1) {
375 6 : continue;
376 : }
377 :
378 24 : if (cr1->isChord() != cr2->isChord()) {
379 2 : return false;
380 : }
381 :
382 22 : if (cr1->durationType() != cr2->durationType() || cr1->actualTicks() != cr2->actualTicks()) {
383 4 : return false;
384 : }
385 :
386 18 : if (!cr1->isChord()) {
387 6 : continue;
388 : }
389 :
390 12 : Chord* c1 = toChord(cr1);
391 12 : Chord* c2 = toChord(cr2);
392 :
393 12 : auto checkChordsForSameVoice = [&](Chord* c1, Chord* c2) {
394 12 : if (c1->durationType() != c2->durationType() || c1->noteType() != c2->noteType()) {
395 0 : return false;
396 : }
397 :
398 12 : const std::vector<Note*>& notes1 = c1->notes();
399 12 : const std::vector<Note*>& notes2 = c2->notes();
400 12 : if (notes1.size() != notes2.size()) {
401 0 : return false;
402 : }
403 :
404 24 : for (size_t i = 0; i < notes1.size(); ++i) {
405 12 : Note* n1 = notes1[i];
406 12 : Note* n2 = notes2[i];
407 :
408 12 : if (n2->pitch() > n1->pitch()) {
409 0 : return false;
410 : }
411 :
412 12 : if (!n2->isExactUnison(n1)) {
413 8 : if (n2->pitch() == n1->pitch() || muse::contains(localUnisonNotes, n1)) {
414 0 : return false;
415 : }
416 :
417 8 : continue;
418 : }
419 :
420 4 : if (!checkNoteSpannersForUnison(n1, n2)) {
421 0 : return false;
422 : }
423 :
424 8 : for (track_idx_t track : curTrackGroup) {
425 4 : if (track == prevTrack) {
426 4 : continue;
427 : }
428 0 : if (ChordRest* cr = toChordRest(segment->element(track)); cr && cr->isChord()) {
429 0 : for (Note* n : toChord(cr)->notes()) {
430 0 : if (!n2->isExactUnison(n)) {
431 0 : return false;
432 : }
433 : }
434 : }
435 : }
436 :
437 4 : potentialUnisonNotes.push_back(n2);
438 : }
439 :
440 12 : return true;
441 12 : };
442 :
443 12 : if (!checkChordsForSameVoice(c1, c2)) {
444 0 : return false;
445 : }
446 :
447 12 : const std::vector<Chord*>& grace1 = c1->graceNotes();
448 12 : const std::vector<Chord*>& grace2 = c2->graceNotes();
449 12 : if (grace1.size() != grace2.size()) {
450 0 : return false;
451 : }
452 12 : for (size_t i = 0; i < grace1.size(); ++i) {
453 0 : if (!checkChordsForSameVoice(grace1[i], grace2[i])) {
454 0 : return false;
455 : }
456 : }
457 :
458 12 : if (!checkArticulationsForSameVoice(c1, c2)) {
459 0 : return false;
460 : }
461 : }
462 :
463 20 : for (Segment* segment : ctx.allSegments) {
464 18 : if (!checkSegmentForSameVoice(segment, prevTrack, nextTrack)) {
465 0 : return false;
466 : }
467 : }
468 :
469 2 : if (!checkSpannersForSameVoice(prevTrack, nextTrack, ctx)) {
470 2 : return false;
471 : }
472 :
473 0 : for (Note* unisonNote : potentialUnisonNotes) {
474 0 : localUnisonNotes.insert(unisonNote);
475 : }
476 :
477 0 : return true;
478 14 : }
479 :
480 4288 : bool StaveSharingLayout::checkSegmentForSameVoice(Segment* segment, track_idx_t prevTrack, track_idx_t nextTrack)
481 : {
482 4288 : if (segment->header()) {
483 1131 : return true;
484 : }
485 :
486 3157 : if (!checkClefKeyTimeSigForSameVoice(segment, prevTrack, nextTrack)) {
487 0 : return false;
488 : }
489 :
490 6314 : if (segment->isBreathType()
491 3157 : && !checkItemsForSameVoice(segment, prevTrack, nextTrack, [](const EngravingItem* item1, const EngravingItem* item2) {
492 0 : return toBreath(item1)->symId() == toBreath(item2)->symId();
493 : })) {
494 0 : return false;
495 : }
496 :
497 3157 : if (!checkSegmentAnnotationsForSameVoice(segment, prevTrack, nextTrack)) {
498 0 : return false;
499 : }
500 :
501 3157 : return true;
502 : }
503 :
504 3219 : bool StaveSharingLayout::checkClefKeyTimeSigForSameVoice(Segment* segment, track_idx_t prevTrack, track_idx_t nextTrack)
505 : {
506 6438 : if (segment->isClefType()
507 3219 : && !checkItemsForSameVoice(segment, prevTrack, nextTrack, [](const EngravingItem* item1, const EngravingItem* item2) {
508 0 : return toClef(item1)->clefType() == toClef(item2)->clefType();
509 : })) {
510 0 : return false;
511 : }
512 :
513 6438 : if (segment->isTimeSigType()
514 3219 : && !checkItemsForSameVoice(segment, prevTrack, nextTrack, [](const EngravingItem* item1, const EngravingItem* item2) {
515 118 : IF_ASSERT_FAILED(item1->isTimeSig() && item2->isTimeSig()) {
516 0 : return false;
517 : }
518 118 : return *toTimeSig(item1) == *toTimeSig(item2);
519 : })) {
520 0 : return false;
521 : }
522 :
523 6438 : if (segment->isKeySigType()
524 3219 : && !checkItemsForSameVoice(segment, prevTrack, nextTrack, [](const EngravingItem* item1, const EngravingItem* item2) {
525 1 : IF_ASSERT_FAILED(item1->isKeySig() && item2->isKeySig()) {
526 0 : return false;
527 : }
528 1 : return *toKeySig(item1) == *toKeySig(item2);
529 : })) {
530 0 : return false;
531 : }
532 :
533 3219 : return true;
534 : }
535 :
536 6 : bool StaveSharingLayout::segHasLocalKeySig(const Segment* segment, const SharedPart* sharedPart)
537 : {
538 : // Key signatures applying to all staves are added to the shared staves at the same time as the origin staves,
539 : // so only key signatures which differ between origin staves need copying to the shared staves
540 6 : const KeySig* firstKeySig = nullptr;
541 10 : for (const Part* originPart : sharedPart->originParts()) {
542 12 : for (const Staff* staff : originPart->staves()) {
543 8 : if (staff->isDrumStaff(segment->tick())) {
544 0 : continue;
545 : }
546 :
547 8 : const EngravingItem* item = segment->element(staff->idx() * VOICES);
548 8 : if (!item || !item->isKeySig()) {
549 4 : return true;
550 : }
551 :
552 4 : const KeySig* keySig = toKeySig(item);
553 4 : if (keySig->forInstrumentChange()) {
554 0 : return true;
555 : }
556 :
557 4 : if (!firstKeySig) {
558 2 : firstKeySig = keySig;
559 2 : } else if (!(*firstKeySig == *keySig)) {
560 0 : return true;
561 : }
562 : }
563 : }
564 :
565 2 : return false;
566 : }
567 :
568 3157 : bool StaveSharingLayout::checkSegmentAnnotationsForSameVoice(Segment* segment, track_idx_t prevTrack, track_idx_t nextTrack)
569 : {
570 3157 : std::multimap<ElementType, EngravingItem*> annotationsOnPrevTrack;
571 3157 : std::multimap<ElementType, EngravingItem*> annotationsOnNextTrack;
572 :
573 3173 : for (EngravingItem* annotation : segment->annotations()) {
574 16 : if (annotation->isInstrumentChange()) {
575 : // Instrument change labels should not prevent combining
576 0 : continue;
577 : }
578 :
579 16 : if (annotation->track() == prevTrack) {
580 0 : annotationsOnPrevTrack.insert({ annotation->type(), annotation });
581 16 : } else if (annotation->track() == nextTrack) {
582 0 : annotationsOnNextTrack.insert({ annotation->type(), annotation });
583 : }
584 : }
585 :
586 3157 : if (annotationsOnPrevTrack.size() != annotationsOnNextTrack.size()) {
587 0 : return false;
588 : }
589 :
590 3157 : for (auto [type, item] : annotationsOnPrevTrack) {
591 0 : auto range = annotationsOnNextTrack.equal_range(type);
592 0 : if (range.first == range.second) {
593 : // No annotation of this type exists in nextTrack
594 0 : return false;
595 : }
596 :
597 0 : if (muse::contains(TEXTBASE_TYPES, type)) {
598 0 : EngravingItem* matchingAnnotation = nullptr;
599 0 : for (auto i = range.first; i != range.second; ++i) {
600 0 : EngravingItem* nextItem = i->second;
601 0 : if (nextItem->type() == type && toTextBase(item)->xmlText() == toTextBase(nextItem)->xmlText()) {
602 0 : matchingAnnotation = nextItem;
603 0 : break;
604 : }
605 : }
606 :
607 0 : if (!matchingAnnotation) {
608 0 : return false;
609 : }
610 : }
611 :
612 : // TODO: other types will probably need other checks
613 : }
614 :
615 3157 : return true;
616 3157 : }
617 :
618 119 : bool StaveSharingLayout::checkItemsForSameVoice(Segment* segment, track_idx_t prevTrack, track_idx_t nextTrack,
619 : const std::function<bool(const EngravingItem*, const EngravingItem*)>& isEqual)
620 : {
621 119 : EngravingItem* item1 = segment->element(prevTrack);
622 119 : EngravingItem* item2 = segment->element(nextTrack);
623 :
624 119 : if (bool(item1) != bool(item2)) {
625 0 : return false;
626 : }
627 :
628 119 : if (item1 && !isEqual(item1, item2)) {
629 0 : return false;
630 : }
631 :
632 119 : return true;
633 : }
634 :
635 58 : bool StaveSharingLayout::checkNoteSpannersForUnison(const Note* note1, const Note* note2)
636 : {
637 58 : const Tie* tieBack1 = note1->tieBack();
638 58 : const Tie* tieBack2 = note2->tieBack();
639 58 : if (bool(tieBack1) != bool(tieBack2)) {
640 0 : return false;
641 : }
642 58 : if (tieBack1 && bool(tieBack1->startElement()) != bool(tieBack2->startElement())) {
643 0 : return false;
644 : }
645 :
646 58 : const Tie* tieFor1 = note1->tieFor();
647 58 : const Tie* tieFor2 = note2->tieFor();
648 58 : if (bool(tieFor1) != bool(tieFor2)) {
649 0 : return false;
650 : }
651 58 : if (tieFor1 && bool(tieFor1->endElement()) != bool(tieFor2->endElement())) {
652 0 : return false;
653 : }
654 :
655 58 : const std::vector<Spanner*>& spannerBack1 = note1->spannerBack();
656 58 : const std::vector<Spanner*>& spannerBack2 = note2->spannerBack();
657 58 : if (spannerBack1.size() != spannerBack2.size()) {
658 0 : return false;
659 : }
660 :
661 58 : for (Spanner* sp1 : spannerBack1) {
662 0 : auto i = std::find_if(spannerBack2.begin(), spannerBack2.end(), [sp1](Spanner* sp2) { return sp2->type() == sp1->type(); });
663 0 : if (i == spannerBack2.end()) {
664 0 : return false;
665 : }
666 :
667 0 : Spanner* sp2 = *i;
668 0 : if (bool(sp1->startElement()) != bool(sp2->startElement())) {
669 0 : return false;
670 : }
671 : }
672 :
673 58 : const std::vector<Spanner*>& spannerFor1 = note1->spannerFor();
674 58 : const std::vector<Spanner*>& spannerFor2 = note2->spannerFor();
675 58 : if (spannerFor1.size() != spannerFor2.size()) {
676 0 : return false;
677 : }
678 :
679 58 : for (Spanner* sp1 : spannerFor1) {
680 0 : auto i = std::find_if(spannerFor2.begin(), spannerFor2.end(), [sp1](Spanner* sp2) { return sp2->type() == sp1->type(); });
681 0 : if (i == spannerFor2.end()) {
682 0 : return false;
683 : }
684 :
685 0 : Spanner* sp2 = *i;
686 0 : if (bool(sp1->endElement()) != bool(sp2->endElement())) {
687 0 : return false;
688 : }
689 : }
690 :
691 58 : return true;
692 : }
693 :
694 304 : bool StaveSharingLayout::checkSpannersForSameVoice(track_idx_t prevTrack, track_idx_t nextTrack, StaveSharingContext& ctx)
695 : {
696 304 : std::multimap<ElementType, Spanner*> spannersOnPrevTrack;
697 304 : std::multimap<ElementType, Spanner*> spannersOnNextTrack;
698 :
699 307 : for (Spanner* spanner : ctx.overlappingSpanners) {
700 3 : if (spanner->track() == prevTrack) {
701 2 : spannersOnPrevTrack.insert({ spanner->type(), spanner });
702 1 : } else if (spanner->track() == nextTrack) {
703 0 : spannersOnNextTrack.insert({ spanner->type(), spanner });
704 : }
705 : }
706 :
707 304 : if (spannersOnPrevTrack.size() != spannersOnNextTrack.size()) {
708 2 : return false;
709 : }
710 :
711 302 : for (auto [type, spanner] : spannersOnPrevTrack) {
712 0 : auto range = spannersOnNextTrack.equal_range(type);
713 0 : if (range.first == range.second) {
714 : // No spanner of this type exists in nextTrack
715 0 : return false;
716 : }
717 :
718 0 : Spanner* matchingSpanner = nullptr;
719 0 : for (auto i = range.first; i != range.second; ++i) {
720 0 : Spanner* nextSpanner = i->second;
721 0 : if (nextSpanner->tick() == spanner->tick() && nextSpanner->ticks() == spanner->ticks()) {
722 0 : matchingSpanner = nextSpanner;
723 0 : break;
724 : }
725 : }
726 :
727 0 : if (!matchingSpanner) {
728 0 : return false;
729 : }
730 : }
731 :
732 302 : return true;
733 304 : }
734 :
735 66 : bool StaveSharingLayout::checkArticulationsForSameVoice(Chord* chord1, Chord* chord2)
736 : {
737 66 : const std::vector<Articulation*>& articulations1 = chord1->articulations();
738 66 : const std::vector<Articulation*>& articulations2 = chord2->articulations();
739 :
740 66 : if (articulations1.size() != articulations2.size()) {
741 0 : return false;
742 : }
743 :
744 68 : for (size_t i = 0; i < articulations1.size(); ++i) {
745 2 : Articulation* art1 = articulations1[i];
746 2 : Articulation* art2 = articulations2[i];
747 2 : if (art1->subtype() != art2->subtype()) {
748 0 : return false;
749 : }
750 : }
751 :
752 66 : return true;
753 : }
754 :
755 14 : bool StaveSharingLayout::canGoToSameStave(track_idx_t prevTrack, track_idx_t nextTrack,
756 : StaveSharingContext& ctx)
757 : {
758 14 : const bool allowVoiceCrossing = ctx.style.styleB(Sid::allowVoiceCrossing);
759 :
760 112 : for (Segment* segment : ctx.allSegments) {
761 110 : if (!sameInstrument(prevTrack, nextTrack, segment->tick(), ctx)) {
762 12 : return false;
763 : }
764 :
765 104 : if (segment->header()) {
766 42 : continue;
767 : }
768 :
769 62 : if (!checkClefKeyTimeSigForSameVoice(segment, prevTrack, nextTrack)) {
770 0 : return false;
771 : }
772 :
773 62 : if (!segment->isChordRestType() || allowVoiceCrossing) {
774 16 : continue;
775 : }
776 46 : ChordRest* cr1 = toChordRest(segment->element(prevTrack));
777 46 : ChordRest* cr2 = toChordRest(segment->element(nextTrack));
778 46 : if (cr1 && cr2 && cr1->isChord() && cr2->isChord()) {
779 26 : Chord* c1 = toChord(cr1);
780 26 : Chord* c2 = toChord(cr2);
781 26 : if (c2->upNote()->pitch() > c1->downNote()->pitch()) {
782 6 : return false;
783 : }
784 : }
785 : }
786 :
787 2 : return true;
788 : }
789 :
790 351 : void StaveSharingLayout::updateNotation(StaveSharingContext& ctx)
791 : {
792 : // 0. Compute segments to update (if possible update only last measure)
793 351 : computeSegmentsToUpdate(ctx);
794 :
795 : // 1. Disconnect the shared notation from the origin
796 351 : disconnectAll(ctx);
797 :
798 : // 2. Recompute shared notation and reconnect
799 351 : makeSharedNotation(ctx);
800 :
801 : // 3. Remove from shared notation any item that wasn't reconnected
802 351 : cleanup(ctx);
803 351 : }
804 :
805 351 : void StaveSharingLayout::computeSegmentsToUpdate(StaveSharingContext& ctx)
806 : {
807 351 : if (!ctx.updateForLastAdded || ctx.trackMapChanged) {
808 15 : ctx.segmentsToUpdate = ctx.allSegments;
809 15 : ctx.crSegmentsToUpdate = ctx.crSegments;
810 15 : return;
811 : }
812 :
813 336 : Measure* lastMeasure = ctx.crSegments.back()->measure();
814 1223 : for (Segment& seg : lastMeasure->segments()) {
815 887 : ctx.segmentsToUpdate.push_back(&seg);
816 887 : if (seg.isChordRestType()) {
817 386 : ctx.crSegmentsToUpdate.push_back(&seg);
818 : }
819 : }
820 : }
821 :
822 351 : void StaveSharingLayout::disconnectAll(StaveSharingContext& ctx)
823 : {
824 351 : SharedPart* p = ctx.curSharedPart;
825 :
826 351 : const TrackRange range = p->trackRange();
827 :
828 351 : ctx.oldStaveSharingLabels.clear();
829 351 : ctx.updatedStaveSharingLabels.clear();
830 :
831 1351 : for (Segment* seg : ctx.segmentsToUpdate) {
832 1007 : for (EngravingItem* item : seg->annotations()) {
833 7 : if (!item->systemFlag() && item->track() >= range.startTrack && item->track() < range.endTrack) {
834 5 : EngravingItem::disconnectAllOriginItems(item);
835 :
836 5 : if (item->isStaveSharingLabel()) {
837 4 : ctx.oldStaveSharingLabels.push_back(toStaveSharingLabel(item));
838 : }
839 : }
840 : }
841 :
842 1000 : if (seg->header()) {
843 222 : continue;
844 : }
845 :
846 778 : if (seg->isType(SegmentType::Breath | SegmentType::Clef | SegmentType::TimeSig | SegmentType::KeySig)) {
847 320 : for (track_idx_t track = range.startTrack; track < range.endTrack; ++track) {
848 284 : EngravingItem* item = seg->element(track);
849 284 : if (!item || (item->isTimeSig() && !toTimeSig(item)->isLocal())) {
850 279 : continue;
851 : }
852 5 : EngravingItem::disconnectAllOriginItems(item);
853 : }
854 36 : continue;
855 36 : }
856 :
857 742 : if (!seg->isChordRestType()) {
858 323 : continue;
859 : }
860 :
861 3631 : for (track_idx_t track = range.startTrack; track < range.endTrack; ++track) {
862 3212 : ChordRest* cr = toChordRest(seg->element(track));
863 3212 : if (!cr) {
864 2470 : continue;
865 : }
866 :
867 781 : for (DurationElement* de = cr; de;) {
868 781 : Tuplet* tuplet = de->tuplet();
869 781 : if (!tuplet) {
870 742 : break;
871 : }
872 :
873 39 : if (de == tuplet->elements().front()) {
874 17 : EngravingItem::disconnectAllOriginItems(tuplet);
875 : }
876 39 : de = tuplet;
877 : }
878 :
879 742 : if (cr->isRest()) {
880 673 : EngravingItem::disconnectAllOriginItems(cr);
881 : }
882 :
883 742 : if (cr->isChord()) {
884 69 : auto disconnectChord = [&](Chord* c) {
885 138 : for (Note* note : c->notes()) {
886 69 : EngravingItem::disconnectAllOriginItems(note);
887 :
888 69 : if (Tie* tieFor = note->tieFor(); tieFor && !tieFor->endNote()) { // if it does have endNote it will be disconnected when we process that note
889 0 : EngravingItem::disconnectAllOriginItems(tieFor);
890 : }
891 :
892 69 : for (Spanner* spannerFor : note->spannerFor()) {
893 0 : if (!spannerFor->endElement()) {
894 0 : EngravingItem::disconnectAllOriginItems(spannerFor); // if it does have endElement it will be disconnected when we process that note
895 : }
896 : }
897 :
898 69 : if (Tie* tieBack = note->tieBack()) {
899 1 : EngravingItem::disconnectAllOriginItems(tieBack);
900 : }
901 :
902 69 : for (Spanner* spannerBack : note->spannerBack()) {
903 0 : EngravingItem::disconnectAllOriginItems(spannerBack);
904 : }
905 : }
906 :
907 70 : for (Articulation* art : c->articulations()) {
908 1 : EngravingItem::disconnectAllOriginItems(art);
909 : }
910 69 : };
911 :
912 69 : Chord* c = toChord(cr);
913 69 : disconnectChord(c);
914 :
915 69 : for (Chord* grace : c->graceNotes()) {
916 0 : disconnectChord(grace);
917 : }
918 : }
919 : }
920 : }
921 :
922 354 : for (Spanner* spanner : ctx.overlappingSpanners) {
923 3 : if (!spanner->systemFlag() && spanner->track() >= range.startTrack && spanner->track() < range.endTrack) {
924 1 : EngravingItem::disconnectAllOriginItems(spanner);
925 : }
926 : }
927 351 : }
928 :
929 351 : void StaveSharingLayout::makeSharedNotation(StaveSharingContext& ctx)
930 : {
931 351 : makeSharedClefKeyTimeSigs(ctx);
932 351 : makeSharedChordRests(ctx);
933 351 : makeSharedBreaths(ctx);
934 351 : makeSharedAnnotations(ctx);
935 351 : makeSharedSpanners(ctx);
936 351 : makeStaveSharingLabels(ctx);
937 351 : }
938 :
939 6 : EngravingItem* StaveSharingLayout::makeSharedItem(EngravingItem* originItem, EngravingItem* possibleSharedItem, track_idx_t sharedTrack,
940 : EngravingObject* sharedParent,
941 : const std::function<bool(const EngravingItem*, const EngravingItem*)>& isEqual)
942 : {
943 : // Get a shared item by either reusing an existing or creating a new one
944 : // Connect it to the origin item and return it
945 6 : Score* score = originItem->score();
946 :
947 6 : EngravingItem* sharedItem = nullptr;
948 6 : if (possibleSharedItem) {
949 3 : if (isEqual(originItem, possibleSharedItem)) {
950 3 : sharedItem = possibleSharedItem;
951 : } else {
952 0 : score->undoRemoveElement(possibleSharedItem);
953 : }
954 : }
955 :
956 6 : if (!sharedItem) {
957 3 : sharedItem = originItem->clone();
958 3 : sharedItem->setTrack(sharedTrack);
959 3 : sharedItem->setOwnershipParent(sharedParent);
960 3 : score->undoAddElement(sharedItem);
961 : }
962 :
963 6 : EngravingItem::connectSharedItem(sharedItem, originItem);
964 :
965 6 : return sharedItem;
966 : }
967 :
968 351 : void StaveSharingLayout::makeSharedClefKeyTimeSigs(StaveSharingContext& ctx)
969 : {
970 : // This function copies local time signatures, key signatures and clefs to shared staves
971 : // These items only apply to a single staff. Time signatures and key signatures applying to all staves
972 : // were added at the same time as the origin items
973 :
974 351 : const SharedTrackMap& trackMap = ctx.curTrackMap;
975 351 : if (trackMap.empty()) {
976 0 : return;
977 : }
978 :
979 1351 : for (Segment* seg : ctx.segmentsToUpdate) {
980 1000 : if (seg->header() || (!seg->isClefType() && !seg->isKeySigType() && !seg->isTimeSigType())) {
981 964 : continue;
982 : }
983 36 : if (seg->isKeySigType() && !segHasLocalKeySig(seg, ctx.curSharedPart)) {
984 1 : continue;
985 : }
986 104 : for (const auto& [originTrack, sharedTrack] : ctx.curTrackMap) {
987 69 : if (track2voice(sharedTrack) != 0) {
988 : // Clefs, key, and time signatures can only appear in the 1st voice
989 : // At this point, any items in subsequent voices should match what is in voice 1
990 2 : continue;
991 : }
992 :
993 67 : if (seg->isClefType()) {
994 4 : Clef* originClef = toClef(seg->element(originTrack));
995 4 : if (originClef) {
996 2 : makeSharedItem(originClef, seg->element(sharedTrack), sharedTrack, seg,
997 2 : [](const EngravingItem* origin, const EngravingItem* shared) {
998 1 : return toClef(shared)->clefType() == toClef(origin)->clefType();
999 : });
1000 : }
1001 : }
1002 :
1003 67 : if (seg->isTimeSigType()) {
1004 59 : TimeSig* originTimeSig = toTimeSig(seg->element(originTrack));
1005 59 : if (originTimeSig && originTimeSig->isLocal()) {
1006 2 : makeSharedItem(originTimeSig, seg->element(sharedTrack), sharedTrack, seg,
1007 2 : [](const EngravingItem* origin, const EngravingItem* shared) {
1008 1 : IF_ASSERT_FAILED(origin->isTimeSig() && shared->isTimeSig()) {
1009 0 : return false;
1010 : }
1011 1 : return *toTimeSig(shared) == *toTimeSig(origin);
1012 : });
1013 : }
1014 : }
1015 :
1016 67 : if (seg->isKeySigType()) {
1017 4 : KeySig* originKeySig = toKeySig(seg->element(originTrack));
1018 4 : if (originKeySig) {
1019 2 : makeSharedItem(originKeySig, seg->element(sharedTrack), sharedTrack, seg,
1020 2 : [](const EngravingItem* origin, const EngravingItem* shared) {
1021 1 : IF_ASSERT_FAILED(origin->isKeySig() && shared->isKeySig()) {
1022 0 : return false;
1023 : }
1024 1 : return *toKeySig(shared) == *toKeySig(origin);
1025 : });
1026 : }
1027 : }
1028 : }
1029 : }
1030 : }
1031 :
1032 351 : void StaveSharingLayout::makeSharedChordRests(StaveSharingContext& ctx)
1033 : {
1034 351 : Score* score = ctx.score;
1035 :
1036 351 : ctx.sharedUnisonNotes.clear();
1037 :
1038 770 : for (Segment* seg : ctx.crSegmentsToUpdate) {
1039 1222 : for (const auto& [originTrack, sharedTrack] : ctx.curTrackMap) {
1040 803 : ChordRest* originCR = toChordRest(seg->element(originTrack));
1041 803 : if (!originCR) {
1042 697 : continue;
1043 : }
1044 :
1045 791 : ChordRest* sharedCR = toChordRest(seg->element(sharedTrack));
1046 791 : if (sharedCR) {
1047 768 : bool sameType = sharedCR->type() == originCR->type();
1048 768 : bool sameDuration = sharedCR->durationType() == originCR->durationType()
1049 768 : && sharedCR->actualTicks() == originCR->actualTicks();
1050 768 : if (!(sameType && sameDuration)) {
1051 7 : score->undoRemoveElement(sharedCR);
1052 7 : sharedCR = nullptr;
1053 : }
1054 : }
1055 :
1056 791 : bool barRestInSecVoice = originCR->isRest() && toRest(originCR)->isFullMeasureRest() && track2voice(sharedTrack) != 0;
1057 791 : if (barRestInSecVoice) {
1058 0 : continue;
1059 : }
1060 :
1061 791 : if (!sharedCR) {
1062 30 : sharedCR = toChordRest(originCR->clone());
1063 30 : sharedCR->setTrack(sharedTrack);
1064 30 : sharedCR->setOwnershipParent(seg);
1065 30 : score->undoAddElement(sharedCR);
1066 : }
1067 :
1068 791 : DurationElement* sharedDE = sharedCR;
1069 873 : for (DurationElement* originDE = originCR; originDE;) {
1070 873 : Tuplet* originTuplet = originDE->tuplet();
1071 873 : if (!originTuplet) {
1072 791 : break;
1073 : }
1074 :
1075 82 : Tuplet* sharedTuplet = nullptr;
1076 82 : if (originDE == originTuplet->elements().front()) {
1077 38 : sharedTuplet = sharedDE->tuplet();
1078 38 : if (!sharedTuplet) {
1079 3 : sharedTuplet = toTuplet(originTuplet->clone());
1080 3 : sharedTuplet->setTrack(sharedTrack);
1081 3 : sharedTuplet->setOwnershipParent(originTuplet->measure());
1082 3 : score->undoAddElement(sharedTuplet);
1083 : }
1084 : } else {
1085 44 : sharedTuplet = toTuplet(originTuplet->sharedItem());
1086 : }
1087 :
1088 82 : IF_ASSERT_FAILED(sharedTuplet) {
1089 0 : break;
1090 : }
1091 :
1092 82 : EngravingItem::connectSharedItem(sharedTuplet, originTuplet);
1093 :
1094 82 : if (!sharedTuplet->contains(sharedDE)) {
1095 9 : sharedTuplet->add(sharedDE);
1096 : }
1097 :
1098 82 : originDE = originTuplet;
1099 82 : sharedDE = sharedTuplet;
1100 : }
1101 :
1102 791 : if (originCR->isRest()) {
1103 685 : EngravingItem::connectSharedItem(sharedCR, originCR);
1104 : }
1105 :
1106 791 : if (!originCR->isChord()) {
1107 685 : continue;
1108 : }
1109 :
1110 106 : auto makeSharedNotes = [&](Chord* originChord, Chord* sharedChord) {
1111 106 : Note* originNote = originChord->upNote();
1112 106 : Note* sharedNote = nullptr;
1113 106 : for (Note* n : sharedChord->notes()) {
1114 106 : if (n->isExactUnison(originNote)) {
1115 106 : sharedNote = n;
1116 106 : break;
1117 : }
1118 : }
1119 :
1120 106 : if (!sharedNote) {
1121 0 : sharedNote = originNote->clone();
1122 0 : sharedNote->setTrack(sharedTrack);
1123 0 : sharedNote->setOwnershipParent(sharedChord);
1124 0 : score->undoAddElement(sharedNote);
1125 : }
1126 :
1127 106 : EngravingItem::connectSharedItem(sharedNote, originNote);
1128 :
1129 106 : if (sharedNote->originItems().size() > 1) {
1130 14 : ctx.sharedUnisonNotes.insert(sharedNote);
1131 : }
1132 :
1133 106 : makeSharedTiesAndNoteSpanners(originNote, sharedNote);
1134 :
1135 106 : makeSharedArticulations(originChord, sharedChord);
1136 106 : };
1137 :
1138 106 : Chord* originChord = toChord(originCR);
1139 106 : Chord* sharedChord = toChord(sharedCR);
1140 :
1141 106 : makeSharedNotes(originChord, sharedChord);
1142 :
1143 106 : const std::vector<Chord*>& originGraces = originChord->graceNotes();
1144 106 : const std::vector<Chord*>& sharedGraces = sharedChord->graceNotes();
1145 106 : for (size_t i = 0; i < originGraces.size(); ++i) {
1146 0 : Chord* originGrace = originGraces[i];
1147 0 : Chord* sharedGrace = i < sharedGraces.size() ? sharedGraces[i] : nullptr;
1148 0 : if (!sharedGrace) {
1149 0 : sharedGrace = toChord(originGrace->clone());
1150 0 : sharedGrace->setTrack(sharedTrack);
1151 0 : sharedGrace->setOwnershipParent(sharedChord);
1152 0 : score->undoAddElement(sharedGrace);
1153 : }
1154 :
1155 0 : makeSharedNotes(originGrace, sharedGrace);
1156 : }
1157 : }
1158 : }
1159 351 : }
1160 :
1161 351 : void StaveSharingLayout::makeSharedBreaths(StaveSharingContext& ctx)
1162 : {
1163 351 : std::vector<EngravingItem*> sharedBreaths;
1164 :
1165 351 : const SharedTrackMap& trackMap = ctx.curTrackMap;
1166 351 : if (trackMap.empty()) {
1167 0 : return;
1168 : }
1169 351 : track_idx_t startOriginTrack = trackMap.begin()->first;
1170 351 : track_idx_t endOriginTrack = trackMap.rbegin()->first;
1171 :
1172 1351 : for (Segment* seg : ctx.segmentsToUpdate) {
1173 1000 : if (!seg->isBreathType()) {
1174 1000 : continue;
1175 : }
1176 :
1177 0 : for (const auto& [originTrack, sharedTrack] : trackMap) {
1178 0 : Breath* originBreath = toBreath(seg->element(originTrack));
1179 0 : if (!originBreath) {
1180 0 : continue;
1181 : }
1182 :
1183 0 : EngravingItem* sharedBreath = makeSharedItem(originBreath, seg->element(sharedTrack), sharedTrack, seg,
1184 0 : [](const EngravingItem* origin, const EngravingItem* shared) {
1185 0 : return toBreath(shared)->symId() == toBreath(origin)->symId();
1186 0 : });
1187 0 : sharedBreaths.push_back(sharedBreath);
1188 : }
1189 : }
1190 :
1191 351 : manageVoicePropertyAndTrackForSharedItems(sharedBreaths, startOriginTrack, endOriginTrack, trackMap);
1192 351 : }
1193 :
1194 106 : void StaveSharingLayout::makeSharedArticulations(Chord* originChord, Chord* sharedChord)
1195 : {
1196 106 : Score* score = originChord->score();
1197 :
1198 110 : for (Articulation* originArt : originChord->articulations()) {
1199 4 : Articulation* sharedArt = nullptr;
1200 4 : for (Articulation* possibleSharedArt : sharedChord->articulations()) {
1201 4 : if (possibleSharedArt->subtype() == originArt->subtype()) {
1202 4 : sharedArt = possibleSharedArt;
1203 4 : break;
1204 : }
1205 : }
1206 :
1207 4 : if (!sharedArt) {
1208 0 : sharedArt = originArt->clone();
1209 0 : sharedArt->setTrack(sharedChord->track());
1210 0 : sharedArt->setOwnershipParent(sharedChord);
1211 :
1212 0 : score->undoAddElement(sharedArt);
1213 : }
1214 :
1215 4 : EngravingItem::connectSharedItem(sharedArt, originArt);
1216 : }
1217 106 : }
1218 :
1219 106 : void StaveSharingLayout::makeSharedTiesAndNoteSpanners(Note* originNote, Note* sharedNote)
1220 : {
1221 106 : Score* score = originNote->score();
1222 :
1223 106 : Tie* tieBack = originNote->tieBack();
1224 106 : if (tieBack) {
1225 2 : Tie* sharedTieBack = sharedNote->tieBack();
1226 2 : if (!sharedTieBack) {
1227 1 : sharedTieBack = toTie(tieBack->clone());
1228 1 : Note* sharedStartNote = tieBack->startNote() ? toNote(tieBack->startNote()->sharedItem()) : nullptr;
1229 1 : sharedTieBack->setNoteSpan(sharedStartNote, sharedNote);
1230 :
1231 1 : score->undoAddElement(sharedTieBack);
1232 : }
1233 :
1234 2 : EngravingItem::connectSharedItem(sharedTieBack, tieBack);
1235 : }
1236 :
1237 106 : Tie* tieFor = originNote->tieFor();
1238 106 : if (tieFor && !tieFor->endNote()) { // If it does have an end note we add it when we process the end note
1239 0 : Tie* sharedTieFor = sharedNote->tieFor();
1240 0 : if (!sharedTieFor) {
1241 0 : sharedTieFor = toTie(tieFor->clone());
1242 0 : sharedTieFor->setNoteSpan(sharedNote, nullptr);
1243 :
1244 0 : score->undoAddElement(sharedTieFor);
1245 : }
1246 :
1247 0 : EngravingItem::connectSharedItem(sharedTieFor, tieFor);
1248 : }
1249 :
1250 106 : const std::vector<Spanner*>& originSpannerBack = originNote->spannerBack();
1251 106 : const std::vector<Spanner*>& sharedSpannerBack = sharedNote->spannerBack();
1252 :
1253 106 : for (Spanner* spanner : originSpannerBack) {
1254 0 : Spanner* sharedSpanner = nullptr;
1255 0 : for (Spanner* sp : sharedSpannerBack) {
1256 0 : if (sp && sp->type() == spanner->type()) {
1257 0 : sharedSpanner = sp;
1258 0 : break;
1259 : }
1260 : }
1261 :
1262 0 : if (!sharedSpanner) {
1263 0 : sharedSpanner = toSpanner(spanner->clone());
1264 0 : Note* startNote = spanner->startElement() ? toNote(spanner->startElement()->sharedItem()) : nullptr;
1265 0 : sharedSpanner->setNoteSpan(startNote, sharedNote);
1266 :
1267 0 : score->undoAddElement(sharedSpanner);
1268 : }
1269 :
1270 0 : EngravingItem::connectSharedItem(sharedSpanner, spanner);
1271 : }
1272 :
1273 106 : for (Spanner* spanner : originNote->spannerFor()) {
1274 0 : if (spanner->endElement()) { // If it does have an end note we add it when we process the end note
1275 0 : continue;
1276 : }
1277 :
1278 0 : Spanner* sharedSpanner = nullptr;
1279 0 : for (Spanner* sp : sharedNote->spannerFor()) {
1280 0 : if (sp->type() == spanner->type()) {
1281 0 : sharedSpanner = sp;
1282 0 : break;
1283 : }
1284 : }
1285 :
1286 0 : if (!sharedSpanner) {
1287 0 : sharedSpanner = toSpanner(spanner->clone());
1288 0 : sharedSpanner->setTrack(sharedNote->track());
1289 0 : sharedSpanner->setStartElement(sharedNote);
1290 :
1291 0 : score->undoAddElement(sharedSpanner);
1292 : }
1293 :
1294 0 : EngravingItem::connectSharedItem(sharedSpanner, spanner);
1295 : }
1296 106 : }
1297 :
1298 351 : void StaveSharingLayout::makeSharedAnnotations(StaveSharingContext& ctx)
1299 : {
1300 351 : Score* score = ctx.score;
1301 :
1302 351 : const SharedTrackMap& trackMap = ctx.curTrackMap;
1303 351 : if (trackMap.empty()) {
1304 0 : return;
1305 : }
1306 351 : track_idx_t startOriginTrack = trackMap.begin()->first;
1307 351 : track_idx_t endOriginTrack = trackMap.rbegin()->first;
1308 :
1309 351 : std::vector<EngravingItem*> sharedAnnotations;
1310 :
1311 1351 : for (Segment* seg : ctx.segmentsToUpdate) {
1312 1000 : std::vector<EngravingItem*> annotations = seg->annotations(); // Copy because we are about to add
1313 1007 : for (EngravingItem* originItem : annotations) {
1314 7 : track_idx_t originTrack = originItem->track();
1315 7 : if (originTrack < startOriginTrack || originTrack > endOriginTrack) {
1316 5 : continue;
1317 : }
1318 :
1319 2 : IF_ASSERT_FAILED(muse::contains(trackMap, originTrack)) {
1320 0 : continue;
1321 : }
1322 :
1323 2 : if (originItem->isFermata()) {
1324 0 : EngravingItem* fermataOriginBaseItem = seg->element(originTrack);
1325 0 : if (fermataOriginBaseItem && fermataOriginBaseItem->isChord()) {
1326 0 : fermataOriginBaseItem = toChord(fermataOriginBaseItem)->upNote();
1327 : }
1328 0 : if (fermataOriginBaseItem && !(fermataOriginBaseItem->sharedItem() || fermataOriginBaseItem->isBarLine())) {
1329 : // chordrests can't be linked, need to scan notes & check
1330 0 : continue;
1331 : }
1332 : }
1333 :
1334 2 : track_idx_t sharedTrack = trackMap.at(originTrack);
1335 2 : EngravingItem* sharedItem = nullptr;
1336 4 : for (EngravingItem* item : seg->annotations()) {
1337 3 : if (item->track() != sharedTrack || item->type() != originItem->type()) {
1338 2 : continue;
1339 : }
1340 :
1341 1 : if (!item->isInstrumentChange() && item->isTextBase() && toTextBase(item)->xmlText() != toTextBase(originItem)->xmlText()) {
1342 0 : continue;
1343 : }
1344 :
1345 1 : sharedItem = item;
1346 1 : break;
1347 : }
1348 :
1349 2 : if (!sharedItem) {
1350 1 : sharedItem = originItem->clone();
1351 1 : sharedItem->setTrack(sharedTrack);
1352 1 : sharedItem->setOwnershipParent(seg);
1353 1 : score->undoAddElement(sharedItem);
1354 : }
1355 :
1356 2 : EngravingItem::connectSharedItem(sharedItem, originItem);
1357 :
1358 2 : sharedAnnotations.push_back(sharedItem);
1359 :
1360 2 : if (sharedItem->isInstrumentChange()) {
1361 2 : InstrumentChange* originChange = toInstrumentChange(originItem);
1362 2 : InstrumentChange* sharedChange = toInstrumentChange(sharedItem);
1363 2 : String text = formatInstrumentChangeLable(originChange, sharedChange, ctx);
1364 :
1365 2 : sharedChange->setXmlText(text);
1366 2 : }
1367 : }
1368 1000 : }
1369 :
1370 351 : manageVoicePropertyAndTrackForSharedItems(sharedAnnotations, startOriginTrack, endOriginTrack, trackMap);
1371 351 : }
1372 :
1373 351 : void StaveSharingLayout::makeSharedSpanners(StaveSharingContext& ctx)
1374 : {
1375 351 : const SharedTrackMap& trackMap = ctx.curTrackMap;
1376 351 : if (trackMap.empty()) {
1377 0 : return;
1378 : }
1379 351 : track_idx_t startOriginTrack = trackMap.begin()->first;
1380 351 : track_idx_t endOriginTrack = trackMap.rbegin()->first;
1381 :
1382 351 : std::vector<EngravingItem*> sharedSpanners;
1383 :
1384 351 : std::vector<Spanner*> overlappingSpanners = ctx.overlappingSpanners; // copy because we may add
1385 354 : for (Spanner* spanner : overlappingSpanners) {
1386 3 : if (spanner->track() < startOriginTrack || spanner->track() > endOriginTrack) {
1387 1 : continue;
1388 : }
1389 :
1390 2 : track_idx_t originTrack = spanner->track();
1391 2 : IF_ASSERT_FAILED(muse::contains(trackMap, originTrack)) {
1392 0 : continue;
1393 : }
1394 :
1395 2 : track_idx_t sharedTrack = trackMap.at(originTrack);
1396 :
1397 2 : Spanner* sharedSpanner = nullptr;
1398 4 : for (Spanner* possibleSharedSpanner : ctx.overlappingSpanners) {
1399 4 : if (possibleSharedSpanner->track() == sharedTrack && possibleSharedSpanner->type() == spanner->type()
1400 4 : && possibleSharedSpanner->tick() == spanner->tick() && possibleSharedSpanner->ticks() == spanner->ticks()) {
1401 1 : sharedSpanner = possibleSharedSpanner;
1402 1 : break;
1403 : }
1404 : }
1405 :
1406 2 : if (!sharedSpanner) {
1407 1 : sharedSpanner = toSpanner(spanner->clone());
1408 1 : sharedSpanner->setTrack(sharedTrack);
1409 1 : sharedSpanner->setTrack2(sharedTrack);
1410 1 : sharedSpanner->setTick(spanner->tick());
1411 1 : sharedSpanner->setTicks(spanner->ticks());
1412 1 : sharedSpanner->setStartElement(nullptr);
1413 1 : sharedSpanner->setEndElement(nullptr);
1414 1 : sharedSpanner->reset();
1415 :
1416 1 : ctx.score->undoAddElement(sharedSpanner);
1417 1 : ctx.overlappingSpanners.push_back(sharedSpanner);
1418 : }
1419 :
1420 2 : EngravingItem::connectSharedItem(sharedSpanner, spanner);
1421 :
1422 2 : sharedSpanners.push_back(sharedSpanner);
1423 : }
1424 :
1425 351 : manageVoicePropertyAndTrackForSharedItems(sharedSpanners, startOriginTrack, endOriginTrack, trackMap);
1426 351 : }
1427 :
1428 351 : void StaveSharingLayout::makeStaveSharingLabels(StaveSharingContext& ctx)
1429 : {
1430 351 : const SharedTrackMap& trackMap = ctx.curTrackMap;
1431 :
1432 351 : std::vector<EngravingItem*> updatedStaveSharingLabels;
1433 :
1434 365 : for (Note* unisonNote : ctx.sharedUnisonNotes) {
1435 14 : bool needsRestateOnNewSystem = false;
1436 14 : if (!unisonNoteNeedsLabel(unisonNote, needsRestateOnNewSystem, ctx)) {
1437 8 : continue;
1438 : }
1439 :
1440 6 : String text = formatUnisonLabel(unisonNote, trackMap, needsRestateOnNewSystem, ctx);
1441 6 : Segment* segment = unisonNote->chord()->segment();
1442 :
1443 6 : StaveSharingLabel* label = nullptr;
1444 6 : for (EngravingItem* item : segment->annotations()) {
1445 4 : if (item->isStaveSharingLabel() && item->track() == unisonNote->track() && toStaveSharingLabel(item)->xmlText() == text) {
1446 4 : label = toStaveSharingLabel(item);
1447 4 : break;
1448 : }
1449 : }
1450 :
1451 6 : if (!label) {
1452 2 : label = Factory::createStaveSharingLabel(segment);
1453 2 : label->setOwnershipParent(segment);
1454 2 : label->setTrack(unisonNote->track());
1455 2 : label->setXmlText(text);
1456 :
1457 2 : ctx.score->undoAddElement(label, /*addToLinkedStaves*/ false);
1458 : }
1459 :
1460 6 : updatedStaveSharingLabels.push_back(label);
1461 6 : }
1462 :
1463 351 : const TrackRange curSharedPartTrackRange = ctx.curSharedPart->trackRange();
1464 351 : manageVoicePropertyAndTrackForSharedItems(updatedStaveSharingLabels, curSharedPartTrackRange.startTrack,
1465 351 : curSharedPartTrackRange.endTrack, trackMap);
1466 :
1467 351 : ctx.updatedStaveSharingLabels.reserve(updatedStaveSharingLabels.size());
1468 357 : for (EngravingItem* el : updatedStaveSharingLabels) {
1469 6 : ctx.updatedStaveSharingLabels.push_back(toStaveSharingLabel(el));
1470 : }
1471 351 : }
1472 :
1473 14 : bool StaveSharingLayout::unisonNoteNeedsLabel(Note* unisonNote, bool& needsRestateOnNewSystem, StaveSharingContext& ctx)
1474 : {
1475 14 : std::vector<track_idx_t> originTracksOfThisNote;
1476 42 : for (EngravingItem* originNote : unisonNote->originItems()) {
1477 28 : originTracksOfThisNote.push_back(originNote->track());
1478 : }
1479 :
1480 14 : Note* prevNote = nullptr;
1481 14 : track_idx_t curTrack = unisonNote->track();
1482 14 : Segment* curSegment = unisonNote->chord()->segment();
1483 30 : for (Segment* seg = curSegment->prev1(SegmentType::ChordRest); seg; seg = seg->prev1(SegmentType::ChordRest)) {
1484 24 : if (EngravingItem* el = seg->element(curTrack); el && el->isChord()) {
1485 8 : for (Note* note : toChord(el)->notes()) {
1486 8 : if (!note->originItems().empty()) {
1487 8 : prevNote = note;
1488 8 : break;
1489 : }
1490 : }
1491 : }
1492 24 : if (prevNote) {
1493 8 : break;
1494 : }
1495 : }
1496 :
1497 14 : if (!prevNote) {
1498 6 : return true;
1499 : }
1500 :
1501 8 : std::vector<track_idx_t> originTracksOfPrevNote;
1502 24 : for (EngravingItem* originNote : prevNote->originItems()) {
1503 16 : originTracksOfPrevNote.push_back(originNote->track());
1504 : }
1505 :
1506 8 : bool restateOnNewSystem = ctx.style.styleV(Sid::unisonLabelRestateOnNewSystem).value<AutoOnOff>() != AutoOnOff::OFF;
1507 8 : needsRestateOnNewSystem = restateOnNewSystem
1508 8 : && unisonNote->chord()->segment()->system() != prevNote->chord()->segment()->system()
1509 16 : && originTracksOfPrevNote == originTracksOfThisNote;
1510 :
1511 8 : return originTracksOfPrevNote != originTracksOfThisNote || needsRestateOnNewSystem;
1512 14 : }
1513 :
1514 6 : String StaveSharingLayout::formatUnisonLabel(Note* unisonNote, const SharedTrackMap& trackMap, bool needsRestateOnNewSystem,
1515 : const StaveSharingContext& ctx)
1516 : {
1517 6 : const MStyle& style = ctx.style;
1518 :
1519 6 : String result;
1520 :
1521 6 : size_t originUnisonsCount = unisonNote->originItems().size();
1522 6 : result += style.styleSt(Sid::textForUnisonLabel) + String::number(originUnisonsCount);
1523 6 : bool trailingDot = false; // TODO: style
1524 6 : if (trailingDot) {
1525 0 : result += '.';
1526 : }
1527 :
1528 6 : track_idx_t curTrack = unisonNote->track();
1529 6 : staff_idx_t curStaff = track2staff(curTrack);
1530 :
1531 6 : std::vector<track_idx_t> originTracks;
1532 6 : originTracks.reserve(originUnisonsCount);
1533 6 : std::vector<const Instrument*> originInstruments;
1534 6 : originInstruments.reserve(originUnisonsCount);
1535 6 : std::vector<track_idx_t> tracksMappedToThisStave;
1536 6 : tracksMappedToThisStave.reserve(originUnisonsCount);
1537 :
1538 18 : for (auto [originTrack, sharedTrack] : trackMap) {
1539 12 : if (track2staff(sharedTrack) == curStaff) {
1540 12 : tracksMappedToThisStave.push_back(originTrack);
1541 : }
1542 12 : if (sharedTrack == curTrack) {
1543 12 : originTracks.push_back(originTrack);
1544 12 : originInstruments.push_back(ctx.score->staff(track2staff(originTrack))->part()->instrument());
1545 : }
1546 : }
1547 :
1548 : bool addParenthesis = needsRestateOnNewSystem
1549 6 : && ctx.style.styleV(Sid::unisonLabelRestateOnNewSystem).value<AutoOnOff>() == AutoOnOff::AUTO;
1550 :
1551 6 : if (tracksMappedToThisStave.size() <= originUnisonsCount) {
1552 6 : if (addParenthesis) {
1553 0 : result.prepend('(');
1554 0 : result.append(')');
1555 : }
1556 6 : return result;
1557 : }
1558 :
1559 : bool trailingDotSingle;
1560 : bool trailingDotMultiple;
1561 : int hyphenLimit;
1562 0 : if (style.styleB(Sid::sharedOnStaffNumeralsFollowInstrumentNumerals)) {
1563 0 : trailingDotSingle = style.styleB(Sid::instrumentNumeralsTrailingDotSingle);
1564 0 : trailingDotMultiple = style.styleB(Sid::instrumentNumeralsTrailingDotMultiple);
1565 0 : hyphenLimit = style.styleB(Sid::instrumentNumeralsHyphenEnable) ? style.styleI(Sid::instrumentNumeralsHyphenThreshold) : INT_MAX;
1566 : } else {
1567 0 : trailingDotSingle = style.styleB(Sid::sharedOnStaffNumeralsTrailingDotSingle);
1568 0 : trailingDotMultiple = style.styleB(Sid::sharedOnStaffNumeralsTrailingDotMultiple);
1569 0 : hyphenLimit = style.styleB(Sid::sharedOnStaffNumeralsHyphenEnable)
1570 0 : ? style.styleI(Sid::sharedOnStaffNumeralsHyphenThreshold) : INT_MAX;
1571 : }
1572 :
1573 0 : String prefix = SystemHeaderLayout::formatSharedVoiceLabel(originInstruments, trailingDotSingle, trailingDotMultiple, hyphenLimit);
1574 :
1575 0 : result.prepend(prefix + ' ');
1576 :
1577 0 : if (addParenthesis) {
1578 0 : result.prepend('(');
1579 0 : result.append(')');
1580 : }
1581 :
1582 0 : return result;
1583 6 : }
1584 :
1585 2 : String StaveSharingLayout::formatInstrumentChangeLable(const InstrumentChange* originChange, const InstrumentChange* sharedChange,
1586 : const StaveSharingContext& ctx)
1587 : {
1588 : // Prefix the number to the string shown
1589 : // TODO make these editable by the user
1590 2 : const MStyle& style = ctx.style;
1591 :
1592 2 : if (sharedChange->originItems().empty() || !style.styleB(Sid::staveSharingInstrChangePlayerNum)) {
1593 0 : return originChange->xmlText();
1594 : }
1595 :
1596 : bool trailingDotSingle;
1597 : bool trailingDotMultiple;
1598 : int hyphenLimit;
1599 2 : if (style.styleB(Sid::sharedOnStaffNumeralsFollowInstrumentNumerals)) {
1600 2 : trailingDotSingle = style.styleB(Sid::instrumentNumeralsTrailingDotSingle);
1601 2 : trailingDotMultiple = style.styleB(Sid::instrumentNumeralsTrailingDotMultiple);
1602 2 : hyphenLimit = style.styleB(Sid::instrumentNumeralsHyphenEnable) ? style.styleI(
1603 : Sid::instrumentNumeralsHyphenThreshold) : INT_MAX;
1604 : } else {
1605 0 : trailingDotSingle = style.styleB(Sid::sharedOnStaffNumeralsTrailingDotSingle);
1606 0 : trailingDotMultiple = style.styleB(Sid::sharedOnStaffNumeralsTrailingDotMultiple);
1607 0 : hyphenLimit = style.styleB(Sid::sharedOnStaffNumeralsHyphenEnable)
1608 0 : ? style.styleI(Sid::sharedOnStaffNumeralsHyphenThreshold) : INT_MAX;
1609 : }
1610 :
1611 2 : String prefix;
1612 2 : std::vector<const Instrument*> originInstruments;
1613 4 : for (const EngravingItem* sharedOriginChange : sharedChange->originItems()) {
1614 2 : const Part* originPart = sharedOriginChange->part();
1615 2 : originInstruments.push_back(originPart->instrument());
1616 : }
1617 4 : prefix = SystemHeaderLayout::formatSharedVoiceLabel(originInstruments, trailingDotSingle, trailingDotMultiple,
1618 2 : hyphenLimit);
1619 :
1620 2 : return prefix + u" " + originChange->xmlText();
1621 2 : }
1622 :
1623 1404 : void StaveSharingLayout::manageVoicePropertyAndTrackForSharedItems(const std::vector<EngravingItem*>& sharedItems,
1624 : track_idx_t startOriginTrack, track_idx_t endOriginTrack,
1625 : const SharedTrackMap& trackMap)
1626 : {
1627 1414 : for (EngravingItem* sharedItem : sharedItems) {
1628 10 : bool refersToVoice2 = track2voice(sharedItem->track()) != 0;
1629 10 : bool voice2isUsed = refersToVoice2;
1630 10 : if (!voice2isUsed) {
1631 10 : track_idx_t trackOfVoice2 = trackZeroVoice(sharedItem->track()) + 1;
1632 34 : for (track_idx_t originTrack = startOriginTrack; originTrack <= endOriginTrack; originTrack += VOICES) {
1633 26 : if (trackMap.contains(originTrack) && trackMap.at(originTrack) == trackOfVoice2) {
1634 2 : voice2isUsed = true;
1635 2 : break;
1636 : }
1637 : }
1638 : }
1639 :
1640 10 : if (voice2isUsed) {
1641 2 : if (sharedItem->hasVoiceAssignmentProperties()) {
1642 0 : sharedItem->setProperty(Pid::VOICE_ASSIGNMENT, VoiceAssignment::CURRENT_VOICE_ONLY);
1643 0 : sharedItem->setPlacementBasedOnVoiceAssignment(sharedItem->propertyDefault(Pid::DIRECTION).value<DirectionV>());
1644 : } else {
1645 2 : if (refersToVoice2) {
1646 0 : sharedItem->setPlacement(PlacementV::BELOW);
1647 : } else {
1648 2 : sharedItem->setPlacement(PlacementV::ABOVE);
1649 : }
1650 : }
1651 : } else {
1652 8 : if (sharedItem->hasVoiceAssignmentProperties()) {
1653 0 : sharedItem->setProperty(Pid::VOICE_ASSIGNMENT, VoiceAssignment::ALL_VOICE_IN_INSTRUMENT);
1654 0 : sharedItem->setPlacementBasedOnVoiceAssignment(sharedItem->propertyDefault(Pid::DIRECTION).value<DirectionV>());
1655 : } else {
1656 8 : sharedItem->resetProperty(Pid::PLACEMENT);
1657 : }
1658 : }
1659 : }
1660 1404 : }
1661 :
1662 351 : void StaveSharingLayout::cleanup(StaveSharingContext& ctx)
1663 : {
1664 351 : SharedPart* p = ctx.curSharedPart;
1665 :
1666 351 : Score* score = ctx.score;
1667 :
1668 351 : const TrackRange range = p->trackRange();
1669 :
1670 1351 : for (Segment* seg : ctx.segmentsToUpdate) {
1671 1000 : std::vector<EngravingItem*> annotations = seg->annotations(); // Copy because we may remove elements
1672 1010 : for (EngravingItem* item : annotations) {
1673 20 : if (!item->systemFlag() && !item->isStaveSharingLabel() && item->track() >= range.startTrack && item->track() < range.endTrack
1674 20 : && item->originItems().empty()) {
1675 0 : score->undoRemoveElement(item);
1676 : }
1677 : }
1678 :
1679 1000 : if (seg->header()) {
1680 222 : continue;
1681 : }
1682 :
1683 778 : if (seg->isType(SegmentType::Breath | SegmentType::Clef | SegmentType::TimeSig)
1684 778 : || (seg->isKeySigType() && segHasLocalKeySig(seg, p))) {
1685 311 : for (track_idx_t track = range.startTrack; track < range.endTrack; ++track) {
1686 276 : EngravingItem* item = seg->element(track);
1687 276 : if (!item || !item->originItems().empty() || (item->isTimeSig() && !toTimeSig(item)->isLocal())) {
1688 276 : continue;
1689 : }
1690 0 : ctx.score->undoRemoveElement(item);
1691 : }
1692 35 : continue;
1693 35 : }
1694 :
1695 743 : if (!seg->isChordRestType()) {
1696 324 : continue;
1697 : }
1698 :
1699 3631 : for (track_idx_t track = range.startTrack; track < range.endTrack; ++track) {
1700 3212 : ChordRest* cr = toChordRest(seg->element(track));
1701 3212 : if (!cr) {
1702 2447 : continue;
1703 : }
1704 :
1705 815 : for (Tuplet* tuplet = cr->tuplet(); tuplet; tuplet = tuplet->tuplet()) {
1706 50 : if (tuplet->originItems().empty()) {
1707 0 : score->undoRemoveElement(tuplet);
1708 : }
1709 : }
1710 :
1711 765 : if (cr->isRest()) {
1712 673 : bool barRestOnFirstVoice = toRest(cr)->isFullMeasureRest() && cr->voice() == 0;
1713 673 : if (barRestOnFirstVoice) {
1714 651 : continue;
1715 : }
1716 22 : if (cr->originItems().empty()) {
1717 0 : score->undoRemoveElement(cr);
1718 : }
1719 : }
1720 :
1721 114 : if (cr->isChord()) {
1722 92 : auto cleanupChord = [&](Chord* c) {
1723 92 : std::vector<Articulation*> articulations = c->articulations(); // copy because may be removed
1724 94 : for (Articulation* art : articulations) {
1725 2 : if (art->originItems().empty()) {
1726 0 : score->undoRemoveElement(art);
1727 : }
1728 : }
1729 :
1730 92 : std::vector<Note*> notes = c->notes(); // copy because may be removed
1731 184 : for (Note* note : notes) {
1732 92 : if (Tie* tieBack = note->tieBack(); tieBack && tieBack->originItems().empty()) {
1733 0 : score->undoRemoveElement(tieBack);
1734 : }
1735 :
1736 92 : for (Spanner* sp : note->spannerBack()) {
1737 0 : if (sp->originItems().empty()) {
1738 0 : score->undoRemoveElement(sp);
1739 : }
1740 : }
1741 :
1742 92 : if (Tie* tieFor = note->tieFor(); tieFor && !tieFor->endElement() && tieFor->originItems().empty()) {
1743 0 : score->undoRemoveElement(tieFor);
1744 : }
1745 :
1746 92 : for (Spanner* sp : note->spannerFor()) {
1747 0 : if (!sp->endElement() && sp->originItems().empty()) {
1748 0 : score->undoRemoveElement(sp);
1749 : }
1750 : }
1751 :
1752 92 : if (note->originItems().empty()) {
1753 0 : score->undoRemoveElement(note);
1754 : }
1755 : }
1756 92 : if (c->notes().empty()) {
1757 0 : score->undoRemoveElement(c);
1758 : }
1759 92 : };
1760 :
1761 92 : Chord* c = toChord(cr);
1762 92 : cleanupChord(c);
1763 :
1764 92 : std::vector<Chord*> graceNotes = c->graceNotes(); // copy because we may remove elements
1765 92 : for (Chord* grace : graceNotes) {
1766 0 : cleanupChord(grace);
1767 : }
1768 92 : }
1769 : }
1770 1000 : }
1771 :
1772 351 : addMeasureRestsIfNeed(ctx);
1773 :
1774 355 : for (Spanner* spanner : ctx.overlappingSpanners) {
1775 8 : if (!spanner->systemFlag() && spanner->track() >= range.startTrack && spanner->track() < range.endTrack
1776 8 : && spanner->originItems().empty()) {
1777 0 : score->undoRemoveElement(spanner);
1778 : }
1779 : }
1780 :
1781 355 : for (StaveSharingLabel* oldLabel : ctx.oldStaveSharingLabels) {
1782 4 : if (!muse::contains(ctx.updatedStaveSharingLabels, oldLabel)) {
1783 0 : ctx.score->undoRemoveElement(oldLabel);
1784 : }
1785 : }
1786 351 : }
1787 :
1788 351 : void StaveSharingLayout::addMeasureRestsIfNeed(StaveSharingContext& ctx)
1789 : {
1790 351 : Measure* startMeas = ctx.segmentsToUpdate.front()->measure();
1791 351 : Measure* endMeas = ctx.segmentsToUpdate.back()->measure();
1792 :
1793 351 : TrackRange trackRange = ctx.curSharedPart->trackRange();
1794 351 : staff_idx_t startStaff = track2staff(trackRange.startTrack);
1795 351 : staff_idx_t endStaff = track2staff(trackRange.endTrack);
1796 :
1797 704 : for (Measure* measure = startMeas; measure && measure->isBeforeOrEqual(endMeas); measure = measure->nextMeasure()) {
1798 1024 : for (staff_idx_t staffIdx = startStaff; staffIdx < endStaff; ++staffIdx) {
1799 671 : if (!measure->isEmpty(staffIdx)) {
1800 22 : continue;
1801 : }
1802 649 : if (ChordRest* firstCR = measure->firstChordRest(staffIdx * VOICES); firstCR && firstCR->isFullMeasureRest()) {
1803 649 : continue;
1804 : }
1805 :
1806 0 : ctx.score->setRest(measure->tick(), staffIdx * VOICES, measure->ticks(), false, nullptr);
1807 : }
1808 : }
1809 351 : }
1810 :
1811 31761 : StaveSharingLayout::StaveSharingContext::StaveSharingContext(MeasureBase* first, MeasureBase* last, LayoutContext& ctx)
1812 31761 : : layoutCtx(ctx), style(ctx.conf().style())
1813 : {
1814 125594 : for (MeasureBase* mb = first; mb; mb = mb->next()) {
1815 125509 : if (!mb->isMeasure()) {
1816 316 : continue;
1817 : }
1818 :
1819 543076 : for (Segment& seg : toMeasure(mb)->segments()) {
1820 417883 : allSegments.push_back(&seg);
1821 417883 : if (seg.isChordRestType()) {
1822 218396 : crSegments.push_back(&seg);
1823 : }
1824 : }
1825 :
1826 125193 : if (mb == last) {
1827 31676 : break;
1828 : }
1829 : }
1830 :
1831 31761 : if (crSegments.empty()) {
1832 64 : return;
1833 : }
1834 :
1835 31697 : sTick = crSegments.front()->tick();
1836 31697 : eTick = crSegments.back()->measure()->endTick();
1837 31697 : score = crSegments.front()->score();
1838 :
1839 31697 : auto spanners = score->spannerMap().findOverlapping(sTick.ticks(), eTick.ticks());
1840 39885 : for (auto& i : spanners) {
1841 8188 : overlappingSpanners.push_back(i.value);
1842 : }
1843 31697 : }
1844 : }
|