Skip to content

Commit

Permalink
Extract beam layout algorithm into separate class
Browse files Browse the repository at this point in the history
so that two-note tremolos can use it as well
  • Loading branch information
asattely committed Mar 20, 2023
1 parent d2be567 commit a62faf7
Show file tree
Hide file tree
Showing 9 changed files with 1,244 additions and 402 deletions.
13 changes: 13 additions & 0 deletions src/engraving/layout/layoutbeams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "containers.h"

#include "libmscore/beam.h"
#include "libmscore/tremolo.h"
#include "libmscore/chord.h"
#include "libmscore/factory.h"
#include "libmscore/measure.h"
Expand Down Expand Up @@ -495,6 +496,18 @@ void LayoutBeams::layoutNonCrossBeams(Segment* s)
// layout beam
if (LayoutBeams::isTopBeam(cr)) {
cr->beam()->layout();
if (!cr->beam()->tremAnchors().empty()) {
// there are inset tremolos in here
for (ChordRest* beamCr : cr->beam()->elements()) {
if (!beamCr->isChord()) {
continue;
}
Chord* c = toChord(beamCr);
if (c->tremolo() && c->tremolo()->twoNotes()) {
c->tremolo()->layout();
}
}
}
}
if (!cr->isChord()) {
continue;
Expand Down
12 changes: 12 additions & 0 deletions src/engraving/layout/layoutsystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1480,6 +1480,18 @@ void LayoutSystem::updateCrossBeams(System* system, const LayoutContext& ctx)
LayoutChords::layoutChords1(chord->score(), &seg, chord->vStaffIdx());
seg.createShape(chord->vStaffIdx());
}
} else if (chord->tremolo() && chord->tremolo()->twoNotes()) {
Tremolo* t = chord->tremolo();
Chord* c1 = t->chord1();
Chord* c2 = t->chord2();
if (t->userModified() || (c1->staffMove() != 0 || c2->staffMove() != 0)) {
bool prevUp = chord->up();
chord->computeUp();
if (chord->up() != prevUp) {
LayoutChords::layoutChords1(chord->score(), &seg, chord->vStaffIdx());
seg.createShape(chord->vStaffIdx());
}
}
}
}
}
Expand Down
799 changes: 622 additions & 177 deletions src/engraving/libmscore/beam.cpp

Large diffs are not rendered by default.

123 changes: 95 additions & 28 deletions src/engraving/libmscore/beam.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,105 @@ class ChordRest;
class Factory;
class Skyline;
class System;

class Beam;
enum class ActionIconType;
enum class SpannerSegmentType;

struct BeamFragment;
//---------------------------------------------------------
// BeamFragment
// position of primary beam
// idx 0 - DirectionV::AUTO or DirectionV::DOWN
// 1 - DirectionV::UP
//---------------------------------------------------------

struct BeamFragment {
double py1[2];
double py2[2];
};

struct BeamSegment {
mu::LineF line;
int level;
bool above; // above level 0 or below? (meaningless for level 0)
};

struct TremAnchor {
ChordRest* chord1;
double y1;
double y2;
};

