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 "beamtremololayout.h"
24 :
25 : #include <array>
26 :
27 : #include "../dom/beam.h"
28 : #include "../dom/chordrest.h"
29 : #include "../dom/chord.h"
30 : #include "../dom/measure.h"
31 : #include "../dom/note.h"
32 : #include "../dom/score.h"
33 : #include "../dom/staff.h"
34 : #include "../dom/stem.h"
35 : #include "../dom/stemslash.h"
36 : #include "../dom/system.h"
37 : #include "../dom/tremolotwochord.h"
38 :
39 : #include "tlayout.h"
40 : #include "chordlayout.h"
41 : #include "stemlayout.h"
42 :
43 : #include "log.h"
44 :
45 : using namespace mu;
46 : using namespace mu::engraving;
47 : using namespace mu::engraving::rendering::score;
48 :
49 : constexpr std::array _maxSlopes = { 0, 1, 2, 3, 4, 5, 6, 7 };
50 :
51 17951 : void BeamTremoloLayout::setupLData(const BeamBase* item, BeamBase::LayoutData* ldata, const LayoutContext& ctx)
52 : {
53 17951 : bool isGrace = false;
54 17951 : IF_ASSERT_FAILED(item && (item->isBeam() || item->isType(ElementType::TREMOLO_TWOCHORD))) {
55 : // right now only beams and trems are supported
56 0 : return;
57 17951 : } else if (item->isBeam()) {
58 17556 : ldata->beamType = BeamType::BEAM;
59 17556 : ldata->beam = item_cast<const Beam*>(item);
60 17556 : ldata->up = ldata->beam->up();
61 17556 : ldata->trem = nullptr; // there can be many different trems in a beam, they will all be checked
62 17556 : isGrace = ldata->beam->elements().front()->isGrace();
63 : } else {
64 395 : ldata->trem = item_cast<const TremoloTwoChord*>(item);
65 395 : ldata->beamType = BeamType::TREMOLO;
66 : // check to see if there is a beam happening during this trem
67 : // if so, it needs to be taken into account in trem placement
68 395 : if (ldata->trem->chord1()->beam() && ldata->trem->chord1()->beam() == ldata->trem->chord2()->beam()) {
69 120 : ldata->beam = ldata->trem->chord1()->beam();
70 : } else {
71 275 : ldata->beam = nullptr;
72 : }
73 395 : ldata->up = computeTremoloUp(ldata);
74 : //! HACK Temporary
75 395 : const_cast<TremoloTwoChord*>(ldata->trem)->setUp(ldata->up);
76 395 : isGrace = ldata->trem->chord1()->isGrace();
77 : }
78 :
79 17951 : ldata->spatium = item->spatium();
80 17951 : ldata->tick = item->tick();
81 17951 : ldata->beamSpacing = ctx.conf().styleB(Sid::useWideBeams) ? 4 : 3;
82 17951 : ldata->beamDist = (ldata->beamSpacing / 4.0) * ldata->spatium * ldata->mag() * (isGrace ? ctx.conf().styleD(Sid::graceNoteMag) : 1.);
83 17951 : ldata->beamWidth = (ctx.conf().styleS(Sid::beamWidth).val() * ldata->spatium) * ldata->mag();
84 17951 : const StaffType* staffType = item->staffType();
85 17951 : ldata->tab = (staffType && staffType->isTabStaff()) ? staffType : nullptr;
86 17951 : ldata->isBesideTabStaff = ldata->tab && !ldata->tab->stemless() && !ldata->tab->stemThrough();
87 : }
88 :
89 12137 : int BeamTremoloLayout::minStemLength(const ChordRest* cr, const BeamBase::LayoutData* ldata)
90 : {
91 : // min stem lengths in quarter spaces according to how many beams there are (starting with 1)
92 : static constexpr int BEAMS_COUNT = 9;
93 : static constexpr int minStemLengths[BEAMS_COUNT] = { 11, 13, 15, 18, 21, 24, 27, 30, 33 };
94 12137 : int beams = strokeCount(ldata, cr);
95 12137 : if (beams > BEAMS_COUNT) {
96 0 : LOGE() << "Beam count " << beams << " out of range (" << BEAMS_COUNT - 1 << ")";
97 0 : return minStemLengths[BEAMS_COUNT - 1];
98 : }
99 :
100 12137 : return minStemLengths[std::max(beams - 1, 0)];
101 : }
102 :
103 8961 : void BeamTremoloLayout::offsetBeamToRemoveCollisions(const BeamBase* item, const BeamBase::LayoutData* ldata,
104 : const std::vector<ChordRest*>& chordRests,
105 : int& dictator,
106 : int& pointer,
107 : const double startX, const double endX,
108 : bool isFlat, bool isStartDictator)
109 : {
110 8961 : if (endX == startX) {
111 0 : return;
112 : }
113 :
114 8961 : IF_ASSERT_FAILED(!isFlat || (isFlat == (dictator == pointer))) {
115 : // This will produce false positives if the pointer & dictator are not flat when they should be
116 0 : LOGE() << "Beam is flat, dictator and pointer should be equal";
117 0 : pointer = dictator;
118 : }
119 :
120 : // tolerance eliminates all possibilities of floating point rounding errors
121 8961 : const double tolerance = ldata->beamWidth * 0.25 * (ldata->up ? -1 : 1);
122 8961 : const bool isSmall = ldata->isGrace || ldata->mag() < 1.;
123 :
124 8961 : double startY = (isStartDictator ? dictator : pointer) * ldata->spatium / 4 + tolerance;
125 8961 : double endY = (isStartDictator ? pointer : dictator) * ldata->spatium / 4 + tolerance;
126 :
127 8961 : const ChordRest* firstChordRest = chordRests.front();
128 8961 : const ChordRest* lastChordRest = chordRests.back();
129 :
130 8961 : size_t curChordRest = 0;
131 39033 : for (ChordRest* chordRest : chordRests) {
132 30072 : if (!chordRest->isChord() || chordRest == ldata->elements.back() || chordRest == ldata->elements.front()) {
133 17989 : curChordRest++;
134 17989 : continue;
135 : }
136 :
137 12083 : int sameLineException = 0;
138 :
139 12083 : if (chordRest->isChord()
140 12083 : && ((curChordRest == 1 && firstChordRest->isChord()) || (curChordRest == chordRests.size() - 2 && lastChordRest->isChord()))) {
141 9057 : const Chord* innerChord = toChord(chordRest);
142 9057 : const Chord* outerChord = curChordRest == 1 && firstChordRest->isChord() ? toChord(firstChordRest) : toChord(lastChordRest);
143 9057 : const int innerLine = ldata->up ? innerChord->upNote()->line() : innerChord->downNote()->line();
144 9057 : const int outerLine = ldata->up ? outerChord->upNote()->line() : outerChord->downNote()->line();
145 :
146 9057 : if (innerLine == outerLine) {
147 2691 : sameLineException = 1;
148 6366 : } else if (chordRests.size() == 3) {
149 1609 : const ChordRest* otherOuterCR = outerChord == firstChordRest ? lastChordRest : firstChordRest;
150 1609 : const Chord* otherOuterChord = otherOuterCR->isChord() ? toChord(otherOuterCR) : nullptr;
151 1609 : if (!otherOuterChord) {
152 0 : continue;
153 : }
154 :
155 1609 : const int otherOuterLine = ldata->up ? otherOuterChord->upNote()->line() : otherOuterChord->downNote()->line();
156 1609 : if (innerLine == otherOuterLine) {
157 300 : sameLineException = 1;
158 : }
159 : }
160 : }
161 :
162 12083 : PointF anchor = chordBeamAnchor(ldata, chordRest, ChordBeamAnchorType::Middle) - item->pagePos();
163 :
164 12083 : const int minLen = minStemLength(chordRest, ldata) - sameLineException;
165 :
166 : // avoid division by zero for zero-length beams (can exist as a pre-layout state used
167 : // for horizontal spacing computations)
168 12083 : if (!muse::RealIsEqual(endX, startX)) {
169 12083 : const double proportionAlongX = (anchor.x() - startX) / (endX - startX);
170 : while (true) {
171 16267 : const int slope = std::abs(dictator - pointer);
172 16267 : double reduction = 0.0;
173 16267 : if (!isFlat) {
174 6014 : if (slope <= 3) {
175 5550 : reduction = 0.25 * ldata->spatium;
176 464 : } else if (slope <= 6) {
177 413 : reduction = 0.5 * ldata->spatium;
178 : } else { // slope > 6
179 51 : reduction = 0.75 * ldata->spatium;
180 : }
181 : }
182 : // Ensure the beam clears the anchor's height and inner note's stem minimum stem length is enforced
183 16267 : const double desiredY = proportionAlongX * (endY - startY) + startY; // start note stem len + extra for slope
184 10057 : const bool beamClearsAnchor = (ldata->up && muse::RealIsEqualOrLess(desiredY, anchor.y() + reduction))
185 26324 : || (!ldata->up && muse::RealIsEqualOrMore(desiredY, anchor.y() - reduction));
186 :
187 16267 : const Note* note = ldata->up ? toChord(chordRest)->downNote() : toChord(chordRest)->upNote();
188 16267 : const double noteAnchor = (ldata->up ? note->stemUpSE().y() : note->stemDownNW().y()) + note->pagePos().y()
189 16267 : - item->pagePos().y();
190 : // Resultant length in quarter spaces
191 16267 : const int desiredLen = std::abs(round((desiredY - noteAnchor) / ldata->spatium * 4)) + 1;
192 :
193 16267 : if (beamClearsAnchor && desiredLen >= round(minLen * chordRest->mag())) {
194 12083 : break;
195 : }
196 :
197 4184 : if (isFlat || (isSmall && dictator == pointer)) {
198 3707 : dictator += ldata->up ? -1 : 1;
199 3707 : pointer += ldata->up ? -1 : 1;
200 477 : } else if (std::abs(dictator - pointer) == 1) {
201 225 : dictator += ldata->up ? -1 : 1;
202 : } else {
203 252 : pointer += ldata->up ? -1 : 1;
204 : }
205 :
206 4184 : startY = (isStartDictator ? dictator : pointer) * ldata->spatium / 4 + tolerance;
207 4184 : endY = (isStartDictator ? pointer : dictator) * ldata->spatium / 4 + tolerance;
208 4184 : }
209 : }
210 12083 : curChordRest++;
211 : }
212 : }
213 :
214 8605 : void BeamTremoloLayout::offsetBeamWithAnchorShortening(const BeamBase::LayoutData* ldata, const std::vector<ChordRest*>& chordRests,
215 : int& dictator, int& pointer, int staffLines, bool isStartDictator,
216 : int stemLengthDictator, const int targetLine)
217 : {
218 8605 : const Chord* startChord = nullptr;
219 8605 : const Chord* endChord = nullptr;
220 37681 : for (const ChordRest* cr : chordRests) {
221 29076 : if (cr->isChord()) {
222 29009 : endChord = toChord(cr);
223 29009 : if (!startChord) {
224 8605 : startChord = toChord(cr);
225 : }
226 : }
227 : }
228 8605 : if (!startChord) {
229 : // beam full of only rests, don't adjust this
230 0 : return;
231 : }
232 : // min stem lengths according to how many beams there are (starting with 1)
233 : static constexpr int BEAMS_COUNT = 9;
234 : static constexpr int minStemLengths[BEAMS_COUNT] = { 11, 13, 15, 18, 21, 24, 27, 30, 33 };
235 8605 : int dictatorBeams = strokeCount(ldata, isStartDictator ? startChord : endChord);
236 8605 : int pointerBeams = strokeCount(ldata, isStartDictator ? endChord : startChord);
237 8605 : if (dictatorBeams > BEAMS_COUNT) {
238 0 : LOGE() << "Beam count " << dictatorBeams << " out of range (" << BEAMS_COUNT - 1 << ")";
239 0 : dictatorBeams = BEAMS_COUNT - 1;
240 : }
241 8605 : if (pointerBeams > BEAMS_COUNT) {
242 0 : LOGE() << "Beam count " << pointerBeams << " out of range (" << BEAMS_COUNT - 1 << ")";
243 0 : pointerBeams = BEAMS_COUNT - 1;
244 : }
245 :
246 8605 : int maxDictatorReduce = stemLengthDictator - minStemLengths[std::max(dictatorBeams - 1, 0)];
247 8605 : maxDictatorReduce = std::min(std::abs(dictator - targetLine), maxDictatorReduce);
248 :
249 8605 : bool isFlat = dictator == pointer;
250 8605 : const BeamBase::NotePosition startPos = BeamBase::NotePosition(startChord->line(), startChord->vStaffIdx());
251 8605 : const BeamBase::NotePosition endPos = BeamBase::NotePosition(endChord->line(), endChord->vStaffIdx());
252 8605 : bool isAscending = startPos > endPos;
253 8605 : int towardBeam = ldata->up ? -1 : 1;
254 8605 : int newDictator = dictator;
255 8605 : int newPointer = pointer;
256 8605 : int reduce = 0;
257 28998 : auto fourBeamException = [](int beams, int yPos) {
258 28998 : yPos += 400; // because there is some weirdness with modular division around zero, add
259 : // a large multiple of 4 so that we can guarantee that yPos%4 will be correct
260 28998 : return beams >= 4 && (yPos % 4 == 2);
261 : };
262 9854 : while (!fourBeamException(dictatorBeams, newDictator)
263 9854 : && !isValidBeamPosition(ldata->up, newDictator, isStartDictator, isAscending, isFlat, staffLines, true)) {
264 1326 : if (++reduce > maxDictatorReduce) {
265 : // we can't shorten this stem at all. bring it back to default and start extending
266 77 : newDictator = dictator;
267 77 : newPointer = pointer;
268 154 : while (!isValidBeamPosition(ldata->up, newDictator, isStartDictator, isAscending, isFlat, staffLines, true)) {
269 77 : newDictator += towardBeam;
270 77 : newPointer += towardBeam;
271 : }
272 77 : break;
273 : }
274 1249 : newDictator += -towardBeam;
275 1249 : newPointer += -towardBeam;
276 : }
277 :
278 : // newDictator is guaranteed either valid, or ==dictator
279 : // first, constrain pointer to valid position
280 8605 : newPointer = ldata->up ? std::min(newPointer, targetLine) : std::max(newPointer, targetLine);
281 : // walk it back beamwards until we get a position that satisfies both pointer and dictator
282 19144 : while (!fourBeamException(dictatorBeams, newDictator) && !fourBeamException(pointerBeams, newPointer)
283 28673 : && (!isValidBeamPosition(ldata->up, newDictator, isStartDictator, isAscending, isFlat, staffLines, true)
284 9529 : || !isValidBeamPosition(ldata->up, newPointer, !isStartDictator, isAscending, isFlat, staffLines, true))) {
285 997 : if (isFlat) {
286 0 : newDictator += towardBeam;
287 0 : newPointer += towardBeam;
288 997 : } else if (std::abs(newDictator - newPointer) == 1) {
289 0 : newDictator += towardBeam;
290 : } else {
291 997 : newPointer += towardBeam;
292 : }
293 : }
294 8605 : dictator = newDictator;
295 8605 : pointer = newPointer;
296 :
297 8605 : if (isFlat) {
298 3561 : pointer = dictator;
299 : }
300 : }
301 :
302 30226 : void BeamTremoloLayout::extendStem(const BeamBase::LayoutData* ldata, Chord* chord, double addition)
303 : {
304 30226 : LayoutContext ctx(chord->score());
305 :
306 30226 : PointF anchor = chordBeamAnchor(ldata, chord, ChordBeamAnchorType::Middle);
307 : double desiredY;
308 30226 : if (ldata->endAnchor.x() > ldata->startAnchor.x()) {
309 30097 : double proportionAlongX = (anchor.x() - ldata->startAnchor.x()) / (ldata->endAnchor.x() - ldata->startAnchor.x());
310 30097 : desiredY = proportionAlongX * (ldata->endAnchor.y() - ldata->startAnchor.y()) + ldata->startAnchor.y();
311 : } else {
312 129 : desiredY = std::max(ldata->endAnchor.y(), ldata->startAnchor.y());
313 : }
314 :
315 30226 : if (chord->up()) {
316 15661 : chord->setBeamExtension(anchor.y() - desiredY + addition);
317 : } else {
318 14565 : chord->setBeamExtension(desiredY - anchor.y() + addition);
319 : }
320 30226 : TLayout::layoutStem(chord->stem(), chord->stem()->mutldata(), ctx.conf());
321 :
322 30226 : if (chord->stemSlash()) {
323 63 : TLayout::layoutStemSlash(chord->stemSlash(), chord->stemSlash()->mutldata(), ctx.conf());
324 : }
325 :
326 : // Update stem shape
327 30226 : Stem* stem = chord->stem();
328 101728 : for (ShapeElement& shapeEl : chord->segment()->staffShape(chord->vStaffIdx()).elements()) {
329 71502 : if (shapeEl.item() == stem) {
330 21912 : shapeEl = ShapeElement(stem->ldata()->bbox().translated(stem->pos() + chord->pos()), stem);
331 : }
332 : }
333 30226 : }
334 :
335 99062 : bool BeamTremoloLayout::isBeamInsideStaff(int yPos, int staffLines, bool allowFloater)
336 : {
337 99062 : int aboveStaff = allowFloater ? -2 : -3;
338 99062 : int belowStaff = (staffLines - 1) * 4 + (allowFloater ? 2 : 3);
339 99062 : return yPos > aboveStaff && yPos < belowStaff;
340 : }
341 :
342 8961 : void BeamTremoloLayout::setSmallInnerBeamPos(const BeamBase::LayoutData* ldata, int& dictator, int& pointer, const int staffLines,
343 : const bool isFlat, const bool isSmall, const LayoutContext& ctx)
344 : {
345 : // We only want this adjustment if the beam is small and flat
346 8961 : if (!isSmall || !isFlat) {
347 8957 : return;
348 : }
349 97 : bool noteOutsideStaff = false;
350 97 : int beamCount = 0;
351 410 : for (const ChordRest* cr : ldata->elements) {
352 313 : if (!cr->isChord()) {
353 0 : continue;
354 : }
355 313 : const Chord* c = toChord(cr);
356 313 : beamCount = std::max(beamCount, strokeCount(ldata, cr));
357 313 : noteOutsideStaff |= (c->downLine() < -2 && !ldata->up) || (c->upLine() >= 11 && ldata->up);
358 : }
359 :
360 : // AND stems have been extended to the second stave line when the notes are far enough outside of the stave
361 97 : if (!noteOutsideStaff) {
362 93 : return;
363 : }
364 :
365 : // Allow fractions of quarter spaces in inner beam position, as mag can change
366 4 : const double mag = ldata->isGrace ? ctx.conf().style().value(Sid::graceNoteMag).toDouble() : ldata->mag();
367 4 : const double innerBeam = dictator + (beamCount - 1) * (ldata->up ? ldata->beamSpacing : -ldata->beamSpacing) * mag;
368 4 : const int aboveStaff = 1;
369 4 : const int belowStaff = (staffLines - 1) * 4 - 1;
370 4 : int offset = 0;
371 :
372 : // Make sure the innermost beam is at least 0.5sp within the stave
373 4 : if (ldata->up) {
374 0 : offset = std::min(offset, int(std::floor(belowStaff - innerBeam)));
375 : } else {
376 4 : offset = std::max(offset, int(std::ceil(aboveStaff - innerBeam)));
377 : }
378 :
379 : // the beam is flat, so dictator and pointer should match
380 4 : dictator += offset;
381 4 : pointer = dictator;
382 : }
383 :
384 28836 : int BeamTremoloLayout::getOuterBeamPosOffset(const BeamBase::LayoutData* ldata, int innerBeam, int beamCount, int staffLines)
385 : {
386 28836 : int spacing = (ldata->up ? -ldata->beamSpacing : ldata->beamSpacing);
387 28836 : int offset = (beamCount - 1) * spacing;
388 28836 : bool isInner = false;
389 39138 : while (offset != 0 && !isBeamInsideStaff(innerBeam + offset, staffLines, isInner)) {
390 10302 : offset -= spacing;
391 10302 : isInner = true;
392 : }
393 28836 : return offset;
394 : }
395 :
396 86005 : bool BeamTremoloLayout::isValidBeamPosition(const bool isUp, int yPos, bool isStart, bool isAscending, bool isFlat, int staffLines,
397 : bool isOuter)
398 : {
399 : // outside the staff
400 86005 : bool slantsAway = (isUp && isAscending == isStart) || (!isUp && isAscending != isStart);
401 86005 : if (!isBeamInsideStaff(yPos, staffLines, isOuter && (slantsAway || isFlat))) {
402 45404 : return true;
403 : }
404 :
405 : // removes modulo weirdness with negative numbers (i.e., right above staff)
406 40601 : yPos += 8;
407 : // is floater
408 40601 : if (yPos % 4 == 2) {
409 1720 : return false;
410 : }
411 38881 : if (isFlat) {
412 21528 : return true;
413 : }
414 : // is on line
415 17353 : if (yPos % 4 == 0) {
416 6428 : return true;
417 : }
418 : // is sitting
419 10925 : if (yPos % 4 == 3) {
420 : // return true only if we're starting here and descending, or ascending and ending here
421 5262 : return isAscending != isStart;
422 : }
423 : // is hanging
424 : // return true only if we're starting here and ascending, or decending and ending here
425 5663 : return isAscending == isStart;
426 : }
427 :
428 32 : bool BeamTremoloLayout::is64thBeamPositionException(const int beamSpacing, int& yPos, int staffLines)
429 : {
430 32 : if (beamSpacing == 4) {
431 0 : return false;
432 : }
433 32 : return yPos == 2 || yPos == staffLines * 4 - 2 || yPos == staffLines * 4 - 6 || yPos == -2;
434 : }
435 :
436 28166 : int BeamTremoloLayout::findValidBeamOffset(const BeamBase::LayoutData* ldata, int outer, int beamCount, int staffLines, bool isStart,
437 : bool isAscending, bool isFlat)
438 : {
439 28166 : bool isBeamValid = false;
440 28166 : int offset = 0;
441 28166 : int innerBeam = outer + (beamCount - 1) * (ldata->up ? ldata->beamSpacing : -ldata->beamSpacing);
442 56373 : while (!isBeamValid) {
443 28792 : while (!isValidBeamPosition(ldata->up, innerBeam + offset, isStart, isAscending, isFlat, staffLines, beamCount < 2)) {
444 585 : offset += ldata->up ? -1 : 1;
445 : }
446 28207 : int outerMostBeam = innerBeam + offset + getOuterBeamPosOffset(ldata, innerBeam + offset, beamCount, staffLines);
447 28207 : if (isValidBeamPosition(ldata->up, outerMostBeam, isStart, isAscending, isFlat, staffLines, true)
448 28207 : || (beamCount == 4 && is64thBeamPositionException(ldata->beamSpacing, outerMostBeam, staffLines))) {
449 28166 : isBeamValid = true;
450 : } else {
451 41 : offset += ldata->up ? -1 : 1;
452 : }
453 : }
454 28166 : return offset;
455 : }
456 :
457 8724 : void BeamTremoloLayout::setValidBeamPositions(const BeamBase::LayoutData* ldata, int& dictator, int& pointer, int beamCountD,
458 : int beamCountP,
459 : int staffLines,
460 : bool isStartDictator,
461 : bool isFlat, bool isAscending)
462 : {
463 8724 : bool areBeamsValid = false;
464 8724 : bool has3BeamsInsideStaff = beamCountD >= 3 || beamCountP >= 3;
465 8861 : while (!areBeamsValid && has3BeamsInsideStaff && ldata->beamSpacing != 4) {
466 629 : int dictatorInner = dictator + (beamCountD - 1) * (ldata->up ? ldata->beamSpacing : -ldata->beamSpacing);
467 : // use dictatorInner for both to simulate flat beams
468 629 : int outerDictatorOffset = getOuterBeamPosOffset(ldata, dictatorInner, beamCountD, staffLines);
469 629 : if (std::abs(outerDictatorOffset) <= ldata->beamSpacing) {
470 492 : has3BeamsInsideStaff = false;
471 492 : break;
472 : }
473 137 : int offsetD = findValidBeamOffset(ldata, dictator, beamCountD, staffLines, isStartDictator, false, true);
474 137 : int offsetP = findValidBeamOffset(ldata, pointer, beamCountP, staffLines, isStartDictator, false, true);
475 137 : int offset = (offsetD == 0 ? offsetP : offsetD);
476 137 : if (pointer == dictator) {
477 123 : dictator += offset;
478 : }
479 137 : pointer = dictator;
480 137 : if (offset == 0) {
481 109 : areBeamsValid = true;
482 : }
483 : }
484 8724 : if (isFlat) {
485 : // flat beams need more checks (non-dictator/pointer notes with floater inner beams)
486 3586 : areBeamsValid = false;
487 : }
488 17647 : while (!areBeamsValid) {
489 8923 : int dictatorOffset = findValidBeamOffset(ldata, dictator, beamCountD, staffLines, isStartDictator, isAscending, isFlat);
490 8923 : dictator += dictatorOffset;
491 8923 : pointer += dictatorOffset;
492 8923 : if (isFlat) {
493 3586 : pointer = dictator;
494 3586 : int currOffset = 0;
495 17237 : for (ChordRest* cr : ldata->elements) {
496 13651 : if (!cr->isChord() && (cr != ldata->elements.front() && cr != ldata->elements.back())) {
497 19 : continue;
498 : }
499 : // we can use dictator beam position because all of the notes have the same beam position
500 13632 : int beamCount = strokeCount(ldata, cr);
501 13632 : currOffset = findValidBeamOffset(ldata, dictator, beamCount, staffLines, isStartDictator, isAscending, isFlat);
502 13632 : if (currOffset) {
503 0 : break;
504 : }
505 : }
506 :
507 3586 : if (currOffset == 0) {
508 3586 : areBeamsValid = true;
509 : } else {
510 0 : dictator += currOffset;
511 0 : pointer += currOffset;
512 : }
513 : } else {
514 5337 : pointer += findValidBeamOffset(ldata, pointer, beamCountP, staffLines, !isStartDictator, isAscending, isFlat);
515 5337 : if ((ldata->up && pointer <= dictator) || (!ldata->up && pointer >= dictator)) {
516 213 : dictator = pointer + (ldata->up ? -1 : 1);
517 : } else {
518 5124 : areBeamsValid = true;
519 : }
520 : }
521 : }
522 8724 : }
523 :
524 5657 : void BeamTremoloLayout::addMiddleLineSlant(const BeamBase::LayoutData* ldata, int& dictator, int& pointer, int beamCount, int targetLine,
525 : int interval, int desiredSlant)
526 : {
527 5657 : bool isSmall = ldata->mag() < 1. || ldata->isGrace;
528 5657 : if (interval == 0 || (!isSmall && beamCount > 2 && ldata->beamSpacing != 4) || noSlope(ldata->beam)) {
529 466 : return;
530 : }
531 5191 : bool isOnTargetLine = pointer == targetLine && (std::abs(pointer - dictator) < 2);
532 5191 : if (isOnTargetLine) {
533 236 : if (std::abs(desiredSlant) == 1 || interval == 1 || (beamCount == 2 && ldata->beamSpacing != 4 && !isSmall)) {
534 120 : dictator = targetLine + (ldata->up ? -1 : 1);
535 : } else {
536 116 : dictator = targetLine + (ldata->up ? -2 : 2);
537 : }
538 : }
539 : }
540 :
541 9000 : void BeamTremoloLayout::add8thSpaceSlant(BeamBase::LayoutData* ldata, PointF& dictatorAnchor, int dictator, int pointer, int beamCount,
542 : int interval, int targetLine, bool isFlat)
543 : {
544 9000 : if (beamCount != 3 || noSlope(ldata->beam) || ldata->beamSpacing != 3) {
545 8496 : return;
546 : }
547 504 : if ((isFlat && dictator != targetLine) || (dictator != pointer) || interval == 0) {
548 480 : return;
549 : }
550 24 : if ((ldata->up && (dictator + 4) % 4 == 3) || (!ldata->up && (dictator + 4) % 4 == 1)) {
551 4 : return;
552 : }
553 20 : dictatorAnchor.setY(dictatorAnchor.y() + (ldata->up ? -0.125 * ldata->spatium : 0.125 * ldata->spatium));
554 20 : ldata->beamDist += 0.0625 * ldata->spatium;
555 : }
556 :
557 395 : bool BeamTremoloLayout::computeTremoloUp(const BeamBase::LayoutData* ldata)
558 : {
559 395 : if (!ldata->beam || !ldata->beam->cross()) {
560 395 : return ldata->trem->up();
561 : }
562 0 : Chord* c1 = ldata->trem->chord1();
563 0 : Chord* c2 = ldata->trem->chord2();
564 0 : int staffMove = 0;
565 0 : for (ChordRest* cr : ldata->beam->elements()) {
566 0 : if (cr->staffMove() != 0) {
567 0 : staffMove = cr->staffMove();
568 0 : break;
569 : }
570 : }
571 : // for inset trems in cross-staff beams, there are two potentialities:
572 : // 1) both trem notes are on the same staff
573 0 : if (c1->staffMove() == c2->staffMove()) {
574 : // (in which case, up is opposite to the move)
575 0 : if (staffMove < 0) {
576 0 : return c1->staffMove() != staffMove;
577 : } else {
578 0 : return c1->staffMove() == staffMove;
579 : }
580 : } else {
581 : // or 2) the trem notes are between staves. what a pain.
582 0 : return ldata->trem->up();
583 : }
584 : }
585 :
586 79340 : int BeamTremoloLayout::strokeCount(const BeamBase::LayoutData* ldata, const ChordRest* cr)
587 : {
588 79340 : if (cr->isRest()) {
589 0 : return cr->beams();
590 : }
591 79340 : int strokes = 0;
592 79340 : const Chord* c = toChord(cr);
593 79340 : if (ldata->beamType == BeamType::TREMOLO) {
594 1650 : strokes = ldata->trem->lines();
595 77690 : } else if (ldata->beamType == BeamType::BEAM && c->tremoloTwoChord()) {
596 240 : strokes = c->tremoloTwoChord()->lines();
597 : }
598 79340 : strokes += ldata->beam ? cr->beams() : 0;
599 79340 : return strokes;
600 : }
601 :
602 9150 : bool BeamTremoloLayout::calculateAnchors(const BeamBase* item, BeamBase::LayoutData* ldata, const LayoutContext& ctx,
603 : const std::vector<ChordRest*>& chordRests,
604 : const std::vector<BeamBase::NotePosition>& notePositions)
605 : {
606 9150 : ldata->startAnchor = PointF();
607 9150 : ldata->endAnchor = PointF();
608 9150 : if (ldata->beamType == BeamType::TREMOLO && ldata->beam) {
609 : // this is a trem inside a beam, and we are currently calculating the anchors of the tremolo.
610 : // we can do this as long as the beam has been layed out first: it saves trem anchor positions
611 120 : ChordRest* cr1 = ldata->trem->chord1();
612 120 : ChordRest* cr2 = ldata->trem->chord2();
613 120 : double anchorX1 = chordBeamAnchorX(ldata, cr1, ChordBeamAnchorType::Middle);
614 120 : double anchorX2 = chordBeamAnchorX(ldata, cr2, ChordBeamAnchorType::Middle);
615 120 : for (const TremAnchor& t : ldata->beam->tremAnchors()) {
616 120 : if (t.chord1 == cr1) {
617 120 : ldata->startAnchor = PointF(anchorX1, t.y1);
618 120 : ldata->endAnchor = PointF(anchorX2, t.y2);
619 120 : ldata->slope = (t.y2 - t.y1) / (anchorX2 - anchorX1);
620 120 : return true;
621 : }
622 : }
623 : // if we finish the loop before we find the anchor for this tremolo, it could mean that the
624 : // beam hasn't been laid out yet
625 0 : return false;
626 : }
627 9030 : Chord* startChord = nullptr;
628 9030 : Chord* endChord = nullptr;
629 9030 : ChordRest* startCr = nullptr;
630 9030 : ChordRest* endCr = nullptr;
631 9030 : ldata->elements = chordRests;
632 9030 : if (chordRests.empty()) {
633 0 : return false;
634 : }
635 :
636 9030 : bool hasChordAboveBeam = false;
637 9030 : bool hasChordBelowBeam = false;
638 :
639 39384 : for (auto chordRest : chordRests) {
640 30354 : if (!startCr) {
641 9030 : startCr = chordRest;
642 : }
643 30354 : endCr = chordRest;
644 30354 : if (chordRest->isChord()) {
645 30279 : if (!startChord) {
646 9030 : startChord = toChord(chordRest);
647 9030 : endChord = startChord;
648 : } else {
649 21249 : endChord = toChord(chordRest);
650 : }
651 30279 : ChordLayout::layoutStem(toChord(chordRest), ctx);
652 : }
653 30354 : if (chordRest->isBelowCrossBeam(item)) {
654 30308 : hasChordBelowBeam = true;
655 : } else {
656 46 : hasChordAboveBeam = true;
657 : }
658 : }
659 9030 : if (!startChord) {
660 : // we were passed a vector of only rests. we don't support beams across only rests
661 : // this beam will be deleted in LayoutBeams
662 0 : return false;
663 : }
664 9030 : ldata->isGrace = startChord->isGrace();
665 9030 : ldata->notePositions = notePositions;
666 :
667 : // Set cross staff beam position, above, below or between staves
668 9030 : ldata->crossStaffBeamPos = BeamBase::CrossStaffBeamPosition::INVALID;
669 9030 : if (item->minCRMove() != item->maxCRMove()) {
670 26 : if (hasChordAboveBeam && !hasChordBelowBeam) {
671 0 : ldata->crossStaffBeamPos = BeamBase::CrossStaffBeamPosition::BELOW;
672 26 : } else if (hasChordBelowBeam && !hasChordAboveBeam) {
673 0 : ldata->crossStaffBeamPos = BeamBase::CrossStaffBeamPosition::ABOVE;
674 26 : } else if (hasChordAboveBeam && hasChordBelowBeam) {
675 26 : ldata->crossStaffBeamPos = BeamBase::CrossStaffBeamPosition::BETWEEN;
676 : }
677 : }
678 :
679 9030 : if (calculateAnchorsCross(item, ldata, ctx.conf())) {
680 26 : return true;
681 : }
682 :
683 9004 : const int closestMove = ldata->crossStaffBeamPos == BeamBase::CrossStaffBeamPosition::ABOVE ? item->minCRMove() : item->maxCRMove();
684 9004 : const staff_idx_t closestStaffToBeam = ldata->crossStaffBeamPos == BeamBase::CrossStaffBeamPosition::ABOVE
685 9004 : || ldata->crossStaffBeamPos
686 9004 : == BeamBase::CrossStaffBeamPosition::BELOW ? item->staffIdx()
687 9004 : + closestMove : startChord->vStaffIdx();
688 :
689 9004 : std::vector<Chord*> chordsClosestToBeam;
690 :
691 39269 : for (ChordRest* cr : ldata->elements) {
692 30265 : if (cr->isChord() && cr->vStaffIdx() == closestStaffToBeam) {
693 30190 : chordsClosestToBeam.push_back(toChord(cr));
694 : }
695 : }
696 :
697 9004 : ldata->startAnchor = chordBeamAnchor(ldata, startChord, ChordBeamAnchorType::Start);
698 9004 : ldata->endAnchor = chordBeamAnchor(ldata, endChord, ChordBeamAnchorType::End);
699 :
700 9004 : double startLength = startChord->defaultStemLength();
701 9004 : double endLength = endChord->defaultStemLength();
702 9004 : double startAnchorBase = ldata->startAnchor.y() + (ldata->up ? startLength : -startLength);
703 9004 : double endAnchorBase = ldata->endAnchor.y() + (ldata->up ? endLength : -endLength);
704 9004 : int startNote = ldata->up ? startChord->upNote()->line() : startChord->downNote()->line();
705 9004 : int endNote = ldata->up ? endChord->upNote()->line() : endChord->downNote()->line();
706 9004 : if (ldata->tab) {
707 4 : startNote = ldata->up ? startChord->upString() : startChord->downString();
708 4 : endNote = ldata->up ? endChord->upString() : endChord->downString();
709 : }
710 9004 : staff_idx_t startStaff = startChord->vStaffIdx();
711 9004 : staff_idx_t endStaff = endChord->vStaffIdx();
712 9004 : BeamBase::NotePosition startPos = BeamBase::NotePosition(startNote, startStaff);
713 9004 : BeamBase::NotePosition endPos = BeamBase::NotePosition(endNote, endStaff);
714 :
715 9004 : const int interval = std::abs(startNote - endNote);
716 9004 : const bool isStartDictator = ldata->up ? startPos < endPos : startPos > endPos;
717 9004 : const double quarterSpace = ldata->spatium / 4;
718 9004 : PointF startAnchor = ldata->startAnchor - item->pagePos();
719 9004 : PointF endAnchor = ldata->endAnchor - item->pagePos();
720 9004 : int dictator = round((isStartDictator ? startAnchor.y() : endAnchor.y()) / quarterSpace);
721 9004 : int pointer = round((isStartDictator ? endAnchor.y() : startAnchor.y()) / quarterSpace);
722 :
723 9004 : const int staffLines = ctx.dom().staff(closestStaffToBeam)->lines(ldata->tick);
724 : // This is the middle of the stave if the notes are normal size or the second line from the bottom if notes are small
725 9004 : const int targetLine = getTargetStaffLine(ldata, ctx, startChord, endChord, staffLines, closestStaffToBeam, item->staffIdx());
726 :
727 : int slant
728 9004 : = computeDesiredSlant(item, ldata, startPos, endPos, chordsClosestToBeam, targetLine, dictator, pointer);
729 9004 : bool isFlat = slant == 0;
730 : SlopeConstraint specialSlant
731 9004 : = isFlat ? getSlopeConstraint(ldata, startPos, endPos) : SlopeConstraint::NO_CONSTRAINT;
732 9004 : bool forceFlat = specialSlant == SlopeConstraint::FLAT;
733 9004 : bool smallSlant = specialSlant == SlopeConstraint::SMALL_SLOPE;
734 9004 : if (isFlat) {
735 3659 : dictator = ldata->up ? std::min(pointer, dictator) : std::max(pointer, dictator);
736 3659 : pointer = dictator;
737 : } else {
738 5345 : if ((dictator > pointer) != (isStartDictator ? startPos > endPos : endPos > startPos)) {
739 4 : dictator = pointer - slant;
740 : } else {
741 5341 : pointer = dictator + slant;
742 : }
743 : }
744 9004 : bool isAscending = startPos > endPos;
745 9004 : int beamCountD = strokeCount(ldata, isStartDictator ? startChord : endChord);
746 9004 : int beamCountP = strokeCount(ldata, isStartDictator ? endChord : startChord);
747 :
748 9004 : int stemLengthStart = std::abs(round((startAnchorBase - ldata->startAnchor.y()) / ldata->spatium * 4));
749 9004 : int stemLengthEnd = std::abs(round((endAnchorBase - ldata->endAnchor.y()) / ldata->spatium * 4));
750 9004 : int stemLengthDictator = isStartDictator ? stemLengthStart : stemLengthEnd;
751 9004 : bool isSmall = ldata->mag() < 1. || ldata->isGrace;
752 9004 : if (endAnchor.x() > startAnchor.x()) {
753 : /* When beam layout is called before horizontal spacing (see LayoutMeasure::getNextMeasure() to
754 : * know why) the x positions aren't yet determined and may be all zero, which would cause the
755 : * following function to get stuck in a loop. The if() condition avoids that case. */
756 :
757 : // Make sure grace & small note inner beams are within the stave
758 8961 : setSmallInnerBeamPos(ldata, dictator, pointer, staffLines, isFlat, isSmall, ctx);
759 :
760 8961 : if (!isSmall) {
761 : // Adjust anchor stems
762 8605 : offsetBeamWithAnchorShortening(ldata, chordRests, dictator, pointer, staffLines, isStartDictator, stemLengthDictator,
763 : targetLine);
764 : }
765 : // Adjust inner stems
766 8961 : offsetBeamToRemoveCollisions(item, ldata, chordRests, dictator, pointer, startAnchor.x(), endAnchor.x(), isFlat, isStartDictator);
767 : }
768 :
769 9004 : int beamCount = std::max(beamCountD, beamCountP);
770 9004 : if (!ldata->tab) {
771 9000 : if (!ldata->isGrace) {
772 8724 : setValidBeamPositions(ldata, dictator, pointer, beamCountD, beamCountP, staffLines, isStartDictator, isFlat, isAscending);
773 : }
774 9000 : if (!forceFlat) {
775 5657 : addMiddleLineSlant(ldata, dictator, pointer, beamCount, targetLine, interval, smallSlant ? 1 : slant);
776 : }
777 : }
778 :
779 9004 : ldata->startAnchor.setY(quarterSpace * (isStartDictator ? dictator : pointer) + item->pagePos().y());
780 9004 : ldata->endAnchor.setY(quarterSpace * (isStartDictator ? pointer : dictator) + item->pagePos().y());
781 :
782 9004 : bool add8th = true;
783 9004 : if (item->isType(ElementType::BEAM) && item_cast<const Beam*>(item)->userModified()) {
784 0 : add8th = false;
785 : }
786 9004 : if (!ldata->tab && add8th) {
787 9000 : add8thSpaceSlant(ldata, isStartDictator ? ldata->startAnchor : ldata->endAnchor, dictator, pointer, beamCount, interval,
788 : targetLine, isFlat);
789 : }
790 9004 : ldata->startAnchor.setX(chordBeamAnchorX(ldata, startCr, ChordBeamAnchorType::Start));
791 9004 : ldata->endAnchor.setX(chordBeamAnchorX(ldata, endCr, ChordBeamAnchorType::End));
792 9004 : return true;
793 9004 : }
794 :
795 9030 : bool BeamTremoloLayout::calculateAnchorsCross(const BeamBase* item, BeamBase::LayoutData* ldata,
796 : const LayoutConfiguration& conf)
797 : {
798 9030 : if (ldata->crossStaffBeamPos != BeamBase::CrossStaffBeamPosition::BETWEEN) {
799 : // The beam is either not cross or above/below and must be layed out like a non-cross beam
800 9004 : return false;
801 : }
802 :
803 26 : double spatium = conf.spatium();
804 26 : ChordRest* startCr = ldata->elements.front();
805 26 : ChordRest* endCr = ldata->elements.back();
806 :
807 26 : const double quarterSpace = spatium / 4;
808 : // imagine a line of beamed notes all in a row on the same staff. the first and last of those
809 : // are the 'outside' notes, and the slant of the beam is going to be affected by the 'middle' notes
810 : // between them.
811 : // we have to keep track of this for both staves.
812 26 : Chord* topFirst = nullptr;
813 26 : Chord* topLast = nullptr;
814 26 : Chord* bottomFirst = nullptr;
815 26 : Chord* bottomLast = nullptr;
816 26 : int maxMiddleTopLine = std::numeric_limits<int>::min(); // lowest note in the top staff
817 26 : int minMiddleBottomLine = std::numeric_limits<int>::max(); // highest note in the bottom staff
818 26 : int prevTopLine = maxMiddleTopLine; // previous note's line position (top)
819 26 : int prevBottomLine = minMiddleBottomLine; // previous note's line position (bottom)
820 : // if the immediate neighbor of one of the two 'outside' notes on either the top or bottom
821 : // are the same as that outside note, we need to record it so that we can add a 1/4 space slant.
822 26 : bool secondTopIsSame = false;
823 26 : bool secondBottomIsSame = false;
824 26 : bool penultimateTopIsSame = false;
825 26 : bool penultimateBottomIsSame = false;
826 26 : double maxY = std::numeric_limits<double>::max();
827 26 : double minY = std::numeric_limits<double>::min();
828 : // Get switches between above & below the beam
829 : // The direction is neutral if there is more than one switch
830 26 : bool prevBelowBeam = ldata->elements.front()->isBelowCrossBeam(item);
831 26 : bool hasBeamSideSwitch = false;
832 26 : int beamSideSwitchDirection = 0;
833 :
834 : // Get switches between staves on the same side of a beam
835 : // The direction is neutral is there is more than one switch
836 26 : staff_idx_t prevStaff = ldata->elements.front()->vStaffIdx();
837 26 : bool hasStaffSwitch = false;
838 26 : int staffSwitchDirection = 0;
839 :
840 : // Find the notes on the top and bottom of staves
841 : //
842 26 : bool checkNextTop = false;
843 26 : bool checkNextBottom = false;
844 115 : for (ChordRest* cr : ldata->elements) {
845 89 : if (!cr->isChord()) {
846 0 : continue;
847 : }
848 89 : Chord* c = toChord(cr);
849 :
850 89 : if (c->isBelowCrossBeam(item) == prevBelowBeam) {
851 42 : c->vStaffIdx();
852 42 : if (prevStaff != c->vStaffIdx()) {
853 0 : int diff = static_cast<int>(prevStaff - c->vStaffIdx());
854 0 : staffSwitchDirection = hasStaffSwitch ? 0 : (diff < 0 ? -1 : 1);
855 0 : hasStaffSwitch = true;
856 : }
857 : }
858 89 : prevStaff = c->vStaffIdx();
859 :
860 89 : if (c->isBelowCrossBeam(item)) {
861 : // this chord is on the bottom staff
862 43 : if (penultimateBottomIsSame) {
863 : // the chord we took as the penultimate bottom note wasn't.
864 : // so treat it properly as a middle note
865 0 : minMiddleBottomLine = std::min(minMiddleBottomLine, prevBottomLine);
866 0 : penultimateBottomIsSame = false;
867 : }
868 43 : checkNextTop = false; // we are no longer looking for the second note in the top
869 : // staff being the same as the first--this note is on the bottom.
870 43 : if (!bottomFirst) {
871 26 : bottomFirst = c;
872 26 : checkNextBottom = true; // this was the first bottom note, so check for second next time
873 : } else {
874 17 : penultimateBottomIsSame = prevBottomLine == c->line();
875 17 : if (!penultimateBottomIsSame) {
876 17 : minMiddleBottomLine = std::min(minMiddleBottomLine, prevBottomLine);
877 : }
878 17 : if (checkNextBottom) {
879 : // this is the second bottom note, so we should see if this one is same line as first
880 11 : secondBottomIsSame = c->line() == bottomFirst->line();
881 11 : checkNextBottom = false;
882 : } else {
883 6 : prevBottomLine = c->line();
884 : }
885 17 : bottomLast = c;
886 : }
887 43 : if (!prevBelowBeam) {
888 22 : beamSideSwitchDirection = hasBeamSideSwitch ? 0 : -1;
889 22 : hasBeamSideSwitch = true;
890 : }
891 43 : prevBelowBeam = true;
892 43 : maxY = std::min(maxY, chordBeamAnchorY(ldata, toChord(c)));
893 : } else {
894 : // this chord is on the top staff
895 46 : if (penultimateTopIsSame) {
896 : // the chord we took as the penultimate top note wasn't.
897 : // so treat it properly as a middle note
898 0 : maxMiddleTopLine = std::max(maxMiddleTopLine, prevTopLine);
899 0 : penultimateTopIsSame = false;
900 : }
901 46 : checkNextBottom = false; // no longer looking for a bottom second note since this is on top
902 46 : if (!topFirst) {
903 26 : topFirst = c;
904 26 : checkNextTop = true;
905 : } else {
906 20 : penultimateTopIsSame = prevTopLine == c->line();
907 20 : if (!penultimateTopIsSame) {
908 20 : maxMiddleTopLine = std::max(maxMiddleTopLine, prevTopLine);
909 : }
910 20 : if (checkNextTop) {
911 5 : secondTopIsSame = c->line() == topFirst->line();
912 5 : checkNextTop = false;
913 : } else {
914 15 : prevTopLine = c->line();
915 : }
916 20 : topLast = c;
917 : }
918 46 : if (prevBelowBeam) {
919 25 : beamSideSwitchDirection = hasBeamSideSwitch ? 0 : 1;
920 25 : hasBeamSideSwitch = true;
921 : }
922 46 : prevBelowBeam = false;
923 46 : minY = std::max(minY, chordBeamAnchorY(ldata, toChord(c)));
924 : }
925 : }
926 :
927 26 : double yMidPoint = (maxY + minY) / 2;
928 26 : if (item->isBeam()) {
929 : // Cross-staff beams may have segments both above and below the midline.
930 : // Compute the y-difference between the top and bottom segment and aim for centering in between.
931 26 : const Beam* beam = toBeam(item);
932 26 : const std::vector<BeamSegment*>& beamSegments = beam->beamSegments();
933 26 : const Chord* limitingChordAbove = beam->ldata()->limitingChordAbove;
934 26 : const Chord* limitingChordBelow = beam->ldata()->limitingChordBelow;
935 26 : if (!beamSegments.empty() && limitingChordAbove && limitingChordBelow) {
936 16 : double yCenterSegment = 0.5 * (beamSegments.front()->line.y1() + beamSegments.front()->line.y2());
937 :
938 16 : const BeamSegment* topBeamSegmentForChordAbove = beam->topLevelSegmentForElement(limitingChordAbove);
939 16 : double yTopSegAbove = 0.5 * (topBeamSegmentForChordAbove->line.y1() + topBeamSegmentForChordAbove->line.y2());
940 16 : double distAbove = yTopSegAbove - yCenterSegment;
941 :
942 16 : const BeamSegment* topBeamSegmentForChordBelow = beam->topLevelSegmentForElement(limitingChordBelow);
943 16 : double yTopSegBelow = 0.5 * (topBeamSegmentForChordBelow->line.y1() + topBeamSegmentForChordBelow->line.y2());
944 16 : double distBelow = yTopSegBelow - yCenterSegment;
945 :
946 16 : double averageDist = 0.5 * (distAbove + distBelow);
947 16 : yMidPoint -= 0.5 * averageDist;
948 : }
949 : }
950 :
951 26 : ldata->startAnchor.setY(yMidPoint);
952 26 : ldata->endAnchor.setY(yMidPoint);
953 26 : ldata->startAnchor.setX(chordBeamAnchorX(ldata, startCr, ChordBeamAnchorType::Start));
954 26 : ldata->endAnchor.setX(chordBeamAnchorX(ldata, endCr, ChordBeamAnchorType::End));
955 :
956 26 : ldata->slope = 0;
957 :
958 : // Calculates & sets slope
959 26 : if (!noSlope(ldata->beam)) {
960 26 : int topFirstLine = topFirst ? topFirst->downNote()->line() : 0;
961 26 : int topLastLine = topLast ? topLast->downNote()->line() : 0;
962 26 : int bottomFirstLine = bottomFirst ? bottomFirst->upNote()->line() : 0;
963 26 : int bottomLastLine = bottomLast ? bottomLast->upNote()->line() : 0;
964 26 : bool constrainTopToQuarter = false;
965 26 : bool constrainBottomToQuarter = false;
966 26 : if ((topFirstLine > topLastLine && secondTopIsSame)
967 26 : || (topFirstLine < topLastLine && penultimateTopIsSame)) {
968 0 : constrainTopToQuarter = true;
969 : }
970 26 : if ((bottomFirstLine < bottomLastLine && secondBottomIsSame)
971 26 : || (bottomFirstLine > bottomLastLine && penultimateBottomIsSame)) {
972 0 : constrainBottomToQuarter = true;
973 : }
974 26 : if (!topLast && !bottomLast && topFirst && bottomFirst) {
975 : // if there are only two notes, one on each staff, special case
976 : // take max slope into account
977 : double yFirst, yLast;
978 : // we can't rely on comparing topFirst and bottomFirst ->tick() because beamed
979 : // graces have the same tick
980 3 : if (ldata->elements[0] == topFirst) {
981 3 : yFirst = StemLayout::stemPos(topFirst).y();
982 3 : yLast = StemLayout::stemPos(bottomFirst).y();
983 : } else {
984 0 : yFirst = StemLayout::stemPos(bottomFirst).y();
985 0 : yLast = StemLayout::stemPos(topFirst).y();
986 : }
987 3 : int desiredSlant = round((yFirst - yLast) / spatium);
988 3 : int slant = std::min(std::abs(desiredSlant), getMaxSlope(ldata));
989 3 : slant *= (desiredSlant < 0) ? -quarterSpace : quarterSpace;
990 3 : ldata->startAnchor.ry() += (slant / 2);
991 3 : ldata->endAnchor.ry() -= (slant / 2);
992 3 : } else {
993 : // Get the direction of groups of notes on top and bottom staves and the direction of switches between sides of the beam and switches between staves
994 : // Directions are up, down or neutral
995 23 : bool forceHoriz = false;
996 23 : bool singleNoteTop = false;
997 23 : bool singleNoteBottom = false;
998 :
999 23 : int topSlant = topFirstLine - topLastLine;
1000 23 : if (constrainTopToQuarter && topSlant != 0) {
1001 0 : topSlant = topFirstLine < topLastLine ? -1 : 1;
1002 : }
1003 23 : if (!topLast) {
1004 : // If there's only one note, set direction to neutral
1005 3 : topSlant = 0;
1006 3 : singleNoteTop = true;
1007 : }
1008 23 : int bottomSlant = bottomFirstLine - bottomLastLine;
1009 23 : if (constrainBottomToQuarter && bottomSlant != 0) {
1010 0 : bottomSlant = bottomFirstLine < bottomLastLine ? -1 : 1;
1011 : }
1012 23 : if (!bottomLast) {
1013 : // If there's only one note, set direction to neutral
1014 6 : bottomSlant = 0;
1015 6 : singleNoteBottom = true;
1016 : }
1017 23 : if ((maxMiddleTopLine >= std::max(topFirstLine, topLastLine)
1018 23 : || (minMiddleBottomLine <= std::min(bottomFirstLine, bottomLastLine)))) {
1019 0 : forceHoriz = true;
1020 : }
1021 :
1022 23 : const int topSlantDir = topSlant <= 0 ? (topSlant == 0 ? 0 : -1) : 1;
1023 23 : const int bottomSlantDir = bottomSlant <= 0 ? (bottomSlant == 0 ? 0 : -1) : 1;
1024 : // Check all directions which aren't neutral (0) match
1025 : // If there is any contradiction between top staff direction, bottom direction, beam side switch direction and staff switch direction, force horizontal
1026 : // If all are neutral, force horizontal
1027 23 : int overallDirection = 0;
1028 58 : for (int direction : { topSlantDir, bottomSlantDir, beamSideSwitchDirection, staffSwitchDirection }) {
1029 50 : if (direction != 0) {
1030 15 : overallDirection = direction;
1031 15 : break;
1032 : }
1033 : }
1034 :
1035 23 : const bool overallSlantFlat = overallDirection == 0;
1036 23 : const bool topSlantOverallDiff = (topSlantDir != 0 && topSlantDir != overallDirection);
1037 23 : const bool bottomSlantOverallDiff = (bottomSlantDir != 0 && bottomSlantDir != overallDirection);
1038 23 : const bool beamSideSwitchOverallDiff = (beamSideSwitchDirection != 0 && beamSideSwitchDirection != overallDirection);
1039 23 : const bool staffSwitchOverallDiff = (staffSwitchDirection != 0 && staffSwitchDirection != overallDirection);
1040 17 : const bool flattenNeutralBeamSideSwitch = beamSideSwitchDirection == 0 && (topSlantDir != bottomSlantDir)
1041 40 : && !(singleNoteTop || singleNoteBottom);
1042 :
1043 23 : forceHoriz = forceHoriz || overallSlantFlat || flattenNeutralBeamSideSwitch
1044 46 : || (topSlantOverallDiff || bottomSlantOverallDiff || beamSideSwitchOverallDiff || staffSwitchOverallDiff);
1045 :
1046 23 : if (!forceHoriz) {
1047 9 : int slant = 0;
1048 9 : if (topSlant == 0) {
1049 3 : slant = bottomSlant;
1050 6 : } else if (bottomSlant == 0) {
1051 6 : slant = topSlant;
1052 : } else {
1053 0 : slant = (std::abs(topSlant) < std::abs(bottomSlant)) ? topSlant : bottomSlant;
1054 : }
1055 :
1056 9 : const int absSlant = std::min(std::abs(slant), getMaxSlope(ldata));
1057 9 : const double slope = absSlant * ((slant < 0) ? -quarterSpace : quarterSpace);
1058 9 : ldata->startAnchor.ry() += (slope / 2);
1059 9 : ldata->endAnchor.ry() -= (slope / 2);
1060 : }
1061 : }
1062 26 : ldata->slope = (ldata->endAnchor.y() - ldata->startAnchor.y()) / (ldata->endAnchor.x() - ldata->startAnchor.x());
1063 : }
1064 26 : return true;
1065 : }
1066 :
1067 5759 : bool BeamTremoloLayout::noSlope(const Beam* beam)
1068 : {
1069 5759 : return beam && beam->noSlope();
1070 : }
1071 :
1072 9004 : int BeamTremoloLayout::getTargetStaffLine(const BeamBase::LayoutData* ldata, const LayoutContext& ctx,
1073 : const ChordRest* startChord, const ChordRest* endChord,
1074 : const int staffLines, const staff_idx_t beamClosestStaffIdx, const staff_idx_t actualBeamStaffIdx)
1075 : {
1076 9004 : bool isFullSize = muse::RealIsEqual(ldata->mag(), 1.0) && !ldata->isGrace;
1077 9004 : bool useWideBeams = ctx.conf().styleB(Sid::useWideBeams);
1078 9004 : int startBeams = strokeCount(ldata, startChord);
1079 9004 : int endBeams = strokeCount(ldata, endChord);
1080 18008 : int startTargetLine = StemLayout::minStaffOverlap(ldata->up, staffLines, startBeams, false,
1081 9004 : ldata->beamSpacing / 4.0, useWideBeams, isFullSize);
1082 18008 : int endTargetLine = StemLayout::minStaffOverlap(ldata->up, staffLines, endBeams, false,
1083 9004 : ldata->beamSpacing / 4.0, useWideBeams, !ldata->isGrace);
1084 :
1085 9004 : int diff = 0;
1086 : // Get diff between beam closest staff and actual staff, unless its full cross?
1087 9004 : if (ldata->crossStaffBeamPos == BeamBase::CrossStaffBeamPosition::ABOVE
1088 9004 : || ldata->crossStaffBeamPos == BeamBase::CrossStaffBeamPosition::BELOW) {
1089 : // Make sure we calculate the distance to the target line on the stave closest to the beam
1090 0 : const System* curSys = startChord->measure()->system();
1091 0 : const int dir = ldata->up ? -1 : 1;
1092 0 : if (curSys && beamClosestStaffIdx != actualBeamStaffIdx) {
1093 0 : diff = std::round(std::abs(curSys->staffYpage(beamClosestStaffIdx) - curSys->staffYpage(
1094 0 : actualBeamStaffIdx)) / ldata->spatium * 4 * dir);
1095 : }
1096 : }
1097 : // offset target line by 1 or -1 since the anchor is at the middle of the beam,
1098 : // not at the tip of the stem
1099 9004 : if (ldata->up) {
1100 4357 : return std::min(startTargetLine + diff, endTargetLine + diff) + 1;
1101 : }
1102 4647 : return std::max(startTargetLine + diff, endTargetLine + diff) - 1;
1103 : }
1104 :
1105 9004 : int BeamTremoloLayout::computeDesiredSlant(const BeamBase* item, const BeamBase::LayoutData* ldata, const BeamBase::NotePosition& startPos,
1106 : const BeamBase::NotePosition& endPos, std::vector<Chord*> closestChordsToBeam,
1107 : int targetLine, int dictator, int pointer)
1108 : {
1109 9004 : if (item->isType(ElementType::BEAM) && item_cast<const Beam*>(item)->noSlope()) {
1110 18 : return 0;
1111 : }
1112 8986 : int dictatorExtension = targetLine - dictator; // we need to make sure that beams extended to the middle line
1113 8986 : int pointerExtension = targetLine - pointer; // are properly treated as flat.
1114 8986 : if (ldata->up) {
1115 4345 : dictatorExtension = std::min(dictatorExtension, 0);
1116 4345 : pointerExtension = std::min(pointerExtension, 0);
1117 : } else {
1118 4641 : dictatorExtension = std::max(dictatorExtension, 0);
1119 4641 : pointerExtension = std::max(pointerExtension, 0);
1120 : }
1121 8986 : if (dictator + dictatorExtension == targetLine && pointer + pointerExtension == targetLine) {
1122 239 : return 0;
1123 : }
1124 8747 : if (startPos == endPos) {
1125 2818 : return 0;
1126 : }
1127 5929 : SlopeConstraint slopeConstrained = getSlopeConstraint(ldata, startPos, endPos);
1128 5929 : if (slopeConstrained == SlopeConstraint::FLAT) {
1129 584 : return 0;
1130 5345 : } else if (slopeConstrained == SlopeConstraint::SMALL_SLOPE) {
1131 128 : return dictator > pointer ? -1 : 1;
1132 : }
1133 :
1134 : // calculate max slope based on distance between first and last chords
1135 5217 : int maxSlope = getMaxSlope(ldata);
1136 :
1137 5217 : if (ldata->crossStaffBeamPos == BeamBase::CrossStaffBeamPosition::ABOVE
1138 5217 : || ldata->crossStaffBeamPos == BeamBase::CrossStaffBeamPosition::BELOW) {
1139 : // Calculate slant between first and last notes
1140 0 : int beamDir = startPos == endPos ? 0 : (startPos > endPos ? 1 : -1);
1141 :
1142 : // Calculate slant between notes on stave closest to the beam
1143 0 : size_t closestChordsSize = closestChordsToBeam.size();
1144 0 : BeamBase::NotePosition closestStartPos(closestChordsSize > 0 ? closestChordsToBeam.front()->line() : 0,
1145 0 : closestChordsSize > 0 ? closestChordsToBeam.front()->vStaffIdx() : 0);
1146 0 : BeamBase::NotePosition closestEndPos(closestChordsSize > 0 ? closestChordsToBeam.back()->line() : 0,
1147 0 : closestChordsSize > 0 ? closestChordsToBeam.back()->vStaffIdx() : 0);
1148 0 : int staveClosestToBeamDir = closestStartPos == closestEndPos ? 0 : (closestStartPos > closestEndPos ? 1 : -1);
1149 :
1150 : // Contradiction, beam must be flat
1151 0 : if (beamDir != staveClosestToBeamDir && beamDir != 0 && staveClosestToBeamDir != 0) {
1152 0 : return 0;
1153 : }
1154 :
1155 : // When the notes on the stave closest to the beam have a neutral direction, set a slant of 1/-1 in the direction of the staff change between first and last chords
1156 0 : if (staveClosestToBeamDir == 0 && beamDir != 0) {
1157 0 : if (startPos.staff != endPos.staff) {
1158 0 : return std::abs(beamDir) * (ldata->up ? 1 : -1);
1159 : }
1160 : }
1161 : }
1162 :
1163 : // calculate max slope based on note interval
1164 5217 : int interval = std::min(std::abs(endPos.line - startPos.line), (int)_maxSlopes.size() - 1);
1165 5217 : return std::min(maxSlope, _maxSlopes[interval]) * (ldata->up ? 1 : -1);
1166 : }
1167 :
1168 9588 : SlopeConstraint BeamTremoloLayout::getSlopeConstraint(const BeamBase::LayoutData* ldata, const BeamBase::NotePosition& startPos,
1169 : const BeamBase::NotePosition& endPos)
1170 : {
1171 9588 : if (ldata->notePositions.empty()) {
1172 176 : return SlopeConstraint::NO_CONSTRAINT;
1173 : }
1174 :
1175 : // 0 to constrain to flat, 1 to constrain to 0.25, <0 for no constraint
1176 9412 : if (startPos == endPos) {
1177 2755 : return SlopeConstraint::FLAT;
1178 6657 : } else if (ldata->beamType == BeamType::TREMOLO) {
1179 : // tremolos don't need the small slope constraint since they only have two notes
1180 260 : return SlopeConstraint::NO_CONSTRAINT;
1181 : }
1182 :
1183 : // if a note is more extreme than the endpoints, slope is 0
1184 : // p.s. _notes is a sorted vector
1185 6397 : const std::vector<ChordRest*>& elements = ldata->elements;
1186 6397 : const std::vector<BeamBase::NotePosition>& notePositions = ldata->notePositions;
1187 :
1188 6397 : if (elements.size() > 2) {
1189 4493 : if (ldata->up) {
1190 2583 : BeamBase::NotePosition higherEnd = std::min(startPos, endPos);
1191 2583 : if (higherEnd > notePositions[0]) {
1192 802 : return SlopeConstraint::FLAT; // a note is higher in the staff than the highest end
1193 : }
1194 2195 : if (higherEnd == notePositions[0] && higherEnd >= notePositions[1]) {
1195 414 : if (higherEnd > notePositions[1]) {
1196 0 : return SlopeConstraint::FLAT; // a note is higher in the staff than the highest end
1197 : }
1198 414 : size_t chordCount = elements.size();
1199 414 : if (chordCount >= 3 && notePositions.size() >= 3) {
1200 414 : bool middleNoteHigherThanHigherEnd = higherEnd >= notePositions[2];
1201 414 : if (middleNoteHigherThanHigherEnd) {
1202 34 : return SlopeConstraint::FLAT; // two notes are the same as the highest end (notes [0] [1] and [2] higher than or same as higherEnd)
1203 : }
1204 380 : Chord* secondNote = elements[1]->isChord() ? toChord(elements[1]) : nullptr;
1205 : Chord* secondToLastNote
1206 380 : = elements[chordCount - 2]->isChord() ? toChord(elements[chordCount - 2]) : nullptr;
1207 522 : bool secondNoteSameHeightAsHigherEnd = startPos < endPos && secondNote && BeamBase::NotePosition(
1208 142 : secondNote->upLine(), secondNote->vStaffIdx()) == higherEnd;
1209 618 : bool secondToLastNoteSameHeightAsHigherEnd = endPos.line < startPos.line && secondToLastNote && BeamBase::NotePosition(
1210 238 : secondToLastNote->upLine(), secondToLastNote->vStaffIdx()) == higherEnd;
1211 380 : if (!(secondNoteSameHeightAsHigherEnd || secondToLastNoteSameHeightAsHigherEnd)) {
1212 346 : return SlopeConstraint::FLAT; // only one note same as higher end, but it is not a neighbor
1213 : } else {
1214 : // there is a single note next to the highest one with equivalent height
1215 : // and they are neighbors. this is our exception, so
1216 : // the slope may be a max of 0.25.
1217 34 : return SlopeConstraint::SMALL_SLOPE;
1218 : }
1219 : } else {
1220 0 : return SlopeConstraint::FLAT; // only two notes in entire beam, in this case startNote == endNote
1221 : }
1222 : }
1223 : } else {
1224 1910 : BeamBase::NotePosition lowerEnd = std::max(startPos, endPos);
1225 1910 : if (lowerEnd < notePositions[notePositions.size() - 1]) {
1226 502 : return SlopeConstraint::FLAT;
1227 : }
1228 1702 : if (lowerEnd == notePositions[notePositions.size() - 1]
1229 1702 : && lowerEnd <= notePositions[notePositions.size() - 2]) {
1230 294 : if (lowerEnd < notePositions[notePositions.size() - 2]) {
1231 0 : return SlopeConstraint::FLAT;
1232 : }
1233 294 : size_t chordCount = elements.size();
1234 294 : if (chordCount >= 3 && notePositions.size() >= 3) {
1235 294 : bool middleNoteLowerThanLowerEnd = lowerEnd <= notePositions[notePositions.size() - 3];
1236 294 : if (middleNoteLowerThanLowerEnd) {
1237 44 : return SlopeConstraint::FLAT;
1238 : }
1239 250 : const Chord* secondNote = elements[1]->isChord() ? toChord(elements[1]) : nullptr;
1240 : const Chord* secondToLastNote
1241 250 : = elements[chordCount - 2]->isChord() ? toChord(elements[chordCount - 2]) : nullptr;
1242 250 : const BeamBase::NotePosition secondNotePos = secondNote ? BeamBase::NotePosition(
1243 250 : secondNote->downLine(), secondNote->vStaffIdx()) : BeamBase::NotePosition(0, muse::nidx);
1244 250 : const BeamBase::NotePosition secondToLastNotePos = secondToLastNote ? BeamBase::NotePosition(
1245 250 : secondToLastNote->downLine(), secondToLastNote->vStaffIdx()) : BeamBase::NotePosition(0, muse::nidx);
1246 :
1247 250 : bool secondNoteSameHeightAsLowerEnd = startPos.line > endPos.line && secondNotePos == lowerEnd;
1248 250 : bool secondToLastNoteSameHeightAsLowerEnd = endPos.line > startPos.line && secondToLastNotePos == lowerEnd;
1249 250 : if (!(secondNoteSameHeightAsLowerEnd || secondToLastNoteSameHeightAsLowerEnd)) {
1250 156 : return SlopeConstraint::FLAT;
1251 : } else {
1252 94 : return SlopeConstraint::SMALL_SLOPE;
1253 : }
1254 : } else {
1255 0 : return SlopeConstraint::FLAT;
1256 : }
1257 : }
1258 : }
1259 : }
1260 5093 : return SlopeConstraint::NO_CONSTRAINT;
1261 : }
1262 :
1263 5229 : int BeamTremoloLayout::getMaxSlope(const BeamBase::LayoutData* ldata)
1264 : {
1265 : // for 2-indexed interval i (seconds, thirds, etc.)
1266 : // maxSlopes[i] = max slope of beam for notes with interval i
1267 :
1268 : // calculate max slope based on distance between first and last chords
1269 5229 : double endX = chordBeamAnchorX(ldata, ldata->elements[ldata->elements.size() - 1], ChordBeamAnchorType::Start);
1270 5229 : double startX = chordBeamAnchorX(ldata, ldata->elements[0], ChordBeamAnchorType::End);
1271 5229 : double beamWidth = endX - startX;
1272 5229 : beamWidth /= ldata->spatium;
1273 5229 : int maxSlope = _maxSlopes.back();
1274 5229 : if (beamWidth < 3.0) {
1275 2138 : maxSlope = _maxSlopes[1];
1276 3091 : } else if (beamWidth < 5.0) {
1277 610 : maxSlope = _maxSlopes[2];
1278 2481 : } else if (beamWidth < 7.5) {
1279 1017 : maxSlope = _maxSlopes[3];
1280 1464 : } else if (beamWidth < 10.0) {
1281 759 : maxSlope = _maxSlopes[4];
1282 705 : } else if (beamWidth < 15.0) {
1283 258 : maxSlope = _maxSlopes[5];
1284 447 : } else if (beamWidth < 20.0) {
1285 391 : maxSlope = _maxSlopes[6];
1286 : } else {
1287 56 : maxSlope = _maxSlopes[7];
1288 : }
1289 :
1290 5229 : return maxSlope;
1291 : }
1292 :
1293 0 : int BeamTremoloLayout::getBeamCount(const BeamBase::LayoutData* ldata, const std::vector<const ChordRest*>& chordRests)
1294 : {
1295 0 : int maxBeams = 0;
1296 0 : for (const ChordRest* chordRest : chordRests) {
1297 0 : if (chordRest->isChord() && strokeCount(ldata, chordRest) > maxBeams) {
1298 0 : maxBeams = strokeCount(ldata, chordRest);
1299 : }
1300 : }
1301 0 : return maxBeams;
1302 : }
1303 :
1304 131809 : double BeamTremoloLayout::chordBeamAnchorX(const BeamBase::LayoutData* ldata, const ChordRest* cr, ChordBeamAnchorType anchorType)
1305 : {
1306 131809 : double pagePosX = ldata->trem ? ldata->trem->pagePos().x() : ldata->beam ? ldata->beam->pagePos().x() : 0.0;
1307 131809 : double stemPosX = StemLayout::stemPosX(cr) + cr->pagePos().x() - pagePosX;
1308 :
1309 131809 : if (!cr->isChord() || !toChord(cr)->stem()) {
1310 548 : if (!ldata->up) {
1311 : // rests always return the right side of the glyph as their stemPosX
1312 : // so we need to adjust back to the left side if stems are down
1313 248 : stemPosX -= StemLayout::stemPosX(cr);
1314 : }
1315 548 : return stemPosX;
1316 : }
1317 131261 : const Chord* chord = toChord(cr);
1318 131261 : const Stem* stem = chord->stem();
1319 :
1320 131261 : double stemWidth = stem->lineWidthMag();
1321 :
1322 131261 : switch (anchorType) {
1323 44294 : case ChordBeamAnchorType::Start:
1324 44294 : if (ldata->tab) {
1325 16 : return stemPosX - 0.5 * stemWidth;
1326 : }
1327 :
1328 44278 : if (chord->up()) {
1329 21745 : return stemPosX - stemWidth;
1330 : }
1331 :
1332 22533 : break;
1333 42650 : case ChordBeamAnchorType::Middle:
1334 42650 : if (ldata->tab) {
1335 12 : return stemPosX;
1336 : }
1337 :
1338 42638 : return chord->up() ? stemPosX - 0.5 * stemWidth : stemPosX + 0.5 * stemWidth;
1339 :
1340 44317 : case ChordBeamAnchorType::End:
1341 44317 : if (ldata->tab) {
1342 16 : return stemPosX + 0.5 * stemWidth;
1343 : }
1344 :
1345 44301 : if (!chord->up()) {
1346 22556 : return stemPosX + stemWidth;
1347 : }
1348 :
1349 21745 : break;
1350 : }
1351 :
1352 44278 : return stemPosX;
1353 : }
1354 :
1355 78855 : double BeamTremoloLayout::chordBeamAnchorY(const BeamBase::LayoutData* ldata, const ChordRest* cr)
1356 : {
1357 78855 : if (!cr->isChord()) {
1358 0 : return cr->pagePos().y();
1359 : }
1360 :
1361 78855 : const Chord* chord = toChord(cr);
1362 78855 : Note* note = cr->up() ? chord->downNote() : chord->upNote();
1363 78855 : PointF position = note->pagePos();
1364 :
1365 78855 : int upValue = chord->up() ? -1 : 1;
1366 78855 : double beamOffset = ldata->beamWidth / 2 * upValue;
1367 :
1368 78855 : if (ldata->isBesideTabStaff) {
1369 0 : double stemLength = StemLayout::tabStemLength(chord, ldata->tab) * (ldata->up ? -1 : 1);
1370 0 : double y = StemLayout::tabRestStemPosY(chord, ldata->tab) + stemLength;
1371 0 : y *= ldata->spatium;
1372 0 : y -= beamOffset;
1373 0 : return y + chord->pagePos().y();
1374 : }
1375 :
1376 78855 : return position.y() - note->offset().y() + (chord->defaultStemLength() * upValue) - beamOffset;
1377 : }
1378 :
1379 78766 : PointF BeamTremoloLayout::chordBeamAnchor(const BeamBase::LayoutData* ldata, const ChordRest* cr, ChordBeamAnchorType anchorType)
1380 : {
1381 78766 : return PointF(chordBeamAnchorX(ldata, cr, anchorType), chordBeamAnchorY(ldata, cr));
1382 : }
|