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 "stemlayout.h"
24 : #include "dom/chord.h"
25 : #include "dom/hook.h"
26 : #include "dom/note.h"
27 : #include "dom/rest.h"
28 : #include "dom/score.h"
29 : #include "dom/staff.h"
30 : #include "dom/tremolosinglechord.h"
31 : #include "dom/tremolotwochord.h"
32 :
33 : using namespace mu::engraving;
34 : using namespace mu::engraving::rendering::score;
35 :
36 : //-----------------------------------------------------------------------------
37 : // defaultStemLength
38 : /// Get the default stem length for this chord
39 : /// all internal calculation is done in quarter spaces
40 : /// using integers to eliminate all possibilities for rounding errors
41 : //-----------------------------------------------------------------------------
42 144130 : double StemLayout::calcDefaultStemLength(Chord* item, const LayoutContext& ctx)
43 : {
44 : // returns default length even if the chord doesn't have a stem
45 144130 : const MStyle& style = ctx.conf().style();
46 144130 : const Chord::LayoutData* ldata = item->ldata();
47 144130 : const Staff* staff = item->staff();
48 :
49 144130 : double spatium = item->spatium();
50 144130 : double lineDistance = (staff ? staff->lineDistance(item->tick()) : 1.0);
51 :
52 144130 : const StaffType* staffType = staff ? staff->staffTypeForElement(item) : nullptr;
53 144130 : const StaffType* tab = (staffType && staffType->isTabStaff()) ? staffType : nullptr;
54 :
55 144130 : bool isBesideTabStaff = tab && !tab->stemless() && !tab->stemThrough();
56 144130 : if (isBesideTabStaff) {
57 420 : return tabStemLength(item, tab) * spatium;
58 : }
59 :
60 143710 : int defaultStemLength = style.styleD(Sid::stemLength) * 4;
61 143710 : defaultStemLength += stemLengthBeamAddition(item, ctx);
62 143710 : if (tab) {
63 656 : defaultStemLength *= 1.5;
64 : }
65 : // extraHeight represents the extra vertical distance between notehead and stem start
66 : // eg. slashed noteheads etc
67 143710 : double extraHeight = (ldata->up ? item->upNote()->stemUpSE().y() : item->downNote()->stemDownNW().y()) / item->intrinsicMag()
68 143710 : / spatium;
69 143710 : int shortestStem = style.styleB(Sid::useWideBeams) ? 12 : (style.styleD(Sid::shortestStem) + std::abs(extraHeight)) * 4;
70 143710 : int quarterSpacesPerLine = std::floor(lineDistance * 2);
71 143710 : int chordHeight = (item->downLine() - item->upLine()) * quarterSpacesPerLine; // convert to quarter spaces
72 143710 : int stemLength = defaultStemLength;
73 :
74 143710 : int minStemLengthQuarterSpaces = calcMinStemLength(item, ctx);
75 :
76 143710 : int staffLineCount = staff ? staff->lines(item->tick()) : 5;
77 143710 : int shortStemStart = style.styleI(Sid::shortStemStartLocation) * quarterSpacesPerLine + 1;
78 143710 : bool useWideBeams = style.styleB(Sid::useWideBeams);
79 143710 : int beamCount = calcBeamCount(item);
80 287420 : int middleLine = minStaffOverlap(ldata->up, staffLineCount,
81 143710 : beamCount, !!item->hook(), useWideBeams ? 4 : 3,
82 143710 : useWideBeams, !(item->isGrace() || item->isSmall()));
83 143710 : int idealStemLength = defaultStemLength;
84 :
85 143710 : if (ldata->up) {
86 75683 : int stemEndPosition = item->upLine() * quarterSpacesPerLine - defaultStemLength;
87 75683 : double stemEndPositionMag = (double)item->upLine() * quarterSpacesPerLine - (defaultStemLength * item->intrinsicMag());
88 :
89 75683 : if (stemEndPositionMag <= -shortStemStart) {
90 32377 : int reduction = maxReduction(item, ctx, std::abs((int)floor(stemEndPositionMag) + shortStemStart));
91 32377 : idealStemLength = std::max(idealStemLength - reduction, shortestStem);
92 43306 : } else if (stemEndPosition > middleLine) {
93 : // this case will be taken care of below; even if we were to adjust here we'd have
94 : // to adjust again later if the line spacing != 1.0 or if _relativeMag != 1.0
95 : } else {
96 40636 : idealStemLength -= stemOpticalAdjustment(item, stemEndPosition);
97 40636 : idealStemLength = std::max(idealStemLength, shortestStem);
98 : }
99 75683 : stemLength = std::max(idealStemLength, minStemLengthQuarterSpaces);
100 : } else {
101 68027 : int stemEndPosition = item->downLine() * quarterSpacesPerLine + defaultStemLength;
102 68027 : double stemEndPositionMag = (double)item->downLine() * quarterSpacesPerLine + (defaultStemLength * item->intrinsicMag());
103 :
104 68027 : int downShortStemStart = (staffLineCount - 1) * (2 * quarterSpacesPerLine) + shortStemStart;
105 68027 : if (stemEndPositionMag >= downShortStemStart) {
106 46365 : int reduction = maxReduction(item, ctx, std::abs((int)ceil(stemEndPositionMag) - downShortStemStart));
107 46365 : idealStemLength = std::max(idealStemLength - reduction, shortestStem);
108 21662 : } else if (stemEndPosition < middleLine) {
109 : // this case will be taken care of below; even if we were to adjust here we'd have
110 : // to adjust again later if the line spacing != 1.0 or if _relativeMag != 1.0
111 : } else {
112 19688 : idealStemLength -= stemOpticalAdjustment(item, stemEndPosition);
113 19688 : idealStemLength = std::max(idealStemLength, shortestStem);
114 : }
115 :
116 68027 : stemLength = std::max(idealStemLength, minStemLengthQuarterSpaces);
117 : }
118 143710 : if (beamCount == 4 && !item->hook()) {
119 1377 : stemLength = calc4BeamsException(item, stemLength);
120 : }
121 :
122 143710 : double finalStemLength = (chordHeight / 4.0 * spatium) + ((stemLength / 4.0 * spatium) * item->intrinsicMag());
123 143710 : double extraLength = 0.;
124 143710 : Note* startNote = ldata->up ? item->downNote() : item->upNote();
125 143710 : if (!startNote->fixed()) {
126 : // when the chord's magnitude is < 1, the stem length with mag can find itself below the middle line.
127 : // in those cases, we have to add the extra amount to it to bring it to a minimum.
128 143386 : double upValue = ldata->up ? -1. : 1.;
129 143386 : double stemStart = startNote->ldata()->pos().y();
130 143386 : double stemEndMag = stemStart + (finalStemLength * upValue);
131 143386 : double topLine = 0.0;
132 143386 : lineDistance *= spatium;
133 143386 : double bottomLine = lineDistance * (staffLineCount - 1.0);
134 143386 : double target = 0.0;
135 143386 : double midLine = middleLine / 4.0 * lineDistance;
136 143386 : if (muse::RealIsEqualOrMore(lineDistance / spatium, 1.0)) {
137 : // need to extend to middle line, or to opposite line if staff is < 2sp tall
138 143386 : if (bottomLine < 2 * spatium) {
139 322 : target = ldata->up ? topLine : bottomLine;
140 : } else {
141 143064 : double twoSpIn = ldata->up ? bottomLine - (2 * spatium) : topLine + (2 * spatium);
142 143064 : target = muse::RealIsEqual(lineDistance / spatium, 1.0) ? midLine : twoSpIn;
143 : }
144 : } else {
145 : // need to extend to second line in staff, or to opposite line if staff has < 3 lines
146 0 : if (staffLineCount < 3) {
147 0 : target = ldata->up ? topLine : bottomLine;
148 : } else {
149 0 : target = ldata->up ? bottomLine - (2 * lineDistance) : topLine + (2 * lineDistance);
150 : }
151 : }
152 143386 : extraLength = 0.0;
153 143386 : if (ldata->up && stemEndMag > target) {
154 2849 : extraLength = stemEndMag - target;
155 140537 : } else if (!ldata->up && stemEndMag < target) {
156 1889 : extraLength = target - stemEndMag;
157 : }
158 : }
159 143710 : return finalStemLength + extraLength;
160 : }
161 :
162 161718 : int StemLayout::minStaffOverlap(bool up, int staffLines, int beamCount, bool hasHook, double beamSpacing, bool useWideBeams,
163 : bool isFullSize)
164 : {
165 161718 : int beamOverlap = 8;
166 161718 : if (isFullSize) {
167 157079 : if (beamCount == 3 && !hasHook) {
168 6738 : beamOverlap = 12;
169 150341 : } else if (beamCount >= 4 && !hasHook) {
170 1819 : beamOverlap = (beamCount - 4) * beamSpacing + (useWideBeams ? 16 : 14);
171 : }
172 : }
173 :
174 161718 : int staffLineOffset = isFullSize ? 1 : 4;
175 161718 : int staffOverlap = std::min(beamOverlap, (staffLines - staffLineOffset) * 4);
176 161718 : if (!up) {
177 77321 : return staffOverlap;
178 : }
179 84397 : return (staffLines - 1) * 4 - staffOverlap;
180 : }
181 :
182 132057 : double StemLayout::stemPosX(const ChordRest* item)
183 : {
184 132057 : return item->isChord() ? stemPosX(toChord(item)) : stemPosX(toRest(item));
185 : }
186 :
187 318597 : double StemLayout::stemPosX(const Chord* item)
188 : {
189 318597 : const StaffType* staffType = item->staffType();
190 318597 : if (!staffType || !staffType->isTabStaff()) {
191 318184 : bool up = item->ldata()->up;
192 318184 : const Note* refNote = up ? item->upNote() : item->downNote();
193 318184 : PointF stemAttach = up ? refNote->stemUpSE() : refNote->stemDownNW();
194 318184 : if (stemAttach.isNull()) {
195 2196 : return up ? item->noteHeadWidth() : 0.0;
196 : }
197 315988 : double noteWidthOffset = up ? (refNote->headBodyWidth() - item->noteHeadWidth()) : 0.0;
198 315988 : return stemAttach.x() - noteWidthOffset;
199 : }
200 :
201 413 : const Note* refNote = item->ldata()->up ? item->upNote() : item->downNote();
202 413 : double xPos = refNote->pos().x() + 0.5 * refNote->width();
203 413 : if (item->isGraceBendEnd()) {
204 4 : GraceNotesGroup& graceBefore = item->graceNotesBefore();
205 4 : Chord* grace = graceBefore.empty() ? nullptr : graceBefore.front();
206 4 : if (grace) {
207 4 : xPos += grace->pos().x();
208 : }
209 : }
210 413 : return xPos;
211 : }
212 :
213 0 : double StemLayout::stemPosX(const Rest* item)
214 : {
215 0 : if (item->ldata()->up) {
216 0 : return item->ldata()->bbox().right();
217 : }
218 :
219 0 : return item->ldata()->bbox().left();
220 : }
221 :
222 0 : PointF StemLayout::stemPos(const ChordRest* item)
223 : {
224 0 : return item->isChord() ? stemPos(toChord(item)) : stemPos(toRest(item));
225 : }
226 :
227 265 : PointF StemLayout::stemPos(const Chord* item)
228 : {
229 265 : const Staff* staff = item->staff();
230 265 : const StaffType* staffType = staff ? staff->staffTypeForElement(item) : nullptr;
231 265 : if (staffType && staffType->isTabStaff()) {
232 0 : return item->pagePos() + rendering::score::StemLayout::tabStemPos(item, staffType) * item->spatium();
233 : }
234 :
235 265 : if (item->ldata()->up) {
236 123 : const Note* downNote = item->downNote();
237 123 : double nhw = item->notes().size() == 1 ? downNote->bboxRightPos() : item->noteHeadWidth();
238 123 : return item->pagePos() + PointF(nhw, downNote->pos().y());
239 : }
240 :
241 142 : return item->pagePos() + PointF(0.0, item->upNote()->pos().y());
242 : }
243 :
244 0 : PointF StemLayout::stemPos(const Rest* item)
245 : {
246 0 : return item->pagePos();
247 : }
248 :
249 : //---------------------------------------------------------
250 : // tabRestStemPosY / chordStemPos / chordStemPosBeam / chordStemLength
251 : //
252 : // computes the stem data for the given chord, according to TAB settings
253 : // NOTE: unit: spatium, position: relative to chord (DIFFERENT from Chord own functions)
254 : //
255 : // tabRestStemPosY
256 : // returns the vertical position of stem start point
257 : //---------------------------------------------------------
258 :
259 95 : double StemLayout::tabRestStemPosY(const ChordRest* item, const StaffType* st)
260 : {
261 95 : if (st->stemThrough()) { // does not make sense for "stems through staves" setting; just return top line vert. position
262 0 : return 0.0;
263 : }
264 :
265 : // if stems beside staff, position are fixed, but take into account delta for half notes
266 : double delta // displacement for half note stems (if used)
267 : = // if half notes have not a short stem OR not a half note => 0
268 170 : (st->minimStyle() != TablatureMinimStyle::SHORTER || item->durationType().type() != DurationType::V_HALF)
269 170 : ? 0.0
270 : : // if stem is up, displace of half stem length down (positive)
271 : // if stem is down, displace of half stem length up (negative)
272 10 : (item->up()
273 10 : ? -STAFFTYPE_TAB_DEFAULTSTEMLEN_UP : STAFFTYPE_TAB_DEFAULTSTEMLEN_DN) * 0.5;
274 : // if fret marks above lines and chordRest is up, move half a line distance up
275 95 : if (!st->onLines() && item->up()) {
276 0 : delta -= st->lineDistance().val() * 0.5;
277 : }
278 : double y
279 95 : = (item->up() ? STAFFTYPE_TAB_DEFAULTSTEMPOSY_UP : (st->lines() - 1) * st->lineDistance().val() + STAFFTYPE_TAB_DEFAULTSTEMPOSY_DN)
280 95 : + delta;
281 95 : return y;
282 : }
283 :
284 : //---------------------------------------------------------
285 : // tabStemLength
286 : // return length of stem
287 : //---------------------------------------------------------
288 :
289 420 : double StemLayout::tabStemLength(const Chord* item, const StaffType* st)
290 : {
291 : double stemLen;
292 : // if stems are through staff, length should be computed by relevant chord algorithm;
293 : // here, just return default length (= 1 'octave' = 3.5 line spaces)
294 420 : if (st->stemThrough()) {
295 0 : return STAFFTYPE_TAB_DEFAULTSTEMLEN_THRU * st->lineDistance().val();
296 : }
297 : // if stems beside staff, length is fixed, but take into account shorter half note stems
298 : else {
299 420 : bool shrt = (st->minimStyle() == TablatureMinimStyle::SHORTER) && (item->durationType().type() == DurationType::V_HALF);
300 420 : stemLen = (st->stemsDown() ? STAFFTYPE_TAB_DEFAULTSTEMLEN_DN : STAFFTYPE_TAB_DEFAULTSTEMLEN_UP)
301 420 : * (shrt ? STAFFTYPE_TAB_SHORTSTEMRATIO : 1.0) * (item->style().styleB(Sid::useWideBeams) ? 1.25 : 1.0);
302 : }
303 : // scale length by scale of parent chord, but relative to scale of context staff
304 420 : return stemLen * item->mag() / item->staff()->staffMag(item->tick());
305 : }
306 :
307 : //---------------------------------------------------------
308 : // tabStemPos
309 : // return position of note at other side of beam
310 : //---------------------------------------------------------
311 :
312 115 : PointF StemLayout::tabStemPos(const Chord* item, const StaffType* st)
313 : {
314 115 : double y = 0.0;
315 115 : if (st->stemThrough()) {
316 : // if stems are through staff, stem goes from farthest note string
317 20 : y = (item->up() ? item->downString() : item->upString()) * st->lineDistance().val();
318 : } else {
319 : // if stems are beside staff, stem start point has a fixed vertical position,
320 : // according to TAB parameters and stem up/down
321 95 : y = tabRestStemPosY(item, st);
322 : }
323 115 : return PointF(0.5 * item->score()->noteHeadWidth() * item->mag() / item->spatium(), y);
324 : }
325 :
326 143710 : int StemLayout::stemLengthBeamAddition(const Chord* item, const LayoutContext& ctx)
327 : {
328 143710 : if (item->hook()) {
329 3489 : return 0;
330 : }
331 140221 : const MStyle& style = ctx.conf().style();
332 140221 : int beamCount = calcBeamCount(item);
333 140221 : switch (beamCount) {
334 132616 : case 0:
335 : case 1:
336 : case 2:
337 132616 : return 0;
338 6054 : case 3:
339 6054 : return 2;
340 1551 : default:
341 1551 : return (beamCount - 3) * (style.styleB(Sid::useWideBeams) ? 4 : 3);
342 : }
343 : }
344 :
345 78742 : int StemLayout::maxReduction(const Chord* item, const LayoutContext& ctx, int extensionOutsideStaff)
346 : {
347 78742 : const MStyle& style = ctx.conf().style();
348 78742 : if (!style.styleB(Sid::shortenStem)) {
349 0 : return 0;
350 : }
351 : // [extensionOutsideStaff][beamCount]
352 : static const int maxReductions[4][5] = {
353 : //1sp 1.5sp 2sp 2.5sp >=3sp -- extensionOutsideStaff
354 : { 1, 2, 3, 4, 4 }, // 0 beams
355 : { 0, 1, 2, 3, 3 }, // 1 beam
356 : { 0, 1, 1, 1, 1 }, // 2 beams
357 : { 0, 0, 0, 1, 1 }, // 3 beams
358 : };
359 78742 : int beamCount = 0;
360 78742 : if (!item->hook()) {
361 76412 : beamCount = calcBeamCount(item);
362 : }
363 78742 : bool hasTradHook = item->hook() && !style.styleB(Sid::useStraightNoteFlags);
364 78742 : if (item->hook() && !hasTradHook) {
365 0 : beamCount = std::min(beamCount, 2); // the straight glyphs extend outwards after 2 beams
366 : }
367 78742 : if (beamCount >= 4) {
368 1102 : return 0;
369 : }
370 77640 : int extensionHalfSpaces = floor(extensionOutsideStaff / 2.0);
371 77640 : extensionHalfSpaces = std::min(extensionHalfSpaces, 4);
372 77640 : int reduction = maxReductions[beamCount][extensionHalfSpaces];
373 77640 : if (item->intrinsicMag() < 1) {
374 : // there is an exception for grace-sized stems with hooks.
375 : // reducing by the full amount puts the hooks too low. Limit reduction to 0.5sp
376 1995 : if (hasTradHook) {
377 146 : reduction = std::min(reduction, 1);
378 : }
379 : } else {
380 : // there are a few exceptions for normal-sized (non-grace) beams
381 75645 : if (beamCount == 1 && extensionHalfSpaces < 2) {
382 : // 1) if the extension is less than 1sp above or below the staff, they've been adjusted
383 : // already to play nicely with staff lines. Reduce by 1sp.
384 16341 : reduction = 2;
385 59304 : } else if (beamCount == 3 && extensionHalfSpaces == 3) {
386 : // 2) if there are three beams and it extends 1.5sp above or below the staff, we need to
387 : // *extend* the stem rather than reduce it.
388 429 : reduction = 0;
389 : }
390 75645 : if (hasTradHook) {
391 2184 : reduction = std::min(reduction, 1);
392 73461 : } else if (item->hook() && item->beams() > 2) {
393 0 : reduction += 1;
394 : }
395 : }
396 77640 : return reduction;
397 : }
398 :
399 60324 : int StemLayout::stemOpticalAdjustment(const Chord* item, int stemEndPosition)
400 : {
401 60324 : if (item->hook() && !item->beam()) {
402 1126 : return 0;
403 : }
404 59198 : int beamCount = calcBeamCount(item);
405 59198 : if (beamCount == 0 || beamCount > 2) {
406 35805 : return 0;
407 : }
408 23393 : bool isOnEvenLine = fmod(stemEndPosition + 4, 4) == 2;
409 23393 : if (isOnEvenLine) {
410 14332 : return 1;
411 : }
412 9061 : return 0;
413 : }
414 :
415 143710 : int StemLayout::calcMinStemLength(Chord* item, const LayoutContext& ctx)
416 : {
417 143710 : int minStemLength = 0; // in quarter spaces
418 143710 : double spatium = item->spatium();
419 143710 : const MStyle& style = ctx.conf().style();
420 143710 : const Chord::LayoutData* ldata = item->ldata();
421 143710 : const Staff* staff = item->staff();
422 :
423 143710 : if (item->tremoloSingleChord()) {
424 : // buzz roll's height is actually half of the visual height,
425 : // so we need to multiply it by 2 to get the actual height
426 751 : int buzzRollMultiplier = item->tremoloSingleChord()->isBuzzRoll() ? 2 : 1;
427 751 : minStemLength += ceil(item->tremoloSingleChord()->minHeight() / item->intrinsicMag() * 4.0 * buzzRollMultiplier);
428 751 : int outSidePadding = style.styleAbsolute(Sid::tremoloOutSidePadding) / spatium * 4.0;
429 751 : int noteSidePadding = style.styleAbsolute(Sid::tremoloNoteSidePadding) / spatium * 4.0;
430 :
431 751 : int outsideStaffOffset = 0;
432 751 : if (!staff->isTabStaff(item->tick())) {
433 747 : Note* lineNote = ldata->up ? item->upNote() : item->downNote();
434 747 : if (lineNote->line() == INVALID_LINE) {
435 0 : lineNote->updateLine();
436 : }
437 :
438 747 : int line = lineNote->line();
439 747 : line *= 2; // convert to quarter spaces
440 :
441 747 : if (!ldata->up && line < -2) {
442 0 : outsideStaffOffset = -line;
443 747 : } else if (ldata->up && line > staff->lines(item->tick()) * 4) {
444 0 : outsideStaffOffset = line - (staff->lines(item->tick()) * 4) + 4;
445 : }
446 : }
447 751 : minStemLength += (outSidePadding + std::max(noteSidePadding, outsideStaffOffset));
448 :
449 751 : if (item->hook()) {
450 4 : bool straightFlags = style.styleB(Sid::useStraightNoteFlags);
451 4 : double smuflAnchor = item->hook()->smuflAnchor().y() * (ldata->up ? 1 : -1);
452 4 : int hookOffset = floor((item->hook()->height() / item->intrinsicMag() + smuflAnchor) / spatium * 4) - (straightFlags ? 0 : 2);
453 : // some fonts have hooks that extend very far down (making the height of the hook very large)
454 : // so we constrain to a reasonable maximum for hook length
455 4 : hookOffset = std::min(hookOffset, 11);
456 : // TODO: when the SMuFL metadata includes a cutout for flags, replace this with that metadata
457 : // https://github.com/w3c/smufl/issues/203
458 4 : int cutout = ldata->up ? 5 : 7;
459 4 : if (straightFlags) {
460 : // don't need cutout for straight flags (they are similar to beams)
461 0 : cutout = 0;
462 4 : } else if (item->beams() >= 2) {
463 : // beams greater than two extend outwards and thus don't factor into the cutout
464 0 : cutout -= 2;
465 : }
466 :
467 4 : minStemLength += hookOffset - cutout;
468 :
469 : // hooks with trems inside them no longer ceil (snap) to nearest 0.5sp.
470 : // if we want to add that back in, here is the place to do it:
471 : // minStemLength = ceil(minStemLength / 2.0) * 2;
472 : }
473 : }
474 143710 : if (item->beam() || item->tremoloTwoChord()) {
475 69364 : int beamCount = calcBeamCount(item);
476 : static const int minInnerStemLengths[4] = { 10, 9, 8, 7 };
477 69364 : int innerStemLength = minInnerStemLengths[std::min(beamCount, 3)];
478 69364 : int beamsHeight = beamCount * (style.styleB(Sid::useWideBeams) ? 4 : 3) - 1;
479 69364 : int newMinStemLength = std::max(minStemLength, innerStemLength);
480 69364 : newMinStemLength += beamsHeight;
481 : // for 4+ beams, there are a few situations where we need to lengthen the stem by 1
482 69364 : int noteLine = item->line();
483 69364 : int staffLines = staff->lines(item->tick());
484 69364 : bool noteInStaff = (ldata->up && noteLine > 0) || (!ldata->up && noteLine < (staffLines - 1) * 2);
485 69364 : if (beamCount >= 4 && noteInStaff) {
486 1525 : newMinStemLength++;
487 : }
488 69364 : minStemLength = std::max(minStemLength, newMinStemLength);
489 : }
490 143710 : return minStemLength;
491 : }
492 :
493 1377 : int StemLayout::calc4BeamsException(const Chord* item, int stemLength)
494 : {
495 1377 : const Chord::LayoutData* ldata = item->ldata();
496 1377 : int difference = 0;
497 1377 : int staffLines = (item->staff()->lines(item->tick()) - 1) * 2;
498 1377 : if (ldata->up && item->upNote()->line() > staffLines) {
499 133 : difference = item->upNote()->line() - staffLines;
500 1244 : } else if (!ldata->up && item->downNote()->line() < 0) {
501 190 : difference = std::abs(item->downNote()->line());
502 : }
503 1377 : switch (difference) {
504 60 : case 2:
505 60 : return std::max(stemLength, 21);
506 90 : case 3:
507 : case 4:
508 90 : return std::max(stemLength, 23);
509 1227 : default:
510 1227 : return stemLength;
511 : }
512 : }
513 :
514 488905 : int StemLayout::calcBeamCount(const Chord* item)
515 : {
516 488905 : const TremoloTwoChord* trem = item->tremoloTwoChord();
517 488905 : return (trem ? trem->lines() : 0) + (item->beam() ? item->beams() : 0);
518 : }
|