LCOV - code coverage report
Current view: top level - src/engraving/rendering/score - stavesharinglayout.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 77.2 % 1056 815
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-Studio-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2026 MuseScore Limited 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              : }
        

Generated by: LCOV version 2.0-1