Skip to content

Commit

Permalink
Merge pull request musescore#26380 from mike-spa/crossBeamSpacingImpr…
Browse files Browse the repository at this point in the history
…ovements

Cross beam spacing improvements
  • Loading branch information
miiizen authored Feb 10, 2025
2 parents 9af8b2d + b2ac8be commit b4cee3f
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 22 deletions.
131 changes: 119 additions & 12 deletions src/engraving/rendering/score/horizontalspacing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "horizontalspacing.h"

#include "dom/barline.h"
#include "dom/beam.h"
#include "dom/chord.h"
#include "dom/engravingitem.h"
#include "dom/glissando.h"
Expand All @@ -33,6 +34,7 @@
#include "dom/score.h"
#include "dom/stemslash.h"
#include "dom/staff.h"
#include "dom/stem.h"
#include "dom/system.h"
#include "dom/tie.h"
#include "dom/timesig.h"
Expand Down Expand Up @@ -239,7 +241,7 @@ void HorizontalSpacing::spaceMeasureGroup(const std::vector<Measure*>& measureGr
ctx.xCur = lastMeas->x() + lastMeas->width();
}

std::vector<HorizontalSpacing::SegmentPosition> HorizontalSpacing::spaceSegments(const std::vector<Segment*> segList, int startSegIdx,
std::vector<HorizontalSpacing::SegmentPosition> HorizontalSpacing::spaceSegments(const std::vector<Segment*>& segList, int startSegIdx,
HorizontalSpacingContext& ctx)
{
std::vector<SegmentPosition> placedSegments;
Expand Down Expand Up @@ -614,6 +616,54 @@ void HorizontalSpacing::moveRightAlignedSegments(std::vector<SegmentPosition>& p
}
}

void HorizontalSpacing::checkCollisionsWithCrossStaffStems(const Segment* thisSeg, const Segment* nextSeg, staff_idx_t staffIdx,
double& curMinDist)
{
std::vector<ChordRest*> itemsToCheck;
itemsToCheck.reserve(VOICES);

for (EngravingItem* el : nextSeg->elist()) {
if (el && el->vStaffIdx() == staffIdx) {
ChordRest* cr = toChordRest(el);
if (cr->beam() && cr->beam()->cross()) {
itemsToCheck.push_back(toChordRest(el));
}
}
}

for (ChordRest* cr : itemsToCheck) {
Chord* potentialCollidingChord = nullptr;
for (ChordRest* beamElement : cr->beam()->elements()) {
if (beamElement->isChord() && beamElement->parent() == thisSeg) {
potentialCollidingChord = toChord(beamElement);
break;
}
}

if (!potentialCollidingChord || potentialCollidingChord->up() != cr->up() || !potentialCollidingChord->stem()) {
return;
}

bool checkStemCollision = (cr->up() && potentialCollidingChord->vStaffIdx() > cr->vStaffIdx())
|| (!cr->up() && potentialCollidingChord->vStaffIdx() < cr->vStaffIdx());
if (!checkStemCollision) {
return;
}

Stem* stem = potentialCollidingChord->stem();
Shape prevStemShape = stem->shape().translated(stem->pos() + potentialCollidingChord->pos());
// We don't know where this stem is vertically, but we know from the beam arrangement that
// it's going to be crossed anyway, so we can space to it as if it had infinite height.
prevStemShape.adjust(0.0, -100000, 0.0, 100000);

const Shape& staffShape = nextSeg->staffShape(staffIdx);

double minDist = minHorizontalDistance(prevStemShape, staffShape, stem->spatium());

curMinDist = std::max(curMinDist, minDist);
}
}

double HorizontalSpacing::chordRestSegmentNaturalWidth(Segment* segment, HorizontalSpacingContext& ctx)
{
double durationStretch = computeSegmentDurationStretch(segment, segment->prev(SegmentType::ChordRest));
Expand Down Expand Up @@ -725,37 +775,76 @@ bool HorizontalSpacing::needsCueSizeSpacing(const Segment* segment)

void HorizontalSpacing::applyCrossBeamSpacingCorrection(Segment* thisSeg, Segment* nextSeg, double& width)
{
Measure* m = thisSeg->measure();
CrossBeamType crossBeamType = computeCrossBeamType(thisSeg, nextSeg);
CrossBeamSpacing crossBeamSpacing = computeCrossBeamSpacing(thisSeg, nextSeg);

const Score* score = thisSeg->score();
const PaddingTable& paddingTable = score->paddingTable();
const MStyle& style = score->style();

double displacement = score->noteHeadWidth() - style.styleMM(Sid::stemWidth);

double displacement = m->score()->noteHeadWidth() - m->score()->style().styleMM(Sid::stemWidth);
if (crossBeamType.upDown && crossBeamType.canBeAdjusted) {
if (crossBeamSpacing.upDown && crossBeamSpacing.canBeAdjusted) {
thisSeg->addWidthOffset(displacement);
width += displacement;
} else if (crossBeamType.downUp && crossBeamType.canBeAdjusted) {
} else if (crossBeamSpacing.downUp && crossBeamSpacing.canBeAdjusted) {
thisSeg->addWidthOffset(-displacement);
width -= displacement;
}

if (crossBeamType.upDown) {
if (crossBeamType.hasOpposingBeamlets) {
double minBeamletClearance = m->style().styleMM(Sid::beamMinLen) * 2.0
+ m->score()->paddingTable().at(ElementType::BEAM).at(ElementType::BEAM);
if (crossBeamSpacing.upDown) {
if (crossBeamSpacing.hasOpposingBeamlets) {
double minBeamletClearance = style.styleMM(Sid::beamMinLen) * 2.0 + paddingTable.at(ElementType::BEAM).at(ElementType::BEAM);
width = std::max(width, displacement + minBeamletClearance);
} else {
width = std::max(width, 2 * displacement);
}
}

if (crossBeamSpacing.preventCrossStaffKerning) {
double padding = crossBeamSpacing.ensureMinStemDistance ? paddingTable.at(ElementType::STEM).at(ElementType::STEM)
: style.styleMM(Sid::minNoteDistance);
width = std::max(width, score->noteHeadWidth() + padding);
} else if (crossBeamSpacing.ensureMinStemDistance) {
width = std::max(width, score->paddingTable().at(ElementType::STEM).at(ElementType::STEM));
}
}

HorizontalSpacing::CrossBeamType HorizontalSpacing::computeCrossBeamType(Segment* thisSeg, Segment* nextSeg)
HorizontalSpacing::CrossBeamSpacing HorizontalSpacing::computeCrossBeamSpacing(Segment* thisSeg, Segment* nextSeg)
{
CrossBeamType crossBeamType;
CrossBeamSpacing crossBeamType;

if (!thisSeg->isChordRestType() || !nextSeg || !nextSeg->isChordRestType()) {
return crossBeamType;
}

bool preventCrossStaffKerning = false;
bool ensureMinStemDistance = false;
for (EngravingItem* e : thisSeg->elist()) {
if (!e || !e->isChord() || !e->visible() || !e->staff()->visible()) {
continue;
}

Chord* thisChord = toChord(e);
ChordRest* nextCR = toChordRest(nextSeg->element(thisChord->track()));
Chord* nextChord = nextCR && nextCR->isChord() ? toChord(nextCR) : nullptr;
if (!nextChord) {
continue;
}

int thisStaffMove = thisChord->staffMove();
int nextStaffMove = nextChord->staffMove();
if (thisStaffMove == nextStaffMove) {
continue;
}

preventCrossStaffKerning = thisStaffMove > nextStaffMove;
ensureMinStemDistance = (thisStaffMove > nextStaffMove && thisChord->up() && !nextChord->up())
|| (thisStaffMove < nextStaffMove && thisChord->up() == nextChord->up());
}

crossBeamType.preventCrossStaffKerning = preventCrossStaffKerning;
crossBeamType.ensureMinStemDistance = ensureMinStemDistance;

bool upDown = false;
bool downUp = false;
bool canBeAdjusted = true;
Expand Down Expand Up @@ -1115,6 +1204,10 @@ double HorizontalSpacing::minHorizontalDistance(const Segment* f, const Segment*
d = std::max(d, f->staffShape(staffIdx).right());
}

if (f->isChordRestType() && ns->isChordRestType()) {
checkCollisionsWithCrossStaffStems(f, ns, staffIdx, d);
}

ww = std::max(ww, d);
}
double w = std::max(ww, 0.0); // non-negative
Expand Down Expand Up @@ -1354,6 +1447,10 @@ void HorizontalSpacing::computeLyricsPadding(const Lyrics* lyrics1, const Engrav

KerningType HorizontalSpacing::computeKerning(const EngravingItem* item1, const EngravingItem* item2)
{
if (ignoreItems(item1, item2)) {
return KerningType::ALLOW_COLLISION;
}

if (isSameVoiceKerningLimited(item1) && isSameVoiceKerningLimited(item2) && item1->track() == item2->track()) {
return KerningType::NON_KERNING;
}
Expand Down Expand Up @@ -1420,6 +1517,16 @@ bool HorizontalSpacing::isAlwaysKernable(const EngravingItem* item)
return item->isTextBase() || item->isChordLine() || item->isParenthesis();
}

bool HorizontalSpacing::ignoreItems(const EngravingItem* item1, const EngravingItem* item2)
{
if (item1->isRest() && toRest(item1)->isFullMeasureRest()) {
// Full-measure rest must ignore cross-stave notes
return item1->staffIdx() != item2->staffIdx();
}

return false;
}

KerningType HorizontalSpacing::doComputeKerningType(const EngravingItem* item1, const EngravingItem* item2)
{
ElementType type1 = item1->type();
Expand Down
18 changes: 8 additions & 10 deletions src/engraving/rendering/score/horizontalspacing.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,24 +82,19 @@ class HorizontalSpacing
: segment(s), xPosInSystemCoords(x) {}
};

struct CrossBeamType
struct CrossBeamSpacing
{
bool upDown = false;
bool downUp = false;
bool canBeAdjusted = true;
bool hasOpposingBeamlets = false;
void reset()
{
upDown = false;
downUp = false;
canBeAdjusted = true;
hasOpposingBeamlets = false;
}
bool preventCrossStaffKerning = false;
bool ensureMinStemDistance = false;
};

static void spaceMeasureGroup(const std::vector<Measure*>& measureGroup, HorizontalSpacingContext& ctx);
static double getFirstSegmentXPos(Segment* segment, HorizontalSpacingContext& ctx);
static std::vector<SegmentPosition> spaceSegments(const std::vector<Segment*> segList, int startSegIdx, HorizontalSpacingContext& ctx);
static std::vector<SegmentPosition> spaceSegments(const std::vector<Segment*>& segList, int startSegIdx, HorizontalSpacingContext& ctx);
static bool ignoreSegmentForSpacing(const Segment* segment);
static bool ignoreAllSegmentsForSpacing(const std::vector<SegmentPosition>& segmentPositions);
static void spaceAgainstPreviousSegments(Segment* segment, std::vector<SegmentPosition>& prevSegPositions,
Expand All @@ -110,6 +105,8 @@ class HorizontalSpacing
static double spaceLyricsAgainstBarlines(Segment* firstSeg, Segment* secondSeg, const HorizontalSpacingContext& ctx);
static void checkLargeTimeSigAgainstRightMargin(std::vector<SegmentPosition>& segPositions);
static void moveRightAlignedSegments(std::vector<SegmentPosition>& placedSegments, const HorizontalSpacingContext& ctx);
static void checkCollisionsWithCrossStaffStems(const Segment* thisSeg, const Segment* nextSeg, staff_idx_t staffIdx,
double& curMinDist);

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

static void applyCrossBeamSpacingCorrection(Segment* thisSeg, Segment* nextSeg, double& width);
static CrossBeamType computeCrossBeamType(Segment* thisSeg, Segment* nextSeg);
static CrossBeamSpacing computeCrossBeamSpacing(Segment* thisSeg, Segment* nextSeg);

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

static KerningType doComputeKerningType(const EngravingItem* item1, const EngravingItem* item2);
static KerningType computeNoteKerningType(const Note* note, const EngravingItem* item2);
Expand Down
Binary file added vtest/scores/cross-12.mscz
Binary file not shown.

0 comments on commit b4cee3f

Please sign in to comment.