Skip to content

Commit

Permalink
Merge pull request #20481 from mike-spa/articulationTieSlurImprovement
Browse files Browse the repository at this point in the history
Several engraving improvements around articulations, ties, slurs
  • Loading branch information
cbjeukendrup authored Dec 29, 2023
2 parents 0dd4f78 + b08591e commit 602a95d
Show file tree
Hide file tree
Showing 18 changed files with 193 additions and 76 deletions.
2 changes: 2 additions & 0 deletions src/engraving/dom/articulation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
#include "staff.h"
#include "stafftype.h"
#include "system.h"
#include "note.h"
#include "tie.h"

#include "log.h"

Expand Down
24 changes: 24 additions & 0 deletions src/engraving/dom/slur.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,30 @@ bool Slur::isDirectionMixture(Chord* c1, Chord* c2)
return false;
}

double Slur::scalingFactor() const
{
Chord* startC = startElement() && startElement()->isChord() ? toChord(startElement()) : nullptr;
Chord* endC = endElement() && endElement()->isChord() ? toChord(endElement()) : nullptr;

if (!startC || !endC) {
return 1.0;
}

if ((startC->isGraceBefore() && startC->parent() == endC)
|| (endC->isGraceAfter() && endC->parent() == startC)) {
return style().styleD(Sid::graceNoteMag);
}

if (startC->isGrace()) {
startC = toChord(startC->parent());
}
if (endC->isGrace()) {
endC = toChord(endC->parent());
}

return 0.5 * (startC->intrinsicMag() + endC->intrinsicMag());
}

//---------------------------------------------------------
// setTrack
//---------------------------------------------------------
Expand Down
2 changes: 2 additions & 0 deletions src/engraving/dom/slur.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ class Slur final : public SlurTie
static int calcStemArrangement(EngravingItem* start, EngravingItem* end);
static bool isDirectionMixture(Chord* c1, Chord* c2);

double scalingFactor() const override;

private:

friend class Factory;
Expand Down
2 changes: 2 additions & 0 deletions src/engraving/dom/slurtie.h
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ class SlurTie : public Spanner

void fixupSegments(unsigned nsegs);

virtual double scalingFactor() const = 0;

protected:

bool m_up = true; // actual direction
Expand Down
16 changes: 16 additions & 0 deletions src/engraving/dom/tie.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,22 @@ bool Tie::setProperty(Pid propertyId, const PropertyValue& v)
return true;
}

double Tie::scalingFactor() const
{
const Note* startN = startNote();
const Note* endN = endNote();

if (!startN || !endN) {
return 1.0;
}

if (startN->isGrace()) {
return style().styleD(Sid::graceNoteMag);
}

return 0.5 * (startN->chord()->intrinsicMag() + endN->chord()->intrinsicMag());
}

//---------------------------------------------------------
// setStartNote
//---------------------------------------------------------
Expand Down
2 changes: 2 additions & 0 deletions src/engraving/dom/tie.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ class Tie final : public SlurTie

SlurTieSegment* newSlurTieSegment(System* parent) override { return new TieSegment(parent); }

double scalingFactor() const override;

private:
static Note* editStartNote;
static Note* editEndNote;
Expand Down
1 change: 1 addition & 0 deletions src/engraving/infrastructure/shape.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ class Shape
// ---

const std::vector<ShapeElement>& elements() const { return m_elements; }
std::vector<ShapeElement>& elements() { return m_elements; }

std::optional<ShapeElement> find_if(const std::function<bool(const ShapeElement&)>& func) const;
std::optional<ShapeElement> find_first(ElementType type) const;
Expand Down
61 changes: 51 additions & 10 deletions src/engraving/rendering/dev/chordlayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,8 @@ void ChordLayout::layoutArticulations(Chord* item, LayoutContext& ctx)
const bool keepArticsTogether = ctx.conf().styleB(Sid::articulationKeepTogether);
const double stemSideDistance = ctx.conf().styleMM(Sid::propertyDistanceStem) * mag;
const double headSideDistance = ctx.conf().styleMM(Sid::propertyDistanceHead) * mag;
const double tenutoAdditionalTieDistance = 0.6 * _spatium;
const double staccatoAdditionalTieDistance = 0.4 * _spatium;

int numCloseArtics = 0;
bool hasStaffArticsUp = false;
Expand Down Expand Up @@ -783,6 +785,7 @@ void ChordLayout::layoutArticulations(Chord* item, LayoutContext& ctx)