class BeamLayout
{
private:
static constexpr std::array _maxSlopes = { 0, 1, 2, 3, 4, 5, 6, 7 };
enum class BeamType {
INVALID,
BEAM,
TREMOLO
};
BeamType _beamType{ BeamType::INVALID };
EngravingItem* _e{ nullptr };
Beam* _beam{ nullptr };
Tremolo* _trem{ nullptr };
bool _isValid{ false };
bool _up{ false };
Fraction _tick{ Fraction(0, 1) };
double _spatium{ 0. };
PointF _startAnchor;
PointF _endAnchor;
double _slope;
bool _isGrace{ false };
int _beamSpacing{ 0 };
double _beamDist{ 0. };
double _beamWidth{ 0. };
std::vector<ChordRest*> _elements;
std::vector<int> _notes;
StaffType const* _tab;
bool _isBesideTabStaff;

int getMiddleStaffLine(ChordRest* startChord, ChordRest* endChord, int staffLines) const;
int computeDesiredSlant(int startNote, int endNote, int middleLine, int dictator, int pointer) const;
int isSlopeConstrained(int startNote, int endNote) const;
void offsetBeamWithAnchorShortening(std::vector<ChordRest*> chordRests, int& dictator, int& pointer, int staffLines,
bool isStartDictator, int stemLengthDictator) const;
bool isValidBeamPosition(int yPos, bool isStart, bool isAscending, bool isFlat, int staffLines, bool isOuter) const;
bool isBeamInsideStaff(int yPos, int staffLines, bool allowFloater) const;
int getOuterBeamPosOffset(int innerBeam, int beamCount, int staffLines) const;
void offsetBeamToRemoveCollisions(std::vector<ChordRest*> chordRests, int& dictator, int& pointer, const double startX,
const double endX, bool isFlat, bool isStartDictator) const;
int getBeamCount(std::vector<ChordRest*> chordRests) const;
bool is64thBeamPositionException(int& yPos, int staffLines) const;
int findValidBeamOffset(int outer, int beamCount, int staffLines, bool isStart, bool isAscending, bool isFlat) const;
void setValidBeamPositions(int& dictator, int& pointer, int beamCountD, int beamCountP, int staffLines, bool isStartDictator,
bool isFlat, bool isAscending);
void addMiddleLineSlant(int& dictator, int& pointer, int beamCount, int middleLine, int interval, int desiredSlant);
void add8thSpaceSlant(mu::PointF& dictatorAnchor, int dictator, int pointer, int beamCount, int interval, int middleLine, bool Flat);
bool noSlope();
int strokeCount(ChordRest* cr) const;
bool calculateAnchorsCross();
bool computeTremoloUp();
public:
BeamLayout() {}
BeamLayout(EngravingItem* e);

double beamDist() { return _beamDist; }
PointF startAnchor() { return _startAnchor; }
PointF endAnchor() { return _endAnchor; }
void setAnchors(PointF startAnchor, PointF endAnchor) { _startAnchor = startAnchor; _endAnchor = endAnchor; }

bool calculateAnchors(const std::vector<ChordRest*>& chordRests, const std::vector<int>& notes);

enum class ChordBeamAnchorType {
Start, End, Middle
};
double chordBeamAnchorX(const ChordRest* chord, ChordBeamAnchorType anchorType) const;
double chordBeamAnchorY(const ChordRest* chord) const;
PointF chordBeamAnchor(const ChordRest* chord, ChordBeamAnchorType anchorType) const;
int getMaxSlope() const;
void extendStem(Chord* chord, double addition);
};

//---------------------------------------------------------
// @@ Beam
//---------------------------------------------------------
Expand All @@ -70,6 +157,7 @@ class Beam final : public EngravingItem
double _beamWidth { 0.0f }; // how wide each beam is
mu::PointF _startAnchor;
mu::PointF _endAnchor;
BeamLayout _layoutInfo;

// for tabs
bool _isBesideTabStaff { false };
Expand All @@ -86,30 +174,12 @@ class Beam final : public EngravingItem
double _slope { 0.0 };

std::vector<int> _notes;
std::vector<TremAnchor> _tremAnchors;

friend class Factory;
Beam(System* parent);
Beam(const Beam&);

int getMiddleStaffLine(ChordRest* startChord, ChordRest* endChord, int staffLines) const;
int computeDesiredSlant(int startNote, int endNote, int middleLine, int dictator, int pointer) const;
int isSlopeConstrained(int startNote, int endNote) const;
int getMaxSlope() const;
int getBeamCount(std::vector<ChordRest*> chordRests) const;
void offsetBeamToRemoveCollisions(std::vector<ChordRest*> chordRests, int& dictator, int& pointer, const double startX,
const double endX, bool isFlat, bool isStartDictator) const;
void offsetBeamWithAnchorShortening(std::vector<ChordRest*> chordRests, int& dictator, int& pointer, int staffLines,
bool isStartDictator, int stemLengthDictator) const;
bool isBeamInsideStaff(int yPos, int staffLines, bool allowFloater) const;
int getOuterBeamPosOffset(int innerBeam, int beamCount, int staffLines) const;
bool isValidBeamPosition(int yPos, bool isStart, bool isAscending, bool isFlat, int staffLines, bool isOuter) const;
bool is64thBeamPositionException(int& yPos, int staffLines) const;
int findValidBeamOffset(int outer, int beamCount, int staffLines, bool isStart, bool isAscending, bool isFlat) const;
void setValidBeamPositions(int& dictator, int& pointer, int beamCountD, int beamCountP, int staffLines, bool isStartDictator,
bool isFlat, bool isAscending);
void addMiddleLineSlant(int& dictator, int& pointer, int beamCount, int middleLine, int interval, int desiredSlant);
void add8thSpaceSlant(mu::PointF& dictatorAnchor, int dictator, int pointer, int beamCount, int interval, int middleLine, bool Flat);
void extendStem(Chord* chord, double addition);
bool calcIsBeamletBefore(Chord* chord, int i, int level, bool isAfter32Break, bool isAfter64Break) const;
void createBeamSegment(ChordRest* startChord, ChordRest* endChord, int level);
void createBeamletSegment(ChordRest* chord, bool isBefore, int level);
Expand All @@ -120,6 +190,7 @@ class Beam final : public EngravingItem
void removeChordRest(ChordRest* a);

