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

Fix cross staff glissando angle and improve note anchored line layout #24992

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
7 changes: 4 additions & 3 deletions src/engraving/dom/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1187,13 +1187,14 @@ Fraction actualTicks(Fraction duration, Tuplet* tuplet, Fraction timeStretch)
return f;
}

double yStaffDifference(const System* system1, staff_idx_t staffIdx1, const System* system2, staff_idx_t staffIdx2)
double yStaffDifference(const System* system1, const System* system2, staff_idx_t staffIdx)
{
if (!system1 || !system2) {
return 0.0;
}
const SysStaff* staff1 = system1->staff(staffIdx1);
const SysStaff* staff2 = system2->staff(staffIdx2);

const SysStaff* staff1 = system1->staff(staffIdx);
const SysStaff* staff2 = system2->staff(staffIdx);
if (!staff1 || !staff2) {
return 0.0;
}
Expand Down
2 changes: 1 addition & 1 deletion src/engraving/dom/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ extern Segment* skipTuplet(Tuplet* tuplet);
extern SymIdList timeSigSymIdsFromString(const String&);
extern Fraction actualTicks(Fraction duration, Tuplet* tuplet, Fraction timeStretch);

extern double yStaffDifference(const System* system1, staff_idx_t staffIdx1, const System* system2, staff_idx_t staffIdx2);
extern double yStaffDifference(const System* system1, const System* system2, staff_idx_t staffIdx1);

extern bool allowRemoveWhenRemovingStaves(EngravingItem* item, staff_idx_t startStaff, staff_idx_t endStaff = 0);
extern bool moveDownWhenAddingStaves(EngravingItem* item, staff_idx_t startStaff, staff_idx_t endStaff = 0);
Expand Down
326 changes: 164 additions & 162 deletions src/engraving/rendering/score/tlayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2712,16 +2712,16 @@ void TLayout::layoutFretDiagram(const FretDiagram* item, FretDiagram::LayoutData
}
}

static void _layoutGlissando(Glissando* item, LayoutContext& ctx, Glissando::LayoutData* ldata)
void TLayout::layoutGlissando(Glissando* item, LayoutContext& ctx)
{
double _spatium = item->spatium();

LAYOUT_CALL_ITEM(item);
TLayout::layoutLine(const_cast<Glissando*>(item), ctx);

if (item->spannerSegments().empty()) {
LOGD("no segments");
return;
}
Glissando::LayoutData* ldata = item->mutldata();
ldata->setPos(0.0, 0.0);

String instrId = item->staff()->part()->instrumentId(item->tick());
Expand Down Expand Up @@ -2750,169 +2750,11 @@ static void _layoutGlissando(Glissando* item, LayoutContext& ctx, Glissando::Lay
}
}

Note* anchor1 = toNote(item->startElement());
Note* anchor2 = toNote(item->endElement());
Chord* cr1 = anchor1->chord();
Chord* cr2 = anchor2->chord();
GlissandoSegment* segm1 = toGlissandoSegment(const_cast<Glissando*>(item)->frontSegment());
GlissandoSegment* segm2 = toGlissandoSegment(const_cast<Glissando*>(item)->backSegment());

// Note: line segments are defined by
// initial point: ipos() (relative to system origin)
// ending point: pos2() (relative to initial point)

// LINE ENDING POINTS TO NOTEHEAD CENTRES

// assume gliss. line goes from centre of initial note centre to centre of ending note:
// move first segment origin and last segment ending point from notehead origin to notehead centre
// For TAB: begin at the right-edge of initial note rather than centre
PointF offs1 = (cr1->staff()->isTabStaff(cr1->tick()))
? PointF(anchor1->ldata()->bbox().right(), 0.0)
: PointF(anchor1->headWidth() * 0.5, 0.0);

PointF offs2 = PointF(anchor2->headWidth() * 0.5, 0.0);

// AVOID HORIZONTAL LINES

