LCOV - code coverage report
Current view: top level - src/engraving/rendering/score - beamtremololayout.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 89.0 % 807 718
Test Date: 2026-11-05 04:07:02

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

Generated by: LCOV version 2.0-1