Skip to content

Commit

Permalink
Merge pull request #3836 from brdvd/feat/beam-overlap
Browse files Browse the repository at this point in the history
Improve beam shortening to resolve overlap
  • Loading branch information
lpugin authored Oct 22, 2024
2 parents 4464ef7 + ee55ea0 commit 26afb67
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 135 deletions.
9 changes: 9 additions & 0 deletions include/vrv/adjustbeamsfunctor.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ class AdjustBeamsFunctor : public DocFunctor {
// Get the drawing interface of the outer beam or the outer ftrem
BeamDrawingInterface *GetOuterBeamInterface() const;

/**
* Calculate the overlap with other layer elements that
* are placed within the duration of the element
*/
int CalcLayerOverlap(const LayerElement *beamElement) const;

// Rounds the overlap to the closest multiple of a half unit
int AdjustOverlapToHalfUnit(int overlap, int unit) const;

public:
//
private:
Expand Down
7 changes: 0 additions & 7 deletions include/vrv/beam.h
Original file line number Diff line number Diff line change
Expand Up @@ -375,11 +375,6 @@ class Beam : public LayerElement,
*/
void FilterList(ListOfConstObjects &childList) const override;

/**
* See LayerElement::SetElementShortening
*/
void SetElementShortening(int shortening) override;

private:
/**
* A pointer to the beam with which stems are shared.
Expand Down Expand Up @@ -410,7 +405,6 @@ class BeamElementCoord {
m_tabDurSym = NULL;
m_stem = NULL;
m_overlapMargin = 0;
m_maxShortening = -1;
m_beamRelativePlace = BEAMPLACE_NONE;
m_partialFlagPlace = BEAMPLACE_NONE;
}
Expand Down Expand Up @@ -458,7 +452,6 @@ class BeamElementCoord {
data_DURATION m_dur; // drawing duration
int m_breaksec;
int m_overlapMargin;
int m_maxShortening; // maximum allowed shortening in half units
bool m_centered; // beam is centered on the line
data_BEAMPLACE m_beamRelativePlace;
char m_partialFlags[MAX_DURATION_PARTIALS];
Expand Down
5 changes: 0 additions & 5 deletions include/vrv/ftrem.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,6 @@ class FTrem : public LayerElement, public BeamDrawingInterface, public AttFTremV
*/
void FilterList(ListOfConstObjects &childList) const override;

/**
* See LayerElement::SetElementShortening
*/
void SetElementShortening(int shortening) override;

public:
/** */
BeamSegment m_beamSegment;
Expand Down
11 changes: 0 additions & 11 deletions include/vrv/layerelement.h
Original file line number Diff line number Diff line change
Expand Up @@ -304,11 +304,6 @@ class LayerElement : public Object,
std::pair<int, bool> CalcElementHorizontalOverlap(const Doc *doc, const std::vector<LayerElement *> &otherElements,
bool areDotsAdjusted, bool isChordElement, bool isLowerElement = false, bool unison = true);

/**
* Helper function to set shortening for elements with beam interface
*/
virtual void SetElementShortening(int shortening) {}

/**
* Get the stem mod for the element (if any)
*/
Expand All @@ -330,12 +325,6 @@ class LayerElement : public Object,
*/
MapOfDotLocs CalcOptimalDotLocations();

/**
* Calculate the overlap with other layer elements that
* are placed within the duration of the element
*/
int CalcLayerOverlap(const Doc *doc, int direction, int y1, int y2);

//----------//
// Functors //
//----------//
Expand Down
103 changes: 92 additions & 11 deletions src/adjustbeamsfunctor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ FunctorCode AdjustBeamsFunctor::VisitBeam(Beam *beam)
m_x2 = beamSegment.m_beamElementCoordRefs.back()->m_x;
m_beamSlope = beamSegment.m_beamSlope;
m_directionBias = (beam->m_drawingPlace == BEAMPLACE_above) ? 1 : -1;
m_overlapMargin = beam->CalcLayerOverlap(m_doc, m_directionBias, m_y1, m_y2);
m_overlapMargin = this->CalcLayerOverlap(beam);
}
return FUNCTOR_CONTINUE;
}
Expand Down Expand Up @@ -148,15 +148,13 @@ FunctorCode AdjustBeamsFunctor::VisitClef(Clef *clef)
// calculate margins for the clef
const int leftMargin = m_directionBias * (currentBeamYLeft - clefBounds) - beams * beamWidth;
const int rightMargin = m_directionBias * (currentBeamYRight - clefBounds) - beams * beamWidth;
const int overlapMargin = std::min(leftMargin, rightMargin);
int overlapMargin = std::min(leftMargin, rightMargin);
if (overlapMargin >= 0) return FUNCTOR_CONTINUE;
// calculate offset required for the beam
overlapMargin *= -m_directionBias;
const int unit = m_doc->GetDrawingUnit(staff->m_drawingStaffSize);
const int unitChangeNumber = ((std::abs(overlapMargin) + unit / 6) / unit);
if (unitChangeNumber > 0) {
const int adjust = unitChangeNumber * unit * m_directionBias;
if (std::abs(adjust) > std::abs(m_overlapMargin)) m_overlapMargin = adjust;
}
const int adjust = this->AdjustOverlapToHalfUnit(overlapMargin, unit);
if (std::abs(adjust) > std::abs(m_overlapMargin)) m_overlapMargin = adjust;