// for microtonality read tuning, or check note accidental
double tune1 = anchor1->tuning();
double tune2 = anchor2->tuning();
AccidentalType acc1 = anchor1->accidentalType();
AccidentalType acc2 = anchor2->accidentalType();
if (muse::RealIsNull(tune1) && Accidental::isMicrotonal(acc1)) {
tune1 = Accidental::subtype2centOffset(acc1);
}
if (muse::RealIsNull(tune2) && Accidental::isMicrotonal(acc2)) {
tune2 = Accidental::subtype2centOffset(acc2);
}

int upDown = (0 < (anchor2->ppitch() - anchor1->ppitch())) - ((anchor2->ppitch() - anchor1->ppitch()) < 0);
// same note, so compare tunings
if (upDown == 0) {
upDown = (0 < (tune2 - tune1)) - ((tune2 - tune1) < 0);
}

// on TAB's, glissando are by necessity on the same string, this gives an horizontal glissando line;
// make bottom end point lower and top ending point higher
if (cr1->staff()->isTabStaff(cr1->tick())) {
double yOff = cr1->staff()->lineDistance(cr1->tick()) * 0.4 * _spatium;
offs1.ry() += yOff * upDown;
offs2.ry() -= yOff * upDown;
}
// if not TAB, angle glissando between notes on the same line
else {
if (anchor1->line() == anchor2->line()) {
offs1.ry() += _spatium * 0.25 * upDown;
offs2.ry() -= _spatium * 0.25 * upDown;
}
}

// move initial point of first segment and adjust its length accordingly
segm1->setPos(segm1->ldata()->pos() + offs1);
segm1->setPos2(segm1->ipos2() - offs1);
// adjust ending point of last segment
segm2->setPos2(segm2->ipos2() + offs2);

// INTERPOLATION OF INTERMEDIATE POINTS
// This probably belongs to SLine class itself; currently it does not seem
// to be needed for anything else than Glissando, though

// get total x-width and total y-height of all segments
double xTot = 0.0;
for (SpannerSegment* segm : item->spannerSegments()) {
xTot += segm->ipos2().x();
}
double y0 = segm1->ldata()->pos().y();
double yTot = segm2->ldata()->pos().y() + segm2->ipos2().y() - y0;
yTot -= yStaffDifference(segm2->system(), track2staff(item->track2()), segm1->system(), track2staff(item->track()));
double ratio = muse::divide(yTot, xTot, 1.0);
// interpolate y-coord of intermediate points across total width and height
double xCurr = 0.0;
double yCurr;
for (unsigned i = 0; i + 1 < item->spannerSegments().size(); i++) {
SpannerSegment* segm = const_cast<Glissando*>(item)->segmentAt(i);
xCurr += segm->ipos2().x();
yCurr = y0 + ratio * xCurr;
segm->rypos2() = yCurr - segm->ldata()->pos().y(); // position segm. end point at yCurr
// next segment shall start where this segment stopped, corrected for the staff y-difference
SpannerSegment* nextSeg = const_cast<Glissando*>(item)->segmentAt(i + 1);
yCurr += yStaffDifference(nextSeg->system(), track2staff(item->track2()), segm->system(), track2staff(item->track()));
segm = nextSeg;
segm->rypos2() += segm->ldata()->pos().y() - yCurr; // adjust next segm. vertical length
segm->mutldata()->setPosY(yCurr); // position next segm. start point at yCurr
}

// KEEP CLEAR OF ALL ELEMENTS OF THE CHORD
// Remove offset already applied
offs1 *= -1.0;
offs2 *= -1.0;
// Look at chord shapes (but don't consider lyrics)
Shape cr1shape = cr1->shape();
cr1shape.remove_if([](ShapeElement& s) {
if (!s.item() || s.item()->isLyrics()) {
return true;
} else {
return false;
}
});

