Skip to content

Commit b4cee3f

Browse files
authored
Merge pull request #26380 from mike-spa/crossBeamSpacingImprovements
Cross beam spacing improvements
2 parents 9af8b2d + b2ac8be commit b4cee3f

File tree

3 files changed

+127
-22
lines changed

3 files changed

+127
-22
lines changed

src/engraving/rendering/score/horizontalspacing.cpp

Lines changed: 119 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "horizontalspacing.h"
2525

2626
#include "dom/barline.h"
27+
#include "dom/beam.h"
2728
#include "dom/chord.h"
2829
#include "dom/engravingitem.h"
2930
#include "dom/glissando.h"
@@ -33,6 +34,7 @@
3334
#include "dom/score.h"
3435
#include "dom/stemslash.h"
3536
#include "dom/staff.h"
37+
#include "dom/stem.h"
3638
#include "dom/system.h"
3739
#include "dom/tie.h"
3840
#include "dom/timesig.h"
@@ -239,7 +241,7 @@ void HorizontalSpacing::spaceMeasureGroup(const std::vector<Measure*>& measureGr
239241
ctx.xCur = lastMeas->x() + lastMeas->width();
240242
}
241243

242-
std::vector<HorizontalSpacing::SegmentPosition> HorizontalSpacing::spaceSegments(const std::vector<Segment*> segList, int startSegIdx,
244+
std::vector<HorizontalSpacing::SegmentPosition> HorizontalSpacing::spaceSegments(const std::vector<Segment*>& segList, int startSegIdx,
243245
HorizontalSpacingContext& ctx)
244246
{
245247
std::vector<SegmentPosition> placedSegments;
@@ -614,6 +616,54 @@ void HorizontalSpacing::moveRightAlignedSegments(std::vector<SegmentPosition>& p
614616
}
615617
}
616618

619+
void HorizontalSpacing::checkCollisionsWithCrossStaffStems(const Segment* thisSeg, const Segment* nextSeg, staff_idx_t staffIdx,
620+
double& curMinDist)
621+
{
622+
std::vector<ChordRest*> itemsToCheck;
623+
itemsToCheck.reserve(VOICES);
624+
625+
for (EngravingItem* el : nextSeg->elist()) {
626+
if (el && el->vStaffIdx() == staffIdx) {
627+
ChordRest* cr = toChordRest(el);
628+
if (cr->beam() && cr->beam()->cross()) {
629+
itemsToCheck.push_back(toChordRest(el));
630+
}
631+
}
632+
}
633+
634+
for (ChordRest* cr : itemsToCheck) {
635+
Chord* potentialCollidingChord = nullptr;
636+
for (ChordRest* beamElement : cr->beam()->elements()) {
637+
if (beamElement->isChord() && beamElement->parent() == thisSeg) {
638+
potentialCollidingChord = toChord(beamElement);
639+
break;
640+
}
641+
}
642+
643+
if (!potentialCollidingChord || potentialCollidingChord->up() != cr->up() || !potentialCollidingChord->stem()) {
644+
return;
645+
}
646+
647+
bool checkStemCollision = (cr->up() && potentialCollidingChord->vStaffIdx() > cr->vStaffIdx())
648+
|| (!cr->up() && potentialCollidingChord->vStaffIdx() < cr->vStaffIdx());
649+
if (!checkStemCollision) {
650+
return;
651+
}
652+
653+
Stem* stem = potentialCollidingChord->stem();
654+
Shape prevStemShape = stem->shape().translated(stem->pos() + potentialCollidingChord->pos());
655+
// We don't know where this stem is vertically, but we know from the beam arrangement that
656+
// it's going to be crossed anyway, so we can space to it as if it had infinite height.
657+
prevStemShape.adjust(0.0, -100000, 0.0, 100000);
658+
659+
const Shape& staffShape = nextSeg->staffShape(staffIdx);
660+
661+
double minDist = minHorizontalDistance(prevStemShape, staffShape, stem->spatium());
662+
663+
curMinDist = std::max(curMinDist, minDist);
664+
}
665+
}
666+
617667
double HorizontalSpacing::chordRestSegmentNaturalWidth(Segment* segment, HorizontalSpacingContext& ctx)
618668
{
619669
double durationStretch = computeSegmentDurationStretch(segment, segment->prev(SegmentType::ChordRest));
@@ -725,37 +775,76 @@ bool HorizontalSpacing::needsCueSizeSpacing(const Segment* segment)
725775

726776
void HorizontalSpacing::applyCrossBeamSpacingCorrection(Segment* thisSeg, Segment* nextSeg, double& width)
727777
{
728-
Measure* m = thisSeg->measure();
729-
CrossBeamType crossBeamType = computeCrossBeamType(thisSeg, nextSeg);
778+
CrossBeamSpacing crossBeamSpacing = computeCrossBeamSpacing(thisSeg, nextSeg);
779+
780+
const Score* score = thisSeg->score();
781+
const PaddingTable& paddingTable = score->paddingTable();
782+
const MStyle& style = score->style();
783+
784+
double displacement = score->noteHeadWidth() - style.styleMM(Sid::stemWidth);
730785

731-
double displacement = m->score()->noteHeadWidth() - m->score()->style().styleMM(Sid::stemWidth);
732-
if (crossBeamType.upDown && crossBeamType.canBeAdjusted) {
786+
if (crossBeamSpacing.upDown && crossBeamSpacing.canBeAdjusted) {
733787
thisSeg->addWidthOffset(displacement);
734788
width += displacement;
735-
} else if (crossBeamType.downUp && crossBeamType.canBeAdjusted) {
789+
} else if (crossBeamSpacing.downUp && crossBeamSpacing.canBeAdjusted) {
736790
thisSeg->addWidthOffset(-displacement);
737791
width -= displacement;
738792
}
739793

740-
if (crossBeamType.upDown) {
741-
if (crossBeamType.hasOpposingBeamlets) {
742-
double minBeamletClearance = m->style().styleMM(Sid::beamMinLen) * 2.0
743-
+ m->score()->paddingTable().at(ElementType::BEAM).at(ElementType::BEAM);
794+
if (crossBeamSpacing.upDown) {
795+
if (crossBeamSpacing.hasOpposingBeamlets) {
796+
double minBeamletClearance = style.styleMM(Sid::beamMinLen) * 2.0 + paddingTable.at(ElementType::BEAM).at(ElementType::BEAM);
744797
width = std::max(width, displacement + minBeamletClearance);
745798
} else {
746799
width = std::max(width, 2 * displacement);
747800
}
748801
}
802+
803+
if (crossBeamSpacing.preventCrossStaffKerning) {
804+
double padding = crossBeamSpacing.ensureMinStemDistance ? paddingTable.at(ElementType::STEM).at(ElementType::STEM)
805+
: style.styleMM(Sid::minNoteDistance);
806+
width = std::max(width, score->noteHeadWidth() + padding);
807+
} else if (crossBeamSpacing.ensureMinStemDistance) {
808+
width = std::max(width, score->paddingTable().at(ElementType::STEM).at(ElementType::STEM));
809+
}
749810
}
750811

751-
HorizontalSpacing::CrossBeamType HorizontalSpacing::computeCrossBeamType(Segment* thisSeg, Segment* nextSeg)
812+
HorizontalSpacing::CrossBeamSpacing HorizontalSpacing::computeCrossBeamSpacing(Segment* thisSeg, Segment* nextSeg)
752813
{
753-
CrossBeamType crossBeamType;
814+
CrossBeamSpacing crossBeamType;
754815

755816
if (!thisSeg->isChordRestType() || !nextSeg || !nextSeg->isChordRestType()) {
756817
return crossBeamType;
757818
}
758819

820+
bool preventCrossStaffKerning = false;
821+
bool ensureMinStemDistance = false;
822+
for (EngravingItem* e : thisSeg->elist()) {
823+
if (!e || !e->isChord() || !e->visible() || !e->staff()->visible()) {
824+
continue;
825+
}
826+
827+
Chord* thisChord = toChord(e);
828+
ChordRest* nextCR = toChordRest(nextSeg->element(thisChord->track()));
829+
Chord* nextChord = nextCR && nextCR->isChord() ? toChord(nextCR) : nullptr;
830+
if (!nextChord) {
831+
continue;
832+
}
833+
834+
int thisStaffMove = thisChord->staffMove();
835+
int nextStaffMove = nextChord->staffMove();
836+
if (thisStaffMove == nextStaffMove) {
837+
continue;
838+
}
839+
840+
preventCrossStaffKerning = thisStaffMove > nextStaffMove;
841+
ensureMinStemDistance = (thisStaffMove > nextStaffMove && thisChord->up() && !nextChord->up())
842+
|| (thisStaffMove < nextStaffMove && thisChord->up() == nextChord->up());
843+
}
844+
845+
crossBeamType.preventCrossStaffKerning = preventCrossStaffKerning;
846+
crossBeamType.ensureMinStemDistance = ensureMinStemDistance;
847+
759848
bool upDown = false;
760849
bool downUp = false;
761850
bool canBeAdjusted = true;
@@ -1115,6 +1204,10 @@ double HorizontalSpacing::minHorizontalDistance(const Segment* f, const Segment*
11151204
d = std::max(d, f->staffShape(staffIdx).right());
11161205
}
11171206

1207+
if (f->isChordRestType() && ns->isChordRestType()) {
1208+
checkCollisionsWithCrossStaffStems(f, ns, staffIdx, d);
1209+
}
1210+
11181211
ww = std::max(ww, d);
11191212
}
11201213
double w = std::max(ww, 0.0); // non-negative
@@ -1354,6 +1447,10 @@ void HorizontalSpacing::computeLyricsPadding(const Lyrics* lyrics1, const Engrav
13541447

13551448
KerningType HorizontalSpacing::computeKerning(const EngravingItem* item1, const EngravingItem* item2)
13561449
{
1450+
if (ignoreItems(item1, item2)) {
1451+
return KerningType::ALLOW_COLLISION;
1452+
}
1453+
13571454
if (isSameVoiceKerningLimited(item1) && isSameVoiceKerningLimited(item2) && item1->track() == item2->track()) {
13581455
return KerningType::NON_KERNING;
13591456
}
@@ -1420,6 +1517,16 @@ bool HorizontalSpacing::isAlwaysKernable(const EngravingItem* item)
14201517
return item->isTextBase() || item->isChordLine() || item->isParenthesis();
14211518
}
14221519

1520+
bool HorizontalSpacing::ignoreItems(const EngravingItem* item1, const EngravingItem* item2)
1521+
{
1522+
if (item1->isRest() && toRest(item1)->isFullMeasureRest()) {
1523+
// Full-measure rest must ignore cross-stave notes
1524+
return item1->staffIdx() != item2->staffIdx();
1525+
}
1526+
1527+
return false;
1528+
}
1529+
14231530
KerningType HorizontalSpacing::doComputeKerningType(const EngravingItem* item1, const EngravingItem* item2)
14241531
{
14251532
ElementType type1 = item1->type();

src/engraving/rendering/score/horizontalspacing.h

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -82,24 +82,19 @@ class HorizontalSpacing
8282
: segment(s), xPosInSystemCoords(x) {}
8383
};
8484

85-
struct CrossBeamType
85+
struct CrossBeamSpacing
8686
{
8787
bool upDown = false;
8888
bool downUp = false;
8989
bool canBeAdjusted = true;
9090
bool hasOpposingBeamlets = false;
91-
void reset()
92-
{
93-
upDown = false;
94-
downUp = false;
95-
canBeAdjusted = true;
96-
hasOpposingBeamlets = false;
97-
}
91+
bool preventCrossStaffKerning = false;
92+
bool ensureMinStemDistance = false;
9893
};
9994

10095
static void spaceMeasureGroup(const std::vector<Measure*>& measureGroup, HorizontalSpacingContext& ctx);
10196
static double getFirstSegmentXPos(Segment* segment, HorizontalSpacingContext& ctx);
102-
static std::vector<SegmentPosition> spaceSegments(const std::vector<Segment*> segList, int startSegIdx, HorizontalSpacingContext& ctx);
97+
static std::vector<SegmentPosition> spaceSegments(const std::vector<Segment*>& segList, int startSegIdx, HorizontalSpacingContext& ctx);
10398
static bool ignoreSegmentForSpacing(const Segment* segment);
10499
static bool ignoreAllSegmentsForSpacing(const std::vector<SegmentPosition>& segmentPositions);
105100
static void spaceAgainstPreviousSegments(Segment* segment, std::vector<SegmentPosition>& prevSegPositions,
@@ -110,6 +105,8 @@ class HorizontalSpacing
110105
static double spaceLyricsAgainstBarlines(Segment* firstSeg, Segment* secondSeg, const HorizontalSpacingContext& ctx);
111106
static void checkLargeTimeSigAgainstRightMargin(std::vector<SegmentPosition>& segPositions);
112107
static void moveRightAlignedSegments(std::vector<SegmentPosition>& placedSegments, const HorizontalSpacingContext& ctx);
108+
static void checkCollisionsWithCrossStaffStems(const Segment* thisSeg, const Segment* nextSeg, staff_idx_t staffIdx,
109+
double& curMinDist);
113110

114111
static double chordRestSegmentNaturalWidth(Segment* segment, HorizontalSpacingContext& ctx);
115112
static double computeSegmentDurationStretch(const Segment* curSeg, const Segment* prevSeg);
@@ -118,7 +115,7 @@ class HorizontalSpacing
118115
static bool needsCueSizeSpacing(const Segment* segment);
119116

120117
static void applyCrossBeamSpacingCorrection(Segment* thisSeg, Segment* nextSeg, double& width);
121-
static CrossBeamType computeCrossBeamType(Segment* thisSeg, Segment* nextSeg);
118+
static CrossBeamSpacing computeCrossBeamSpacing(Segment* thisSeg, Segment* nextSeg);
122119

123120
static void enforceMinimumMeasureWidths(const std::vector<Measure*> measureGroup);
124121
static double computeMinMeasureWidth(Measure* m);
@@ -137,6 +134,7 @@ class HorizontalSpacing
137134
static bool isSameVoiceKerningLimited(const EngravingItem* item);
138135
static bool isNeverKernable(const EngravingItem* item);
139136
static bool isAlwaysKernable(const EngravingItem* item);
137+
static bool ignoreItems(const EngravingItem* item1, const EngravingItem* item2);
140138

141139
static KerningType doComputeKerningType(const EngravingItem* item1, const EngravingItem* item2);
142140
static KerningType computeNoteKerningType(const Note* note, const EngravingItem* item2);

vtest/scores/cross-12.mscz

23 KB
Binary file not shown.

0 commit comments

Comments
 (0)