Skip to content

A complete overhaul of rest positioning logic #16719

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions src/engraving/layout/layoutbeams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,16 @@
#include "libmscore/chord.h"
#include "libmscore/factory.h"
#include "libmscore/measure.h"
#include "libmscore/rest.h"
#include "libmscore/score.h"
#include "libmscore/segment.h"
#include "libmscore/staff.h"
#include "libmscore/system.h"
#include "libmscore/timesig.h"
#include "libmscore/utils.h"

#include "layoutcontext.h"
#include "layoutchords.h"

using namespace mu::engraving;

Expand Down Expand Up @@ -511,3 +514,58 @@ void LayoutBeams::layoutNonCrossBeams(Segment* s)
}
}
}

void LayoutBeams::verticalAdjustBeamedRests(Rest* rest, Beam* beam)
{
const double spatium = rest->spatium();
static constexpr Fraction rest32nd(1, 32);
const bool up = beam->up();

double restToBeamPadding;
if (rest->ticks() <= rest32nd) {
restToBeamPadding = 0.2 * spatium;
} else {
restToBeamPadding = 0.35 * spatium;
}

Shape beamShape = beam->shape().translated(beam->pagePos());
mu::remove_if(beamShape, [&](ShapeElement& el) {
return el.toItem && el.toItem->isBeamSegment() && toBeamSegment(el.toItem)->isBeamlet;
});

Shape restShape = rest->shape().translated(rest->pagePos() - rest->offset());

double restToBeamClearance = up ? beamShape.verticalClearance(restShape) : restShape.verticalClearance(beamShape);
if (restToBeamClearance > restToBeamPadding) {
return;
}

if (up) {
rest->verticalClearance().setAbove(restToBeamClearance);
} else {
rest->verticalClearance().setBelow(restToBeamClearance);
}

bool restIsLocked = rest->verticalClearance().locked();
if (!restIsLocked) {
double overlap = (restToBeamPadding - restToBeamClearance);
double lineDistance = rest->staff()->lineDistance(rest->tick()) * spatium;
int lineMoves = ceil(overlap / lineDistance);
lineMoves *= up ? 1 : -1;
double yMove = lineMoves * lineDistance;
rest->movePosY(yMove);
for (Rest* mergedRest : rest->mergedRests()) {
mergedRest->movePosY(yMove);
}

Segment* segment = rest->segment();
staff_idx_t staffIdx = rest->vStaffIdx();
Score* score = rest->score();
std::vector<Chord*> chords;
std::vector<Rest*> rests;
collectChordsAndRest(segment, staffIdx, chords, rests);
LayoutChords::resolveRestVSChord(rests, chords, score, segment, staffIdx);
LayoutChords::resolveRestVSRest(rests, score, segment, staffIdx, /*considerBeams*/ true);
}
beam->layout();
}
3 changes: 3 additions & 0 deletions src/engraving/layout/layoutbeams.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
#include <vector>