double yAbove = anchor1->ldata()->pos().y() + anchor1->ldata()->bbox().topRight().y();
double yBelow = yAbove + anchor1->ldata()->bbox().height();
offs1.rx() += cr1shape.rightMostEdgeAtHeight(yAbove, yBelow) - anchor1->pos().x();
if (!cr2->staff()->isTabStaff(cr2->tick())) {
double yAbove2 = anchor2->ldata()->pos().y() + anchor2->ldata()->bbox().topLeft().y();
double yBelow2 = yAbove2 + anchor2->ldata()->bbox().height();
double noteMiddle = yAbove2 + anchor2->ldata()->bbox().height() / 2;
if (upDown != 0) {
int llWidth = ctx.conf().styleS(Sid::ledgerLineWidth).val() * _spatium;
// Only check top/bottom half of note depending on gliss approach direction
// to avoid clearing acidentals the line won't collide with
yAbove2 = upDown == 1 ? noteMiddle - llWidth : yAbove2;
yBelow2 = upDown == 1 ? yBelow2 : noteMiddle + llWidth;
}

offs2.rx() -= anchor2->pos().x() - cr2->shape().leftMostEdgeAtHeight(yAbove2, yBelow2);
}
// Add note distance
const double glissNoteDist = 0.25 * item->spatium(); // TODO: style
offs1.rx() += glissNoteDist;
offs2.rx() -= glissNoteDist;

// apply offsets: shorten first segment by x1 (and proportionally y) and adjust its length accordingly
offs1.ry() = segm1->ipos2().y() * muse::divide(offs1.x(), segm1->ipos2().x(), 1.0);
segm1->setPos(segm1->ldata()->pos() + offs1);
segm1->setPos2(segm1->ipos2() - offs1);
// adjust last segment length by x2 (and proportionally y)
offs2.ry() = segm2->ipos2().y() * muse::divide(offs2.x(), segm2->ipos2().x(), 1.0);
segm2->setPos2(segm2->ipos2() + offs2);

for (SpannerSegment* segm : item->spannerSegments()) {
TLayout::layoutItem(segm, ctx);
}

// compute glissando bbox as the bbox of the last segment, relative to the end anchor note
PointF anchor2PagePos = anchor2->pagePos();
PointF system2PagePos;
IF_ASSERT_FAILED(cr2->segment()->system()) {
system2PagePos = segm2->pos();
} else {
system2PagePos = cr2->segment()->system()->pagePos();
}

PointF anchor2SystPos = anchor2PagePos - system2PagePos;
RectF r = RectF(anchor2SystPos - segm2->pos(), anchor2SystPos - segm2->pos() - segm2->pos2()).normalized();
double lw = item->absoluteFromSpatium(item->lineWidth()) * .5;
ldata->setBbox(r.adjusted(-lw, -lw, lw, lw));
layoutNoteAnchoredLine(item, ldata, ctx);

const_cast<Glissando*>(item)->addLineAttachPoints();
}

void TLayout::layoutGlissando(Glissando* item, LayoutContext& ctx)
{
LAYOUT_CALL_ITEM(item);
_layoutGlissando(item, ctx, item->mutldata());
}

void TLayout::layoutGlissandoSegment(GlissandoSegment* item, LayoutContext&)
{
LAYOUT_CALL_ITEM(item);
Expand Down Expand Up @@ -5037,6 +4879,162 @@ void TLayout::layoutLine(SLine* item, LayoutContext& ctx)
}
}