const Chord* findChordWithCustomStemDirection() const;
void setTremAnchors();

public:
~Beam();
Expand Down Expand Up @@ -151,13 +222,7 @@ class Beam final : public EngravingItem
void layout1();
void layout() override;

enum class ChordBeamAnchorType {
Start, End, Middle
};

double chordBeamAnchorX(const ChordRest* chord, ChordBeamAnchorType anchorType) const;
double chordBeamAnchorY(const ChordRest* chord) const;
PointF chordBeamAnchor(const ChordRest* chord, ChordBeamAnchorType anchorType) const;
PointF chordBeamAnchor(const ChordRest* chord, BeamLayout::ChordBeamAnchorType anchorType) const;

const std::vector<ChordRest*>& elements() const { return _elements; }
void clear() { _elements.clear(); }
Expand Down Expand Up @@ -232,6 +297,8 @@ class Beam final : public EngravingItem

bool hasAllRests();

const std::vector<TremAnchor>& tremAnchors() const { return _tremAnchors; }

private:
void initBeamEditData(EditData& ed);

Expand Down
70 changes: 58 additions & 12 deletions src/engraving/libmscore/chord.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1081,8 +1081,8 @@ void Chord::computeUp()
double noteX = stemPosX() + pagePos().x() - base.x();
PointF startAnchor = PointF();
PointF endAnchor = PointF();
startAnchor = _beam->chordBeamAnchor(firstChord, Beam::ChordBeamAnchorType::Start);
endAnchor = _beam->chordBeamAnchor(lastChord, Beam::ChordBeamAnchorType::End);
startAnchor = _beam->chordBeamAnchor(firstChord, BeamLayout::ChordBeamAnchorType::Start);
endAnchor = _beam->chordBeamAnchor(lastChord, BeamLayout::ChordBeamAnchorType::End);

if (this == _beam->elements().front()) {
_up = noteY > startAnchor.y();
Expand All @@ -1095,10 +1095,43 @@ void Chord::computeUp()
}
}
_beam->layout();
if (cross && _tremolo && _tremolo->twoNotes() && _tremolo->chord1() == this
&& _tremolo->chord1()->beam() == _tremolo->chord2()->beam()) {
_tremolo->layout(); // beam-infixed two-note trems have to be laid out here
}
if (!cross && !_beam->userModified()) {
_up = _beam->up();
}
return;
} else if (_tremolo && _tremolo->twoNotes()) {
Chord* c1 = _tremolo->chord1();
Chord* c2 = _tremolo->chord2();
bool cross = c1->staffMove() != c2->staffMove();
Measure* measure = findMeasure();
if (!cross && !_tremolo->userModified()) {
_up = _tremolo->up();
}
if (!measure->explicitParent()) {
// this method will be called later (from Measure::layoutCrossStaff) after the
// system is completely laid out.
// this is necessary because otherwise there's no way to deal with cross-staff beams
// because we don't know how far apart the staves actually are
return;
}
_tremolo->layout();
if (_tremolo->userModified()) {
Note* baseNote = _up ? downNote() : upNote();
PairF beamPos = _tremolo->beamPos();
double tremY = c1 == this ? beamPos.first : beamPos.second;
tremY *= spatium();
tremY += pagePos().y();
double noteY = baseNote->pagePos().y();
_up = noteY > tremY;
}
if (!cross && !_tremolo->userModified()) {
_up = _tremolo->up();
}
return;
}

