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 : #include "beamlayout.h"
23 :
24 : #include <unordered_map>
25 : #include <set>
26 :
27 : #include "containers.h"
28 :
29 : #include "dom/beam.h"
30 : #include "dom/drumset.h"
31 : #include "dom/part.h"
32 : #include "dom/tremolotwochord.h"
33 : #include "dom/tremolosinglechord.h"
34 : #include "dom/chord.h"
35 : #include "dom/factory.h"
36 : #include "dom/measure.h"
37 : #include "dom/rest.h"
38 : #include "dom/score.h"
39 : #include "dom/segment.h"
40 : #include "dom/staff.h"
41 : #include "dom/system.h"
42 : #include "dom/timesig.h"
43 : #include "dom/tuplet.h"
44 : #include "dom/utils.h"
45 : #include "dom/note.h"
46 :
47 : #include "layoutcontext.h"
48 : #include "tlayout.h"
49 : #include "chordlayout.h"
50 : #include "beamtremololayout.h"
51 : #include "tremololayout.h"
52 : #include "restlayout.h"
53 :
54 : #include "log.h"
55 :
56 : using namespace mu::engraving;
57 : using namespace mu::engraving::rendering::score;
58 :
59 8778 : void BeamLayout::layout(Beam* item, const LayoutContext& ctx)
60 : {
61 8788 : TRACEFUNC;
62 :
63 8778 : Beam::LayoutData* ldata = item->mutldata();
64 : // all of the beam layout code depends on _elements being in order by tick
65 : // this may not be the case if two cr's were recently swapped.
66 8778 : std::sort(item->elements().begin(), item->elements().end(),
67 42550 : [](const ChordRest* a, const ChordRest* b) -> bool {
68 42550 : return a->tick() < b->tick();
69 : });
70 8778 : System* system = item->system();
71 :
72 8778 : std::vector<ChordRest*> crl;
73 :
74 8778 : size_t n = 0;
75 38651 : for (ChordRest* cr : item->elements()) {
76 29873 : auto newSystem = cr->measure()->system();
77 29873 : if (newSystem && newSystem != system) {
78 : SpannerSegmentType st;
79 0 : if (n == 0) {
80 0 : st = SpannerSegmentType::BEGIN;
81 : } else {
82 0 : st = SpannerSegmentType::MIDDLE;
83 : }
84 0 : ++n;
85 0 : if (item->beamFragments().size() < n) {
86 0 : item->beamFragments().push_back(new BeamFragment);
87 : }
88 0 : layout2(item, ctx, crl, st, static_cast<int>(n) - 1);
89 0 : crl.clear();
90 0 : system = cr->measure()->system();
91 : }
92 29873 : crl.push_back(cr);
93 : }
94 :
95 8778 : Shape beamShape(Shape::Type::Composite);
96 :
97 8778 : if (!crl.empty()) {
98 : SpannerSegmentType st;
99 8778 : if (n == 0) {
100 8778 : st = SpannerSegmentType::SINGLE;
101 : } else {
102 0 : st = SpannerSegmentType::END;
103 : }
104 8778 : if (item->beamFragments().size() < (n + 1)) {
105 2836 : item->beamFragments().push_back(new BeamFragment);
106 : }
107 8778 : layout2(item, ctx, crl, st, static_cast<int>(n));
108 :
109 21119 : for (const BeamSegment* bs : item->beamSegments()) {
110 12341 : beamShape.add(bs->shape());
111 : }
112 : }
113 :
114 8778 : ldata->setShape(beamShape);
115 :
116 : // The beam may have changed shape. one-note trems within this beam need to be layed out here
117 38651 : for (ChordRest* cr : item->elements()) {
118 29873 : ChordLayout::computeUp(cr, ctx);
119 29873 : if (cr->isChord() && toChord(cr)->tremoloSingleChord()) {
120 142 : TremoloLayout::layout(toChord(cr)->tremoloSingleChord(), ctx);
121 : }
122 : }
123 8778 : }
124 :
125 0 : void BeamLayout::layoutIfNeed(Beam* item, const LayoutContext& ctx)
126 : {
127 0 : if (!item->ldata()->isValid()) {
128 0 : BeamLayout::layout(item, ctx);
129 : }
130 0 : }
131 :
132 : //---------------------------------------------------------
133 : // - remove beam segments
134 : // - detach from system
135 : // - calculate stem direction and set chord
136 : //---------------------------------------------------------
137 4432 : void BeamLayout::layout1(Beam* item, LayoutContext& ctx)
138 : {
139 4432 : Beam::LayoutData* ldata = item->mutldata();
140 :
141 4432 : const StaffType* staffType = item->staffType();
142 4432 : item->setTab((staffType && staffType->isTabStaff()) ? staffType : nullptr);
143 4432 : item->setIsBesideTabStaff(item->tab() && !item->tab()->stemless() && !item->tab()->stemThrough());
144 :
145 : // TAB's with stem beside staves have special layout
146 4432 : if (item->isBesideTabStaff()) {
147 0 : item->setUp(!item->tab()->stemsDown());
148 0 : item->setSlope(0.0);
149 0 : item->setCross(false);
150 0 : item->setMinMove(0);
151 0 : item->setMaxMove(0);
152 0 : for (ChordRest* cr : item->elements()) {
153 0 : if (cr->isChord()) {
154 0 : item->setUp(cr->up());
155 0 : break;
156 : }
157 : }
158 86 : return;
159 : }
160 :
161 4432 : if (item->staff()->isDrumStaff(item->tick())) {
162 86 : if (item->direction() != DirectionV::AUTO) {
163 28 : item->setUp(item->direction() == DirectionV::UP);
164 58 : } else if (item->isGrace()) {
165 2 : item->setUp(true);
166 : } else {
167 : // search through the beam for the first chord with explicit stem direction and use that.
168 : // if there is no explicit stem direction, default to the direction of the first stem.
169 56 : bool firstUp = false;
170 56 : bool firstChord = true;
171 65 : for (ChordRest* cr : item->elements()) {
172 61 : if (!cr->isChord()) {
173 0 : item->setUp(firstUp);
174 0 : continue;
175 : }
176 61 : Chord* chord = toChord(cr);
177 61 : DirectionV crDirection = toChord(cr)->stemDirection();
178 61 : if (crDirection != DirectionV::AUTO) {
179 52 : item->setUp(crDirection == DirectionV::UP);
180 52 : break;
181 : }
182 9 : if (firstChord) {
183 4 : const Staff* staff = item->staff();
184 4 : const Part* part = staff ? staff->part() : nullptr;
185 4 : const Drumset* ds = part ? part->instrument(item->tick())->drumset() : nullptr;
186 4 : firstUp = ds ? ds->stemDirection(chord->upNote()->pitch()) == DirectionV::UP : chord->up();
187 4 : firstChord = false;
188 : }
189 9 : item->setUp(firstUp);
190 : }
191 : }
192 325 : for (ChordRest* cr : item->elements()) {
193 239 : ChordLayout::computeUp(cr, ctx);
194 239 : if (cr->isChord()) {
195 239 : ChordLayout::layoutStem(toChord(cr), ctx);
196 : }
197 : }
198 86 : return;
199 : }
200 :
201 4346 : item->setMinMove(std::numeric_limits<int>::max());
202 4346 : item->setMaxMove(std::numeric_limits<int>::min());
203 4346 : double mag = 0.0;
204 :
205 4346 : item->notePositions().clear();
206 4346 : staff_idx_t staffIdx = muse::nidx;
207 19200 : for (ChordRest* cr : item->elements()) {
208 14854 : double m = cr->isSmall() ? ctx.conf().styleD(Sid::smallNoteMag) : 1.0;
209 14854 : mag = std::max(mag, m);
210 14854 : if (cr->isChord()) {
211 14817 : Chord* chord = toChord(cr);
212 14817 : staffIdx = chord->vStaffIdx();
213 14817 : int i = chord->staffMove();
214 14817 : item->setMinMove(std::min(item->minCRMove(), i));
215 14817 : item->setMaxMove(std::max(item->maxCRMove(), i));
216 :
217 31203 : for (int distance : chord->noteDistances()) {
218 16386 : item->notePositions().push_back(BeamBase::NotePosition(distance, cr->vStaffIdx()));
219 14817 : }
220 : }
221 : }
222 :
223 4346 : std::sort(item->notePositions().begin(), item->notePositions().end());
224 4346 : ldata->setMag(mag);
225 :
226 4346 : item->setCross(item->minCRMove() != item->maxCRMove());
227 :
228 : //
229 : // determine beam stem direction
230 : //
231 4346 : if (item->elements().empty()) {
232 0 : return;
233 : }
234 4346 : ChordRest* firstNote = item->elements().front();
235 4346 : Measure* measure = firstNote->measure();
236 4346 : bool hasMultipleVoices = measure->hasVoices(firstNote->staffIdx(), item->tick(), item->ticks());
237 4346 : if (computeUpForMovedCross(item)) {
238 4346 : } else if (item->direction() != DirectionV::AUTO) {
239 986 : item->setUp(item->direction() == DirectionV::UP);
240 3360 : } else if (item->maxCRMove() > 0) {
241 17 : item->setUp(false);
242 3343 : } else if (item->minCRMove() < 0) {
243 4 : item->setUp(true);
244 3339 : } else if (item->isGrace()) {
245 86 : if (hasMultipleVoices) {
246 6 : item->setUp(firstNote->track() % 2 == 0);
247 : } else {
248 80 : item->setUp(true);
249 : }
250 3253 : } else if (item->notePositions().size()) {
251 3253 : if (hasMultipleVoices) {
252 589 : item->setUp(firstNote->track() % 2 == 0);
253 : } else {
254 2664 : if (const Chord* chord = item->findChordWithCustomStemDirection()) {
255 16 : item->setUp(chord->stemDirection() == DirectionV::UP);
256 : } else {
257 2648 : std::set<BeamBase::NotePosition> notePosSet(item->notePositions().begin(), item->notePositions().end());
258 2648 : std::vector<int> noteLines;
259 2648 : noteLines.reserve(notePosSet.size());
260 9456 : for (BeamBase::NotePosition pos : notePosSet) {
261 6808 : noteLines.push_back(pos.line);
262 : }
263 2648 : item->setUp(ChordLayout::computeAutoStemDirection(noteLines) > 0);
264 2648 : }
265 : }
266 : } else {
267 0 : item->setUp(true);
268 : }
269 :
270 4346 : int middleStaffLine = firstNote->staffType()->middleLine();
271 20732 : for (size_t i = 0; i < item->notePositions().size(); i++) {
272 16386 : item->notePositions()[i].line += middleStaffLine;
273 : }
274 :
275 4346 : bool isEntirelyMoved = false;
276 4346 : if (item->minCRMove() == item->maxCRMove() && item->minCRMove() != 0) {
277 45 : isEntirelyMoved = true;
278 45 : item->setStaffIdx(staffIdx);
279 45 : if (item->direction() == DirectionV::AUTO) {
280 13 : item->setUp(item->maxCRMove() > 0);
281 : }
282 4301 : } else if (item->elements().size()) {
283 4301 : item->setStaffIdx(item->elements().at(0)->staffIdx());
284 : }
285 4346 : item->setFullCross(isEntirelyMoved);
286 :
287 4346 : item->setSlope(0.0);
288 :
289 19200 : for (ChordRest* cr : item->elements()) {
290 14854 : bool prevUp = cr->up();
291 14854 : ChordLayout::computeUp(cr, ctx);
292 14854 : if (cr->isChord() && cr->up() != prevUp) {
293 1432 : ChordLayout::layoutChords1(ctx, cr->segment(), cr->staffIdx());
294 1432 : cr->segment()->createShape(cr->staffIdx());
295 1432 : if (toChord(cr)->isGrace()) {
296 0 : ChordLayout::layoutStem(toChord(cr), ctx);
297 : }
298 : }
299 : }
300 : }
301 :
302 8778 : void BeamLayout::layout2(Beam* item, const LayoutContext& ctx, const std::vector<ChordRest*>& chordRests, SpannerSegmentType, int frag)
303 : {
304 8788 : TRACEFUNC;
305 :
306 8778 : BeamTremoloLayout::setupLData(item, item->mutldata(), ctx);
307 8778 : Chord* startChord = nullptr;
308 8778 : Chord* endChord = nullptr;
309 8778 : if (chordRests.empty()) {
310 0 : return;
311 : }
312 38651 : for (ChordRest* chordRest : chordRests) {
313 29873 : if (chordRest->isChord()) {
314 29798 : if (!startChord) {
315 8778 : startChord = toChord(chordRest);
316 8778 : endChord = startChord;
317 : } else {
318 21020 : endChord = toChord(chordRest);
319 : }
320 29798 : ChordLayout::layoutStem(toChord(chordRest), ctx);
321 : }
322 : }
323 8778 : if (!startChord) {
324 : // we were passed a vector of only rests. we don't support beams across only rests
325 : // this beam will be deleted in LayoutBeams
326 0 : return;
327 : }
328 :
329 8778 : item->setBeamSpacing(ctx.conf().styleB(Sid::useWideBeams) ? 4 : 3);
330 8778 : item->setBeamDist((item->beamSpacing() / 4.0) * item->spatium() * item->mag());
331 8778 : item->setBeamWidth(item->absoluteFromSpatium(ctx.conf().styleS(Sid::beamWidth)) * item->mag());
332 :
333 8778 : item->setStartAnchor(BeamTremoloLayout::chordBeamAnchor(item->ldata(), startChord, ChordBeamAnchorType::Start));
334 8778 : item->setEndAnchor(BeamTremoloLayout::chordBeamAnchor(item->ldata(), endChord, ChordBeamAnchorType::End));
335 :
336 8778 : if (item->isGrace()) {
337 276 : item->setBeamDist(item->beamDist() * ctx.conf().styleD(Sid::graceNoteMag));
338 276 : item->setBeamWidth(item->beamWidth() * ctx.conf().styleD(Sid::graceNoteMag));
339 : }
340 :
341 8778 : int fragmentIndex = item->directionIdx();
342 8778 : if (item->userModified()) {
343 23 : BeamTremoloLayout::setupLData(item, item->mutldata(), ctx);
344 23 : double startY = item->beamFragments()[frag]->py1[fragmentIndex];
345 23 : double endY = item->beamFragments()[frag]->py2[fragmentIndex];
346 23 : if (ctx.conf().styleB(Sid::snapCustomBeamsToGrid)) {
347 23 : const double quarterSpace = item->spatium() / 4;
348 23 : startY = round(startY / quarterSpace) * quarterSpace;
349 23 : endY = round(endY / quarterSpace) * quarterSpace;
350 : }
351 23 : startY += item->pagePos().y();
352 23 : endY += item->pagePos().y();
353 23 : item->startAnchor().setY(startY);
354 23 : item->endAnchor().setY(endY);
355 23 : item->mutldata()->setAnchors(item->startAnchor(), item->endAnchor());
356 23 : item->computeAndSetSlope();
357 23 : createBeamSegments(item, ctx, chordRests);
358 23 : BeamLayout::setTremAnchors(item, ctx);
359 23 : return;
360 : }
361 :
362 : // anchor represents the middle of the beam, not the tip of the stem
363 : // location depends on _isBesideTabStaff
364 :
365 8755 : if (!item->isBesideTabStaff()) {
366 8755 : BeamTremoloLayout::setupLData(item, item->mutldata(), ctx);
367 8755 : BeamTremoloLayout::calculateAnchors(item, item->mutldata(), ctx, chordRests, item->notePositions());
368 8755 : item->setStartAnchor(item->ldata()->startAnchor);
369 8755 : item->setEndAnchor(item->ldata()->endAnchor);
370 8755 : item->computeAndSetSlope();
371 8755 : item->setBeamDist(item->ldata()->beamDist);
372 : } else {
373 0 : item->setSlope(0.0);
374 0 : Chord* startChord2 = nullptr;
375 0 : for (ChordRest* cr : chordRests) {
376 0 : if (cr->isChord()) {
377 0 : startChord2 = toChord(cr);
378 0 : break;
379 : }
380 : }
381 0 : BeamTremoloLayout::setupLData(item, item->mutldata(), ctx);
382 0 : double x1 = BeamTremoloLayout::chordBeamAnchorX(item->ldata(), chordRests.front(), ChordBeamAnchorType::Start);
383 0 : double x2 = BeamTremoloLayout::chordBeamAnchorX(item->ldata(), chordRests.back(), ChordBeamAnchorType::End);
384 0 : double y = BeamTremoloLayout::chordBeamAnchorY(item->ldata(), startChord2);
385 0 : item->startAnchor() = PointF(x1, y);
386 0 : item->endAnchor() = PointF(x2, y);
387 0 : item->mutldata()->setAnchors(item->startAnchor(), item->endAnchor());
388 : }
389 :
390 8755 : item->beamFragments()[frag]->py1[fragmentIndex] = item->startAnchor().y() - item->pagePos().y();
391 8755 : item->beamFragments()[frag]->py2[fragmentIndex] = item->endAnchor().y() - item->pagePos().y();
392 :
393 8755 : createBeamSegments(item, ctx, chordRests);
394 8755 : setTremAnchors(item, ctx);
395 8778 : }
396 :
397 269395 : bool BeamLayout::isStartOfNonCrossBeam(ChordRest* cr)
398 : {
399 269395 : Beam* b = cr->beam();
400 269395 : if (b && b->elements().front() == cr) {
401 12821 : if (b->cross()) {
402 30 : return false;
403 : }
404 :
405 12791 : return true;
406 : }
407 :
408 256574 : return false;
409 : }
410 :
411 92140 : bool BeamLayout::isStartOfCrossBeam(ChordRest* cr)
412 : {
413 92140 : Beam* b = cr->beam();
414 92140 : if (b && b->elements().front() == cr) {
415 4225 : if (b->cross()) {
416 10 : return true;
417 : }
418 : }
419 :
420 92130 : return false;
421 : }
422 :
423 : //---------------------------------------------------------
424 : // restoreBeams
425 : //---------------------------------------------------------
426 :
427 142 : void BeamLayout::restoreBeams(Measure* m, LayoutContext& ctx)
428 : {
429 381 : for (Segment* s = m->first(SegmentType::ChordRest); s; s = s->next(SegmentType::ChordRest)) {
430 1455 : for (EngravingItem* e : s->elist()) {
431 1216 : if (e && e->isChordRest()) {
432 316 : ChordRest* cr = toChordRest(e);
433 316 : Beam* b = cr->beam();
434 316 : if (b && !b->elements().empty() && b->elements().front() == cr) {
435 0 : TLayout::layoutBeam(b, ctx);
436 0 : b->addSkyline(m->system()->staff(b->staffIdx())->skyline());
437 : }
438 : }
439 : }
440 : }
441 142 : }
442 :
443 178 : bool BeamLayout::measureMayHaveBeamsJoinedIntoNext(const Measure* measure)
444 : {
445 178 : const MeasureBase* next = measure->next();
446 178 : if (!(next && next->isMeasure())) {
447 9 : return false;
448 : }
449 :
450 169 : const Measure* nextMeasure = toMeasure(next);
451 169 : const Segment* firstCrSeg = nextMeasure->findFirstR(SegmentType::ChordRest, Fraction(0, 1));
452 169 : if (!firstCrSeg) {
453 0 : return false;
454 : }
455 :
456 1165 : for (const EngravingItem* item : firstCrSeg->elist()) {
457 996 : if (!item) {
458 742 : continue;
459 : }
460 254 : BeamMode beamMode = toChordRest(item)->beamMode();
461 254 : if (beamMode == BeamMode::MID || beamMode == BeamMode::BEGIN16 || beamMode == BeamMode::BEGIN32) {
462 0 : return true;
463 : }
464 : }
465 :
466 169 : return false;
467 : }
468 :
469 : //---------------------------------------------------------
470 : // breakCrossMeasureBeams
471 : //---------------------------------------------------------
472 :
473 7150 : void BeamLayout::breakCrossMeasureBeams(Measure* measure, LayoutContext& ctx)
474 : {
475 7150 : MeasureBase* mbNext = measure->next();
476 7150 : if (!mbNext || !mbNext->isMeasure()) {
477 3858 : return;
478 : }
479 :
480 3292 : Measure* next = toMeasure(mbNext);
481 3292 : const size_t ntracks = ctx.dom().ntracks();
482 3292 : Segment* fstSeg = next->first(SegmentType::ChordRest);
483 3292 : if (!fstSeg) {
484 0 : return;
485 : }
486 :
487 22248 : for (size_t track = 0; track < ntracks; ++track) {
488 18956 : const Staff* stf = ctx.dom().staff(track2staff(track));
489 :
490 : // don’t compute beams for invisible staves and tablature without stems
491 18956 : if (!stf->show() || (stf->isTabStaff(measure->tick()) && stf->staffType(measure->tick())->stemless())) {
492 18954 : continue;
493 : }
494 :
495 18544 : EngravingItem* e = fstSeg->element(static_cast<int>(track));
496 18544 : if (!e || !e->isChordRest()) {
497 13744 : continue;
498 : }
499 :
500 4800 : ChordRest* cr = toChordRest(e);
501 4800 : Beam* beam = cr->beam();
502 4800 : if (!beam || beam->elements().front()->measure() == next) { // no beam or not cross-measure beam
503 4798 : continue;
504 : }
505 :
506 2 : std::vector<ChordRest*> mElements;
507 2 : std::vector<ChordRest*> nextElements;
508 :
509 16 : for (ChordRest* beamCR : beam->elements()) {
510 14 : if (beamCR->tick() < next->tick()) {
511 6 : mElements.push_back(beamCR);
512 : } else {
513 8 : nextElements.push_back(beamCR);
514 : }
515 : }
516 :
517 2 : if (mElements.size() == 1) {
518 0 : mElements[0]->removeDeleteBeam(false);
519 : }
520 :
521 2 : Beam* newBeam = nullptr;
522 2 : if (nextElements.size() > 1) {
523 2 : newBeam = Factory::createBeam(ctx.mutDom().score());
524 2 : newBeam->setGenerated(true);
525 2 : newBeam->setTrack(track);
526 : }
527 :
528 2 : const bool nextBeamed = bool(newBeam);
529 10 : for (ChordRest* nextCR : nextElements) {
530 8 : nextCR->removeDeleteBeam(nextBeamed);
531 8 : if (newBeam) {
532 8 : newBeam->add(nextCR);
533 : }
534 : }
535 :
536 2 : if (newBeam) {
537 2 : layout1(newBeam, ctx);
538 : }
539 2 : }
540 : }
541 :
542 26 : static bool beamNoContinue(BeamMode mode)
543 : {
544 26 : return mode == BeamMode::END || mode == BeamMode::NONE || mode == BeamMode::INVALID;
545 : }
546 :
547 : #define beamModeMid(a) (a == BeamMode::MID || a == BeamMode::BEGIN16 || a == BeamMode::BEGIN32)
548 :
549 : //---------------------------------------------------------
550 : // beamGraceNotes
551 : //---------------------------------------------------------
552 :
553 95100 : void BeamLayout::beamGraceNotes(LayoutContext& ctx, Chord* mainNote, bool after)
554 : {
555 95100 : ChordRest* a1 = 0; // start of (potential) beam
556 95100 : Beam* beam = 0; // current beam
557 95100 : BeamMode bm = BeamMode::AUTO;
558 95100 : std::vector<Chord*> graceNotes = after ? mainNote->graceNotesAfter() : mainNote->graceNotesBefore();
559 :
560 95100 : if (beam) {
561 0 : beam->setIsGrace(true);
562 : }
563 :
564 95980 : for (ChordRest* cr : graceNotes) {
565 880 : bm = Groups::baseBeamMode(cr);
566 880 : if ((cr->durationType().type() <= DurationType::V_QUARTER) || (bm == BeamMode::NONE)) {
567 277 : if (beam) {
568 5 : beam->setIsGrace(true);
569 5 : layout1(beam, ctx);
570 5 : beam = 0;
571 : }
572 277 : if (a1) {
573 1 : a1->removeDeleteBeam(false);
574 1 : a1 = 0;
575 : }
576 277 : cr->removeDeleteBeam(false);
577 277 : continue;
578 : }
579 603 : if (beam) {
580 72 : bool beamEnd = bm == BeamMode::BEGIN;
581 72 : if (!beamEnd) {
582 72 : cr->replaceBeam(beam);
583 72 : cr = 0;
584 72 : beamEnd = (bm == BeamMode::END);
585 : }
586 72 : if (beamEnd) {
587 28 : beam->setIsGrace(true);
588 28 : layout1(beam, ctx);
589 28 : beam = 0;
590 : }
591 : }
592 603 : if (!cr) {
593 72 : continue;
594 : }
595 531 : if (a1 == 0) {
596 439 : a1 = cr;
597 : } else {
598 92 : if (!beamModeMid(bm) && (bm == BeamMode::BEGIN)) {
599 0 : a1->removeDeleteBeam(false);
600 0 : a1 = cr;
601 : } else {
602 92 : beam = a1->beam();
603 92 : if (beam == 0 || beam->elements().front() != a1) {
604 33 : beam = Factory::createBeam(ctx.mutDom().score());
605 33 : beam->setGenerated(true);
606 33 : beam->setTrack(mainNote->track());
607 33 : a1->replaceBeam(beam);
608 : }
609 92 : cr->replaceBeam(beam);
610 92 : a1 = 0;
611 : }
612 : }
613 : }
614 95100 : if (beam) {
615 59 : beam->setIsGrace(true);
616 59 : layout1(beam, ctx);
617 95041 : } else if (a1) {
618 346 : a1->removeDeleteBeam(false);
619 : }
620 95100 : }
621 :
622 30347 : void BeamLayout::createBeams(LayoutContext& ctx, Measure* measure)
623 : {
624 30362 : TRACEFUNC;
625 :
626 207651 : for (track_idx_t track = 0; track < ctx.dom().ntracks(); ++track) {
627 177304 : const Staff* stf = ctx.dom().staff(track2staff(track));
628 :
629 : // don’t compute beams for invisible staves and tablature without stems
630 177304 : if (!stf->show() || (stf->isTabStaff(measure->tick()) && stf->staffType(measure->tick())->stemless())) {
631 6028 : continue;
632 : }
633 :
634 171276 : ChordRest* a1 = nullptr; // start of (potential) beam
635 171276 : bool firstCR = true;
636 171276 : Beam* beam = nullptr; // current beam
637 171276 : BeamMode bm = BeamMode::AUTO;
638 171276 : ChordRest* prev = nullptr;
639 171276 : std::unordered_map<int, TDuration> beatSubdivision;
640 :
641 : // if this measure is simple meter (actually X/4),
642 : // then perform a prepass to determine the subdivision of each beat
643 :
644 171276 : beatSubdivision.clear();
645 171276 : TimeSig* ts = stf->timeSig(measure->tick());
646 171276 : const SegmentType st = SegmentType::ChordRest;
647 171276 : if (ts && ts->denominator() == 4) {
648 164516 : Fraction stretch = ts->stretch();
649 494444 : for (Segment* s = measure->first(st); s; s = s->next(st)) {
650 329928 : ChordRest* mcr = toChordRest(s->element(track));
651 329928 : if (mcr == 0) {
652 247930 : continue;
653 : }
654 81998 : int beat = (mcr->rtick() * stretch).ticks() / Constants::DIVISION;
655 81998 : if (muse::contains(beatSubdivision, beat)) {
656 11733 : beatSubdivision[beat] = std::min(beatSubdivision[beat], mcr->durationType());
657 : } else {
658 70265 : beatSubdivision[beat] = mcr->durationType();
659 : }
660 : }
661 : }
662 :
663 517788 : for (Segment* segment = measure->first(st); segment; segment = segment->next(st)) {
664 346512 : ChordRest* cr = segment->cr(track);
665 346512 : if (!cr) {
666 260675 : continue;
667 : }
668 :
669 85837 : if (cr->isChord()) {
670 47550 : Chord* chord = toChord(cr);
671 48430 : for (Chord* c : chord->graceNotes()) {
672 880 : c->setBeamlet(nullptr); // Will be defined during beam layout
673 : }
674 : }
675 85837 : cr->setBeamlet(nullptr); // Will be defined during beam layout
676 :
677 85837 : if (firstCR) {
678 44193 : firstCR = false;
679 : // Handle cross-measure beams
680 44193 : BeamMode mode = cr->beamMode();
681 44193 : if (mode == BeamMode::MID || mode == BeamMode::END || mode == BeamMode::BEGIN16 || mode == BeamMode::BEGIN32) {
682 26 : ChordRest* prevCR = ctx.mutDom().findCR(measure->tick() - Fraction::eps(), track);
683 26 : if (prevCR) {
684 26 : Beam* prevBeam = prevCR->beam();
685 26 : const Measure* pm = prevCR->measure();
686 26 : if (!beamNoContinue(prevCR->beamMode())
687 26 : && !pm->lineBreak() && !pm->pageBreak() && !pm->sectionBreak()
688 26 : && ctx.state().prevMeasure()
689 52 : && !(prevCR->isChord() && prevCR->durationType().type() <= DurationType::V_QUARTER)) {
690 26 : beam = prevBeam;
691 26 : if (prevCR->isChord()) {
692 22 : if (Hook* hook = toChord(prevCR)->hook()) {
693 0 : ctx.mutDom().doUndoRemoveElement(hook);
694 0 : prevCR->segment()->staffShape(prevCR->vStaffIdx()).remove_if([hook](const ShapeElement& el) {
695 0 : return el.item() == hook;
696 : });
697 : }
698 : }
699 : //a1 = beam ? beam->elements().front() : prevCR;
700 26 : a1 = beam ? nullptr : prevCR; // when beam is found, a1 is no longer required.
701 0 : } else if (prevBeam && prevBeam == cr->beam() && prevBeam->elements().front() == prevCR) {
702 : // remove the beam from the previous chordrest because we do not currently
703 : // support cross-system beams
704 0 : prevCR->removeDeleteBeam(false);
705 : }
706 : }
707 : }
708 : }
709 :
710 85837 : bool hasAllRest = cr->beam() && cr->beam()->hasAllRests();
711 85837 : bool isARestBeamStart = cr->isRest() && cr->beamMode() == BeamMode::BEGIN;
712 85837 : if (hasAllRest && !isARestBeamStart) {
713 0 : cr->removeDeleteBeam(false);
714 : }
715 :
716 : // handle grace notes
717 : // (tied chords?)
718 85837 : if (cr->isChord()) {
719 47550 : Chord* chord = toChord(cr);
720 47550 : beamGraceNotes(ctx, chord, false); // grace before
721 47550 : beamGraceNotes(ctx, chord, true); // grace after
722 : }
723 :
724 85837 : bm = Groups::actualBeamMode(cr, prev, &beatSubdivision);
725 :
726 85837 : prev = cr;
727 :
728 : // Rests of any duration can be beamed over, if required
729 85837 : bool canBeBeamed = cr->durationType().type() > DurationType::V_QUARTER || cr->isRest();
730 85837 : if (!canBeBeamed || (bm == BeamMode::NONE)) {
731 69893 : bool removeBeam = true;
732 69893 : if (beam) {
733 1270 : layout1(beam, ctx);
734 1270 : removeBeam = (beam->elements().size() <= 1 || beam->hasAllRests());
735 1270 : beam = 0;
736 : }
737 69893 : if (a1) {
738 1198 : if (removeBeam) {
739 1198 : a1->removeDeleteBeam(false);
740 : }
741 1198 : a1 = 0;
742 : }
743 69893 : cr->removeDeleteBeam(false);
744 69893 : continue;
745 69893 : }
746 :
747 15944 : if (beam) {
748 7400 : bool beamEnd = (bm == BeamMode::BEGIN);
749 7400 : if (!beamEnd) {
750 5963 : cr->replaceBeam(beam);
751 5963 : cr = 0;
752 5963 : beamEnd = (bm == BeamMode::END);
753 : }
754 7400 : if (beamEnd) {
755 1573 : layout1(beam, ctx);
756 1573 : beam = 0;
757 : }
758 : }
759 15944 : if (!cr) {
760 5963 : continue;
761 : }
762 :
763 9981 : if (a1 == 0) {
764 5652 : a1 = cr;
765 : } else {
766 169 : if (!beamModeMid(bm)
767 4530 : &&
768 : (bm == BeamMode::BEGIN
769 32 : || (a1->segment()->segmentType() != cr->segment()->segmentType())
770 4361 : || (a1->endTick() < cr->tick())
771 : )
772 : ) {
773 126 : a1->removeDeleteBeam(false);
774 126 : a1 = cr;
775 : } else {
776 4203 : beam = a1->beam();
777 4203 : if (beam == 0 || beam->elements().front() != a1) {
778 1095 : beam = Factory::createBeam(ctx.mutDom().score());
779 1095 : beam->setGenerated(true);
780 1095 : beam->setTrack(track);
781 1095 : a1->replaceBeam(beam);
782 : }
783 4203 : cr->replaceBeam(beam);
784 4203 : a1 = 0;
785 : }
786 : }
787 : }
788 171276 : if (beam) {
789 1386 : layout1(beam, ctx);
790 169890 : } else if (a1) {
791 251 : Fraction nextTick = a1->endTick();
792 251 : Measure* m = (nextTick >= measure->endTick() ? measure->nextMeasure() : measure);
793 251 : ChordRest* nextCR = (m ? m->findChordRest(nextTick, track) : nullptr);
794 251 : Beam* b = a1->beam();
795 263 : if (!(b && !b->elements().empty() && !b->hasAllRests() && b->elements().front() == a1 && nextCR
796 12 : && beamModeMid(nextCR->beamMode()))) {
797 247 : a1->removeDeleteBeam(false);
798 : }
799 : }
800 171276 : }
801 30347 : }
802 :
803 : /************************************************************
804 : * layoutNonCrossBeams()
805 : * layout all non-cross-staff beams starting on this segment
806 : * **********************************************************/
807 :
808 178594 : void BeamLayout::layoutNonCrossBeams(ChordRest* cr, LayoutContext& ctx)
809 : {
810 178594 : if (cr->isChord()) {
811 99955 : for (Chord* grace : toChord(cr)->graceNotes()) {
812 1810 : layoutNonCrossBeams(grace, ctx);
813 : }
814 : }
815 :
816 178594 : if (!BeamLayout::isStartOfNonCrossBeam(cr)) {
817 170014 : return;
818 : }
819 :
820 8580 : Beam* beam = cr->beam();
821 :
822 8580 : TLayout::layoutBeam(beam, ctx);
823 :
824 8580 : if (!beam->tremAnchors().empty()) {
825 : // there are inset tremolos in here
826 120 : for (ChordRest* beamCr : beam->elements()) {
827 80 : if (!beamCr->isChord()) {
828 0 : continue;
829 : }
830 80 : Chord* c = toChord(beamCr);
831 80 : if (c->tremoloTwoChord()) {
832 80 : TremoloLayout::layout(c->tremoloTwoChord(), ctx);
833 : }
834 : }
835 : }
836 :
837 37752 : for (ChordRest* beamCR : beam->elements()) {
838 29172 : if (beamCR->isRest() && beamCR->vStaffIdx() == beam->staffIdx()) {
839 58 : verticalAdjustBeamedRests(toRest(beamCR), beam, ctx);
840 : }
841 : }
842 : }
843 :
844 58 : void BeamLayout::verticalAdjustBeamedRests(Rest* rest, Beam* beam, LayoutContext& ctx)
845 : {
846 58 : const double spatium = rest->spatium();
847 : static constexpr Fraction rest32nd(1, 32);
848 58 : const bool up = beam->up();
849 58 : const BeamMode restBeamMode = rest->beamMode();
850 58 : const bool firstRest = beam->elements().front() == rest
851 58 : && (restBeamMode == BeamMode::BEGIN || restBeamMode == BeamMode::BEGIN16 || restBeamMode == BeamMode::BEGIN32
852 0 : || restBeamMode == BeamMode::MID);
853 :
854 : double restToBeamPadding;
855 58 : if (rest->ticks() <= rest32nd) {
856 2 : restToBeamPadding = 0.2 * spatium;
857 : } else {
858 56 : restToBeamPadding = 0.35 * spatium;
859 : }
860 :
861 58 : const Shape beamShape = beam->shape().translate(beam->pagePos());
862 58 : const Shape restShape = rest->shape().translate(rest->pagePos() - rest->offset());
863 58 : const double minBeamToRestXDist = up && firstRest ? 0.1 * spatium : 0.0;
864 :
865 : const double restToBeamClearance = up
866 58 : ? beamShape.verticalClearance(restShape, minBeamToRestXDist)
867 32 : : restShape.verticalClearance(beamShape);
868 58 : const double lineDistance = rest->staff()->lineDistance(rest->tick()) * spatium;
869 :
870 58 : int clearanceInSteps = std::ceil(restToBeamClearance / lineDistance);
871 58 : up ? rest->verticalClearance().setAbove(clearanceInSteps) : rest->verticalClearance().setBelow(clearanceInSteps);
872 :
873 58 : if (restToBeamClearance > restToBeamPadding) {
874 49 : return;
875 : }
876 :
877 9 : const bool restIsLocked = rest->verticalClearance().locked();
878 9 : if (!restIsLocked) {
879 9 : double overlap = (restToBeamPadding - restToBeamClearance);
880 9 : int lineMoves = ceil(overlap / lineDistance);
881 9 : lineMoves *= up ? 1 : -1;
882 9 : double yMove = lineMoves * lineDistance;
883 9 : rest->mutldata()->moveY(yMove);
884 9 : for (Rest* mergedRest : rest->ldata()->mergedRests) {
885 0 : mergedRest->mutldata()->moveY(yMove);
886 : }
887 :
888 9 : Segment* segment = rest->segment();
889 9 : staff_idx_t staffIdx = rest->vStaffIdx();
890 9 : const Staff* staff = ctx.dom().staff(staffIdx);
891 9 : std::vector<Chord*> chords;
892 9 : std::vector<Rest*> rests;
893 9 : collectChordsAndRest(segment, staffIdx, chords, rests);
894 9 : RestLayout::resolveRestVSChord(rests, chords, staff, segment);
895 9 : RestLayout::resolveRestVSRest(rests, staff, segment, ctx, /*considerBeams*/ true);
896 9 : }
897 :
898 9 : TLayout::layoutBeam(beam, ctx);
899 :
900 9 : rest->segment()->createShape(rest->vStaffIdx());
901 107 : }
902 :
903 102 : void BeamLayout::checkCrossPosAndStemConsistency(Beam* beam, LayoutContext& ctx)
904 : {
905 102 : if (!beam->cross() || !beam->userModified()) {
906 102 : return;
907 : }
908 :
909 0 : int correctCrossStaffIdx = 0;
910 0 : bool inconsistencyFound = false;
911 :
912 0 : for (ChordRest* cr : beam->elements()) {
913 0 : if (!cr->isChord()) {
914 0 : continue;
915 : }
916 0 : Chord* chord = toChord(cr);
917 0 : bool currentUp = chord->up();
918 0 : bool actualUp = ChordLayout::isChordPosBelowBeam(chord, beam);
919 0 : if (actualUp != currentUp) {
920 0 : inconsistencyFound = true;
921 0 : chord->setUp(actualUp);
922 0 : ChordLayout::layoutChords1(ctx, chord->segment(), chord->staffIdx());
923 0 : chord->segment()->createShape(chord->staffIdx());
924 : }
925 0 : if (actualUp) {
926 0 : correctCrossStaffIdx = std::min(correctCrossStaffIdx, chord->staffMove());
927 : } else {
928 0 : correctCrossStaffIdx = std::max(correctCrossStaffIdx, chord->staffMove() + 1);
929 : }
930 : }
931 :
932 0 : int beamCrossStaffIdx = beam->crossStaffIdx();
933 0 : if (beamCrossStaffIdx != correctCrossStaffIdx) {
934 0 : inconsistencyFound = true;
935 0 : int curCrossStaffMove = beam->crossStaffMove();
936 0 : int diff = correctCrossStaffIdx - beamCrossStaffIdx;
937 0 : beam->setProperty(Pid::BEAM_CROSS_STAFF_MOVE, curCrossStaffMove + diff);
938 : }
939 :
940 0 : if (inconsistencyFound) {
941 0 : BeamLayout::layout(beam, ctx);
942 : }
943 : }
944 :
945 8778 : void BeamLayout::createBeamSegments(Beam* item, const LayoutContext& ctx, const std::vector<ChordRest*>& chordRests)
946 : {
947 8778 : item->clearBeamSegments();
948 :
949 8778 : bool levelHasBeam = false;
950 8778 : int level = 0;
951 8778 : constexpr size_t noLastChord = std::numeric_limits<size_t>::max();
952 8778 : size_t numCr = chordRests.size();
953 8778 : bool frenchStyleBeams = ctx.conf().styleB(Sid::frenchStyleBeams);
954 20362 : do {
955 20362 : levelHasBeam = false;
956 20362 : ChordRest* startCr = nullptr;
957 20362 : ChordRest* endCr = nullptr;
958 20362 : bool breakBeam = false;
959 20362 : bool previousBreak16 = false;
960 20362 : bool previousBreak32 = false;
961 20362 : size_t lastChordIndex = noLastChord;
962 :
963 94159 : for (size_t i = 0; i < numCr; i++) {
964 73797 : ChordRest* chordRest = chordRests[i];
965 73797 : ChordRest* prevChordRest = i < 1 ? nullptr : chordRests[i - 1];
966 :
967 73797 : if (level < chordRest->beams() && !chordRest->isRest()) {
968 42009 : levelHasBeam = true;
969 : }
970 73797 : bool isBroken16 = false;
971 73797 : bool isBroken32 = false;
972 :
973 : // updates isBroken16 and isBroken32
974 73797 : item->calcBeamBreaks(chordRest, prevChordRest, level, isBroken16, isBroken32);
975 73797 : breakBeam = isBroken16 || isBroken32;
976 73797 : bool skipRest = false;
977 73797 : BeamMode beamMode = chordRest->beamMode();
978 74044 : if (chordRest->isRest() && level >= chordRest->beams()
979 74044 : && (beamMode == BeamMode::MID || beamMode == BeamMode::BEGIN16 || beamMode == BeamMode::BEGIN32)) {
980 : // if the level is lower than both the previous and next real chords, don't break here
981 123 : size_t nextChordIndex = i + 1;
982 155 : while (nextChordIndex < numCr && chordRests[nextChordIndex]->isRest()) {
983 32 : ++nextChordIndex;
984 : }
985 123 : if (lastChordIndex < numCr && nextChordIndex < numCr) {
986 123 : Chord* lastChord = toChord(chordRests[lastChordIndex]);
987 123 : Chord* nextChord = toChord(chordRests[nextChordIndex]);
988 : // we have both chords (not rests) on either side of this rest, so if the level is lower than both we can
989 : // just continue the beam through here
990 : // this creates situations where we can have the second beam of a pair of 16th notes continuing through
991 : // an 8th rest, for example, even though an 8th note would very obviously break the beam
992 : // this is desired for BeamType::MID on rests.
993 123 : if (level < lastChord->beams() && level < nextChord->beams()) {
994 0 : skipRest = (isBroken16 && level < 1) || (isBroken32 && level < 2) || (!isBroken16 && !isBroken32);
995 : }
996 : }
997 : }
998 73797 : if ((level < chordRest->beams() && !breakBeam) || skipRest) {
999 40980 : endCr = chordRest;
1000 40980 : if (!startCr || (startCr->isRest() && startCr != item->elements().front())) {
1001 11188 : startCr = chordRest;
1002 : }
1003 32817 : } else if (level >= chordRest->beams() && chordRest->isRest() && !breakBeam) {
1004 : // This rest has duration longer than the beam value, but it may still go
1005 : // under the beam if the next chords or rests are beamed
1006 123 : continue;
1007 : } else {
1008 32694 : size_t beamletIndex = static_cast<size_t>(i) - 1;
1009 32694 : if (lastChordIndex < item->elements().size() && (chordRest->isRest() || (endCr && endCr->isRest()))) {
1010 : // we broke the beam on this chordrest, but the last cr of the beam segment can't end on a rest
1011 : // so it ends on lastChord
1012 6 : ChordRest* lastCr = toChordRest(item->elements()[lastChordIndex]);
1013 6 : if (lastCr && startCr && lastCr->tick() >= startCr->tick()) {
1014 6 : endCr = lastCr;
1015 6 : beamletIndex = lastChordIndex;
1016 6 : lastChordIndex = noLastChord;
1017 : }
1018 : }
1019 32694 : if (startCr && endCr && levelHasBeam) {
1020 1112 : if (startCr == endCr && startCr->isChord()) {
1021 214 : bool isBeamletBefore = calcIsBeamletBefore(item,
1022 : toChord(startCr),
1023 : static_cast<int>(beamletIndex),
1024 : level,
1025 : previousBreak16,
1026 : previousBreak32);
1027 214 : createBeamletSegment(item, ctx, toChord(startCr), isBeamletBefore, level);
1028 898 : } else if (startCr != endCr) {
1029 898 : createBeamSegment(item, startCr, endCr, level, frenchStyleBeams);
1030 : }
1031 : }
1032 32694 : bool setCr = chordRest && chordRest->isChord() && breakBeam && level < chordRest->beams();
1033 32694 : startCr = setCr ? chordRest : nullptr;
1034 32694 : endCr = setCr ? chordRest : nullptr;
1035 : }
1036 73674 : previousBreak16 = isBroken16;
1037 73674 : previousBreak32 = isBroken32;
1038 73674 : if (chordRest && chordRest->isChord()) {
1039 73550 : lastChordIndex = i;
1040 : }
1041 : }
1042 :
1043 : // if the beam ends on the last chord
1044 20362 : if (startCr && (endCr || breakBeam)) {
1045 11229 : if ((startCr == endCr || !endCr) && startCr->isChord()) {
1046 : // this chord is either the last chord, or the first (followed by a rest)
1047 183 : bool isBefore = !(startCr == chordRests.front());
1048 183 : createBeamletSegment(item, ctx, toChord(startCr), isBefore, level);
1049 : } else {
1050 11046 : createBeamSegment(item, startCr, endCr, level, frenchStyleBeams);
1051 : }
1052 : }
1053 20362 : level++;
1054 : } while (levelHasBeam);
1055 8778 : }
1056 :
1057 214 : bool BeamLayout::calcIsBeamletBefore(const Beam* item, Chord* chord, int i, int level, bool isAfter16Break, bool isAfter32Break)
1058 : {
1059 : // if first or last chord in beam group
1060 214 : if (i == 0) {
1061 142 : return false;
1062 72 : } else if (i == static_cast<int>(item->elements().size()) - 1) {
1063 0 : return true;
1064 : }
1065 : // if first or last chord in tuplet
1066 72 : Tuplet* tuplet = chord->tuplet();
1067 72 : if (tuplet && chord == tuplet->elements().front()) {
1068 10 : return false;
1069 62 : } else if (tuplet && chord == tuplet->elements().back()) {
1070 14 : return true;
1071 : }
1072 :
1073 : // next note has a beam break
1074 48 : ChordRest* nextChordRest = item->elements()[i + 1];
1075 48 : ChordRest* currChordRest = item->elements()[i];
1076 48 : ChordRest* prevChordRest = item->elements()[i - 1];
1077 48 : if (nextChordRest->isChord()) {
1078 48 : bool nextBreak16 = false;
1079 48 : bool nextBreak32 = false;
1080 48 : bool currBreak16 = false;
1081 48 : bool currBreak32 = false;
1082 48 : item->calcBeamBreaks(currChordRest, prevChordRest, prevChordRest->beams(), currBreak16, currBreak32);
1083 48 : item->calcBeamBreaks(nextChordRest, currChordRest, level, nextBreak16, nextBreak32);
1084 48 : if ((nextBreak16 && level >= 1) || (!currBreak16 && nextBreak32 && level >= 2)) {
1085 26 : return true;
1086 : }
1087 : }
1088 :
1089 : // if previous or next chord has more beams, point in that direction
1090 22 : int previousChordLevel = -1;
1091 22 : int nextChordLevel = -1;
1092 22 : int previousOffset = 1;
1093 22 : while (i - previousOffset >= 0) {
1094 22 : ChordRest* previous = item->elements()[i - previousOffset];
1095 22 : if (previous->isChord()) {
1096 22 : previousChordLevel = toChord(previous)->beams();
1097 22 : if (isAfter16Break) {
1098 10 : previousChordLevel = std::min(previousChordLevel, 1);
1099 12 : } else if (isAfter32Break) {
1100 0 : previousChordLevel = std::min(previousChordLevel, 2);
1101 : }
1102 22 : break;
1103 : }
1104 0 : ++previousOffset;
1105 : }
1106 :
1107 22 : int nextOffset = 1;
1108 22 : while (i + nextOffset < static_cast<int>(item->elements().size())) {
1109 22 : ChordRest* next = item->elements()[i + nextOffset];
1110 22 : if (next->isChord()) {
1111 22 : nextChordLevel = toChord(next)->beams();
1112 22 : break;
1113 : }
1114 0 : ++nextOffset;
1115 : }
1116 22 : int chordLevelDifference = nextChordLevel - previousChordLevel;
1117 22 : if (chordLevelDifference != 0) {
1118 14 : return chordLevelDifference < 0;
1119 : }
1120 :
1121 : // if the chord ends a subdivision of the beat
1122 8 : Fraction baseTick = tuplet ? tuplet->tick() : chord->measure()->tick();
1123 8 : Fraction stretch = chord->staff()->timeStretch(baseTick);
1124 8 : Fraction tickNext = (nextChordRest->tick() - baseTick) * stretch;
1125 8 : if (tuplet) {
1126 : // for tuplets with odd ratios, apply ratio
1127 : // for tuplets with even ratios, use actual beat
1128 0 : Fraction ratio = tuplet->ratio();
1129 0 : if (ratio.numerator() & 1) {
1130 0 : tickNext *= ratio;
1131 : }
1132 : }
1133 :
1134 : static const int BEAM_TUPLET_TOLERANCE = 6;
1135 8 : int tickLargeSize = chord->ticks().ticks() * 2;
1136 8 : int remainder = tickNext.ticks() % tickLargeSize;
1137 8 : if (remainder <= BEAM_TUPLET_TOLERANCE || (tickLargeSize - remainder) <= BEAM_TUPLET_TOLERANCE) {
1138 4 : return true;
1139 : }
1140 :
1141 : // default case
1142 4 : return false;
1143 : }
1144 :
1145 11825 : static inline int computeStemLengthAdjustmentSteps(const ChordRest* cr, bool isStartOrEnd, bool isAbove, bool frenchStyleBeams,
1146 : int level, int beamsAbove, int beamsBelow)
1147 : {
1148 11825 : if (isAbove == cr->up()) { // beam segment on opposite side
1149 34 : if (!frenchStyleBeams || isStartOrEnd) {
1150 : // lengthen
1151 34 : return level - (isAbove ? beamsBelow : beamsAbove);
1152 : }
1153 : } else { // beamlet on the note side
1154 11791 : if (frenchStyleBeams && !isStartOrEnd) {
1155 : // shorten
1156 0 : return (isAbove ? beamsBelow : beamsAbove) - level;
1157 : }
1158 : }
1159 :
1160 11791 : return 0;
1161 : }
1162 :
1163 11944 : void BeamLayout::createBeamSegment(Beam* item, ChordRest* startCr, ChordRest* endCr, int level, bool frenchStyleBeams)
1164 : {
1165 11944 : const bool isFirstSubgroup = startCr == item->elements().front();
1166 11944 : const bool isLastSubgroup = endCr == item->elements().back();
1167 11944 : const bool firstUp = startCr->up();
1168 11944 : const bool lastUp = endCr->up();
1169 11944 : bool overallUp = item->up();
1170 11944 : if (isFirstSubgroup == isLastSubgroup) {
1171 : // this subgroup is either the only one in the beam, or in the middle
1172 10825 : if (firstUp == lastUp) {
1173 : // the "outside notes" of this subgroup go the same direction so use them
1174 : // to determine the side of the beams
1175 10805 : overallUp = firstUp;
1176 : } else {
1177 : // no perfect way to solve this problem, for now we'll base it on the number of
1178 : // up and down stemmed notes in this subgroup
1179 : int upStems, downStems;
1180 20 : upStems = downStems = 0;
1181 62 : for (ChordRest* cr : item->elements()) {
1182 62 : if (!cr->isChord() || cr->tick() < startCr->tick()) {
1183 0 : continue;
1184 : }
1185 62 : if (cr->tick() > endCr->tick()) {
1186 0 : break;
1187 : }
1188 :
1189 62 : ++(toChord(cr)->up() ? upStems : downStems);
1190 :
1191 62 : if (cr == endCr) {
1192 20 : break;
1193 : }
1194 : }
1195 20 : if (upStems == downStems) {
1196 : // we are officially bamboozled. for now we can just use the default
1197 : // direction based on the staff we're on I guess
1198 14 : overallUp = item->up();
1199 : } else {
1200 : // use the direction with the most stems
1201 6 : overallUp = upStems > downStems;
1202 : }
1203 : }
1204 1119 : } else if (isFirstSubgroup) {
1205 418 : overallUp = lastUp;
1206 701 : } else if (isLastSubgroup) {
1207 701 : overallUp = firstUp;
1208 : }
1209 :
1210 11944 : const double startX = BeamTremoloLayout::chordBeamAnchorX(item->ldata(), startCr, ChordBeamAnchorType::Start);
1211 11944 : const double endX = BeamTremoloLayout::chordBeamAnchorX(item->ldata(), endCr, ChordBeamAnchorType::End);
1212 :
1213 11944 : double startY = item->slope() * (startX - item->startAnchor().x()) + item->startAnchor().y() - item->pagePos().y();
1214 11944 : double endY = item->slope() * (endX - item->startAnchor().x()) + item->startAnchor().y() - item->pagePos().y();
1215 :
1216 11944 : int beamsBelow = 0; // how many beams below level 0?
1217 11944 : int beamsAbove = 0; // how many beams above level 0?
1218 :
1219 : // avoid adjusting for beams on opposite side of level 0
1220 11944 : if (level != 0) {
1221 9682 : for (const BeamSegment* beam : item->beamSegments()) {
1222 6516 : if (beam->level == 0 || beam->endTick < startCr->tick() || beam->startTick > endCr->tick()) {
1223 5301 : continue;
1224 : }
1225 1215 : ++(beam->above ? beamsAbove : beamsBelow);
1226 : }
1227 :
1228 3166 : const int upValue = overallUp ? -1 : 1;
1229 3166 : const int extraBeamAdjust = overallUp ? beamsAbove : beamsBelow;
1230 3166 : const double verticalOffset = item->beamDist() * (level - extraBeamAdjust) * upValue;
1231 :
1232 3166 : if (muse::RealIsEqual(item->growLeft(), item->growRight())) {
1233 3150 : startY -= verticalOffset * item->growLeft();
1234 3150 : endY -= verticalOffset * item->growLeft();
1235 : } else {
1236 : // Feathered beams
1237 16 : double startProportionAlongX = (startX - item->startAnchor().x()) / (item->endAnchor().x() - item->startAnchor().x());
1238 16 : double endProportionAlongX = (endX - item->startAnchor().x()) / (item->endAnchor().x() - item->startAnchor().x());
1239 :
1240 16 : double grow1 = startProportionAlongX * (item->growRight() - item->growLeft()) + item->growLeft();
1241 16 : double grow2 = endProportionAlongX * (item->growRight() - item->growLeft()) + item->growLeft();
1242 :
1243 16 : startY -= verticalOffset * grow1;
1244 16 : endY -= verticalOffset * grow2;
1245 : }
1246 : }
1247 :
1248 11944 : BeamSegment* b = new BeamSegment(item);
1249 11944 : b->above = !overallUp;
1250 11944 : b->level = level;
1251 11944 : b->line = LineF(startX, startY, endX, endY);
1252 11944 : b->startTick = startCr->tick();
1253 11944 : b->endTick = endCr->tick();
1254 11944 : item->beamSegments().push_back(b);
1255 :
1256 11944 : if (level > 0) {
1257 3166 : ++(b->above ? beamsAbove : beamsBelow);
1258 : }
1259 :
1260 : // extend stems properly
1261 45802 : for (ChordRest* cr : item->elements()) {
1262 45779 : if (!cr->isChord() || cr->tick() < startCr->tick()) {
1263 4100 : continue;
1264 : }
1265 41679 : if (cr->tick() > endCr->tick()) {
1266 4 : break;
1267 : }
1268 :
1269 41675 : Chord* chord = toChord(cr);
1270 41675 : if (!chord->stem()) {
1271 94 : continue;
1272 : }
1273 :
1274 41581 : double _up = chord->up() ? -1.0 : 1.0;
1275 :
1276 41581 : if (level == 0) {
1277 29756 : BeamTremoloLayout::extendStem(item->ldata(), chord, 0.0);
1278 : // HACK: extend the bounding box of the stem (NOT the stem itself) by half beam width.
1279 : // This way the bbox of the stem covers also the beam position. Hugely helps with all the collision checks.
1280 29756 : chord->stem()->mutldata()->beamCorrection = _up * item->beamWidth() / 2;
1281 : } else {
1282 11825 : bool isStartOrEnd = cr == startCr || cr == endCr;
1283 11825 : int visualStemLengthAdjustmentSteps = computeStemLengthAdjustmentSteps(cr, isStartOrEnd, b->above, frenchStyleBeams,
1284 : level, beamsAbove, beamsBelow);
1285 11825 : if (visualStemLengthAdjustmentSteps != 0) {
1286 34 : double grow = item->growLeft();
1287 34 : if (!muse::RealIsEqual(item->growLeft(), item->growRight())) {
1288 0 : double anchorX = BeamTremoloLayout::chordBeamAnchorX(item->ldata(), chord, ChordBeamAnchorType::Middle);
1289 0 : double proportionAlongX = (anchorX - item->startAnchor().x()) / (item->endAnchor().x() - item->startAnchor().x());
1290 0 : grow = proportionAlongX * (item->growRight() - item->growLeft()) + item->growLeft();
1291 : }
1292 :
1293 34 : double visualStemLengthAdjustment = grow * visualStemLengthAdjustmentSteps * item->beamDist();
1294 :
1295 34 : if (frenchStyleBeams) {
1296 : // Even if we visually shorten the stem, we need the 'original' length for collisions.
1297 0 : int bboxStemLengthAdjustmentSteps = computeStemLengthAdjustmentSteps(cr, isStartOrEnd, b->above,
1298 : /*frenchStyleBeams*/ false,
1299 : level, beamsAbove, beamsBelow);
1300 0 : chord->stem()->mutldata()->beamCorrection = _up * item->beamWidth() / 2 + _up * grow
1301 0 : * (bboxStemLengthAdjustmentSteps - visualStemLengthAdjustmentSteps)
1302 0 : * item->beamDist();
1303 : }
1304 34 : BeamTremoloLayout::extendStem(item->ldata(), chord, visualStemLengthAdjustment);
1305 : }
1306 : }
1307 :
1308 41581 : if (cr == endCr) {
1309 11917 : break;
1310 : }
1311 : }
1312 11944 : }
1313 :
1314 397 : void BeamLayout::createBeamletSegment(Beam* item, const LayoutContext& ctx, ChordRest* cr, bool isBefore, int level)
1315 : {
1316 397 : const double startX = BeamTremoloLayout::chordBeamAnchorX(item->ldata(), cr,
1317 : isBefore
1318 : ? ChordBeamAnchorType::End
1319 : : ChordBeamAnchorType::Start);
1320 :
1321 397 : const double beamletLength = ctx.conf().styleAbsolute(Sid::beamMinLen) * cr->mag();
1322 :
1323 397 : const double endX = startX + (isBefore ? -beamletLength : beamletLength);
1324 :
1325 397 : double startY = item->slope() * (startX - item->startAnchor().x()) + item->startAnchor().y() - item->pagePos().y();
1326 397 : double endY = item->slope() * (endX - startX) + startY;
1327 :
1328 : // how many beams past level 0 (i.e. beams on the other side of level 0 for this subgroup)
1329 397 : int extraBeamAdjust = 0;
1330 :
1331 : // avoid adjusting for beams on opposite side of level 0
1332 1332 : for (const BeamSegment* beam : item->beamSegments()) {
1333 935 : if (beam->level == 0 || beam->line.x2() < startX || beam->line.x1() > endX) {
1334 802 : continue;
1335 : }
1336 :
1337 133 : if (cr->up() == beam->above) {
1338 0 : extraBeamAdjust++;
1339 : }
1340 : }
1341 :
1342 397 : const int upValue = cr->up() ? -1 : 1;
1343 397 : const double verticalOffset = item->beamDist() * (level - extraBeamAdjust) * upValue;
1344 :
1345 397 : if (muse::RealIsEqual(item->growLeft(), item->growRight())) {
1346 397 : startY -= verticalOffset * item->growLeft();
1347 397 : endY -= verticalOffset * item->growLeft();
1348 : } else {
1349 : // Feathered beams
1350 0 : double startProportionAlongX = (startX - item->startAnchor().x()) / (item->endAnchor().x() - item->startAnchor().x());
1351 0 : double endProportionAlongX = (endX - item->startAnchor().x()) / (item->endAnchor().x() - item->startAnchor().x());
1352 :
1353 0 : double grow1 = startProportionAlongX * (item->growRight() - item->growLeft()) + item->growLeft();
1354 0 : double grow2 = endProportionAlongX * (item->growRight() - item->growLeft()) + item->growLeft();
1355 :
1356 0 : startY -= verticalOffset * grow1;
1357 0 : endY -= verticalOffset * grow2;
1358 : }
1359 :
1360 397 : BeamSegment* b = new BeamSegment(item);
1361 397 : b->above = !cr->up();
1362 397 : b->level = level;
1363 397 : b->line = LineF(startX, startY, endX, endY);
1364 397 : b->isBeamlet = true;
1365 397 : b->isBefore = isBefore;
1366 397 : cr->setBeamlet(b);
1367 :
1368 397 : ChordLayout::fillShape(cr, cr->mutldata(), ctx.conf());
1369 : //! NOTE Moved from ChordRest::setBeamlet
1370 397 : cr->segment()->createShape(cr->vStaffIdx());
1371 397 : item->beamSegments().push_back(b);
1372 397 : }
1373 :
1374 4346 : bool BeamLayout::computeUpForMovedCross(Beam* item)
1375 : {
1376 4346 : if (!item->cross()) {
1377 4336 : return false;
1378 : }
1379 10 : bool isAboveAllChords = true;
1380 10 : bool isBelowAllChords = true;
1381 45 : for (ChordRest* cr : item->elements()) {
1382 35 : if (cr->isBelowCrossBeam(item)) {
1383 17 : isBelowAllChords = false;
1384 : } else {
1385 18 : isAboveAllChords = false;
1386 : }
1387 : }
1388 :
1389 10 : if (isAboveAllChords) {
1390 0 : item->setUp(true);
1391 0 : return true;
1392 : }
1393 :
1394 10 : if (isBelowAllChords) {
1395 0 : item->setUp(false);
1396 0 : return true;
1397 : }
1398 :
1399 10 : return false;
1400 : }
1401 :
1402 103 : PointF BeamLayout::chordBeamAnchor(const Beam* item, const ChordRest* chord, ChordBeamAnchorType anchorType)
1403 : {
1404 103 : return BeamTremoloLayout::chordBeamAnchor(item->ldata(), chord, anchorType);
1405 : }
1406 :
1407 0 : double BeamLayout::chordBeamAnchorY(const Beam* item, const ChordRest* chord)
1408 : {
1409 0 : return BeamTremoloLayout::chordBeamAnchorY(item->ldata(), chord);
1410 : }
1411 :
1412 8778 : void BeamLayout::setTremAnchors(Beam* item, const LayoutContext& ctx)
1413 : {
1414 8778 : item->tremAnchors().clear();
1415 38651 : for (ChordRest* cr : item->elements()) {
1416 29873 : if (!cr || !cr->isChord()) {
1417 75 : continue;
1418 : }
1419 29798 : Chord* c = toChord(cr);
1420 29798 : TremoloTwoChord* t = c ? c->tremoloTwoChord() : nullptr;
1421 29798 : if (t && t->chord1() == c && t->chord2()->beam() == item) {
1422 : // there is an inset tremolo here!
1423 : // figure out up / down
1424 40 : bool tremUp = t->up();
1425 40 : if (item->userModified()) {
1426 0 : tremUp = c->up();
1427 40 : } else if (item->cross() && t->chord1()->staffMove() == t->chord2()->staffMove()) {
1428 0 : tremUp = t->chord1()->staffMove() == item->maxCRMove();
1429 : }
1430 40 : TremAnchor tremAnchor;
1431 40 : tremAnchor.chord1 = c;
1432 40 : int regularBeams = c->beams(); // non-tremolo strokes
1433 40 : double scale = t->spatium() * t->mag();
1434 :
1435 : // find the left-side anchor
1436 40 : double width = item->endAnchor().x() - item->startAnchor().x();
1437 40 : double height = item->endAnchor().y() - item->startAnchor().y();
1438 40 : double x = chordBeamAnchor(item, c, ChordBeamAnchorType::Middle).x();
1439 40 : double proportionAlongX = (x - item->startAnchor().x()) / width;
1440 40 : double y = item->startAnchor().y() + (proportionAlongX * height);
1441 40 : y += regularBeams * (ctx.conf().styleB(Sid::useWideBeams) ? 1.0 : 0.75) * scale * (tremUp ? 1. : -1.);
1442 40 : tremAnchor.y1 = y;
1443 : // find the right-side anchor
1444 40 : x = chordBeamAnchor(item, t->chord2(), ChordBeamAnchorType::Middle).x();
1445 40 : proportionAlongX = (x - item->startAnchor().x()) / width;
1446 40 : y = item->startAnchor().y() + (proportionAlongX * height);
1447 40 : y += regularBeams * (ctx.conf().styleB(Sid::useWideBeams) ? 1.0 : 0.75) * scale * (tremUp ? 1. : -1.);
1448 40 : tremAnchor.y2 = y;
1449 40 : item->tremAnchors().push_back(tremAnchor);
1450 : }
1451 : }
1452 8778 : }
|