namespace mu::engraving {
class Beam;
class Chord;
class Rest;
class ChordRest;
class Measure;
class Score;
Expand All @@ -42,6 +44,7 @@ class LayoutBeams
static void restoreBeams(Measure* m);
static void breakCrossMeasureBeams(const LayoutContext& ctx, Measure* measure);
static void layoutNonCrossBeams(Segment* s);
static void verticalAdjustBeamedRests(Rest* rest, Beam* beam);

private:
static void beamGraceNotes(Score* score, Chord* mainNote, bool after);
Expand Down
213 changes: 212 additions & 1 deletion src/engraving/layout/layoutchords.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,15 @@
#include "libmscore/measure.h"
#include "libmscore/note.h"
#include "libmscore/part.h"
#include "libmscore/rest.h"
#include "libmscore/score.h"
#include "libmscore/segment.h"
#include "libmscore/shape.h"
#include "libmscore/staff.h"
#include "libmscore/stem.h"
#include "libmscore/stemslash.h"
#include "libmscore/tie.h"
#include "libmscore/utils.h"

using namespace mu::engraving;

Expand All @@ -48,7 +51,7 @@ static void layoutSegmentElements(Segment* segment, track_idx_t startTrack, trac
{
for (track_idx_t track = startTrack; track < endTrack; ++track) {
if (EngravingItem* e = segment->element(track)) {
if (!e->isChord() || (e->isChord() && toChord(e)->vStaffIdx() == staffIdx)) {
if (e->vStaffIdx() == staffIdx) {
e->layout();
}
}
Expand Down Expand Up @@ -1394,3 +1397,211 @@ void LayoutChords::updateLineAttachPoints(Chord* chord, bool isFirstInMeasure)
}
}
}

void LayoutChords::resolveVerticalRestConflicts(Score* score, Segment* segment, staff_idx_t staffIdx)
{
std::vector<Rest*> rests;
std::vector<Chord*> chords;

collectChordsAndRest(segment, staffIdx, chords, rests);

for (Rest* rest : rests) {
rest->verticalClearance().reset();
}

if (rests.empty()) {
return;
}

if (!chords.empty()) {
resolveRestVSChord(rests, chords, score, segment, staffIdx);
}

if (rests.size() < 2) {
return;
}

resolveRestVSRest(rests, score, segment, staffIdx);
}

void LayoutChords::resolveRestVSChord(std::vector<Rest*>& rests, std::vector<Chord*>& chords, Score* score, Segment* segment,
staff_idx_t staffIdx)
{
Fraction tick = segment->tick();
Staff* staff = score->staff(staffIdx);
int lines = staff->lines(tick);
double spatium = staff->spatium(tick);
double lineDistance = staff->lineDistance(tick) * spatium;
const double minRestToChordClearance = 0.35 * spatium;

for (Rest* rest : rests) {
if (!rest->visible() || !rest->autoplace()) {
continue;
}
RestVerticalClearance& restVerticalClearance = rest->verticalClearance();
for (Chord* chord : chords) {
if (!chord->visible() || !chord->autoplace()) {
continue;
}
bool restAbove = rest->voice() < chord->voice() || (chord->slash() && !(rest->voice() % 2));
int upSign = restAbove ? -1 : 1;
double restYOffset = rest->offset().y();
bool ignoreYOffset = (restAbove && restYOffset > 0) || (!restAbove && restYOffset < 0);
PointF offset = ignoreYOffset ? PointF(0, restYOffset) : PointF(0, 0);
Shape restShape = rest->shape().translated(rest->pos() - offset);
Shape chordShape = chord->shape().translated(chord->pos());
double clearance = restAbove ? restShape.verticalClearance(chordShape) : chordShape.verticalClearance(restShape);
double margin = clearance - minRestToChordClearance;
int marginInSteps = floor(margin / lineDistance);
if (restAbove) {
restVerticalClearance.setBelow(marginInSteps);
} else {
restVerticalClearance.setAbove(marginInSteps);
}
if (margin > 0) {
continue;
}
rest->verticalClearance().setLocked(true);
bool isWholeOrHalf = rest->isWholeRest() || rest->durationType() == DurationType::V_HALF;
bool outAboveStaff = restAbove && restShape.bottom() + margin < minRestToChordClearance;
bool outBelowStaff = !restAbove && restShape.top() - margin > (lines - 1) * lineDistance - minRestToChordClearance;
bool useHalfSpaceSteps = (outAboveStaff || outBelowStaff) && !isWholeOrHalf;
double yMove;
if (useHalfSpaceSteps) {
int steps = ceil(abs(margin) / (lineDistance / 2));
yMove = steps * lineDistance / 2 * upSign;
rest->movePosY(yMove);
} else {
int steps = ceil(abs(margin) / lineDistance);
yMove = steps * lineDistance * upSign;
rest->movePosY(yMove);
}
for (Rest* mergedRest : rest->mergedRests()) {
mergedRest->movePosY(yMove);
}
if (isWholeOrHalf) {
double y = rest->pos().y();
int line = y < 0 ? floor(y / lineDistance) : floor(y / lineDistance);
rest->updateSymbol(line, lines); // Because it may need to use the symbol with ledger line now
}
}
}
}

void LayoutChords::resolveRestVSRest(std::vector<Rest*>& rests, Score* score, Segment* segment, staff_idx_t staffIdx, bool considerBeams)
{
Fraction tick = segment->tick();
Staff* staff = score->staff(staffIdx);
double spatium = staff->spatium(tick);
double lineDistance = staff->lineDistance(tick) * spatium;
int lines = staff->lines(tick);
const double minRestToRestClearance = 0.35 * spatium;

for (int i = 0; i < rests.size() - 1; ++i) {
Rest* rest1 = rests[i];
if (!rest1->visible() || !rest1->autoplace()) {
continue;
}
RestVerticalClearance& rest1Clearance = rest1->verticalClearance();
Shape shape1 = rest1->shape().translated(rest1->pos() - rest1->offset());

Rest* rest2 = rests[i + 1];
if (!rest2->visible() || !rest2->autoplace()) {
continue;
}

if (mu::contains(rest1->mergedRests(), rest2) || mu::contains(rest2->mergedRests(), rest1)) {
continue;
}

Shape shape2 = rest2->shape().translated(rest2->pos() - rest2->offset());
RestVerticalClearance& rest2Clearance = rest2->verticalClearance();

double clearance;
bool firstAbove = rest1->voice() < rest2->voice();
if (firstAbove) {
clearance = shape1.verticalClearance(shape2);
} else {
clearance = shape2.verticalClearance(shape1);
}
double margin = clearance - minRestToRestClearance;
int marginInSteps = floor(margin / lineDistance);
if (firstAbove) {
rest1Clearance.setBelow(marginInSteps);
rest2Clearance.setAbove(marginInSteps);
} else {
rest1Clearance.setAbove(marginInSteps);
rest2Clearance.setBelow(marginInSteps);
}

if (margin > 0) {
continue;
}

int steps = ceil(abs(margin) / lineDistance);
// Move the two rests away from each other
int step1 = floor(double(steps) / 2);
int step2 = ceil(double(steps) / 2);
int maxStep1 = firstAbove ? rest1Clearance.above() : rest1Clearance.below();
int maxStep2 = firstAbove ? rest2Clearance.below() : rest2Clearance.above();
maxStep1 = std::max(maxStep1, 0);
maxStep2 = std::max(maxStep2, 0);
if (step1 > maxStep1) {
step2 += step1 - maxStep1; // First rest is locked, try move the second more
}
if (step2 > maxStep2) {
step1 += step2 - maxStep2; // Second rest is locked, try move the first more
}
step1 = std::min(step1, maxStep1);
step2 = std::min(step2, maxStep2);
rest1->movePosY(step1 * lineDistance * (firstAbove ? -1 : 1));
rest2->movePosY(step2 * lineDistance * (firstAbove ? 1 : -1));

Beam* beam1 = rest1->beam();
Beam* beam2 = rest2->beam();
if (beam1 && beam2 && considerBeams) {
shape1 = rest1->shape().translated(rest1->pos() - rest1->offset());
shape2 = rest2->shape().translated(rest2->pos() - rest2->offset());

ChordRest* beam1Start = beam1->elements().front();
ChordRest* beam1End = beam1->elements().back();
double y1Start = beam1->chordBeamAnchorY(beam1Start) - beam1Start->pagePos().y();
double y1End = beam1->chordBeamAnchorY(beam1End) - beam1End->pagePos().y();
double beam1Ymid = 0.5 * (y1Start + y1End);

ChordRest* beam2Start = beam2->elements().front();
ChordRest* beam2End = beam2->elements().back();
double y2Start = beam2->chordBeamAnchorY(beam2Start) - beam2Start->pagePos().y();
double y2End = beam2->chordBeamAnchorY(beam2End) - beam2End->pagePos().y();
double beam2Ymid = 0.5 * (y2Start + y2End);

double centerY = 0.5 * (beam1Ymid + beam2Ymid);

double upperBound = shape1.bottom();
double lowerBound = shape2.top();
int steps = 0;
if (centerY < upperBound) {
steps = floor((centerY - upperBound) / lineDistance);
} else if (centerY > lowerBound) {
steps = ceil((centerY - lowerBound) / lineDistance);
}
double moveY = steps * lineDistance;
rest1->movePosY(moveY);
rest2->movePosY(moveY);
shape1.translate(PointF(0.0, moveY));
shape2.translate(PointF(0.0, moveY));

double halfLineDistance = 0.5 * lineDistance;
if (shape1.bottom() < -halfLineDistance) {
rest1->movePosY(halfLineDistance);
} else if (centerY >= (lines - 1) * lineDistance + halfLineDistance) {
rest2->movePosY(-halfLineDistance);
}

rest1->verticalClearance().setLocked(true);
rest2->verticalClearance().setLocked(true);
beam1->layout();
beam2->layout();
}
}
}
6 changes: 6 additions & 0 deletions src/engraving/layout/layoutchords.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class Chord;
class MStyle;
class Measure;
class Note;
class Rest;
class Score;
class Segment;
class Staff;
Expand All @@ -47,6 +48,11 @@ class LayoutChords
static void appendGraceNotes(Chord* chord);
static void clearLineAttachPoints(Measure* measure);
static void updateLineAttachPoints(Chord* chord, bool isFirstInMeasure);
static void resolveVerticalRestConflicts(Score* score, Segment* segment, staff_idx_t staffIdx);
static void resolveRestVSChord(std::vector<Rest*>& rests, std::vector<Chord*>& chords, Score* score, Segment* segment,
staff_idx_t staffIdx);
static void resolveRestVSRest(std::vector<Rest*>& rests, Score* score, Segment* segment, staff_idx_t staffIdx,
bool considerBeams = false);
};
}