bool bottom = !a->up(); // true: articulation is below chord; false: articulation is above chord
TLayout::layoutItem(a, ctx); // must be done after assigning direction, or else symId is not reliable
bool leaveTieSpace = leaveSpaceForTie(a);

bool headSide = bottom == item->up();
double y = 0.0;
Expand Down Expand Up @@ -848,10 +851,16 @@ void ChordLayout::layoutArticulations(Chord* item, LayoutContext& ctx)
line = std::max(line, lines - (3 + ((numCloseArtics - 1) * 2)));
}
if (line < lines - 1) {
if (leaveTieSpace && line % 2) {
line += 1;
}
y = ((line & ~1) + 3) * _lineDist;
y -= a->height() * .5;
} else {
y = item->downPos() + 0.5 * item->downNote()->headHeight() + headSideDistance;
if (leaveTieSpace) {
y += a->isStaccato() ? staccatoAdditionalTieDistance : tenutoAdditionalTieDistance;
}
}
}
if (prevArticulation && (prevArticulation->up() == a->up())) {
Expand Down Expand Up @@ -903,10 +912,16 @@ void ChordLayout::layoutArticulations(Chord* item, LayoutContext& ctx)
line = std::min(line, 3 + ((numCloseArtics - 1) * 2));
}
if (line > 1) {
if (leaveTieSpace && line % 2) {
line -= 1;
}
y = (((line + 1) & ~1) - 3) * _lineDist;
y += a->height() * .5;
} else {
y = item->upPos() - 0.5 * item->downNote()->headHeight() - headSideDistance;
if (leaveTieSpace) {
y -= a->isStaccato() ? staccatoAdditionalTieDistance : tenutoAdditionalTieDistance;
}
}
}
if (prevArticulation && (prevArticulation->up() == a->up())) {
Expand Down Expand Up @@ -1120,19 +1135,23 @@ void ChordLayout::layoutArticulations3(Chord* item, Slur* slur, LayoutContext& c
}
Shape aShape = a->shape().translate(a->pos() + item->pos() + s->pos() + m->pos());
Shape sShape = ss->shape().translate(ss->pos());
if (aShape.intersects(sShape)) {
double d = ctx.conf().styleS(Sid::articulationMinDistance).val() * item->spatium();
d += slur->up()
? std::max(aShape.minVerticalDistance(sShape), 0.0)
: std::max(sShape.minVerticalDistance(aShape), 0.0);
d *= slur->up() ? -1 : 1;
double minDist = ctx.conf().styleMM(Sid::articulationMinDistance);
double vertClearance = a->up() ? aShape.verticalClearance(sShape) : sShape.verticalClearance(aShape);
if (vertClearance < minDist) {
minDist += slur->up()
? std::max(aShape.minVerticalDistance(sShape), 0.0)
: std::max(sShape.minVerticalDistance(aShape), 0.0);
minDist *= slur->up() ? -1 : 1;
for (auto iter2 = iter; iter2 != item->articulations().end(); ++iter2) {
Articulation* aa = *iter2;
aa->mutldata()->moveY(d);
Shape aaShape = aa->shape().translated(aa->pos() + item->pos() + s->pos() + m->pos());
aa->mutldata()->moveY(minDist);
if (sstaff && aa->addToSkyline()) {
sstaff->skyline().add(aaShape);
s->staffShape(item->staffIdx()).add(aaShape);
sstaff->skyline().add(aa->shape().translated(aa->pos() + item->pos() + s->pos() + m->pos()));
for (ShapeElement& sh : s->staffShape(item->staffIdx()).elements()) {
if (sh.item() == aa) {
sh.translate(0.0, minDist);
}
}
}
}
}
Expand Down Expand Up @@ -3626,6 +3645,28 @@ Shape ChordLayout::chordRestShape(const ChordRest* item, const LayoutConfigurati
return shape;
}

bool ChordLayout::leaveSpaceForTie(const Articulation* item)
{
if (!item->explicitParent() || !item->explicitParent()->isChord()) {
return false;
}

Chord* chord = toChord(item->chordRest());
bool up = item->ldata()->up;
Note* note = up ? chord->upNote() : chord->downNote();
Tie* tieFor = note->tieFor();
Tie* tieBack = note->tieBack();

if (!tieFor && !tieBack) {
return false;
}

bool leaveSpace = (tieFor && tieFor->up() == up && tieFor->isOuterTieOfChord(Grip::START))
|| (tieBack && tieBack->up() == up && tieBack->isOuterTieOfChord(Grip::END));

return leaveSpace;
}

void ChordLayout::fillShape(const Chord* item, ChordRest::LayoutData* ldata, const LayoutConfiguration& conf)
{
Shape shape(Shape::Type::Composite);
Expand Down
2 changes: 2 additions & 0 deletions src/engraving/rendering/dev/chordlayout.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ class ChordLayout
static void skipAccidentals(Segment* segment, track_idx_t startTrack, track_idx_t endTrack);

static Shape chordRestShape(const ChordRest* item, const LayoutConfiguration& conf);

static bool leaveSpaceForTie(const Articulation* item);
};
}

Expand Down
98 changes: 46 additions & 52 deletions src/engraving/rendering/dev/slurtielayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1736,8 +1736,13 @@ void SlurTieLayout::adjustX(TieSegment* tieSegment, SlurTiePos& sPos, Grip start
}
Shape chordShape = chord->shape().translate(chordSystemPos);
bool ignoreDot = start && isOuterTieOfChord;
static const std::set<ElementType> IGNORED_TYPES = {
ElementType::HOOK,
ElementType::STEM_SLASH,
ElementType::LEDGER_LINE
};
chordShape.remove_if([&](ShapeElement& s) {
return !s.item() || (s.item() == note || s.item()->isHook() || s.item()->isLedgerLine() || (s.item()->isNoteDot() && ignoreDot));
return !s.item() || s.item() == note || mu::contains(IGNORED_TYPES, s.item()->type()) || (s.item()->isNoteDot() && ignoreDot);
});

const double arcSideMargin = 0.3 * spatium;
Expand Down Expand Up @@ -2167,8 +2172,6 @@ void SlurTieLayout::computeBezier(TieSegment* tieSeg, PointF shoulderOffset)
path.cubicTo(bezier2 + bezier2Offset + tieThickness, bezier1 + bezier1Offset + tieThickness, PointF());
}

