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 : }
|