return FUNCTOR_CONTINUE;
}
Expand All @@ -181,7 +179,7 @@ FunctorCode AdjustBeamsFunctor::VisitFTrem(FTrem *fTrem)
m_x2 = beamSegment.m_beamElementCoordRefs.back()->m_x;
m_beamSlope = beamSegment.m_beamSlope;
m_directionBias = (fTrem->m_drawingPlace == BEAMPLACE_above) ? 1 : -1;
m_overlapMargin = fTrem->CalcLayerOverlap(m_doc, m_directionBias, m_y1, m_y2);
m_overlapMargin = this->CalcLayerOverlap(fTrem);
}
return FUNCTOR_CONTINUE;
}
Expand Down Expand Up @@ -301,7 +299,7 @@ FunctorCode AdjustBeamsFunctor::VisitRest(Rest *rest)
// Calculate possible overlap for the rest with beams
const int beams = m_outerBeam->GetBeamPartDuration(rest, false) - DURATION_4;
const int beamWidth = m_outerBeam->m_beamWidth;
const int overlapMargin = rest->Intersects(m_outerBeam, SELF, beams * beamWidth, true) * m_directionBias;
int overlapMargin = rest->Intersects(m_outerBeam, SELF, beams * beamWidth, true) * m_directionBias;

// Adjust drawing location for the rest based on the overlap with beams.
// Adjustment should be an even number, so that the rest is positioned properly
Expand Down Expand Up @@ -335,9 +333,9 @@ FunctorCode AdjustBeamsFunctor::VisitRest(Rest *rest)
}
}

overlapMargin *= -m_directionBias;
const int unit = m_doc->GetDrawingUnit(staff->m_drawingStaffSize);
const int unitChangeNumber = std::abs(overlapMargin) / unit + 1;
const int adjust = unitChangeNumber * unit * m_directionBias;
const int adjust = this->AdjustOverlapToHalfUnit(overlapMargin, unit);
if (std::abs(adjust) > std::abs(m_overlapMargin)) m_overlapMargin = adjust;

return FUNCTOR_CONTINUE;
Expand All @@ -350,4 +348,87 @@ BeamDrawingInterface *AdjustBeamsFunctor::GetOuterBeamInterface() const
return NULL;
}