tieThickness = PointF(0.0, 3.0 * tieSeg->ldata()->midThickness());

// translate back
t.reset();
t.translate(tieStart.x(), tieStart.y());
Expand All @@ -2182,25 +2185,7 @@ void SlurTieLayout::computeBezier(TieSegment* tieSeg, PointF shoulderOffset)
tieSeg->ups(Grip::DRAG).p = t.map(tieDrag);
tieSeg->ups(Grip::SHOULDER).p = t.map(tieShoulder);

Shape shape(Shape::Type::Composite);
PointF start;
start = t.map(start);

double minH = std::abs(2 * tieSeg->ldata()->midThickness());
int nbShapes = 15;
const CubicBezier b(tieStart, tieSeg->ups(Grip::BEZIER1).pos(), tieSeg->ups(Grip::BEZIER2).pos(), tieSeg->ups(Grip::END).pos());
for (int i = 1; i <= nbShapes; i++) {
const PointF point = b.pointAtPercent(i / float(nbShapes));
RectF re = RectF(start, point).normalized();
if (re.height() < minH) {
tieLengthInSp = (minH - re.height()) * .5;
re.adjust(0.0, -tieLengthInSp, 0.0, tieLengthInSp);
}
shape.add(re, tieSeg);
start = point;
}

tieSeg->mutldata()->setShape(shape);
fillShape(tieSeg, tieLengthInSp);
}

void SlurTieLayout::computeBezier(SlurSegment* slurSeg, PointF shoulderOffset)
Expand Down Expand Up @@ -2346,14 +2331,8 @@ void SlurTieLayout::computeBezier(SlurSegment* slurSeg, PointF shoulderOffset)
slurSeg->ups(Grip::SHOULDER).p = toSystemCoordinates.map(p6);

// Set slur thickness
double w = slurSeg->style().styleMM(Sid::SlurMidWidth) - slurSeg->style().styleMM(Sid::SlurEndWidth);
if (slurSeg->staff()) {
w *= slurSeg->staff()->staffMag(slurSeg->slur()->tick());
}
if ((c2 - c1) <= _spatium) {
w *= .5;
}
PointF thick(0.0, w);
computeMidThickness(slurSeg, p2.x() / slurSeg->spatium());
PointF thick(0.0, slurSeg->ldata()->midThickness());