bool staffHasMultipleVoices = measure()->hasVoices(staffIdx(), tick(), actualTicks());
Expand Down Expand Up @@ -1494,12 +1527,20 @@ int Chord::calcMinStemLength()
// minStemLength = ceil(minStemLength / 2.0) * 2;
}
}
if (_beam) {
if (_beam || (_tremolo && _tremolo->twoNotes())) {
int beamCount = (_beam ? beams() : 0) + (_tremolo ? _tremolo->lines() : 0);
static const int minInnerStemLengths[4] = { 10, 9, 8, 7 };
int innerStemLength = minInnerStemLengths[std::min(beams(), 3)];
int beamsHeight = beams() * (score()->styleB(Sid::useWideBeams) ? 4 : 3) - 1;
int innerStemLength = minInnerStemLengths[std::min(beamCount, 3)];
int beamsHeight = beamCount * (score()->styleB(Sid::useWideBeams) ? 4 : 3) - 1;
minStemLength = std::max(minStemLength, innerStemLength);
minStemLength += beamsHeight;
// for 4+ beams, there are a few situations where we need to lengthen the stem by 1
int noteLine = line();
int staffLines = staff()->lines(tick());
bool noteInStaff = (_up && noteLine > 0) || (!_up && noteLine < (staffLines - 1) * 2);
if (beamCount >= 4 && noteInStaff) {
minStemLength++;
}
}
return minStemLength;
}
Expand All @@ -1510,7 +1551,7 @@ int Chord::stemLengthBeamAddition() const
if (_hook) {
return 0;
}
int beamCount = beams();
int beamCount = (_beam ? beams() : 0) + (_tremolo ? _tremolo->lines() : 0);
switch (beamCount) {
case 0:
case 1:
Expand Down Expand Up @@ -1555,8 +1596,11 @@ int Chord::maxReduction(int extensionOutsideStaff) const
{ 0, 1, 1, 1, 1 }, // 2 beams
{ 0, 0, 0, 1, 1 }, // 3 beams
};
int beamCount = 0;
if (!_hook) {
beamCount = _tremolo ? _tremolo->lines() + (_beam ? beams() : 0) : beams();
}
bool hasTradHook = _hook && !score()->styleB(Sid::useStraightNoteFlags);
int beamCount = hasTradHook ? 0 : beams();
if (_hook && !hasTradHook) {
beamCount = std::min(beamCount, 2); // the straight glyphs extend outwards after 2 beams
}
Expand Down Expand Up @@ -1598,7 +1642,7 @@ int Chord::stemOpticalAdjustment(int stemEndPosition) const
if (_hook) {
return 0;
}
int beamCount = beams();
int beamCount = (_tremolo ? _tremolo->lines() : 0) + (_beam ? beams() : 0);
if (beamCount == 0 || beamCount > 2) {
return 0;
}
Expand Down Expand Up @@ -1671,8 +1715,9 @@ double Chord::calcDefaultStemLength()
int staffLineCount = staffItem ? staffItem->lines(tick()) : 5;
int shortStemStart = score()->styleI(Sid::shortStemStartLocation) * quarterSpacesPerLine + 1;
bool useWideBeams = score()->styleB(Sid::useWideBeams);

int middleLine = minStaffOverlap(_up, staffLineCount, beams(), !!_hook, useWideBeams ? 4 : 3, useWideBeams, !(isGrace() || isSmall()));
int beamCount = (_tremolo ? _tremolo->lines() : 0) + (_beam ? beams() : 0);
int middleLine
= minStaffOverlap(_up, staffLineCount, beamCount, !!_hook, useWideBeams ? 4 : 3, useWideBeams, !(isGrace() || isSmall()));
if (up()) {
int stemEndPosition = upLine() * quarterSpacesPerLine - defaultStemLength;
double stemEndPositionMag = (double)upLine() * quarterSpacesPerLine - (defaultStemLength * _relativeMag);
Expand Down Expand Up @@ -1708,7 +1753,7 @@ double Chord::calcDefaultStemLength()

stemLength = std::max(idealStemLength, minStemLengthQuarterSpaces);
}
if (beams() == 4 && _beam) {
if (beamCount == 4 && !_hook) {
stemLength = calc4BeamsException(stemLength);
}

Expand Down Expand Up @@ -1794,7 +1839,8 @@ bool Chord::shouldHaveHook() const
{
return shouldHaveStem()
&& durationType().hooks() > 0
&& !beam();
&& !beam()
&& !(tremolo() && tremolo()->twoNotes());
}

void Chord::createStem()
Expand Down
5 changes: 4 additions & 1 deletion src/engraving/libmscore/measure.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3347,7 +3347,10 @@ void Measure::layoutCrossStaff()
}
if (e->isChord()) {
Chord* c = toChord(e);
if (c->beam() && (c->beam()->cross() || c->beam()->userModified())) {
Beam* beam = c->beam();
Tremolo* tremolo = c->tremolo();
if ((beam && (beam->cross() || beam->userModified()))
|| tremolo && tremolo->twoNotes() && tremolo->userModified()) {
c->computeUp(); // for cross-staff beams
}
if (!c->graceNotes().empty()) {
Expand Down
Loading

0 comments on commit a62faf7

Please sign in to comment.