LCOV - code coverage report
Current view: top level - src/engraving/dom - groups.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 63.5 % 170 108
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) 2021 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 "groups.h"
      24              : 
      25              : #include <unordered_map>
      26              : 
      27              : #include "chordrest.h"
      28              : #include "durationtype.h"
      29              : #include "measure.h"
      30              : #include "staff.h"
      31              : #include "timesig.h"
      32              : #include "tuplet.h"
      33              : 
      34              : #include "log.h"
      35              : 
      36              : using namespace mu;
      37              : 
      38              : namespace mu::engraving {
      39              : //---------------------------------------------------------
      40              : //   NoteGroup
      41              : //---------------------------------------------------------
      42              : 
      43              : struct NoteGroup {
      44              :     Fraction timeSig;
      45              :     Groups endings;
      46              : };
      47              : 
      48              : //---------------------------------------------------------
      49              : //   noteGroups
      50              : //---------------------------------------------------------
      51              : 
      52              : static std::vector<NoteGroup> noteGroups {
      53              :     { Fraction(2, 2),
      54              :       Groups({ { 4, 0x200 }, { 8, 0x110 }, { 12, 0x200 }, { 16, 0x111 }, { 20, 0x200 }, { 24, 0x110 }, { 28, 0x200 } })
      55              :     },
      56              :     { Fraction(3, 2),
      57              :       Groups({ { 4, 0x200 }, { 8, 0x110 }, { 12, 0x200 }, { 16, 0x111 }, { 20, 0x200 }, { 24, 0x110 }, { 28, 0x200 },
      58              :                  { 32, 0x111 }, { 36, 0x200 }, { 40, 0x110 }, { 44, 0x200 } })
      59              :     },
      60              :     { Fraction(4, 2),
      61              :       Groups({ { 4, 0x200 }, { 8, 0x110 }, { 12, 0x200 }, { 16, 0x111 }, { 20, 0x200 }, { 24, 0x110 }, { 28, 0x200 },
      62              :                  { 32, 0x111 }, { 36, 0x200 }, { 40, 0x110 }, { 44, 0x200 }, { 48, 0x111 }, { 52, 0x200 }, { 56, 0x110 }, { 60, 0x200 } })
      63              :     },
      64              :     { Fraction(2, 4),
      65              :       Groups({ { 4, 0x200 }, { 8, 0x111 }, { 12, 0x200 } })
      66              :     },
      67              :     { Fraction(3, 4),
      68              :       Groups({ { 4, 0x200 }, { 8, 0x111 }, { 12, 0x200 }, { 16, 0x111 }, { 20, 0x200 } })
      69              :     },
      70              :     { Fraction(4, 4),
      71              :       Groups({ { 4, 0x200 }, { 8, 0x110 }, { 12, 0x200 }, { 16, 0x111 }, { 20, 0x200 }, { 24, 0x110 }, { 28, 0x200 } })
      72              :     },
      73              :     { Fraction(5, 4),
      74              :       Groups({ { 4, 0x200 }, { 8, 0x110 }, { 12, 0x200 }, { 16, 0x110 }, { 20, 0x200 }, { 24, 0x111 }, { 28, 0x200 },
      75              :                  { 32, 0x110 }, { 36, 0x200 } })
      76              :     },
      77              :     { Fraction(6, 4),
      78              :       Groups({ { 4, 0x200 }, { 8, 0x110 }, { 12, 0x200 }, { 16, 0x110 }, { 20, 0x200 }, { 24, 0x111 }, { 28, 0x200 },
      79              :                  { 32, 0x110 }, { 36, 0x200 }, { 40, 0x110 }, { 44, 0x200 } })
      80              :     },
      81              :     { Fraction(3, 8),
      82              :       Groups({ { 4, 0x200 }, { 8, 0x200 } })
      83              :     },
      84              :     { Fraction(5, 8),
      85              :       Groups({ { 4, 0x200 }, { 8, 0x200 }, { 12, 0x111 }, { 16, 0x200 } })
      86              :     },
      87              :     { Fraction(6, 8),
      88              :       Groups({ { 4, 0x200 }, { 8, 0x200 }, { 12, 0x111 }, { 16, 0x200 }, { 20, 0x200 } })
      89              :     },
      90              :     { Fraction(7, 8),
      91              :       Groups({ { 4, 0x200 }, { 8, 0x200 }, { 12, 0x111 }, { 16, 0x200 }, { 20, 0x111 }, { 24, 0x200 } })
      92              :     },
      93              :     { Fraction(9, 8),
      94              :       Groups({ { 4, 0x200 }, { 8, 0x200 }, { 12, 0x111 }, { 16, 0x200 }, { 20, 0x200 }, { 24, 0x111 }, { 28, 0x200 }, { 32, 0x200 } })
      95              :     },
      96              :     { Fraction(12, 8),
      97              :       Groups({ { 4, 0x200 }, { 8, 0x200 }, { 12, 0x111 }, { 16, 0x200 }, { 20, 0x200 }, { 24, 0x111 }, { 28, 0x200 },
      98              :                  { 32, 0x200 }, { 36, 0x111 }, { 40, 0x200 }, { 44, 0x200 } })
      99              :     },
     100              : };
     101              : 
     102              : //---------------------------------------------------------
     103              : //   baseBeamMode
     104              : //    based on time signature properties but respecting manual settings
     105              : //---------------------------------------------------------
     106              : 
     107       135470 : BeamMode Groups::baseBeamMode(const ChordRest* cr, const ChordRest* prev)
     108              : {
     109       135470 :     if (cr->isGrace() || cr->beamMode() != BeamMode::AUTO) {
     110        60946 :         return cr->beamMode();
     111              :     }
     112        74524 :     assert(cr->staff());
     113              : 
     114              :     // we need to figure out the longest note value beat upon which cr falls in the measure
     115        74524 :     Fraction maxTickLen = std::max(cr->ticks(), prev ? prev->ticks() : Fraction());
     116        74524 :     Fraction smallestTickLen = Fraction(1, 8); // start with 8th
     117        74524 :     Fraction tickLenLimit = Fraction(1, 32); // only check up to 32nds because that's all thats available
     118              :                                              // in timesig properties
     119       131041 :     while (smallestTickLen > maxTickLen || cr->rtick().ticks() % smallestTickLen.ticks() != 0) {
     120        77071 :         smallestTickLen /= 2; // proceed to 16th, 32nd, etc
     121        77071 :         if (smallestTickLen < tickLenLimit) {
     122        20554 :             smallestTickLen = cr->ticks();
     123        20554 :             break;
     124              :         }
     125              :     }
     126        74524 :     DurationType bigBeatDuration = TDuration(smallestTickLen).type();
     127              : 
     128        74524 :     TDuration crDuration = cr->durationType();
     129        74524 :     const Groups& g = cr->staff()->group(cr->tick());
     130        74524 :     Fraction stretch = cr->staff()->timeStretch(cr->tick());
     131        74524 :     Fraction tick = cr->rtick() * stretch + cr->measure()->anacrusisOffset();
     132              : 
     133              :     // We can choose to break beams based on its place in the measure, or by its duration. These
     134              :     // can be consolidated mostly, with bias towards its duration.
     135        74524 :     BeamMode byType = g.beamMode(tick.ticks(), crDuration.type());
     136        74524 :     BeamMode byPos = g.beamMode(tick.ticks(), bigBeatDuration);
     137        74524 :     BeamMode val = byType == BeamMode::AUTO ? byPos : byType;
     138              : 
     139              :     // context-dependent checks
     140        74524 :     if (val == BeamMode::AUTO && tick.isNotZero()) {
     141              :         // if current or previous cr is in tuplet (but not both in same tuplet):
     142              :         // consider it as if this were next shorter duration
     143        60080 :         if (prev && (cr->tuplet() != prev->tuplet()) && (crDuration == prev->durationType())) {
     144          505 :             if (crDuration >= DurationType::V_EIGHTH) {
     145          224 :                 val = g.beamMode(tick.ticks(), DurationType::V_16TH);
     146          281 :             } else if (crDuration == DurationType::V_16TH) {
     147           83 :                 val = g.beamMode(tick.ticks(), DurationType::V_32ND);
     148              :             } else {
     149          198 :                 val = g.beamMode(tick.ticks(), DurationType::V_64TH);
     150              :             }
     151              :         }
     152              :         // if there is a hole between previous and current cr, break beam
     153              :         // exclude tuplets from this check; tick calculations can be unreliable
     154              :         // and they seem to be handled well anyhow
     155        60080 :         if (cr->voice() && prev && !prev->tuplet() && prev->endTick() < cr->tick()) {
     156            0 :             val = BeamMode::BEGIN;
     157              :         }
     158              :     }
     159              : 
     160        74524 :     return val;
     161              : }
     162              : 
     163              : //---------------------------------------------------------
     164              : //   actualBeamMode
     165              : //    final beam mode after applying contextual corrections
     166              : //---------------------------------------------------------
     167              : 
     168            0 : BeamMode Groups::actualBeamMode(const ChordRest* cr, const ChordRest* prev)
     169              : {
     170            0 :     Measure* measure = cr->measure();
     171            0 :     const Staff* stf = cr->staff();
     172            0 :     TimeSig* ts = stf->timeSig(measure->tick());
     173            0 :     bool checkBeats = ts && ts->denominator() == 4;
     174              : 
     175            0 :     std::unordered_map<int, TDuration> beatSubdivision;
     176            0 :     if (checkBeats && cr->rtick().isNotZero()) {
     177            0 :         Fraction stretch = ts->stretch();
     178            0 :         Fraction tick = cr->rtick() * stretch;
     179              : 
     180            0 :         if ((tick.ticks() % Constants::DIVISION) == 0) {
     181            0 :             for (Segment* s = measure->first(SegmentType::ChordRest); s; s = s->next(SegmentType::ChordRest)) {
     182            0 :                 ChordRest* mcr = toChordRest(s->element(cr->track()));
     183            0 :                 if (mcr == 0) {
     184            0 :                     continue;
     185              :                 }
     186            0 :                 int beat = (mcr->rtick() * stretch).ticks() / Constants::DIVISION;
     187            0 :                 if (muse::contains(beatSubdivision, beat)) {
     188            0 :                     beatSubdivision[beat] = std::min(beatSubdivision[beat], mcr->durationType());
     189              :                 } else {
     190            0 :                     beatSubdivision[beat] = mcr->durationType();
     191              :                 }
     192              :             }
     193              :         }
     194              :     }
     195              : 
     196            0 :     return actualBeamMode(cr, prev, &beatSubdivision);
     197            0 : }
     198              : 
     199        85837 : BeamMode Groups::actualBeamMode(const ChordRest* cr, const ChordRest* prev,
     200              :                                 const std::unordered_map<int, TDuration>* beatSubdivision)
     201              : {
     202              :     // do not beam rests set to BeamMode::AUTO or with only other rests
     203        85837 :     if (cr->isRest() && cr->beamMode() == BeamMode::AUTO) {
     204           63 :         return BeamMode::NONE;
     205              :     }
     206              : 
     207        85774 :     TDuration durationType = cr->durationType();
     208              :     // chord with no hooks cannot be beamed
     209        85774 :     if (cr->isChord() && durationType.hooks() == 0) {
     210        31243 :         return BeamMode::NONE;
     211              :     }
     212              :     // if chord has hooks and is 2nd element of a cross-measure value
     213              :     // set beam mode to NONE (do not combine with following chord beam/hook, if any)
     214        54531 :     if (durationType.hooks() > 0 && cr->crossMeasure() == CrossMeasure::SECOND) {
     215            0 :         return BeamMode::NONE;
     216              :     }
     217              : 
     218        54531 :     BeamMode bm = Groups::baseBeamMode(cr, prev);
     219              : 
     220        54531 :     if (bm == BeamMode::AUTO) {
     221              :         // start beam at the beginning of measures
     222        11552 :         if (cr->rtick().isZero()) {
     223         1498 :             return BeamMode::BEGIN;
     224              :         }
     225              : 
     226              :         // perform additional context-dependent checks
     227        10054 :         Measure* measure = cr->measure();
     228        10054 :         const Staff* stf = cr->staff();
     229        10054 :         TimeSig* ts = stf->timeSig(measure->tick());
     230        10054 :         bool checkBeats = ts && ts->denominator() == 4;
     231        10054 :         Fraction stretch = ts ? ts->stretch() : Fraction(1, 1);
     232              : 
     233              :         // check if we need to break beams according to minimum duration in current / previous beat
     234        10054 :         if (checkBeats) {
     235         9263 :             Fraction tick = cr->rtick() * stretch;
     236              :             // check if on the beat
     237         9263 :             if ((tick.ticks() % Constants::DIVISION) == 0) {
     238         1684 :                 int beat = tick.ticks() / Constants::DIVISION;
     239              :                 // get minimum duration for this & previous beat
     240         1684 :                 auto it_current = beatSubdivision->find(beat);
     241         1684 :                 auto it_prev = beatSubdivision->find(beat - 1);
     242         1684 :                 TDuration current_min = (it_current != beatSubdivision->end()) ? it_current->second : TDuration(DurationType::V_INVALID);
     243         1684 :                 TDuration prev_min = (it_prev != beatSubdivision->end()) ? it_prev->second : TDuration(DurationType::V_INVALID);
     244         1684 :                 TDuration minDuration = std::min(current_min, prev_min);
     245              :                 // re-calculate beam as if this were the duration of current chordrest
     246         1684 :                 TDuration saveDuration        = cr->actualDurationType();
     247         1684 :                 TDuration saveCMDuration      = cr->crossMeasureDurationType();
     248         1684 :                 CrossMeasure saveCrossMeasVal = cr->crossMeasure();
     249         1684 :                 const_cast<ChordRest*>(cr)->setDurationType(minDuration);
     250         1684 :                 bm = Groups::baseBeamMode(cr, prev);
     251         1684 :                 const_cast<ChordRest*>(cr)->setDurationType(saveDuration);
     252         1684 :                 const_cast<ChordRest*>(cr)->setCrossMeasure(saveCrossMeasVal);
     253         1684 :                 const_cast<ChordRest*>(cr)->setCrossMeasureDurationType(saveCMDuration);
     254              :             }
     255              :         }
     256              :     }
     257              : 
     258        53033 :     return bm == BeamMode::AUTO ? BeamMode::MID : bm;
     259              : }
     260              : 
     261              : //---------------------------------------------------------
     262              : //   beamMode
     263              : //    tick is relative to begin of measure
     264              : //---------------------------------------------------------
     265              : 
     266       153351 : BeamMode Groups::beamMode(int tick, DurationType d) const
     267              : {
     268              :     int shift;
     269       153351 :     switch (d) {
     270       104515 :     case DurationType::V_EIGHTH: shift = 0;
     271       104515 :         break;
     272        33342 :     case DurationType::V_16TH:   shift = 4;
     273        33342 :         break;
     274        15444 :     case DurationType::V_32ND:
     275              :     // All beams shorter than 32nds break in the same place
     276              :     case DurationType::V_64TH:
     277              :     case DurationType::V_128TH:
     278              :     case DurationType::V_256TH:
     279              :     case DurationType::V_512TH:
     280              :     case DurationType::V_1024TH:
     281        15444 :         shift = 8;
     282        15444 :         break;
     283           50 :     default:
     284           50 :         return BeamMode::AUTO;
     285              :     }
     286       153301 :     const int dm = Constants::DIVISION / 8;
     287       553710 :     for (const GroupNode& e : m_nodes) {
     288       547202 :         if (e.pos * dm < tick) {
     289       400409 :             continue;
     290              :         }
     291       146793 :         if (e.pos * dm > tick) {
     292        65050 :             break;
     293              :         }
     294              : 
     295        81743 :         int action = (e.action >> shift) & 0xf;
     296        81743 :         switch (action) {
     297        81743 :         case 0: return BeamMode::AUTO;
     298        13721 :         case 1: return BeamMode::BEGIN;
     299         1167 :         case 2: return BeamMode::BEGIN16;
     300            0 :         case 3: return BeamMode::BEGIN32;
     301            0 :         default:
     302            0 :             LOGD("   Groups::beamMode: bad action %d", action);
     303            0 :             return BeamMode::AUTO;
     304              :         }
     305              :     }
     306        71558 :     return BeamMode::AUTO;
     307              : }
     308              : 
     309              : //---------------------------------------------------------
     310              : //   endings
     311              : //---------------------------------------------------------
     312              : 
     313       152365 : const Groups& Groups::endings(const Fraction& f)
     314              : {
     315       942147 :     for (const NoteGroup& g : noteGroups) {
     316       942144 :         if (g.timeSig.identical(f)) {
     317       152362 :             return g.endings;
     318              :         }
     319              :     }
     320            3 :     NoteGroup g;
     321            3 :     g.timeSig = f;
     322            3 :     noteGroups.push_back(g);
     323              : 
     324            3 :     int pos = 0;
     325            3 :     switch (f.denominator()) {
     326            0 :     case 2:     pos = 16;
     327            0 :         break;
     328            1 :     case 4:     pos = 8;
     329            1 :         break;
     330            1 :     case 8:     pos = 4;
     331            1 :         break;
     332            1 :     case 16:    pos = 2;
     333            1 :         break;
     334            0 :     case 32:    pos = 1;
     335            0 :         break;
     336              :         break;
     337              :     }
     338           27 :     for (int i = 1; i < f.numerator(); ++i) {
     339           24 :         GroupNode n;
     340           24 :         n.pos    = pos * i;
     341           24 :         n.action = 0x111;
     342           24 :         g.endings.addNode(n);
     343              :     }
     344            3 :     return noteGroups.back().endings;
     345            3 : }
     346              : 
     347              : //---------------------------------------------------------
     348              : //   addStop
     349              : //---------------------------------------------------------
     350              : 
     351            0 : void Groups::addStop(int pos, DurationType d, BeamMode bm)
     352              : {
     353              :     int shift;
     354            0 :     switch (d) {
     355            0 :     case DurationType::V_EIGHTH: shift = 0;
     356            0 :         break;
     357            0 :     case DurationType::V_16TH:   shift = 4;
     358            0 :         break;
     359            0 :     case DurationType::V_32ND:   shift = 8;
     360            0 :         break;
     361            0 :     default:
     362            0 :         return;
     363              :     }
     364              :     int action;
     365            0 :     if (bm == BeamMode::BEGIN) {
     366            0 :         action = 1;
     367            0 :     } else if (bm == BeamMode::BEGIN16) {
     368            0 :         action = 2;
     369            0 :     } else if (bm == BeamMode::BEGIN32) {
     370            0 :         action = 3;
     371              :     } else {
     372            0 :         return;
     373              :     }
     374              : 
     375            0 :     pos    /= 60;
     376            0 :     action <<= shift;
     377              : 
     378            0 :     auto i = m_nodes.begin();
     379            0 :     for (; i != m_nodes.end(); ++i) {
     380            0 :         if (i->pos == pos) {
     381            0 :             i->action = (i->action & ~(0xf << shift)) | action;
     382            0 :             return;
     383              :         }
     384            0 :         if (i->pos > pos) {
     385            0 :             break;
     386              :         }
     387              :     }
     388            0 :     m_nodes.insert(i, GroupNode({ pos, action }));
     389              : }
     390              : 
     391              : //---------------------------------------------------------
     392              : //   dump
     393              : //---------------------------------------------------------
     394              : 
     395            0 : void Groups::dump(const char* m) const
     396              : {
     397            0 :     LOGD("%s", m);
     398            0 :     for (const GroupNode& n : m_nodes) {
     399            0 :         LOGD("  group tick %d action 0x%02x", n.pos * 60, n.action);
     400              :     }
     401            0 : }
     402              : }
        

Generated by: LCOV version 2.0-1