// Set path
PainterPath path = PainterPath();
Expand All @@ -2362,30 +2341,11 @@ void SlurTieLayout::computeBezier(SlurSegment* slurSeg, PointF shoulderOffset)
if (slurSeg->slur()->styleType() == SlurStyleType::Solid) {
path.cubicTo(p4 + thick, p3 + thick, PointF());
}
thick = PointF(0.0, 3.0 * w);

path = toSystemCoordinates.map(path);
slurSeg->mutldata()->path.set_value(path);

// Create shape for the skyline
Shape shape;
PointF start = pp1;
int nbShapes = 32;
double minH = abs(2 * w);
const CubicBezier b(slurSeg->ups(Grip::START).pos(), slurSeg->ups(Grip::BEZIER1).pos(), slurSeg->ups(Grip::BEZIER2).pos(),
slurSeg->ups(Grip::END).pos());
for (int i = 1; i <= nbShapes; i++) {
const PointF point = b.pointAtPercent(i / float(nbShapes));
RectF re = RectF(start, point).normalized();
if (re.height() < minH) {
double d1 = (minH - re.height()) * .5;
re.adjust(0.0, -d1, 0.0, d1);
}
shape.add(re, slurSeg);
start = point;
}

slurSeg->mutldata()->setShape(shape);
fillShape(slurSeg, p2.x() / slurSeg->spatium());
}

double SlurTieLayout::defaultStemLengthStart(TremoloTwoChord* tremolo)
Expand Down Expand Up @@ -2477,12 +2437,46 @@ void SlurTieLayout::computeMidThickness(SlurTieSegment* slurTieSeg, double slurT

bool invalid = RealIsEqualOrMore(minTieLength, shortTieLimit);

double finalThickness;
if (slurTieLengthInSp > shortTieLimit || invalid) {
slurTieSeg->mutldata()->midThickness.set_value(normalThickness);
finalThickness = normalThickness;
} else {
const double A = 1 / (shortTieLimit - minTieLength);
const double B = normalThickness - minTieThickness;
const double C = shortTieLimit * minTieThickness - minTieLength * normalThickness;
slurTieSeg->mutldata()->midThickness.set_value(A * (B * slurTieLengthInSp + C));
finalThickness = A * (B * slurTieLengthInSp + C);
}

double scalingFactor = slurTieSeg->slurTie()->scalingFactor();

finalThickness = std::min(finalThickness, normalThickness * scalingFactor);

slurTieSeg->mutldata()->midThickness.set_value(finalThickness);
}

void SlurTieLayout::fillShape(SlurTieSegment* slurTieSeg, double slurTieLengthInSp)
{
Shape shape(Shape::Type::Composite);
PointF startPoint = slurTieSeg->ups(Grip::START).pos();

double midThickness = 2 * slurTieSeg->ldata()->midThickness();
int nbShapes = round(5.0 * slurTieLengthInSp);
nbShapes = std::max(nbShapes, 20);
nbShapes = std::min(nbShapes, 50);
const CubicBezier b(startPoint, slurTieSeg->ups(Grip::BEZIER1).pos(), slurTieSeg->ups(Grip::BEZIER2).pos(),
slurTieSeg->ups(Grip::END).pos());
for (int i = 1; i <= nbShapes; i++) {
double percent = pow(sin(0.5 * M_PI * (double(i) / double(nbShapes))), 2);
const PointF point = b.pointAtPercent(percent);
RectF re = RectF(startPoint, point).normalized();
double approxThicknessAtPercent = (1 - 2 * abs(0.5 - percent)) * midThickness;
if (re.height() < approxThicknessAtPercent) {
double adjust = (approxThicknessAtPercent - re.height()) * .5;
re.adjust(0.0, -adjust, 0.0, adjust);
}
shape.add(re, slurTieSeg);
startPoint = point;
}

slurTieSeg->mutldata()->setShape(shape);
}
1 change: 1 addition & 0 deletions src/engraving/rendering/dev/slurtielayout.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ class SlurTieLayout
static void layoutSegment(SlurSegment* item, LayoutContext& ctx, const PointF& p1, const PointF& p2);

static void computeMidThickness(SlurTieSegment* slurTieSeg, double slurTieLengthInSp);
static void fillShape(SlurTieSegment* slurTieSeg, double slurTieLengthInSp);
};
}

Expand Down
Loading

0 comments on commit 602a95d

Please sign in to comment.