LCOV - code coverage report
Current view: top level - src/engraving/editing - regroup.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 83.2 % 179 149
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
       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 "regroup.h"
      24              : 
      25              : #include "editspanner.h"
      26              : #include "transaction/transaction.h"
      27              : 
      28              : #include "../dom/chord.h"
      29              : #include "../dom/factory.h"
      30              : #include "../dom/input.h"
      31              : #include "../dom/measure.h"
      32              : #include "../dom/note.h"
      33              : #include "../dom/rest.h"
      34              : #include "../dom/score.h"
      35              : #include "../dom/segment.h"
      36              : #include "../dom/select.h"
      37              : #include "../dom/sig.h"
      38              : #include "../dom/slur.h"
      39              : #include "../dom/staff.h"
      40              : #include "../dom/tie.h"
      41              : #include "../dom/utils.h"
      42              : 
      43              : #include "log.h"
      44              : 
      45              : using namespace mu::engraving;
      46              : 
      47              : //---------------------------------------------------------
      48              : //   regroupNotesAndRests
      49              : //    * combine consecutive rests into fewer rests of longer duration.
      50              : //    * combine tied notes/chords into fewer notes of longer duration.
      51              : //    Only operates on one voice - protects manual layout adjustment, etc.
      52              : //---------------------------------------------------------
      53              : 
      54           56 : void Regroup::regroupNotesAndRests(Transaction& tx, Score* score, const Fraction& startTick, const Fraction& endTick,
      55              :                                    track_idx_t track)
      56              : {
      57           56 :     InputState& is = score->inputState();
      58           56 :     Segment* inputSegment = is.segment();   // store this so we can get back to it later.
      59           56 :     Segment* seg = score->tick2segment(startTick, true, SegmentType::ChordRest);
      60         1104 :     for (Measure* msr = seg->measure(); msr && msr->tick() < endTick; msr = msr->nextMeasure()) {
      61         1048 :         Fraction maxTick = endTick > msr->endTick() ? msr->endTick() : endTick;
      62         1048 :         if (!seg || seg->measure() != msr) {
      63          992 :             seg = msr->first(SegmentType::ChordRest);
      64              :         }
      65         7502 :         for (; seg; seg = seg->next(SegmentType::ChordRest)) {
      66         6454 :             ChordRest* curr = seg->cr(track);
      67         6454 :             if (!curr) {
      68         5488 :                 continue;         // this voice is empty here (CR overlaps with CR in other track)
      69              :             }
      70          966 :             if (curr->endTick() > maxTick) {
      71            0 :                 break;         // outside range
      72              :             }
      73          966 :             if (curr->isRest() && !(curr->tuplet()) && !(toRest(curr)->isGap())) {
      74              :                 // combine consecutive rests
      75          382 :                 ChordRest* lastRest = curr;
      76         1033 :                 for (Segment* s = seg->next(SegmentType::ChordRest); s; s = s->next(SegmentType::ChordRest)) {
      77          903 :                     ChordRest* cr = s->cr(track);
      78          903 :                     if (!cr) {
      79            0 :                         continue;             // this voice is empty here
      80              :                     }
      81          903 :                     if (!cr->isRest() || cr->tuplet() || cr->endTick() > maxTick || toRest(cr)->isGap()) {
      82          252 :                         break;             // next element in the same voice is not a rest, or it exceeds the selection, or it is a gap
      83              :                     }
      84          651 :                     lastRest = cr;
      85              :                 }
      86          382 :                 Fraction restTicks = (lastRest->endTick() - curr->tick()) * curr->staff()->timeStretch(curr->tick());
      87          382 :                 seg = score->setNoteRest(seg, curr->track(), NoteVal(), restTicks, DirectionV::AUTO, false, {}, true);
      88          584 :             } else if (curr->isChord()) {
      89              :                 // combine tied chords
      90          582 :                 Chord* chord = toChord(curr);
      91          582 :                 Chord* lastTiedChord = chord;
      92         1349 :                 for (Chord* next = chord->nextTiedChord(); next && next->endTick() <= maxTick; next = next->nextTiedChord()) {
      93          767 :                     lastTiedChord = next;
      94              :                 }
      95          582 :                 if (!lastTiedChord) {
      96            0 :                     lastTiedChord = chord;
      97              :                 }
      98          582 :                 Fraction noteTicks = (lastTiedChord->endTick() - chord->tick()) * chord->staff()->timeStretch(chord->tick());
      99          582 :                 if (!(curr->tuplet())) {
     100              :                     // store start/end note for backward/forward ties ending/starting on the group of notes being rewritten
     101          576 :                     size_t numNotes = chord->notes().size();
     102         1152 :                     std::vector<Note*> tieBack(numNotes);
     103          576 :                     std::vector<Note*> tieFor(numNotes);
     104         1166 :                     for (size_t i = 0; i < numNotes; i++) {
     105          590 :                         Note* n = chord->notes()[i];
     106          590 :                         Note* nn = lastTiedChord->notes()[i];
     107          590 :                         if (n->tieBack()) {
     108           82 :                             tieBack[i] = n->tieBack()->startNote();
     109              :                         } else {
     110          508 :                             tieBack[i] = 0;
     111              :                         }
     112          590 :                         if (nn->tieFor()) {
     113           15 :                             tieFor[i] = nn->tieFor()->endNote();
     114              :                         } else {
     115          575 :                             tieFor[i] = 0;
     116              :                         }
     117              :                     }
     118          576 :                     Fraction tick = seg->tick();
     119          576 :                     track_idx_t tr = chord->track();
     120          576 :                     Fraction sd   = noteTicks;
     121          576 :                     std::vector<Tie*> ties;
     122          576 :                     Segment* segment = seg;
     123          576 :                     ChordRest* cr = toChordRest(segment->element(tr));
     124          576 :                     Chord* nchord = toChord(chord->clone());
     125         1166 :                     for (size_t i = 0; i < numNotes; i++) {           // strip ties from cloned chord
     126          590 :                         Note* n = nchord->notes()[i];
     127          590 :                         if (Tie* tieFor2 = n->tieFor()) {
     128          349 :                             n->setTieFor(nullptr);
     129          349 :                             delete tieFor2;
     130              :                         }
     131          590 :                         if (Tie* tieBack2 = n->tieBack()) {
     132            0 :                             n->setTieBack(nullptr);
     133            0 :                             delete tieBack2;
     134              :                         }
     135              :                     }
     136          576 :                     Chord* startChord = nchord;
     137          576 :                     Measure* measure = nullptr;
     138          576 :                     bool firstpart = true;
     139              :                     for (;;) {
     140          576 :                         if (tr % VOICES) {
     141            8 :                             score->expandVoice(segment, tr);
     142              :                         }
     143              :                         // the returned gap ends at the measure boundary or at tuplet end
     144          576 :                         Fraction dd = score->makeGap(segment, tr, sd, cr->tuplet());
     145          576 :                         if (dd.isZero()) {
     146            0 :                             break;
     147              :                         }
     148          576 :                         measure = segment->measure();
     149          576 :                         std::vector<TDuration> dl;
     150         1728 :                         dl = toRhythmicDurationList(dd, false, segment->rtick(), score->sigmap()->timesig(
     151          576 :                                                         tick.ticks()).nominal(), measure, 1,
     152         1152 :                                                     score->staff(track2staff(track))->timeStretch(tick));
     153          576 :                         size_t n = dl.size();
     154         1259 :                         for (size_t i = 0; i < n; ++i) {
     155          683 :                             const TDuration& d = dl[i];
     156          683 :                             Chord* nchord2 = toChord(nchord->clone());
     157          683 :                             if (!firstpart) {
     158          107 :                                 nchord2->removeMarkings(true);
     159              :                             }
     160          683 :                             nchord2->setDurationType(d);
     161          683 :                             nchord2->setTicks(d.fraction());
     162          683 :                             std::vector<Note*> nl1 = nchord->notes();
     163          683 :                             std::vector<Note*> nl2 = nchord2->notes();
     164          683 :                             if (!firstpart) {
     165          214 :                                 for (size_t j = 0; j < nl1.size(); ++j) {
     166          107 :                                     Tie* tie = Factory::createTie(nl1[j]);
     167          107 :                                     tie->setStartNote(nl1[j]);
     168          107 :                                     tie->setEndNote(nl2[j]);
     169          107 :                                     tie->setTick(tie->startNote()->tick());
     170          107 :                                     tie->setTick2(tie->endNote()->tick());
     171          107 :                                     tie->setTrack(tr);
     172          107 :                                     nl1[j]->setTieFor(tie);
     173          107 :                                     nl2[j]->setTieBack(tie);
     174          107 :                                     ties.push_back(tie);
     175              :                                 }
     176              :                             }
     177          683 :                             score->undoAddCR(nchord2, measure, tick);
     178          683 :                             segment = nchord2->segment();
     179          683 :                             tick += nchord2->actualTicks();
     180          683 :                             nchord = nchord2;
     181          683 :                             firstpart = false;
     182          683 :                         }
     183          576 :                         sd -= dd;
     184          576 :                         if (sd.isZero()) {
     185          576 :                             break;
     186              :                         }
     187            0 :                         Segment* nseg = score->tick2segment(tick, false, SegmentType::ChordRest);
     188            0 :                         if (nseg == 0) {
     189            0 :                             break;
     190              :                         }
     191            0 :                         segment = nseg;
     192            0 :                         cr = toChordRest(segment->element(tr));
     193            0 :                         if (cr == 0) {
     194            0 :                             if (tr % VOICES) {
     195            0 :                                 cr = score->addRest(segment, tr, TDuration(DurationType::V_MEASURE), 0);
     196              :                             } else {
     197            0 :                                 break;
     198              :                             }
     199              :                         }
     200          576 :                     }
     201          576 :                     if (is.slur()) {
     202              :                         // extend slur
     203            0 :                         is.slur()->undoChangeProperty(Pid::SPANNER_TICKS, nchord->tick() - is.slur()->tick());
     204            0 :                         for (EngravingObject* e : is.slur()->linkList()) {
     205            0 :                             Slur* slur = toSlur(e);
     206            0 :                             for (EngravingObject* ee : nchord->linkList()) {
     207            0 :                                 EngravingItem* e1 = static_cast<EngravingItem*>(ee);
     208            0 :                                 if (e1->score() == slur->score() && e1->track() == slur->track2()) {
     209            0 :                                     tx.push(new ChangeSpannerElements(slur, slur->startElement(), e1));
     210            0 :                                     break;
     211              :                                 }
     212            0 :                             }
     213            0 :                         }
     214              :                     }
     215              :                     // recreate previously stored pending ties
     216         1166 :                     for (size_t i = 0; i < numNotes; i++) {
     217          590 :                         Note* n = startChord->notes()[i];
     218          590 :                         Note* nn = nchord->notes()[i];
     219          590 :                         if (tieBack[i]) {
     220           82 :                             Tie* tie = Factory::createTie(tieBack[i]);
     221           82 :                             tie->setStartNote(tieBack[i]);
     222           82 :                             tie->setEndNote(n);
     223           82 :                             tie->setTick(tie->startNote()->tick());
     224           82 :                             tie->setTick2(tie->endNote()->tick());
     225           82 :                             tie->setTrack(track);
     226           82 :                             n->setTieBack(tie);
     227           82 :                             tieBack[i]->setTieFor(tie);
     228           82 :                             ties.push_back(tie);
     229              :                         }
     230          590 :                         if (tieFor[i]) {
     231           15 :                             Tie* tie = Factory::createTie(nn);
     232           15 :                             tie->setStartNote(nn);
     233           15 :                             tie->setEndNote(tieFor[i]);
     234           15 :                             tie->setTick(tie->startNote()->tick());
     235           15 :                             tie->setTick2(tie->endNote()->tick());
     236           15 :                             tie->setTrack(track);
     237           15 :                             nn->setTieFor(tie);
     238           15 :                             tieFor[i]->setTieBack(tie);
     239           15 :                             ties.push_back(tie);
     240              :                         }
     241              :                     }
     242          576 :                     if (!ties.empty()) {         // at least one tie was created
     243          377 :                         for (Tie* tie : ties) {
     244          204 :                             score->undoAddElement(tie);
     245              :                         }
     246          173 :                         score->connectTies();
     247              :                     }
     248          576 :                 }
     249              :             }
     250              :         }
     251              :     }
     252              :     // now put the input state back where it was before
     253           56 :     is.setSegment(inputSegment);
     254           56 : }
     255              : 
     256            9 : void Regroup::regroupNotesAndRestsInSelection(Transaction& tx, Score* score)
     257              : {
     258            9 :     bool noSelection = score->selection().isNone();
     259            9 :     if (noSelection) {
     260            0 :         score->cmdSelectAll();
     261            9 :     } else if (!score->selection().isRange()) {
     262            0 :         LOGD("no system or staff selected");
     263            0 :         return;
     264              :     }
     265              : 
     266              :     // save selection values because selection changes during grouping
     267            9 :     Fraction sTick = score->selection().tickStart();
     268            9 :     Fraction eTick = score->selection().tickEnd();
     269            9 :     staff_idx_t sStaff = score->selection().staffStart();
     270            9 :     staff_idx_t eStaff = score->selection().staffEnd();
     271            9 :     Box* sBox = score->selection().startBox();
     272            9 :     Box* eBox = score->selection().endBox();
     273              : 
     274           22 :     for (staff_idx_t staff = sStaff; staff < eStaff; staff++) {
     275           13 :         track_idx_t sTrack = staff * VOICES;
     276           13 :         track_idx_t eTrack = sTrack + VOICES;
     277           65 :         for (track_idx_t track = sTrack; track < eTrack; track++) {
     278           52 :             if (score->selectionFilter().canSelectVoice(track)) {
     279           52 :                 regroupNotesAndRests(tx, score, sTick, eTick, track);
     280              :             }
     281              :         }
     282              :     }
     283              : 
     284            9 :     if (noSelection) {
     285            0 :         score->deselectAll();
     286            0 :         return;
     287              :     }
     288              : 
     289              :     // Reset selection to original selection
     290            9 :     score->selection().setRangeTicks(sTick, eTick, sStaff, eStaff, sBox, eBox);
     291            9 :     score->selection().updateSelectedElements();
     292              : }
        

Generated by: LCOV version 2.0-1