Skip to content
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

Improve beam shortening to resolve overlap #3836

Merged
merged 7 commits into from
Oct 22, 2024
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
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
Loading