Skip to content

Commit

Permalink
Small harmonylayout tidy
Browse files Browse the repository at this point in the history
  • Loading branch information
miiizen committed Feb 21, 2025
1 parent 72ca13b commit f1b87f8
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 143 deletions.
256 changes: 117 additions & 139 deletions src/engraving/rendering/score/harmonylayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,153 +43,24 @@ void HarmonyLayout::layoutHarmonies(const std::vector<Segment*>& sl, LayoutConte
{
for (const Segment* s : sl) {
for (EngravingItem* e : s->annotations()) {
if (e->isHarmony()) {
Harmony* h = toHarmony(e);
// For chord symbols that coincide with a chord or rest,
// a partial layout can also happen (if needed) during ChordRest layout
// in order to calculate a bbox and allocate its shape to the ChordRest.
// But that layout (if it happens at all) does not do autoplace,
// so we need the full layout here.
TLayout::layoutHarmony(h, h->mutldata(), ctx);
Autoplace::autoplaceSegmentElement(h, h->mutldata());
if (!e->isHarmony()) {
continue;
}
Harmony* h = toHarmony(e);
// For chord symbols that coincide with a chord or rest,
// a partial layout can also happen (if needed) during ChordRest layout
// in order to calculate a bbox and allocate its shape to the ChordRest.
// But that layout (if it happens at all) does not do autoplace,
// so we need the full layout here.
TLayout::layoutHarmony(h, h->mutldata(), ctx);
Autoplace::autoplaceSegmentElement(h, h->mutldata());
}
}
}

void HarmonyLayout::alignHarmonies(const System* system, const std::vector<Segment*>& sl, bool harmony, const double maxShiftAbove,
const double maxShiftBelow)
{
// Help class.
// Contains harmonies/fretboard per segment.
class HarmonyList : public std::vector<EngravingItem*>
{
OBJECT_ALLOCATOR(engraving, HarmonyList)

std::map<const Segment*, std::vector<EngravingItem*> > elements;
std::vector<EngravingItem*> modified;

EngravingItem* getReferenceElement(const Segment* s, bool above, bool visible) const
{
// Returns the reference element for aligning.
// When a segments contains multiple harmonies/fretboard, the lowest placed
// element (for placement above, otherwise the highest placed element) is
// used for alignment.
EngravingItem* element { nullptr };
for (EngravingItem* e : elements.at(s)) {
// Only chord symbols have styled offset, fretboards don't.
if (!e->autoplace() || (e->isHarmony() && !e->isStyled(Pid::OFFSET)) || (visible && !e->visible())) {
continue;
}
if (!element) {
element = e;
} else {
if ((e->placeAbove() && above && (element->y() < e->y()))
|| (e->placeBelow() && !above && (element->y() > e->y()))) {
element = e;
}
}
}
return element;
}

public:
HarmonyList()
{
elements.clear();
modified.clear();
}

void append(const Segment* s, EngravingItem* e)
{
elements[s].push_back(e);
}

double getReferenceHeight(bool above) const
{
// The reference height is the height of
// the lowest element if placed above
// or
// the highest element if placed below.
bool first { true };
double ref { 0.0 };
for (auto s : muse::keys(elements)) {
EngravingItem* e { getReferenceElement(s, above, true) };
if (!e) {
continue;
}
if (e->placeAbove() && above) {
ref = first ? e->y() : std::min(ref, e->y());
first = false;
} else if (e->placeBelow() && !above) {
ref = first ? e->y() : std::max(ref, e->y());
first = false;
}
}
return ref;
}

bool align(bool above, double reference, double maxShift)
{
// Align the elements. If a segment contains multiple elements,
// only the reference elements is used in the algorithm. All other
// elements will remain their original placement with respect to
// the reference element.
bool moved { false };
if (muse::RealIsNull(reference)) {
return moved;
}

for (auto s : muse::keys(elements)) {
std::list<EngravingItem*> handled;
EngravingItem* be = getReferenceElement(s, above, false);
if (!be) {
// If there are only invisible elements, we have to use an invisible
// element for alignment reference.
be = getReferenceElement(s, above, true);
}
if (be && ((above && (be->y() < (reference + maxShift))) || ((!above && (be->y() > (reference - maxShift)))))) {
double shift = be->ldata()->pos().y();
be->mutldata()->setPosY(reference - be->ryoffset());
shift -= be->ldata()->pos().y();
for (EngravingItem* e : elements[s]) {
if ((above && e->placeBelow()) || (!above && e->placeAbove())) {
continue;
}
modified.push_back(e);
handled.push_back(e);
moved = true;
if (e != be) {
e->mutldata()->moveY(-shift);
}
}
for (auto e : handled) {
muse::remove(elements[s], e);
}
}
}
return moved;
}

void addToSkyline(const System* system)
{
for (EngravingItem* e : modified) {
const Segment* s = toSegment(e->explicitParent());
const MeasureBase* m = toMeasureBase(s->explicitParent());
system->staff(e->staffIdx())->skyline().add(e->shape().translated(e->pos() + s->pos() + m->pos()));
if (e->isFretDiagram()) {
FretDiagram* fd = toFretDiagram(e);
Harmony* h = fd->harmony();
if (h) {
system->staff(e->staffIdx())->skyline().add(h->shape().translated(h->pos() + fd->pos() + s->pos() + m->pos()));
} else {
system->staff(e->staffIdx())->skyline().add(fd->shape().translated(fd->pos() + s->pos() + m->pos()));
}
}
}
}
};

if (muse::RealIsNull(maxShiftAbove) && muse::RealIsNull(maxShiftBelow)) {
return;
}
Expand Down Expand Up @@ -223,3 +94,110 @@ void HarmonyLayout::alignHarmonies(const System* system, const std::vector<Segme
staves[idx].addToSkyline(system);
}
}

EngravingItem* HarmonyList::getReferenceElement(const Segment* s, bool above, bool visible) const
{
// Returns the reference element for aligning.
// When a segments contains multiple harmonies/fretboard, the lowest placed
// element (for placement above, otherwise the highest placed element) is
// used for alignment.
EngravingItem* element { nullptr };
for (EngravingItem* e : elements.at(s)) {
// Only chord symbols have styled offset, fretboards don't.
if (!e->autoplace() || (e->isHarmony() && !e->isStyled(Pid::OFFSET)) || (visible && !e->visible())) {
continue;
}
if (!element) {
element = e;
} else if ((e->placeAbove() && above && (element->y() < e->y()))
|| (e->placeBelow() && !above && (element->y() > e->y()))) {
element = e;
}
}
return element;
}

double HarmonyList::getReferenceHeight(bool above) const
{
// The reference height is the height of
// the lowest element if placed above
// or
// the highest element if placed below.
bool first { true };
double ref { 0.0 };
for (auto s : muse::keys(elements)) {
EngravingItem* e { getReferenceElement(s, above, true) };
if (!e) {
continue;
}
if (e->placeAbove() && above) {
ref = first ? e->y() : std::min(ref, e->y());
first = false;
} else if (e->placeBelow() && !above) {
ref = first ? e->y() : std::max(ref, e->y());
first = false;
}
}
return ref;
}

bool HarmonyList::align(bool above, double reference, double maxShift)
{
// Align the elements. If a segment contains multiple elements,
// only the reference elements is used in the algorithm. All other
// elements will remain their original placement with respect to
// the reference element.
bool moved { false };
if (muse::RealIsNull(reference)) {
return moved;
}

for (auto s : muse::keys(elements)) {
std::list<EngravingItem*> handled;
EngravingItem* be = getReferenceElement(s, above, false);
if (!be) {
// If there are only invisible elements, we have to use an invisible
// element for alignment reference.
be = getReferenceElement(s, above, true);
}
if (be && ((above && (be->y() < (reference + maxShift))) || ((!above && (be->y() > (reference - maxShift)))))) {
double shift = be->ldata()->pos().y();
be->mutldata()->setPosY(reference - be->ryoffset());
shift -= be->ldata()->pos().y();
for (EngravingItem* e : elements[s]) {
if ((above && e->placeBelow()) || (!above && e->placeAbove())) {
continue;
}
modified.push_back(e);
handled.push_back(e);
moved = true;
if (e != be) {
e->mutldata()->moveY(-shift);
}
}
for (auto e : handled) {
muse::remove(elements[s], e);
}
}
}
return moved;
}

void HarmonyList::addToSkyline(const System* system)
{
for (EngravingItem* e : modified) {
const Segment* s = toSegment(e->explicitParent());
const MeasureBase* m = toMeasureBase(s->explicitParent());
system->staff(e->staffIdx())->skyline().add(e->shape().translated(e->pos() + s->pos() + m->pos()));
if (!e->isFretDiagram()) {
continue;
}
FretDiagram* fd = toFretDiagram(e);
Harmony* h = fd->harmony();
if (h) {
system->staff(e->staffIdx())->skyline().add(h->shape().translated(h->pos() + fd->pos() + s->pos() + m->pos()));
} else {
system->staff(e->staffIdx())->skyline().add(fd->shape().translated(fd->pos() + s->pos() + m->pos()));
}
}
}
32 changes: 28 additions & 4 deletions src/engraving/rendering/score/harmonylayout.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MU_ENGRAVING_HARMONYLAYOUT_DEV_H
#define MU_ENGRAVING_HARMONYLAYOUT_DEV_H
#pragma once

#include <vector>

Expand All @@ -32,6 +31,33 @@ class System;
}

namespace mu::engraving::rendering::score {
// Help class.
// Contains harmonies/fretboard per segment.
class HarmonyList : public std::vector<EngravingItem*>
{
// muse::OBJECT_ALLOCATOR(mu::engraving, HarmonyList);

std::map<const Segment*, std::vector<EngravingItem*> > elements;
std::vector<EngravingItem*> modified;

EngravingItem* getReferenceElement(const Segment* s, bool above, bool visible) const;

public:
HarmonyList()
{
elements.clear();
modified.clear();
}

void append(const Segment* s, EngravingItem* e) { elements[s].push_back(e); }

double getReferenceHeight(bool above) const;

bool align(bool above, double reference, double maxShift);

void addToSkyline(const System* system);
};

class HarmonyLayout
{
public:
Expand All @@ -41,5 +67,3 @@ class HarmonyLayout
const double maxShiftBelow);
};
}

#endif // MU_ENGRAVING_HARMONYLAYOUT_DEV_H

0 comments on commit f1b87f8

Please sign in to comment.