LCOV - code coverage report
Current view: top level - src/engraving/dom - check.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 59.1 % 176 104
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 "chordrest.h"
      24              : #include "durationtype.h"
      25              : #include "factory.h"
      26              : #include "keysig.h"
      27              : #include "masterscore.h"
      28              : #include "measure.h"
      29              : #include "rest.h"
      30              : #include "segment.h"
      31              : #include "sig.h"
      32              : #include "staff.h"
      33              : #include "tuplet.h"
      34              : #include "utils.h"
      35              : 
      36              : #include "engravingerrors.h"
      37              : 
      38              : #include "log.h"
      39              : 
      40              : using namespace mu;
      41              : using namespace muse;
      42              : using namespace mu::engraving;
      43              : 
      44              : namespace mu::engraving {
      45              : //---------------------------------------------------------
      46              : //   checkScore
      47              : //---------------------------------------------------------
      48              : 
      49            0 : void Score::checkScore()
      50              : {
      51            0 :     if (!firstMeasure()) {
      52            0 :         return;
      53              :     }
      54              : 
      55            0 :     for (Segment* s = firstMeasure()->first(); s;) {
      56            0 :         Segment* ns = s->next1();
      57              : 
      58            0 :         if (s->segmentType() & (SegmentType::ChordRest)) {
      59            0 :             bool empty = true;
      60            0 :             for (EngravingItem* e : s->elist()) {
      61            0 :                 if (e) {
      62            0 :                     empty = false;
      63            0 :                     break;
      64              :                 }
      65              :             }
      66            0 :             if (empty) {
      67            0 :                 LOGD("checkScore: remove empty ChordRest segment");
      68              :             }
      69              :         }
      70            0 :         s = ns;
      71              :     }
      72              : 
      73            0 :     ChordRest* lcr = 0;
      74            0 :     for (size_t staffIdx = 0; staffIdx < m_staves.size(); ++staffIdx) {
      75            0 :         size_t track = staffIdx * VOICES;
      76            0 :         Fraction tick  = Fraction(0, 1);
      77            0 :         Staff* st = staff(staffIdx);
      78            0 :         for (Segment* s = firstMeasure()->first(SegmentType::ChordRest); s; s = s->next1(SegmentType::ChordRest)) {
      79            0 :             ChordRest* cr = toChordRest(s->element(static_cast<int>(track)));
      80            0 :             if (!cr) {
      81            0 :                 continue;
      82              :             }
      83            0 :             if (s->tick() != tick) {
      84            0 :                 if (lcr) {
      85            0 :                     Fraction timeStretch = st->timeStretch(lcr->tick());
      86            0 :                     Fraction f = cr->globalTicks() * timeStretch;
      87            0 :                     LOGD() << "Chord/Rest gap at tick " << tick.ticks() << "(" << lcr->typeName() << "+" << f.ticks() << ")-"
      88            0 :                            << s->tick().ticks() << "(" << cr->typeName() << ") staffIdx " << staffIdx
      89            0 :                            << " measure " << cr->measure()->measureNumber()
      90            0 :                            << " (len = " << (cr->tick() - tick).ticks() << ")";
      91              :                 } else {
      92            0 :                     LOGD() << "Chord/Rest gap at tick " << tick.ticks() << "-" << s->tick().ticks() << "(" << cr->typeName() << ") "
      93            0 :                            << "staffIdx " << staffIdx << " measure " << cr->measure()->measureNumber()
      94            0 :                            << "  (len = " << (cr->tick() - tick).ticks() << ")";
      95              :                 }
      96            0 :                 tick = s->tick();
      97              :             }
      98              : 
      99            0 :             Fraction timeStretch = st->timeStretch(tick);
     100            0 :             Fraction f = cr->globalTicks() * timeStretch;
     101            0 :             tick      += f;
     102            0 :             lcr        = cr;
     103              :         }
     104              :     }
     105              : }
     106              : 
     107              : //---------------------------------------------------------
     108              : //   sanityCheck - Simple check for score
     109              : ///    Check that voice 1 is complete
     110              : ///    Check that voices > 1 contains less than measure duration
     111              : //---------------------------------------------------------
     112              : 
     113           63 : Ret MasterScore::sanityCheck()
     114              : {
     115           63 :     std::string accumulatedErrors;
     116              : 
     117          126 :     for (Score* score : scoreList()) {
     118           63 :         Ret ret = score->sanityCheckLocal();
     119           63 :         if (ret) {
     120              :             // everything is fine in this part, let's continue
     121           63 :             continue;
     122              :         }
     123              : 
     124            0 :         if (!accumulatedErrors.empty()) {
     125            0 :             accumulatedErrors += "\n";
     126              :         }
     127              : 
     128            0 :         accumulatedErrors += ret.text();
     129          126 :     }
     130              : 
     131           63 :     if (accumulatedErrors.empty()) {
     132           63 :         return muse::make_ok();
     133              :     }
     134            0 :     return Ret(static_cast<int>(Err::FileCorrupted), accumulatedErrors);
     135           63 : }
     136              : 
     137           63 : Ret Score::sanityCheckLocal()
     138              : {
     139           65 :     TRACEFUNC;
     140              : 
     141           63 :     StringList errors;
     142           63 :     int mNumber = 1;
     143              : 
     144            0 :     auto excerptInfo = [this]() {
     145            0 :         if (isMaster()) {
     146            0 :             return muse::mtrc("engraving", "Full score");
     147              :         }
     148              : 
     149              :         //: %1 is the name of a part score.
     150            0 :         return muse::mtrc("engraving", "Part score: %1").arg(name());
     151           63 :     };
     152              : 
     153           63 :     setHasCorruptedMeasures(false);
     154              : 
     155         1238 :     for (Measure* m = firstMeasure(); m; m = m->nextMeasure()) {
     156         1175 :         Fraction mLen = m->ticks();
     157         1175 :         size_t endStaff  = staves().size();
     158              : 
     159         2376 :         for (size_t staffIdx = 0; staffIdx < endStaff; ++staffIdx) {
     160         1201 :             Rest* fmrest0 = nullptr; // full measure rest in voice 0
     161         4804 :             Fraction voices[VOICES];
     162              : 
     163         1201 :             m->setCorrupted(staffIdx, false);
     164              : 
     165         7359 :             for (Segment* s = m->first(SegmentType::ChordRest); s; s = s->next(SegmentType::ChordRest)) {
     166        30790 :                 for (voice_idx_t v = 0; v < VOICES; ++v) {
     167        24632 :                     EngravingItem* element = s->element(staffIdx * VOICES + v);
     168        24632 :                     if (!element) {
     169        18530 :                         continue;
     170              :                     }
     171              : 
     172         6102 :                     ChordRest* cr = toChordRest(element);
     173         6102 :                     voices[v] += cr->actualTicks();
     174         6102 :                     if (v == 0 && cr->isRest()) {
     175         1796 :                         Rest* r = toRest(cr);
     176         1796 :                         if (r->durationType().isMeasure()) {
     177          596 :                             fmrest0 = r;
     178              :                         }
     179              :                     }
     180              :                 }
     181              :             }
     182              : 
     183         1201 :             bool checkRepeats = m->isMeasureRepeatGroup(staffIdx);
     184         1201 :             bool repeatsIsValid = true;
     185              : 
     186         1201 :             if (checkRepeats) {
     187            0 :                 repeatsIsValid = m->measureRepeatElement(staffIdx) != nullptr;
     188              :             }
     189              : 
     190         1201 :             if (!repeatsIsValid) {
     191            0 :                 errors << muse::mtrc("engraving", "<b>Corrupted measure</b>: %1, measure %2, staff %3.")
     192            0 :                     .arg(excerptInfo()).arg(mNumber).arg(staffIdx + 1);
     193            0 :                 m->setCorrupted(staffIdx, true);
     194            0 :                 setHasCorruptedMeasures(true);
     195              :             }
     196              : 
     197         1201 :             if (voices[0] != mLen) {
     198              :                 //: %1 describes in which score the corruption is (either `Full score` or `"[part name]" part score`)
     199            0 :                 errors << muse::mtrc("engraving", "<b>Incomplete measure</b>: %1, measure %2, staff %3. Found: %4. Expected: %5.")
     200            0 :                     .arg(excerptInfo()).arg(mNumber).arg(staffIdx + 1).arg(voices[0].toString(), mLen.toString());
     201            0 :                 m->setCorrupted(staffIdx, true);
     202            0 :                 setHasCorruptedMeasures(true);
     203              :                 // try to fix a bad full measure rest
     204            0 :                 if (fmrest0) {
     205              :                     // fmrest0->setDuration(mLen * fmrest0->staff()->timeStretch(fmrest0->tick()));
     206            0 :                     fmrest0->setTicks(mLen);
     207              :                 }
     208              :             }
     209              : 
     210         4804 :             for (voice_idx_t v = 1; v < VOICES; ++v) {
     211         3603 :                 if (voices[v] > mLen) {
     212              :                     //: %1 describes in which score the corruption is (either `Full score` or `"[part name]" part score`)
     213            0 :                     errors << muse::mtrc("engraving", "<b>Voice too long</b>: %1, measure %2, staff %3, voice %4. Found: %5. Expected: %6.")
     214            0 :                         .arg(excerptInfo()).arg(mNumber).arg(staffIdx + 1).arg(v + 1).arg(voices[v].toString(), mLen.toString());
     215            0 :                     m->setCorrupted(staffIdx, true);
     216            0 :                     setHasCorruptedMeasures(true);
     217              :                 }
     218              :             }
     219              : 
     220         9278 :             for (Segment* s = m->first(); s; s = s->next()) {
     221         8077 :                 const Fraction rtick = s->rtick();
     222         8077 :                 if ((rtick.negative() || rtick > mLen) && s->hasAnnotationOrElement(staffIdx)) {
     223              :                     //: %1 describes in which score the corruption is (either `Full score` or `"[part name]" part score`)
     224            0 :                     errors << muse::mtrc("engraving",
     225              :                                          "<b>Element outside measure</b>: %1, measure %2, staff %3. Found at: %4. Expected between 0 and %5.")
     226            0 :                         .arg(excerptInfo()).arg(mNumber).arg(staffIdx + 1).arg(rtick.toString(), mLen.toString());
     227            0 :                     m->setCorrupted(staffIdx, true);
     228            0 :                     setHasCorruptedMeasures(true);
     229            0 :                     break;
     230              :                 }
     231              :             }
     232              :         }
     233              : 
     234         1175 :         mNumber++;
     235              :     }
     236              : 
     237           63 :     if (errors.empty()) {
     238           63 :         return muse::make_ok();
     239              :     }
     240              : 
     241            0 :     return Ret(static_cast<int>(Err::FileCorrupted), errors.join(u"\n").toStdString());
     242           63 : }
     243              : 
     244              : //---------------------------------------------------------
     245              : //   checkKeys
     246              : ///    check that key map is in sync with actual keys
     247              : //---------------------------------------------------------
     248              : 
     249            4 : bool Score::checkKeys()
     250              : {
     251            4 :     bool rc = true;
     252            8 :     for (size_t i = 0; i < nstaves(); ++i) {
     253            4 :         Key k = staff(i)->key(Fraction(0, 1));
     254           42 :         for (Measure* m = firstMeasure(); m; m = m->nextMeasure()) {
     255           38 :             Segment* s = m->findSegment(SegmentType::KeySig, m->tick());
     256           38 :             if (s) {
     257           13 :                 EngravingItem* element = s->element(static_cast<int>(i) * VOICES);
     258           13 :                 if (element) {
     259           12 :                     k = toKeySig(element)->key();
     260              :                 }
     261              :             }
     262           38 :             if (staff(i)->key(m->tick()) != k) {
     263            0 :                 LOGD("measure %d (tick %d) : key %d, map %d", m->measureNumber(), m->tick().ticks(), int(k),
     264            0 :                      int(staff(i)->key(m->tick())));
     265            0 :                 rc = false;
     266              :             }
     267              :         }
     268              :     }
     269            4 :     return rc;
     270              : }
     271              : 
     272              : //---------------------------------------------------------
     273              : //   fillGap
     274              : //---------------------------------------------------------
     275              : 
     276          606 : void Measure::fillGap(const Fraction& rtickStart, const Fraction& len, track_idx_t track, const Fraction& stretch, bool useGapRests)
     277              : {
     278              :     LOGN("measure %6d pos %d, len %d/%d, stretch %d/%d track %zu",
     279              :          tick().ticks(),
     280              :          rtickStart.ticks(),
     281              :          len.numerator(), len.denominator(),
     282              :          stretch.numerator(), stretch.denominator(),
     283              :          track);
     284              : 
     285              :     // break the gap into shorter durations if necessary
     286          606 :     std::vector<TDuration> durationList = toRhythmicDurationList(len, true, rtickStart, timesig(), this, 0, stretch);
     287              : 
     288          606 :     Fraction curTick = tick() + actualTicks(rtickStart, nullptr, stretch);
     289         1225 :     for (TDuration d : durationList) {
     290          619 :         Rest* rest = Factory::createRest(score()->dummy());
     291          619 :         rest->setTicks(d.isMeasure() ? ticks() * stretch : d.fraction());
     292          619 :         rest->setDurationType(d);
     293          619 :         rest->setTrack(track);
     294          619 :         rest->setGap(useGapRests);
     295          619 :         score()->undoAddCR(rest, this, curTick);
     296          619 :         curTick += rest->actualTicks();
     297              :     }
     298          606 : }
     299              : 
     300              : //---------------------------------------------------------
     301              : //   checkMeasure
     302              : //    after opening / paste and every read operation
     303              : //    this method checks for gaps and fills them
     304              : //    with invisible rests
     305              : //---------------------------------------------------------
     306              : 
     307        17295 : void Measure::checkMeasure(staff_idx_t staffIdx, bool useGapRests)
     308              : {
     309        17295 :     if (isMMRest()) {
     310           62 :         return;
     311              :     }
     312              : 
     313        17233 :     track_idx_t strack = staffIdx * VOICES;
     314        17233 :     track_idx_t dtrack = strack + (hasVoices(staffIdx) ? VOICES : 1);
     315        17233 :     Fraction stretch = score()->staff(staffIdx)->timeStretch(tick());
     316        17233 :     Fraction f       = ticks() * stretch;
     317              : 
     318        35657 :     for (track_idx_t track = strack; track < dtrack; track++) {
     319        18424 :         Fraction expectedPos = Fraction(0, 1);
     320        18424 :         Fraction currentPos  = Fraction(0, 1);
     321              : 
     322        57096 :         for (Segment* seg = first(SegmentType::ChordRest); seg; seg = seg->next(SegmentType::ChordRest)) {
     323        38674 :             EngravingItem* e = seg->element(track);
     324        38674 :             if (!e) {
     325         7355 :                 continue;
     326              :             }
     327              : 
     328        31319 :             ChordRest* cr = toChordRest(e);
     329        31319 :             currentPos    = seg->rtick() * stretch;
     330              : 
     331        31319 :             if (currentPos < expectedPos) {
     332              :                 LOGN("in measure overrun %6d at %d-%d track %zu", tick().ticks(),
     333              :                      (currentPos / stretch).ticks(), (expectedPos / stretch).ticks(), track);
     334            2 :                 break;
     335        31317 :             } else if (currentPos > expectedPos) {
     336              :                 LOGN("in measure underrun %6d at %d-%d track %zu", tick().ticks(),
     337              :                      (currentPos / stretch).ticks(), (expectedPos / stretch).ticks(), track);
     338           21 :                 fillGap(expectedPos, currentPos - expectedPos, track, stretch, useGapRests);
     339           21 :                 if (currentPos >= f) {
     340            0 :                     break;
     341              :                 }
     342              :             }
     343              : 
     344        31317 :             DurationElement* de = cr;
     345        31317 :             Tuplet* tuplet = cr->topTuplet();
     346        31317 :             if (tuplet) {
     347          536 :                 seg = skipTuplet(tuplet);
     348          536 :                 de  = tuplet;
     349              :             }
     350        31317 :             expectedPos = currentPos + de->ticks();
     351              :         }
     352              : 
     353        18424 :         if (f > expectedPos) {
     354              :             // don't fill empty voices (except for the 1st)
     355         1352 :             if (expectedPos.isNotZero() || track2voice(track) == 0) {
     356          585 :                 fillGap(expectedPos, f - expectedPos, track, stretch);
     357              :             }
     358        17072 :         } else if (f < expectedPos) {
     359            1 :             LOGD("measure overrun %6d, %d > %d, track %zu", tick().ticks(), expectedPos.ticks(), f.ticks(), track);
     360              :         }
     361              :     }
     362              : }
     363              : }
        

Generated by: LCOV version 2.0-1