int AdjustBeamsFunctor::CalcLayerOverlap(const LayerElement *beamElement) const
{
assert(beamElement);

const Layer *parentLayer = vrv_cast<const Layer *>(beamElement->GetFirstAncestor(LAYER));
if (!parentLayer) return 0;
// Check whether there are elements on the other layer in the duration of the current beam
ListOfConstObjects collidingElementsList = parentLayer->GetLayerElementsForTimeSpanOf(beamElement, true);
// Ignore any elements part of a stem-sameas beam
if (beamElement->Is(BEAM)) {
const Beam *beam = vrv_cast<const Beam *>(beamElement);
const Beam *stemSameAsBeam = beam->GetStemSameasBeam();
if (stemSameAsBeam) {
collidingElementsList.remove_if([stemSameAsBeam](const Object *object) {
const LayerElement *layerElement = vrv_cast<const LayerElement *>(object);
return (layerElement->GetAncestorBeam() == stemSameAsBeam);
});
}
}
// If there are none - stop here, there's nothing to be done
if (collidingElementsList.empty()) return 0;

const Staff *staff = beamElement->GetAncestorStaff();
const int drawingY = beamElement->GetDrawingY();
const int yMin = std::min(m_y1, m_y2);
const int yMax = std::max(m_y1, m_y2);
const int unit = m_doc->GetDrawingUnit(staff->m_drawingStaffSize);

int elementOverlap = 0;
std::vector<int> elementOverlaps;
for (const Object *object : collidingElementsList) {
const LayerElement *layerElement = vrv_cast<const LayerElement *>(object);
if (!beamElement->HorizontalContentOverlap(object)) continue;
const int elementBottom = layerElement->GetContentBottom();
const int elementTop = layerElement->GetContentTop();
if (m_directionBias > 0) {
// Ensure that there's actual overlap first
if (elementBottom > yMax) continue;
if (drawingY >= elementTop) continue;
// If there is a mild overlap, then decrease the beam stem length via negative overlap
if (elementBottom > yMax - 3 * unit) {
elementOverlap = std::min(elementBottom - yMax, 0);
}
else {
elementOverlap = std::max(elementTop - yMin, 0);
}
}
else {
// Ensure that there's actual overlap first
if (elementTop < yMin) continue;
if (drawingY <= elementBottom) continue;
// If there is a mild overlap, then decrease the beam stem length via negative overlap
if (elementTop < yMin + 3 * unit) {
elementOverlap = std::min(yMin - elementTop, 0);
}
else {
elementOverlap = std::max(yMax - elementBottom, 0);
}
}
elementOverlaps.emplace_back(elementOverlap);
}
if (elementOverlaps.empty()) return 0;

const auto [minOverlap, maxOverlap] = std::minmax_element(elementOverlaps.begin(), elementOverlaps.end());
int overlap = 0;
if (*maxOverlap > 0) {
overlap = *maxOverlap * m_directionBias;
}
else if (*minOverlap < 0) {
overlap = (*minOverlap - unit) * m_directionBias;
}
const int adjust = this->AdjustOverlapToHalfUnit(overlap, unit);
return adjust;
}

int AdjustBeamsFunctor::AdjustOverlapToHalfUnit(int overlap, int unit) const
{
const int overlapSign = (overlap >= 0) ? 1 : -1;
const int halfUnit = unit / 2;
const int halfUnitChangeNumber = (std::abs(overlap) + halfUnit / 2) / halfUnit;
return halfUnitChangeNumber * halfUnit * overlapSign;
}

} // namespace vrv
12 changes: 1 addition & 11 deletions src/beam.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1951,13 +1951,9 @@ int BeamElementCoord::CalculateStemLength(
const int standardStemLen = STANDARD_STEMLENGTH * 2;
// Check if the stem has to be shortened because outside the staff
// In this case, Note::CalcStemLenInThirdUnits will return a value shorter than 2 * STANDARD_STEMLENGTH
int stemLenInHalfUnits
= !m_maxShortening ? standardStemLen : m_closestNote->CalcStemLenInThirdUnits(staff, stemDir) * 2 / 3;
const int stemLenInHalfUnits = m_closestNote->CalcStemLenInThirdUnits(staff, stemDir) * 2 / 3;
// Do not extend when not on the staff line
if (stemLenInHalfUnits != standardStemLen) {
if ((m_maxShortening > 0) && ((stemLenInHalfUnits - standardStemLen) > m_maxShortening)) {
stemLenInHalfUnits = standardStemLen - m_maxShortening;
}
extend = false;
}

Expand Down Expand Up @@ -2098,12 +2094,6 @@ std::pair<int, int> Beam::GetAdditionalBeamCount() const
return { topShortestDur - DURATION_8, bottomShortestDur - DURATION_8 };
}

void Beam::SetElementShortening(int shortening)
{
std::for_each(m_beamSegment.m_beamElementCoordRefs.begin(), m_beamSegment.m_beamElementCoordRefs.end(),
[shortening](BeamElementCoord *coord) { coord->m_maxShortening = shortening; });
}

int Beam::GetBeamPartDuration(int x, bool includeRests) const
{
// find element with position closest to the specified coordinate
Expand Down
6 changes: 0 additions & 6 deletions src/ftrem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,6 @@ std::pair<int, int> FTrem::GetFloatingBeamCount() const
return { this->GetBeams(), this->GetBeamsFloat() };
}

void FTrem::SetElementShortening(int shortening)
{
std::for_each(m_beamSegment.m_beamElementCoordRefs.begin(), m_beamSegment.m_beamElementCoordRefs.end(),
[shortening](BeamElementCoord *coord) { coord->m_maxShortening = shortening; });
}

