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) 2023 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 "restlayout.h"
24 :
25 : #include "beamlayout.h"
26 : #include "chordlayout.h"
27 : #include "tlayout.h"
28 :
29 : #include "dom/beam.h"
30 : #include "dom/measure.h"
31 : #include "dom/staff.h"
32 : #include "dom/system.h"
33 :
34 : using namespace muse;
35 : using namespace mu::engraving;
36 :
37 : namespace mu::engraving::rendering::score {
38 66760 : void RestLayout::layoutRest(const Rest* item, Rest::LayoutData* ldata, const LayoutContext& ctx)
39 : {
40 66760 : if (item->isGap()) {
41 152 : if (item->debugDrawGap()) {
42 0 : ldata->sym = item->getSymbol(item->durationType().type(), 16, 5);
43 0 : fillShape(item, ldata, ctx.conf());
44 0 : ldata->setPos(PointF(0.0, 10 * item->spatium()));
45 : } else {
46 152 : ldata->reset();
47 : }
48 152 : return;
49 : }
50 :
51 : //! NOTE The types are listed here explicitly to show what types there are (see Rest::add method)
52 : //! and accordingly show what depends on.
53 66608 : for (EngravingItem* e : item->el()) {
54 0 : switch (e->type()) {
55 0 : case ElementType::DEAD_SLAPPED: {
56 0 : DeadSlapped* ds = item_cast<DeadSlapped*>(e);
57 : LD_INDEPENDENT;
58 0 : TLayout::layoutDeadSlapped(ds, ds->mutldata());
59 0 : } break;
60 0 : case ElementType::SYMBOL: {
61 0 : Symbol* s = item_cast<Symbol*>(e);
62 : // LD_X not clear yet
63 0 : TLayout::layoutSymbol(s, s->mutldata(), ctx);
64 0 : } break;
65 0 : case ElementType::IMAGE: {
66 0 : Image* im = item_cast<Image*>(e);
67 : LD_INDEPENDENT;
68 0 : TLayout::layoutImage(im, im->mutldata());
69 0 : } break;
70 0 : default:
71 0 : UNREACHABLE;
72 : }
73 : }
74 :
75 66608 : if (item->deadSlapped()) {
76 0 : ldata->setIsSkipDraw(true);
77 0 : return;
78 : }
79 66608 : ldata->setIsSkipDraw(false);
80 :
81 66608 : double spatium = item->spatium();
82 :
83 66608 : ldata->setPosX(0.0);
84 66608 : const StaffType* stt = item->staffType();
85 66608 : if (stt && stt->isTabStaff()) {
86 : // if rests are shown and note values are shown as duration symbols
87 1451 : if (stt->showRests() && stt->genDurations()) {
88 0 : DurationType type = item->durationType().type();
89 0 : int dots = item->durationType().dots();
90 : // if rest is whole measure, convert into actual type and dot values
91 0 : if (type == DurationType::V_MEASURE && item->measure()) {
92 0 : Fraction ticks = item->measure()->ticks();
93 0 : TDuration dur = TDuration(ticks).type();
94 0 : type = dur.type();
95 0 : dots = dur.dots();
96 : }
97 : // symbol needed; if not exist, create, if exists, update duration
98 0 : if (!item->tabDur()) {
99 0 : const_cast<Rest*>(item)->setTabDur(new TabDurationSymbol(const_cast<Rest*>(item), stt, type, dots));
100 : } else {
101 0 : item->tabDur()->setDuration(type, dots, stt);
102 : }
103 : // needed? _tabDur->setTrack(track());
104 0 : TLayout::layoutTabDurationSymbol(item->tabDur(), item->tabDur()->mutldata());
105 0 : ldata->setBbox(item->tabDur()->ldata()->bbox());
106 0 : ldata->setPos(0.0, 0.0); // no rest is drawn: reset any position might be set for it
107 0 : return;
108 : }
109 : // if no rests or no duration symbols, delete any dur. symbol and chain into standard staff mngmt
110 : // this is to ensure horiz space is reserved for rest, even if they are not displayed
111 : // Rest::draw() will skip their drawing, if not needed
112 1451 : if (item->tabDur()) {
113 0 : delete item->tabDur();
114 0 : const_cast<Rest*>(item)->setTabDur(nullptr);
115 : }
116 : }
117 :
118 66608 : const_cast<Rest*>(item)->setDotLine(Rest::getDotline(item->durationType().type()));
119 :
120 66608 : double yOff = item->offset().y();
121 66608 : const Staff* stf = item->staff();
122 66608 : const StaffType* st = stf ? stf->staffTypeForElement(item) : 0;
123 66608 : double lineDist = st ? st->lineDistance().val() : 1.0;
124 66608 : int userLine = RealIsNull(yOff) ? 0 : lrint(yOff / (lineDist * spatium));
125 66608 : int lines = st ? st->lines() : 5;
126 :
127 66608 : int naturalLine = computeNaturalLine(lines); // Measured in 1sp steps
128 66608 : int voiceOffset = computeVoiceOffset(item, ldata); // Measured in 1sp steps
129 66608 : int wholeRestOffset = computeWholeOrBreveRestOffset(item, voiceOffset, lines);
130 66608 : int finalLine = naturalLine + voiceOffset + wholeRestOffset;
131 :
132 66608 : ldata->sym = item->getSymbol(item->durationType().type(), finalLine + userLine, lines);
133 :
134 66608 : ldata->setPosY(finalLine * lineDist * spatium);
135 66608 : if (!item->shouldNotBeDrawn()) {
136 64946 : fillShape(item, ldata, ctx.conf());
137 : }
138 :
139 66608 : const_cast<Rest*>(item)->checkDots();
140 66608 : double visibleX = item->symWidthNoLedgerLines(ldata) + ctx.conf().styleAbsolute(Sid::dotNoteDistance) * item->mag();
141 66608 : double visibleDX = ctx.conf().styleAbsolute(Sid::dotDotDistance) * item->mag();
142 66608 : double invisibleX = item->symWidthNoLedgerLines(ldata);
143 66608 : double y = item->dotLine() * item->spatium() * .5;
144 66778 : for (NoteDot* dot : item->dotList()) {
145 170 : NoteDot::LayoutData* dotldata = dot->mutldata();
146 170 : TLayout::layoutNoteDot(dot, dotldata);
147 170 : if (dot->visible()) {
148 147 : dotldata->setPos(visibleX, y);
149 147 : visibleX += visibleDX;
150 : } else {
151 23 : invisibleX += 0.1 * item->spatium();
152 23 : dotldata->setPos(invisibleX, y);
153 23 : invisibleX += item->symWidth(SymId::augmentationDot) * dot->mag();
154 : }
155 : }
156 : }
157 :
158 304627 : void RestLayout::fillShape(const Rest* item, Rest::LayoutData* ldata, const LayoutConfiguration& conf)
159 : {
160 304627 : switch (item->type()) {
161 304090 : case ElementType::REST:
162 304090 : fillShape(toRest(item), static_cast<Rest::LayoutData*>(ldata));
163 304090 : break;
164 537 : case ElementType::MMREST:
165 537 : fillShape(static_cast<const MMRest*>(item), static_cast<MMRest::LayoutData*>(ldata), conf);
166 537 : break;
167 0 : default:
168 0 : UNREACHABLE;
169 : return;
170 : }
171 304627 : }
172 :
173 87846 : void RestLayout::resolveVerticalRestConflicts(LayoutContext& ctx, Segment* segment, staff_idx_t staffIdx)
174 : {
175 87846 : std::vector<Rest*> rests;
176 87846 : std::vector<Chord*> chords;
177 :
178 87846 : collectChordsAndRest(segment, staffIdx, chords, rests);
179 :
180 87846 : if (rests.empty()) {
181 77679 : return;
182 : }
183 :
184 10167 : collectChordsOverlappingRests(segment, staffIdx, chords);
185 :
186 21262 : for (Rest* rest : rests) {
187 11095 : rest->verticalClearance().reset();
188 : }
189 :
190 10167 : const Staff* staff = ctx.dom().staff(staffIdx);
191 10167 : if (!chords.empty()) {
192 640 : resolveRestVSChord(rests, chords, staff, segment);
193 : }
194 :
195 10167 : if (rests.size() < 2) {
196 9280 : return;
197 : }
198 :
199 887 : resolveRestVSRest(rests, staff, segment, ctx);
200 174805 : }
201 :
202 649 : void RestLayout::resolveRestVSChord(std::vector<Rest*>& rests, std::vector<Chord*>& chords, const Staff* staff, Segment* segment)
203 : {
204 649 : Fraction tick = segment->tick();
205 649 : int lines = staff->lines(tick);
206 649 : double spatium = staff->spatium(tick);
207 649 : double lineDistance = staff->lineDistance(tick) * spatium;
208 :
209 1320 : for (Rest* rest : rests) {
210 671 : if (!rest->visible() || !rest->autoplace()) {
211 132 : continue;
212 : }
213 539 : bool isWholeOrHalf = rest->isWholeRest() || rest->durationType() == DurationType::V_HALF;
214 539 : double minRestToChordClearance = isWholeOrHalf ? 0.55 * spatium : 0.35 * spatium;
215 539 : RestVerticalClearance& restVerticalClearance = rest->verticalClearance();
216 1088 : for (Chord* chord : chords) {
217 549 : if (!chord->visible() || !chord->autoplace()) {
218 161 : continue;
219 : }
220 :
221 549 : bool restAbove = rest->voice() < chord->voice() || (chord->slash() && !(rest->voice() % 2));
222 549 : int upSign = restAbove ? -1 : 1;
223 549 : double restYOffset = rest->offset().y();
224 549 : bool ignoreYOffset = (restAbove && restYOffset > 0) || (!restAbove && restYOffset < 0);
225 549 : PointF offset = ignoreYOffset ? PointF(0, restYOffset) : PointF(0, 0);
226 :
227 549 : Shape chordShape = chord->shape().translate(chord->pos());
228 549 : chordShape.removeInvisibles();
229 1098 : chordShape.removeTypes({ ElementType::ARPEGGIO });
230 549 : if (chordShape.empty()) {
231 0 : continue;
232 : }
233 :
234 549 : double clearance = 0.0;
235 549 : Shape restShape = rest->shape().translate(rest->pos() - offset);
236 549 : if (chord->segment() == rest->segment()) {
237 467 : clearance = restAbove
238 467 : ? restShape.verticalClearance(chordShape)
239 324 : : chordShape.verticalClearance(restShape);
240 : } else {
241 82 : Note* limitNote = restAbove ? chord->upNote() : chord->downNote();
242 82 : RectF noteShape = limitNote->symBbox(limitNote->noteHead()).translated(limitNote->pos());
243 82 : clearance = restAbove ? noteShape.top() - restShape.bottom() : restShape.top() - noteShape.bottom();
244 82 : minRestToChordClearance = 0.0;
245 : }
246 :
247 549 : double margin = clearance - minRestToChordClearance;
248 549 : int marginInSteps = floor(margin / lineDistance);
249 549 : if (restAbove) {
250 172 : restVerticalClearance.setBelow(marginInSteps);
251 : } else {
252 377 : restVerticalClearance.setAbove(marginInSteps);
253 : }
254 549 : if (margin > 0) {
255 161 : continue;
256 : }
257 :
258 388 : rest->verticalClearance().setLocked(true);
259 388 : bool outAboveStaff = restAbove && restShape.bottom() + margin < minRestToChordClearance;
260 388 : bool outBelowStaff = !restAbove && restShape.top() - margin > (lines - 1) * lineDistance - minRestToChordClearance;
261 388 : bool useHalfSpaceSteps = (outAboveStaff || outBelowStaff) && !isWholeOrHalf;
262 : double yMove;
263 388 : if (useHalfSpaceSteps) {
264 186 : int steps = ceil(std::abs(margin) / (lineDistance / 2));
265 186 : yMove = steps * lineDistance / 2 * upSign;
266 186 : rest->mutldata()->moveY(yMove);
267 : } else {
268 202 : int steps = ceil(std::abs(margin) / lineDistance);
269 202 : yMove = steps * lineDistance * upSign;
270 202 : rest->mutldata()->moveY(yMove);
271 : }
272 388 : for (Rest* mergedRest : rest->ldata()->mergedRests) {
273 0 : mergedRest->mutldata()->moveY(yMove);
274 : }
275 388 : if (isWholeOrHalf) {
276 84 : updateSymbol(rest, rest->mutldata()); // Because it may need to use the symbol with ledger line now
277 : }
278 710 : }
279 : }
280 649 : }
281 :
282 896 : void RestLayout::resolveRestVSRest(std::vector<Rest*>& rests, const Staff* staff,
283 : Segment* segment, LayoutContext& ctx,
284 : bool considerBeams)
285 : {
286 896 : if (rests.empty()) {
287 0 : return;
288 : }
289 :
290 896 : Fraction tick = segment->tick();
291 896 : double spatium = staff->spatium(tick);
292 896 : double lineDistance = staff->lineDistance(tick) * spatium;
293 896 : int lines = staff->lines(tick);
294 896 : const double minRestToRestClearance = 0.55 * spatium;
295 :
296 1824 : for (size_t i = 0; i < rests.size() - 1; ++i) {
297 928 : Rest* rest1 = rests[i];
298 928 : if (!rest1->visible() || !rest1->autoplace()) {
299 196 : continue;
300 : }
301 :
302 919 : RestVerticalClearance& rest1Clearance = rest1->verticalClearance();
303 919 : Shape shape1 = rest1->shape().translate(rest1->pos() - rest1->offset());
304 :
305 919 : Rest* rest2 = rests[i + 1];
306 919 : if (!rest2->visible() || !rest2->autoplace()) {
307 45 : continue;
308 : }
309 :
310 874 : if (muse::contains(rest1->ldata()->mergedRests, rest2) || muse::contains(rest2->ldata()->mergedRests, rest1)) {
311 0 : continue;
312 : }
313 :
314 874 : Shape shape2 = rest2->shape().translate(rest2->pos() - rest2->offset());
315 874 : RestVerticalClearance& rest2Clearance = rest2->verticalClearance();
316 :
317 : double clearance;
318 874 : bool firstAbove = rest1->voice() < rest2->voice();
319 874 : if (firstAbove) {
320 874 : clearance = shape1.verticalClearance(shape2);
321 : } else {
322 0 : clearance = shape2.verticalClearance(shape1);
323 : }
324 874 : double margin = clearance - minRestToRestClearance;
325 874 : int marginInSteps = floor(margin / lineDistance);
326 874 : if (firstAbove) {
327 874 : rest1Clearance.setBelow(marginInSteps);
328 874 : rest2Clearance.setAbove(marginInSteps);
329 : } else {
330 0 : rest1Clearance.setAbove(marginInSteps);
331 0 : rest2Clearance.setBelow(marginInSteps);
332 : }
333 :
334 874 : if (margin > 0) {
335 142 : continue;
336 : }
337 :
338 732 : int steps = ceil(std::abs(margin) / lineDistance);
339 : // Move the two rests away from each other
340 732 : int step1 = floor(double(steps) / 2);
341 732 : int step2 = ceil(double(steps) / 2);
342 732 : int maxStep1 = firstAbove ? rest1Clearance.above() : rest1Clearance.below();
343 732 : int maxStep2 = firstAbove ? rest2Clearance.below() : rest2Clearance.above();
344 732 : maxStep1 = std::max(maxStep1, 0);
345 732 : maxStep2 = std::max(maxStep2, 0);
346 732 : if (step1 > maxStep1) {
347 28 : step2 += step1 - maxStep1; // First rest is locked, try move the second more
348 : }
349 732 : if (step2 > maxStep2) {
350 5 : step1 += step2 - maxStep2; // Second rest is locked, try move the first more
351 : }
352 732 : step1 = std::min(step1, maxStep1);
353 732 : step2 = std::min(step2, maxStep2);
354 732 : rest1->mutldata()->moveY(step1 * lineDistance * (firstAbove ? -1 : 1));
355 732 : rest2->mutldata()->moveY(step2 * lineDistance * (firstAbove ? 1 : -1));
356 :
357 732 : Beam* beam1 = rest1->beam();
358 732 : Beam* beam2 = rest2->beam();
359 732 : if (beam1 && beam2 && considerBeams) {
360 0 : shape1 = rest1->shape().translate(rest1->pos() - rest1->offset());
361 0 : shape2 = rest2->shape().translate(rest2->pos() - rest2->offset());
362 :
363 0 : ChordRest* beam1Start = beam1->elements().front();
364 0 : ChordRest* beam1End = beam1->elements().back();
365 0 : double y1Start = BeamLayout::chordBeamAnchorY(beam1, beam1Start) - beam1Start->pagePos().y();
366 0 : double y1End = BeamLayout::chordBeamAnchorY(beam1, beam1End) - beam1End->pagePos().y();
367 0 : double beam1Ymid = 0.5 * (y1Start + y1End);
368 :
369 0 : ChordRest* beam2Start = beam2->elements().front();
370 0 : ChordRest* beam2End = beam2->elements().back();
371 0 : double y2Start = BeamLayout::chordBeamAnchorY(beam2, beam2Start) - beam2Start->pagePos().y();
372 0 : double y2End = BeamLayout::chordBeamAnchorY(beam2, beam2End) - beam2End->pagePos().y();
373 0 : double beam2Ymid = 0.5 * (y2Start + y2End);
374 :
375 0 : double centerY = 0.5 * (beam1Ymid + beam2Ymid);
376 :
377 0 : double upperBound = shape1.bottom();
378 0 : double lowerBound = shape2.top();
379 0 : int steps2 = 0;
380 0 : if (centerY < upperBound) {
381 0 : steps2 = floor((centerY - upperBound) / lineDistance);
382 0 : } else if (centerY > lowerBound) {
383 0 : steps2 = ceil((centerY - lowerBound) / lineDistance);
384 : }
385 0 : double moveY = steps2 * lineDistance;
386 0 : rest1->mutldata()->moveY(moveY);
387 0 : rest2->mutldata()->moveY(moveY);
388 0 : shape1.translate(PointF(0.0, moveY));
389 0 : shape2.translate(PointF(0.0, moveY));
390 :
391 0 : double halfLineDistance = 0.5 * lineDistance;
392 0 : if (shape1.bottom() < -halfLineDistance) {
393 0 : rest1->mutldata()->moveY(halfLineDistance);
394 0 : } else if (centerY >= (lines - 1) * lineDistance + halfLineDistance) {
395 0 : rest2->mutldata()->moveY(-halfLineDistance);
396 : }
397 :
398 0 : rest1->verticalClearance().setLocked(true);
399 0 : rest2->verticalClearance().setLocked(true);
400 0 : TLayout::layoutBeam(beam1, ctx);
401 0 : TLayout::layoutBeam(beam2, ctx);
402 : }
403 :
404 732 : bool rest1IsWholeOrHalf = rest1->isWholeRest() || rest1->durationType() == DurationType::V_HALF;
405 732 : bool rest2IsWholeOrHalf = rest2->isWholeRest() || rest2->durationType() == DurationType::V_HALF;
406 :
407 732 : if (rest1IsWholeOrHalf) {
408 35 : updateSymbol(rest1, rest1->mutldata());
409 : }
410 732 : if (rest2IsWholeOrHalf) {
411 37 : updateSymbol(rest2, rest2->mutldata());
412 : }
413 1061 : }
414 : }
415 :
416 7388 : void RestLayout::alignRests(const System* system, LayoutContext& ctx)
417 : {
418 7388 : RestGroups restGroups = computeRestGroups(system, ctx);
419 :
420 20516 : for (RestGroup& group : restGroups) {
421 13128 : if (group.size() <= 1) {
422 12786 : continue;
423 : }
424 :
425 342 : Rest* firstRest = group.front();
426 342 : const bool alignUpwards = firstRest->voice() == 0;
427 342 : const double lineDist = firstRest->staff()->lineDistance(firstRest->tick()) * firstRest->spatium();
428 :
429 342 : double yOuterRest = alignUpwards ? DBL_MAX : -DBL_MAX;
430 1286 : for (Rest* rest : group) {
431 944 : double yRest = rest->ldata()->pos().y();
432 944 : yOuterRest = alignUpwards ? std::min(yOuterRest, yRest) : std::max(yOuterRest, yRest);
433 : }
434 1286 : for (Rest* rest : group) {
435 944 : double yCur = rest->ldata()->pos().y();
436 944 : double yResult = yOuterRest;
437 944 : double restVertClearance = lineDist * (alignUpwards ? rest->verticalClearance().above() : rest->verticalClearance().below());
438 944 : double vertPadding = lineDist;
439 944 : restVertClearance = std::max(0.0, restVertClearance - vertPadding);
440 944 : double requiredMove = yOuterRest - yCur;
441 944 : if (std::abs(requiredMove) > restVertClearance) {
442 3 : yResult = alignUpwards ? yCur - restVertClearance : yCur + restVertClearance;
443 : }
444 944 : rest->mutldata()->setPosY(yResult);
445 944 : if (rest->isWholeRest() || rest->durationType() == DurationType::V_HALF) {
446 70 : updateSymbol(rest, rest->mutldata());
447 : }
448 : }
449 : }
450 7388 : }
451 :
452 7388 : RestGroups RestLayout::computeRestGroups(const System* system, LayoutContext& ctx)
453 : {
454 7388 : RestGroups restGroups;
455 :
456 18583 : for (staff_idx_t staffIdx = 0; staffIdx < ctx.dom().nstaves(); ++staffIdx) {
457 58219 : for (const MeasureBase* mb : system->measures()) {
458 47024 : if (!(mb->isMeasure() && toMeasure(mb)->hasVoices(staffIdx))) {
459 45704 : continue;
460 : }
461 :
462 1320 : const Measure* measure = toMeasure(mb);
463 1320 : track_idx_t sTrack = staff2track(staffIdx);
464 :
465 1320 : InterruptionPoints interruptionPoints = computeInterruptionPoints(measure, staffIdx);
466 :
467 6600 : for (voice_idx_t voice = 0; voice < VOICES; ++voice) {
468 5280 : restGroups.push_back(RestGroup());
469 :
470 5280 : std::list<Fraction>& interruptionPointsOnThisVoice = interruptionPoints[voice];
471 5280 : track_idx_t track = sTrack + voice;
472 :
473 31864 : for (const Segment* segment = measure->first(SegmentType::ChordRest); segment;
474 26584 : segment = segment->next(SegmentType::ChordRest)) {
475 26584 : if (interruptionPointsOnThisVoice.size() > 0 && segment->rtick() >= interruptionPointsOnThisVoice.front()) {
476 7848 : restGroups.push_back(RestGroup());
477 7848 : interruptionPointsOnThisVoice.pop_front();
478 : }
479 :
480 26584 : EngravingItem* item = segment->element(track);
481 26584 : if (!(item && item->isRest() && item->visible())) {
482 23981 : continue;
483 : }
484 2603 : Rest* rest = toRest(item);
485 2603 : if (rest->staffMove() == 0 && !rest->isGap() && rest->alignWithOtherRests()) {
486 2457 : restGroups.back().push_back(rest);
487 : }
488 : }
489 : }
490 1320 : }
491 : }
492 :
493 7388 : return restGroups;
494 0 : }
495 :
496 1320 : InterruptionPoints RestLayout::computeInterruptionPoints(const Measure* measure, staff_idx_t staffIdx)
497 : {
498 : // Start with sets to get automatic uniqueness and ordering
499 1320 : std::array<std::set<Fraction>, VOICES> interruptionPointSets;
500 :
501 1320 : track_idx_t sTrack = staffIdx * VOICES;
502 1320 : track_idx_t eTrack = sTrack + VOICES;
503 :
504 : // Compute all-voices interruptions
505 7966 : for (const Segment* segment = measure->first(SegmentType::ChordRest); segment; segment = segment->next(SegmentType::ChordRest)) {
506 32059 : for (track_idx_t track = sTrack; track < eTrack; ++track) {
507 25787 : EngravingItem* item = segment->element(track);
508 25787 : if (!item) {
509 14721 : continue;
510 : }
511 11066 : const bool gapRest = item->isRest() && toRest(item)->isGap();
512 : // TODO: The fact that "merged" rests exists in both voices and are just graphically overlapped is a messy way of
513 : // doing it and way too fragile, because it means that any logic that may move one rest can break the "merging".
514 : // A more solid way of merging rests would be to *delete* the second voice rests, i.e. turn them into gap rests [M.S.].
515 11066 : const bool hasMergedRest = item->isRest() && !toRest(item)->ldata()->mergedRests.empty();
516 11066 : const bool invisible = item->isChord() ? toChord(item)->allElementsInvisible() : !item->visible();
517 11066 : if (gapRest || hasMergedRest || invisible) {
518 1870 : for (voice_idx_t voice = 0; voice < VOICES; ++voice) {
519 1496 : interruptionPointSets[voice].insert(segment->rtick());
520 1496 : interruptionPointSets[voice].insert(segment->rtick() + toChordRest(item)->actualTicks());
521 : }
522 374 : break;
523 : }
524 : }
525 : }
526 :
527 10495 : auto interruptHere = [&](ChordRest* chordRest) {
528 10495 : Fraction crTicks = chordRest->ticks();
529 10495 : if (crTicks > Fraction(1, 8)) {
530 6163 : return true;
531 : }
532 :
533 4332 : BeamMode beamModeChordRest = Groups::baseBeamMode(chordRest);
534 4332 : switch (beamModeChordRest) {
535 386 : case BeamMode::BEGIN:
536 : // Beam certainly starts here
537 386 : return true;
538 148 : case BeamMode::BEGIN16:
539 : case BeamMode::BEGIN32:
540 : case BeamMode::MID:
541 : case BeamMode::END:
542 : // Beam certainly doesn not start here
543 148 : return false;
544 3798 : default:
545 3798 : break;
546 : }
547 :
548 : // If BeamMode is AUTO or NONE check measure beaming
549 3798 : const Groups& groups = chordRest->staff()->group(chordRest->tick());
550 3798 : BeamMode beamModeMeasure = groups.beamMode(chordRest->rtick().ticks(), chordRest->durationType().type());
551 3798 : return beamModeMeasure == BeamMode::BEGIN;
552 : };
553 :
554 : // Compute voice-spefic interruptions
555 6600 : for (voice_idx_t voice = 0; voice < VOICES; ++voice) {
556 5280 : track_idx_t track = sTrack + voice;
557 31864 : for (const Segment* segment = measure->first(SegmentType::ChordRest); segment; segment = segment->next(SegmentType::ChordRest)) {
558 26584 : if (ChordRest* thisCR = toChordRest(segment->element(track))) {
559 11143 : if (thisCR->isRest()) {
560 2808 : const Segment* prevSegment = segment->prevWithElementsOnTrack(track);
561 2808 : bool hasAdjacentRest = prevSegment && prevSegment->element(track)->isRest();
562 2808 : if (hasAdjacentRest) {
563 648 : continue;
564 : }
565 : }
566 10495 : if (interruptHere(thisCR)) {
567 6561 : interruptionPointSets[voice].insert(segment->rtick());
568 : }
569 : }
570 : }
571 : }
572 :
573 : // Convert sets to lists
574 1320 : InterruptionPoints interruptionPoints;
575 6600 : for (voice_idx_t voice = 0; voice < VOICES; ++voice) {
576 5280 : const std::set<Fraction>& set = interruptionPointSets[voice];
577 10560 : interruptionPoints[voice] = std::list(set.begin(), set.end());
578 : }
579 :
580 1320 : return interruptionPoints;
581 1320 : }
582 :
583 7388 : void RestLayout::checkFullMeasureRestCollisions(const System* system, LayoutContext& ctx)
584 : {
585 18583 : for (staff_idx_t staffIdx = 0; staffIdx < ctx.dom().nstaves(); ++staffIdx) {
586 58219 : for (MeasureBase* mb : system->measures()) {
587 47024 : if (!(mb->isMeasure() && toMeasure(mb)->hasVoices(staffIdx))) {
588 46863 : continue;
589 : }
590 :
591 1320 : Measure* measure = toMeasure(mb);
592 1320 : Rest* fullMeasureRest = nullptr;
593 :
594 1320 : Segment* firstCRSeg = measure->first(SegmentType::ChordRest);
595 1320 : IF_ASSERT_FAILED(firstCRSeg) {
596 0 : continue;
597 : }
598 1320 : track_idx_t startTrack = staff2track(staffIdx);
599 1320 : track_idx_t endTrack = startTrack + VOICES;
600 5949 : for (track_idx_t track = startTrack; track < endTrack; ++track) {
601 4792 : EngravingItem* item = firstCRSeg->element(track);
602 4792 : if (item && item->isRest() && toRest(item)->isFullMeasureRest()) {
603 163 : fullMeasureRest = toRest(item);
604 163 : break;
605 : }
606 : }
607 :
608 1320 : if (!fullMeasureRest) {
609 1157 : continue;
610 : }
611 :
612 163 : double xRest = fullMeasureRest->pagePos().x() - system->pagePos().x();
613 163 : Shape restShape = fullMeasureRest->shape().translate(PointF(xRest, fullMeasureRest->ldata()->pos().y()));
614 :
615 163 : Shape measureShape;
616 1195 : for (const Segment& segment : measure->segments()) {
617 1032 : if (!segment.isActive()) {
618 31 : continue;
619 : }
620 1001 : double xSegment = segment.pagePos().x() - system->pagePos().x();
621 1001 : measureShape.add(segment.staffShape(staffIdx).translated(PointF(xSegment, 0.0)));
622 : }
623 163 : measureShape.remove_if([] (const ShapeElement& shapeEl) {
624 1917 : const EngravingItem* shapeItem = shapeEl.item();
625 3826 : return shapeItem && ((shapeItem->isRest() && toRest(shapeItem)->isFullMeasureRest())
626 3826 : || shapeItem->isBarLine() || shapeItem->isAccidental() || shapeItem->isFermata());
627 : });
628 :
629 163 : if (measureShape.size() == 0) {
630 2 : continue;
631 : }
632 :
633 161 : const double spatium = fullMeasureRest->spatium();
634 161 : const double lineDist = fullMeasureRest->staff()->lineDistance(fullMeasureRest->tick()) * spatium;
635 161 : const double minHorizontalDistance = 4 * spatium;
636 161 : const double minVertClearance = 0.75 * spatium;
637 :
638 161 : bool alignAbove = fullMeasureRest->voice() == 0;
639 161 : double verticalClearance = alignAbove ? restShape.verticalClearance(measureShape, minHorizontalDistance)
640 1 : : measureShape.verticalClearance(restShape, minHorizontalDistance);
641 :
642 161 : if (verticalClearance < minVertClearance) {
643 65 : double diff = minVertClearance - verticalClearance;
644 65 : int stepMoves = std::ceil(diff / lineDist);
645 65 : double yMove = stepMoves * lineDist;
646 65 : fullMeasureRest->mutldata()->moveY(alignAbove ? -yMove : yMove);
647 65 : updateSymbol(fullMeasureRest, fullMeasureRest->mutldata());
648 : }
649 165 : }
650 : }
651 7388 : }
652 :
653 304090 : void RestLayout::fillShape(const Rest* item, Rest::LayoutData* ldata)
654 : {
655 304090 : Shape shape(Shape::Type::Composite);
656 :
657 304090 : if ((!item->isGap() || item->debugDrawGap()) && !item->shouldNotBeDrawn()) {
658 298079 : shape.add(ChordLayout::chordRestShape(item));
659 298079 : shape.add(item->symBbox(ldata->sym), item);
660 298840 : for (const NoteDot* dot : item->dotList()) {
661 761 : if (dot->addToSkyline()) {
662 750 : shape.add(item->symBbox(SymId::augmentationDot).translated(dot->pos()), dot);
663 : }
664 : }
665 : }
666 :
667 304090 : for (const EngravingItem* e : item->el()) {
668 0 : if (e->addToSkyline()) {
669 0 : shape.add(e->shape().translate(e->pos()));
670 : }
671 : }
672 :
673 304090 : ldata->setShape(shape);
674 304090 : }
675 :
676 537 : void RestLayout::fillShape(const MMRest* item, MMRest::LayoutData* ldata, const LayoutConfiguration& conf)
677 : {
678 537 : Shape shape(Shape::Type::Composite);
679 :
680 537 : double vStrokeHeight = conf.styleAbsolute(Sid::mmRestHBarVStrokeHeight);
681 537 : shape.add(RectF(0.0, -(vStrokeHeight * .5), ldata->restWidth, vStrokeHeight), item);
682 537 : if (item->showNumber()) {
683 525 : shape.add(item->numberRect().translated(item->numberPos()), item);
684 : }
685 :
686 537 : ldata->setShape(shape);
687 537 : }
688 :
689 66608 : int RestLayout::computeNaturalLine(int lines)
690 : {
691 66608 : int line = (lines % 2) ? floor(double(lines) / 2) : ceil(double(lines) / 2);
692 66608 : return line;
693 : }
694 :
695 66608 : int RestLayout::computeVoiceOffset(const Rest* item, Rest::LayoutData* ldata)
696 : {
697 66608 : ldata->mergedRests.clear();
698 66608 : Segment* s = item->segment();
699 66608 : Measure* measure = item->measure();
700 66608 : bool offsetVoices = s && measure && (item->voice() > 0 || measure->hasVoices(item->staffIdx(), item->tick(), item->actualTicks()));
701 66608 : if (offsetVoices && item->voice() == 0) {
702 : // do not offset voice 1 rest if there exists a matching invisible rest in voice 2;
703 1278 : EngravingItem* e = s->element(item->track() + 1);
704 1278 : if (e && e->isRest() && !e->visible() && !toRest(e)->isGap()) {
705 0 : Rest* r = toRest(e);
706 0 : if (r->globalTicks() == item->globalTicks()) {
707 0 : offsetVoices = false;
708 : }
709 : }
710 : }
711 :
712 66608 : if (offsetVoices && item->voice() < 2) {
713 : // in slash notation voices 1 and 2 are not offset outside the staff
714 : // if the staff contains slash notation then only offset rests in voices 3 and 4
715 2611 : track_idx_t baseTrack = item->staffIdx() * VOICES;
716 13034 : for (voice_idx_t v = 0; v < VOICES; ++v) {
717 10433 : EngravingItem* e = s->element(baseTrack + v);
718 10433 : if (e && e->isChord() && toChord(e)->slash()) {
719 10 : offsetVoices = false;
720 10 : break;
721 : }
722 : }
723 : }
724 :
725 66608 : if (offsetVoices && item->staff()->shouldMergeMatchingRests()) {
726 : // automatically merge matching rests if nothing in any other voice
727 : // this is not always the right thing to do do, but is useful in choral music
728 : // and can be enabled via a staff property
729 0 : bool matchFound = false;
730 0 : track_idx_t baseTrack = item->staffIdx() * VOICES;
731 0 : for (voice_idx_t v = 0; v < VOICES; ++v) {
732 0 : if (v == item->voice()) {
733 0 : continue;
734 : }
735 0 : EngravingItem* e = s->element(baseTrack + v);
736 : // try to find match in any other voice
737 0 : if (e) {
738 0 : if (e->isRest()) {
739 0 : Rest* r = toRest(e);
740 0 : if (r->globalTicks() == item->globalTicks() && r->durationType() == item->durationType()) {
741 0 : matchFound = true;
742 0 : ldata->mergedRests.push_back(r);
743 0 : continue;
744 : }
745 : }
746 : // no match found; no sense looking for anything else
747 0 : matchFound = false;
748 0 : break;
749 : }
750 : }
751 0 : if (matchFound) {
752 0 : offsetVoices = false;
753 : }
754 : }
755 :
756 66608 : if (!offsetVoices) {
757 63902 : return 0;
758 : }
759 :
760 2706 : bool up = item->voice() == 0 || item->voice() == 2;
761 2706 : int upSign = up ? -1 : 1;
762 2706 : int voiceLineOffset = item->style().styleB(Sid::multiVoiceRestTwoSpaceOffset) ? 2 : 1;
763 :
764 2706 : return voiceLineOffset * upSign;
765 : }
766 :
767 66608 : int RestLayout::computeWholeOrBreveRestOffset(const Rest* item, int voiceOffset, int lines)
768 : {
769 66608 : int lineMove = 0;
770 66608 : if (item->isWholeRest()) {
771 55812 : bool moveToLineAbove = (lines > 5)
772 55812 : || ((lines > 1 || voiceOffset == -1 || voiceOffset == 2) && !(voiceOffset == -2 || voiceOffset == 1));
773 55812 : if (moveToLineAbove) {
774 55710 : lineMove = -1;
775 : }
776 10796 : } else if (item->isBreveRest() && lines == 1) {
777 0 : lineMove = 1;
778 : }
779 :
780 66608 : return lineMove;
781 : }
782 :
783 291 : void RestLayout::updateSymbol(const Rest* item, Rest::LayoutData* ldata)
784 : {
785 291 : Fraction t = item->tick();
786 291 : Staff* st = item->staff();
787 291 : double lineDistance = st->lineDistance(t) * item->spatium();
788 291 : int lines = st->lines(t);
789 :
790 291 : double y = item->pos().y();
791 291 : int line = floor(y / lineDistance);
792 :
793 291 : ldata->sym = item->getSymbol(item->durationType().type(), line, lines);
794 291 : }
795 : } // namespace mu::engraving::rendering::score
|