void TLayout::layoutNoteAnchoredLine(SLine* item, EngravingItem::LayoutData* ldata, LayoutContext& ctx)
{
double _spatium = item->spatium();
Note* startAnchor = toNote(item->startElement());
Note* endAnchor = toNote(item->endElement());
Chord* startChord = startAnchor->chord();
Chord* endChord = endAnchor->chord();
LineSegment* startSeg = toLineSegment(item->frontSegment());
LineSegment* endSeg = toLineSegment(item->backSegment());

// Note: line segments are defined by
// initial point: ipos() (relative to system origin)
// ending point: pos2() (relative to initial point)

// LINE ENDING POINTS TO NOTEHEAD CENTRES

// assume line goes from centre of initial note centre to centre of ending note:
// move first segment origin and last segment ending point from notehead origin to notehead centre
// For TAB: begin at the right-edge of initial note rather than centre
PointF startOffset = (startChord->staff()->isTabStaff(startChord->tick()))
? PointF(startAnchor->ldata()->bbox().right(), 0.0)
: PointF(startAnchor->headWidth() * 0.5, 0.0);

PointF endOffset = PointF(endAnchor->headWidth() * 0.5, 0.0);

// AVOID HORIZONTAL LINES

// for microtonality read tuning, or check note accidental
double startTune = startAnchor->tuning();
double endTune = endAnchor->tuning();
AccidentalType startAcc = startAnchor->accidentalType();
AccidentalType endAcc = endAnchor->accidentalType();
if (muse::RealIsNull(startTune) && Accidental::isMicrotonal(startAcc)) {
startTune = Accidental::subtype2centOffset(startAcc);
}
if (muse::RealIsNull(endTune) && Accidental::isMicrotonal(endAcc)) {
endTune = Accidental::subtype2centOffset(endAcc);
}

int upDown = (0 < (endAnchor->ppitch() - startAnchor->ppitch())) - ((endAnchor->ppitch() - startAnchor->ppitch()) < 0);
// same note, so compare tunings
if (upDown == 0) {
upDown = (0 < (endTune - startTune)) - ((endTune - startTune) < 0);
}

// on TAB's, glissando are by necessity on the same string, this gives an horizontal glissando line;
// make bottom end point lower and top ending point higher
if (startChord->staff()->isTabStaff(startChord->tick())) {
double yOff = startChord->staff()->lineDistance(startChord->tick()) * 0.4 * _spatium;
startOffset.ry() += yOff * upDown;
endOffset.ry() -= yOff * upDown;
}
// if not TAB, angle glissando between notes on the same line
else {
if (startAnchor->line() == endAnchor->line()) {
startOffset.ry() += _spatium * 0.25 * upDown;
endOffset.ry() -= _spatium * 0.25 * upDown;
}
}

// move initial point of first segment and adjust its length accordingly
startSeg->setPos(startSeg->ldata()->pos() + startOffset);
startSeg->setPos2(startSeg->ipos2() - startOffset);
// adjust ending point of last segment
endSeg->setPos2(endSeg->ipos2() + endOffset);

// INTERPOLATION OF INTERMEDIATE POINTS

// get total x-width and total y-height of all segments
double xTot = 0.0;
for (SpannerSegment* segm : item->spannerSegments()) {
xTot += segm->ipos2().x();
}
double startY = startSeg->ldata()->pos().y();
double yTot = endSeg->ldata()->pos().y() + endSeg->ipos2().y() - startY;
yTot -= yStaffDifference(endSeg->system(), startSeg->system(), track2staff(item->track2()));
double ratio = muse::divide(yTot, xTot, 1.0);
// interpolate y-coord of intermediate points across total width and height
double xCurr = 0.0;
double yCurr;
for (unsigned i = 0; i + 1 < item->spannerSegments().size(); i++) {
SpannerSegment* segm = item->segmentAt(i);
xCurr += segm->ipos2().x();
yCurr = startY + ratio * xCurr;
segm->rypos2() = yCurr - segm->ldata()->pos().y(); // position segm. end point at yCurr
// next segment shall start where this segment stopped
SpannerSegment* nextSeg = item->segmentAt(i + 1);
yCurr += yStaffDifference(nextSeg->system(), segm->system(), track2staff(item->track2()));
segm = nextSeg;
segm->rypos2() += segm->ldata()->pos().y() - yCurr; // adjust next segm. vertical length
segm->mutldata()->setPosY(yCurr); // position next segm. start point at yCurr
}

// KEEP CLEAR OF ALL ELEMENTS OF THE CHORD
// Remove offset already applied
startOffset *= -1.0;
endOffset *= -1.0;
// Look at chord shapes (but don't consider lyrics)
Shape startCRShape = startChord->shape();
startCRShape.remove_if([](ShapeElement& s) {
if (!s.item() || s.item()->isLyrics()) {
return true;
} else {
return false;
}
});

double startYAbove = startAnchor->ldata()->pos().y() + startAnchor->ldata()->bbox().topRight().y();
double startYBelow = startYAbove + startAnchor->ldata()->bbox().height();
startOffset.rx() += startCRShape.rightMostEdgeAtHeight(startYAbove, startYBelow) - startAnchor->pos().x();
if (!endChord->staff()->isTabStaff(endChord->tick())) {
double endYAbove = endAnchor->ldata()->pos().y() + endAnchor->ldata()->bbox().topLeft().y();
double endYBelow = endYAbove + endAnchor->ldata()->bbox().height();
double noteMiddle = endYAbove + endAnchor->ldata()->bbox().height() / 2;
if (upDown != 0) {
int llWidth = ctx.conf().styleS(Sid::ledgerLineWidth).val() * _spatium;
// Only check top/bottom half of note depending on line approach direction
// to avoid clearing acidentals the line won't collide with
endYAbove = upDown == 1 ? noteMiddle - llWidth : endYAbove;
endYBelow = upDown == 1 ? endYBelow : noteMiddle + llWidth;
}

endOffset.rx() -= endAnchor->pos().x() - endChord->shape().leftMostEdgeAtHeight(endYAbove, endYBelow);
}
// Add note distance
const double lineNoteDist = 0.25 * item->spatium(); // TODO: style
startOffset.rx() += lineNoteDist;
endOffset.rx() -= lineNoteDist;

// apply offsets: shorten first segment by x1 (and proportionally y) and adjust its length accordingly
startOffset.ry() = startSeg->ipos2().y() * muse::divide(startOffset.x(), startSeg->ipos2().x(), 1.0);
startSeg->setPos(startSeg->ldata()->pos() + startOffset);
startSeg->setPos2(startSeg->ipos2() - startOffset);
// adjust last segment length by x2 (and proportionally y)
endOffset.ry() = endSeg->ipos2().y() * muse::divide(endOffset.x(), endSeg->ipos2().x(), 1.0);
endSeg->setPos2(endSeg->ipos2() + endOffset);

for (SpannerSegment* segm : item->spannerSegments()) {
TLayout::layoutItem(segm, ctx);
}

// compute line bbox as the bbox of the last segment, relative to the end anchor note
PointF endAnchorPagePos = endAnchor->pagePos();
PointF endSystemPagePos;
IF_ASSERT_FAILED(endChord->segment()->system()) {
endSystemPagePos = endSeg->pos();
} else {
endSystemPagePos = endChord->segment()->system()->pagePos();
}

PointF endAnchorSystPos = endAnchorPagePos - endSystemPagePos;
RectF r = RectF(endAnchorSystPos - endSeg->pos(), endAnchorSystPos - endSeg->pos() - endSeg->pos2()).normalized();
double lw = item->absoluteFromSpatium(item->lineWidth()) * .5;
ldata->setBbox(r.adjusted(-lw, -lw, lw, lw));
}