//----------------------------------------------------------------------------
// Functors methods
//----------------------------------------------------------------------------
Expand Down
84 changes: 0 additions & 84 deletions src/layerelement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -996,90 +996,6 @@ MapOfDotLocs LayerElement::CalcOptimalDotLocations()
return usePrimary ? dotLocs1 : dotLocs2;
}

int LayerElement::CalcLayerOverlap(const Doc *doc, int direction, int y1, int y2)
{
Layer *parentLayer = vrv_cast<Layer *>(this->GetFirstAncestor(LAYER));
if (!parentLayer) return 0;
// Check whether there are elements on the other layer in the duration of the current beam
ListOfObjects collidingElementsList = parentLayer->GetLayerElementsForTimeSpanOf(this, true);
// Ignore any elements part of a stem-sameas beam
if (this->Is(BEAM)) {
const Beam *beam = vrv_cast<Beam *>(this);
const Beam *stemSameAsBeam = beam->GetStemSameasBeam();
if (stemSameAsBeam) {
collidingElementsList.remove_if([stemSameAsBeam](Object *object) {
const LayerElement *layerElement = vrv_cast<LayerElement *>(object);
return (layerElement->GetAncestorBeam() == stemSameAsBeam);
});
}
}
// If there are none - stop here, there's nothing to be done
if (collidingElementsList.empty()) return 0;

Staff *staff = this->GetAncestorStaff();

const int unit = doc->GetDrawingUnit(staff->m_drawingStaffSize);
int leftMargin = 0;
int rightMargin = 0;
bool sameDirElement = false;
std::vector<int> elementOverlaps;
for (Object *object : collidingElementsList) {
LayerElement *layerElement = vrv_cast<LayerElement *>(object);
if (!this->HorizontalContentOverlap(object)) continue;
const int elementBottom = layerElement->GetDrawingBottom(doc, staff->m_drawingStaffSize);
const int elementTop = layerElement->GetDrawingTop(doc, staff->m_drawingStaffSize);
if (direction > 0) {
// make sure that there's actual overlap first
if ((elementBottom > y1) && (elementBottom > y2)) continue;
const int currentBottom = this->GetDrawingBottom(doc, staff->m_drawingStaffSize);
if (currentBottom >= elementTop) continue;
const StemmedDrawingInterface *stemInterface = layerElement->GetStemmedDrawingInterface();
if (stemInterface && (sameDirElement || (stemInterface->GetDrawingStemDir() == STEMDIRECTION_up))) {
if (elementBottom - stemInterface->GetDrawingStemLen() < currentBottom) continue;
leftMargin = unit + y1 - elementBottom;
rightMargin = unit + y2 - elementBottom;
sameDirElement = true;
}
else {
leftMargin = elementTop - y1;
rightMargin = elementTop - y2;
}
}
else {
// make sure that there's actual overlap first
if ((elementTop < y1) && (elementTop < y2)) continue;
const int currentTop = this->GetDrawingTop(doc, staff->m_drawingStaffSize);
if (currentTop <= elementBottom) continue;
const StemmedDrawingInterface *stemInterface = layerElement->GetStemmedDrawingInterface();
if (stemInterface && (sameDirElement || (stemInterface->GetDrawingStemDir() == STEMDIRECTION_down))) {
if (currentTop - stemInterface->GetDrawingStemLen() > currentTop) continue;
leftMargin = unit + y1 - elementTop;
rightMargin = unit + y2 - elementTop;
sameDirElement = true;
}
else {
leftMargin = elementBottom - y1;
rightMargin = elementBottom - y2;
}
}
elementOverlaps.emplace_back(std::max(leftMargin * direction, rightMargin * direction));
}
if (elementOverlaps.empty()) return 0;

const auto maxOverlap = std::max_element(elementOverlaps.begin(), elementOverlaps.end());
int overlap = 0;
if (*maxOverlap >= 0) {
const int multiplier = sameDirElement ? -1 : 1;
overlap = ((*maxOverlap == 0) ? unit : *maxOverlap) * direction * multiplier;
}
else {
int maxShorteningInHalfUnits = (std::abs(*maxOverlap) / unit) * 2;
if (maxShorteningInHalfUnits > 0) --maxShorteningInHalfUnits;
this->SetElementShortening(maxShorteningInHalfUnits);
}
return overlap;
}

data_STEMMODIFIER LayerElement::GetDrawingStemMod() const
{
const AttStems *stem = dynamic_cast<const AttStems *>(this);
Expand Down

0 comments on commit 26afb67

Please sign in to comment.