Expand Down
3 changes: 3 additions & 0 deletions src/engraving/layout/layoutmeasure.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,7 @@ void LayoutMeasure::getNextMeasure(const LayoutOptions& options, LayoutContext&
layoutDrumsetChord(c, drumset, st, score->spatium());
}
c->layoutStem();
c->setBeamlet(nullptr); // Will be defined during beam layout
}
if (drumset) {
layoutDrumsetChord(chord, drumset, st, score->spatium());
Expand All @@ -804,6 +805,7 @@ void LayoutMeasure::getNextMeasure(const LayoutOptions& options, LayoutContext&
}
}
cr->setMag(m);
cr->setBeamlet(nullptr); // Will be defined during beam layout
}
} else if (segment.isClefType()) {
EngravingItem* e = segment.element(staffIdx * VOICES);
Expand Down Expand Up @@ -837,6 +839,7 @@ void LayoutMeasure::getNextMeasure(const LayoutOptions& options, LayoutContext&
for (Segment& segment : measure->segments()) {
if (segment.isChordRestType()) {
LayoutChords::layoutChords1(score, &segment, staffIdx);
LayoutChords::resolveVerticalRestConflicts(score, &segment, staffIdx);
for (voice_idx_t voice = 0; voice < VOICES; ++voice) {
ChordRest* cr = segment.cr(staffIdx * VOICES + voice);
if (cr) {
Expand Down
Loading