void TLayout::layoutSlur(Slur* item, LayoutContext& ctx)
{
SlurTieLayout::createSlurSegments(item, ctx);
Expand Down Expand Up @@ -5817,6 +5815,10 @@ void TLayout::layoutTextLine(TextLine* item, LayoutContext& ctx)
{
LAYOUT_CALL_ITEM(item);
layoutLine(item, ctx);

if (item->anchor() == Spanner::Anchor::NOTE) {
layoutNoteAnchoredLine(item, item->mutldata(), ctx);
}
}

void TLayout::layoutTextLineSegment(TextLineSegment* item, LayoutContext& ctx)
Expand Down
1 change: 1 addition & 0 deletions src/engraving/rendering/score/tlayout.h
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ class TLayout

static void layoutShadowNote(ShadowNote* item, LayoutContext& ctx);
static void layoutLine(SLine* item, LayoutContext& ctx); // base class
static void layoutNoteAnchoredLine(SLine* item, SLine::LayoutData* ldata, LayoutContext& ctx);
static void layoutSlur(Slur* item, LayoutContext& ctx);
static void layoutSpacer(Spacer* item, LayoutContext& ctx);
static void layoutSpanner(Spanner* item, LayoutContext& ctx);
Expand Down
Binary file added vtest/scores/gliss-6.mscz
Binary file not shown.
Binary file added vtest/scores/note-line.mscz
Binary file not shown.
Loading