//============================================================================= // MuseScore // Music Composition & Notation // // Copyright (C) 2002-2016 Werner Schweer // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 // as published by the Free Software Foundation and appearing in // the file LICENCE.GPL //============================================================================= #include "measure.h" #include "score.h" #include "system.h" #include "undo.h" #include "chord.h" #include "stem.h" #include "slur.h" #include "tie.h" #include "part.h" #include "navigate.h" #include "articulation.h" namespace Ms { //--------------------------------------------------------- // draw //--------------------------------------------------------- void SlurSegment::draw(QPainter* painter) const { QPen pen(curColor()); qreal mag = staff() ? staff()->staffMag(slur()->tick()) : 1.0; switch (slurTie()->lineType()) { case 0: painter->setBrush(QBrush(pen.color())); pen.setCapStyle(Qt::RoundCap); pen.setJoinStyle(Qt::RoundJoin); pen.setWidthF(score()->styleP(Sid::SlurEndWidth) * mag); break; case 1: painter->setBrush(Qt::NoBrush); pen.setWidthF(score()->styleP(Sid::SlurDottedWidth) * mag); pen.setStyle(Qt::DotLine); break; case 2: painter->setBrush(Qt::NoBrush); pen.setWidthF(score()->styleP(Sid::SlurDottedWidth) * mag); pen.setStyle(Qt::DashLine); break; case 3: painter->setBrush(Qt::NoBrush); pen.setWidthF(score()->styleP(Sid::SlurDottedWidth) * mag); pen.setStyle(Qt::CustomDashLine); QVector dashes { 5.0, 5.0 }; pen.setDashPattern(dashes); break; } painter->setPen(pen); painter->drawPath(path); } //--------------------------------------------------------- // searchCR //--------------------------------------------------------- static ChordRest* searchCR(Segment* segment, int startTrack, int endTrack) { // for (Segment* s = segment; s; s = s->next1MM(SegmentType::ChordRest)) { for (Segment* s = segment; s; s = s->next(SegmentType::ChordRest)) { // restrict search to measure if (startTrack > endTrack) { for (int t = startTrack - 1; t >= endTrack; --t) { if (s->element(t)) { return toChordRest(s->element(t)); } } } else { for (int t = startTrack; t < endTrack; ++t) { if (s->element(t)) { return toChordRest(s->element(t)); } } } } return 0; } //--------------------------------------------------------- // edit // return true if event is accepted //--------------------------------------------------------- bool SlurSegment::edit(EditData& ed) { Slur* sl = slur(); if (ed.key == Qt::Key_X) { sl->undoChangeProperty(Pid::SLUR_DIRECTION, QVariant::fromValue(sl->up() ? Direction::DOWN : Direction::UP)); sl->layout(); return true; } if (ed.key == Qt::Key_Home) { ups(ed.curGrip).off = QPointF(); //TODO sl->layout(); return true; } if (!((ed.modifiers & Qt::ShiftModifier) && (isSingleType() || (isBeginType() && ed.curGrip == Grip::START) || (isEndType() && ed.curGrip == Grip::END)))) { return false; } ChordRest* cr = 0; ChordRest* e; ChordRest* e1; if (ed.curGrip == Grip::START) { e = sl->startCR(); e1 = sl->endCR(); } else { e = sl->endCR(); e1 = sl->startCR(); } if (ed.key == Qt::Key_Left) { cr = prevChordRest(e); } else if (ed.key == Qt::Key_Right) { cr = nextChordRest(e); } else if (ed.key == Qt::Key_Up) { Part* part = e->part(); int startTrack = part->startTrack(); int endTrack = e->track(); cr = searchCR(e->segment(), endTrack, startTrack); } else if (ed.key == Qt::Key_Down) { int startTrack = e->track() + 1; Part* part = e->part(); int endTrack = part->endTrack(); cr = searchCR(e->segment(), startTrack, endTrack); } if (cr && cr != e1) { changeAnchor(ed, cr); } return true; } //--------------------------------------------------------- // changeAnchor //--------------------------------------------------------- void SlurSegment::changeAnchor(EditData& ed, Element* element) { ChordRest* cr = element->isChordRest() ? toChordRest(element) : nullptr; ChordRest* scr = spanner()->startCR(); ChordRest* ecr = spanner()->endCR(); if (!cr || !scr || !ecr) { return; } // save current start/end elements for (ScoreElement* e : spanner()->linkList()) { Spanner* sp = toSpanner(e); score()->undoStack()->push1(new ChangeStartEndSpanner(sp, sp->startElement(), sp->endElement())); } if (ed.curGrip == Grip::START) { spanner()->undoChangeProperty(Pid::SPANNER_TICK, cr->tick()); Fraction ticks = ecr->tick() - cr->tick(); spanner()->undoChangeProperty(Pid::SPANNER_TICKS, ticks); int diff = cr->track() - spanner()->track(); for (auto e : spanner()->linkList()) { Spanner* s = toSpanner(e); s->undoChangeProperty(Pid::TRACK, s->track() + diff); } scr = cr; } else { Fraction ticks = cr->tick() - scr->tick(); spanner()->undoChangeProperty(Pid::SPANNER_TICKS, ticks); int diff = cr->track() - spanner()->track(); for (auto e : spanner()->linkList()) { Spanner* s = toSpanner(e); s->undoChangeProperty(Pid::SPANNER_TRACK2, s->track() + diff); } ecr = cr; } // update start/end elements (which could be grace notes) for (ScoreElement* lsp : spanner()->linkList()) { Spanner* sp = static_cast(lsp); if (sp == spanner()) { score()->undo(new ChangeSpannerElements(sp, scr, ecr)); } else { Element* se = 0; Element* ee = 0; if (scr) { QList sel = scr->linkList(); for (ScoreElement* lcr : sel) { Element* le = toElement(lcr); if (le->score() == sp->score() && le->track() == sp->track()) { se = le; break; } } } if (ecr) { QList sel = ecr->linkList(); for (ScoreElement* lcr : sel) { Element* le = toElement(lcr); if (le->score() == sp->score() && le->track() == sp->track2()) { ee = le; break; } } } score()->undo(new ChangeStartEndSpanner(sp, se, ee)); sp->layout(); } } const size_t segments = spanner()->spannerSegments().size(); ups(ed.curGrip).off = QPointF(); spanner()->layout(); if (spanner()->spannerSegments().size() != segments) { const std::vector& ss = spanner()->spannerSegments(); SlurSegment* newSegment = toSlurSegment(ed.curGrip == Grip::END ? ss.back() : ss.front()); ed.view->startEdit(newSegment, ed.curGrip); triggerLayout(); } } //--------------------------------------------------------- // computeBezier // compute help points of slur bezier segment //--------------------------------------------------------- void SlurSegment::computeBezier(QPointF p6o) { qreal _spatium = spatium(); qreal shoulderW; // height as fraction of slur-length qreal shoulderH; // // pp1 and pp2 are the end points of the slur // QPointF pp1 = ups(Grip::START).p + ups(Grip::START).off; QPointF pp2 = ups(Grip::END).p + ups(Grip::END).off; QPointF p2 = pp2 - pp1; if ((p2.x() == 0.0) && (p2.y() == 0.0)) { Measure* m1 = slur()->startCR()->segment()->measure(); Measure* m2 = slur()->endCR()->segment()->measure(); qDebug("zero slur at tick %d(%d) track %d in measure %d-%d tick %d ticks %d", m1->tick().ticks(), tick().ticks(), track(), m1->no(), m2->no(), slur()->tick().ticks(), slur()->ticks().ticks()); slur()->setBroken(true); return; } qreal sinb = atan(p2.y() / p2.x()); QTransform t; t.rotateRadians(-sinb); p2 = t.map(p2); p6o = t.map(p6o); double smallH = 0.5; qreal d = p2.x() / _spatium; if (d <= 2.0) { shoulderH = d * 0.5 * smallH * _spatium; shoulderW = .6; } else { qreal dd = log10(1.0 + (d - 2.0) * .5) * 2.0; if (dd > 3.0) { dd = 3.0; } shoulderH = (dd + smallH) * _spatium + _extraHeight; if (d > 18.0) { shoulderW = 0.7; // 0.8; } else if (d > 10) { shoulderW = 0.6; // 0.7; } else { shoulderW = 0.5; // 0.6; } } shoulderH -= p6o.y(); if (!slur()->up()) { shoulderH = -shoulderH; } qreal c = p2.x(); qreal c1 = (c - c * shoulderW) * .5 + p6o.x(); qreal c2 = c1 + c * shoulderW + p6o.x(); QPointF p5 = QPointF(c * .5, 0.0); QPointF p3(c1, -shoulderH); QPointF p4(c2, -shoulderH); qreal w = score()->styleP(Sid::SlurMidWidth) - score()->styleP(Sid::SlurEndWidth); if (staff()) { w *= staff()->staffMag(slur()->tick()); } if ((c2 - c1) <= _spatium) { w *= .5; } QPointF th(0.0, w); // thickness of slur QPointF p3o = p6o + t.map(ups(Grip::BEZIER1).off); QPointF p4o = p6o + t.map(ups(Grip::BEZIER2).off); if (!p6o.isNull()) { QPointF p6i = t.inverted().map(p6o); ups(Grip::BEZIER1).off += p6i; ups(Grip::BEZIER2).off += p6i; } //-----------------------------------calculate p6 QPointF pp3 = p3 + p3o; QPointF pp4 = p4 + p4o; QPointF ppp4 = pp4 - pp3; qreal r2 = atan(ppp4.y() / ppp4.x()); t.reset(); t.rotateRadians(-r2); QPointF p6 = QPointF(t.map(ppp4).x() * .5, 0.0); t.rotateRadians(2 * r2); p6 = t.map(p6) + pp3 - p6o; //----------------------------------- path = QPainterPath(); path.moveTo(QPointF()); path.cubicTo(p3 + p3o - th, p4 + p4o - th, p2); if (slur()->lineType() == 0) { path.cubicTo(p4 + p4o + th, p3 + p3o + th, QPointF()); } th = QPointF(0.0, 3.0 * w); shapePath = QPainterPath(); shapePath.moveTo(QPointF()); shapePath.cubicTo(p3 + p3o - th, p4 + p4o - th, p2); shapePath.cubicTo(p4 + p4o + th, p3 + p3o + th, QPointF()); // translate back t.reset(); t.translate(pp1.x(), pp1.y()); t.rotateRadians(sinb); path = t.map(path); shapePath = t.map(shapePath); ups(Grip::BEZIER1).p = t.map(p3); ups(Grip::BEZIER2).p = t.map(p4); ups(Grip::END).p = t.map(p2) - ups(Grip::END).off; ups(Grip::DRAG).p = t.map(p5); ups(Grip::SHOULDER).p = t.map(p6); _shape.clear(); QPointF start = pp1; int nbShapes = 32; // (pp2.x() - pp1.x()) / _spatium; qreal minH = qAbs(3 * w); const CubicBezier b(pp1, ups(Grip::BEZIER1).pos(), ups(Grip::BEZIER2).pos(), ups(Grip::END).pos()); for (int i = 1; i <= nbShapes; i++) { const QPointF point = b.pointAtPercent(i / float(nbShapes)); QRectF re = QRectF(start, point).normalized(); if (re.height() < minH) { qreal d1 = (minH - re.height()) * .5; re.adjust(0.0, -d1, 0.0, d1); } _shape.add(re); start = point; } } //--------------------------------------------------------- // layoutSegment //--------------------------------------------------------- void SlurSegment::layoutSegment(const QPointF& p1, const QPointF& p2) { setPos(QPointF()); ups(Grip::START).p = p1; ups(Grip::END).p = p2; _extraHeight = 0.0; computeBezier(); if (autoplace() && system()) { bool up = slur()->up(); Segment* ls = system()->lastMeasure()->last(); Segment* fs = system()->firstMeasure()->first(); Segment* ss = slur()->startSegment(); Segment* es = slur()->endSegment(); QPointF pp1 = ups(Grip::START).p; QPointF pp2 = ups(Grip::END).p; qreal slurMaxMove = spatium(); bool intersection = false; qreal gdist = 0.0; qreal minDistance = score()->styleS(Sid::SlurMinDistance).val() * spatium(); for (int tries = 1; true; ++tries) { for (Segment* s = fs; s && s != ls; s = s->next1()) { if (!s->enabled()) { continue; } // skip start and end segments on assumption start and end points were placed well already // this avoids overcorrection on collision with own ledger lines and accidentals // it also avoids issues where slur appears to be attached to a note in a different voice if (s == ss || s == es) { continue; } // allow slurs to cross barlines if (s->segmentType() & SegmentType::BarLineType) { continue; } qreal x1 = s->x() + s->measure()->x(); qreal x2 = x1 + s->width(); if (pp1.x() > x2) { continue; } if (pp2.x() < x1) { break; } const Shape& segShape = s->staffShape(staffIdx()).translated(s->pos() + s->measure()->pos()); if (!intersection) { intersection = segShape.intersects(_shape); } if (up) { //QPointF pt = QPointF(s->x() + s->measure()->x(), s->staffShape(staffIdx()).top() + s->y() + s->measure()->y()); qreal dist = _shape.minVerticalDistance(segShape); if (dist > 0.0) { gdist = qMax(gdist, dist); } } else { //QPointF pt = QPointF(s->x() + s->measure()->x(), s->staffShape(staffIdx()).bottom() + s->y() + s->measure()->y()); qreal dist = segShape.minVerticalDistance(_shape); if (dist > 0.0) { gdist = qMax(gdist, dist); } } } if (!intersection || gdist <= slurMaxMove || tries >= 2) { break; } // slur would be moved too far // try again with a steeper curve _extraHeight += gdist; computeBezier(); intersection = false; gdist = 0.0; } if (intersection && gdist > 0.0) { qreal min = minDistance + gdist; rypos() += up ? -min : min; } } setbbox(path.boundingRect()); } //--------------------------------------------------------- // isEdited //--------------------------------------------------------- bool SlurSegment::isEdited() const { for (int i = 0; i < int(Grip::GRIPS); ++i) { if (!_ups[i].off.isNull()) { return true; } } return false; } //--------------------------------------------------------- // fixArticulations //--------------------------------------------------------- static qreal fixArticulations(qreal yo, Chord* c, qreal _up, bool stemSide = false) { // // handle special case of tenuto and staccato // yo = current offset of slur from chord position // return unchanged position, or position of outmost "close" articulation // #if 1 for (Articulation* a : c->articulations()) { if (!a->layoutCloseToNote() || !a->addToSkyline()) { continue; } // skip if articulation on stem side but slur is not or vice versa if ((a->up() == c->up()) != stemSide) { continue; } if (a->up()) { yo = qMin(yo, a->y() + (a->height() + c->score()->spatium() * .3) * _up); } else { yo = qMax(yo, a->y() + (a->height() + c->score()->spatium() * .3) * _up); } } return yo; #else const QVector& al = c->articulations(); if (al.size() >= 2) { Articulation* a = al.at(1); if (a->up() == c->up() && !stemSide) { return yo; } else if (a->layoutCloseToNote()) { return a->y() + (a->height() + c->score()->spatium() * .3) * _up; } } else if (al.size() >= 1) { Articulation* a = al.at(0); if (a->up() == c->up() && !stemSide) { return yo; } else if (a->layoutCloseToNote()) { return a->y() + (a->height() + c->score()->spatium() * .3) * _up; } } return yo; #endif } //--------------------------------------------------------- // slurPos // Calculate position of start- and endpoint of slur // relative to System() position. //--------------------------------------------------------- void Slur::slurPosChord(SlurPos* sp) { Chord* stChord; Chord* enChord; if (startChord()->isGraceAfter()) { // grace notes after, coming in reverse order stChord = endChord(); enChord = startChord(); _up = false; } else { stChord = startChord(); enChord = endChord(); } Note* _startNote = stChord->downNote(); Note* _endNote = enChord->downNote(); qreal hw = _startNote->bboxRightPos(); qreal __up = _up ? -1.0 : 1.0; qreal _spatium = spatium(); Measure* measure = endChord()->measure(); sp->system1 = measure->system(); if (!sp->system1) { // DEBUG qDebug("no system1"); return; } Q_ASSERT(sp->system1); sp->system2 = sp->system1; QPointF pp(sp->system1->pagePos()); qreal xo; qreal yo; //------p1 if (_up) { xo = _startNote->x() + hw * 1.12; yo = _startNote->pos().y() + hw * .3 * __up; } else { xo = _startNote->x() + hw * 0.4; yo = _startNote->pos().y() + _spatium * .75 * __up; } sp->p1 = stChord->pagePos() - pp + QPointF(xo, yo); //------p2 if ((enChord->notes().size() > 1) || (enChord->stem() && !enChord->up() && !_up)) { xo = _endNote->x() - hw * 0.12; yo = _endNote->pos().y() + hw * .3 * __up; } else { xo = _endNote->x() + hw * 0.15; yo = _endNote->pos().y() + _spatium * .75 * __up; } sp->p2 = enChord->pagePos() - pp + QPointF(xo, yo); } //--------------------------------------------------------- // slurPos // calculate position of start- and endpoint of slur // relative to System() position //--------------------------------------------------------- void Slur::slurPos(SlurPos* sp) { qreal _spatium = spatium(); if (endCR() == 0) { sp->p1 = startCR()->pagePos(); sp->p1.rx() += startCR()->width(); sp->p2 = sp->p1; sp->p2.rx() += 5 * _spatium; sp->system1 = startCR()->measure()->system(); sp->system2 = sp->system1; return; } bool useTablature = staff() && staff()->isTabStaff(endCR()->tick()); bool staffHasStems = true; // assume staff uses stems const StaffType* stt = 0; if (useTablature) { stt = staff()->staffType(tick()); staffHasStems = stt->stemThrough(); // if tab with stems beside, stems do not count for slur pos } // start and end cr, chord, and note ChordRest* scr = startCR(); ChordRest* ecr = endCR(); Chord* sc = 0; Note* note1 = 0; if (scr->isChord()) { sc = toChord(scr); note1 = _up ? sc->upNote() : sc->downNote(); } Chord* ec = 0; Note* note2 = 0; if (ecr->isChord()) { ec = toChord(ecr); note2 = _up ? ec->upNote() : ec->downNote(); } sp->system1 = scr->measure()->system(); sp->system2 = ecr->measure()->system(); if (sp->system1 == 0) { qDebug("no system1"); return; } sp->p1 = scr->pos() + scr->segment()->pos() + scr->measure()->pos(); sp->p2 = ecr->pos() + ecr->segment()->pos() + ecr->measure()->pos(); // adjust for cross-staff if (scr->vStaffIdx() != vStaffIdx() && sp->system1) { qreal diff = sp->system1->staff(scr->vStaffIdx())->y() - sp->system1->staff(vStaffIdx())->y(); sp->p1.ry() += diff; } if (ecr->vStaffIdx() != vStaffIdx() && sp->system2) { qreal diff = sp->system2->staff(ecr->vStaffIdx())->y() - sp->system2->staff(vStaffIdx())->y(); sp->p2.ry() += diff; } // account for centering or other adjustments (other than mirroring) if (note1 && !note1->mirror()) { sp->p1.rx() += note1->x(); } if (note2 && !note2->mirror()) { sp->p2.rx() += note2->x(); } qreal xo, yo; Stem* stem1 = sc && staffHasStems ? sc->stem() : 0; Stem* stem2 = ec && staffHasStems ? ec->stem() : 0; enum class SlurAnchor : char { NONE, STEM }; SlurAnchor sa1 = SlurAnchor::NONE; SlurAnchor sa2 = SlurAnchor::NONE; // if slur is 'embedded' between either stem or both (as it might happen with voices) // link corresponding slur end to stem position if ((scr->up() == ecr->up()) && !scr->beam() && !ecr->beam() && (_up == scr->up())) { // both chords are facing same direction and slur is also in same direction // and no beams if (stem1) { sa1 = SlurAnchor::STEM; } if (stem2) { sa2 = SlurAnchor::STEM; } } // also link start of slur to stem if start chord & slur are in same direction and there is a hook if (scr->up() == _up && stem1 && sc->hook()) { sa1 = SlurAnchor::STEM; // if end chord is in same direction, link end of slur to stem too if (ecr->up() == scr->up() && stem2 && (!ecr->beam() || !ecr->beam()->cross())) { sa2 = SlurAnchor::STEM; } } qreal __up = _up ? -1.0 : 1.0; qreal hw1 = note1 ? note1->tabHeadWidth(stt) : scr->width(); // if stt == 0, tabHeadWidth() qreal hw2 = note2 ? note2->tabHeadWidth(stt) : ecr->width(); // defaults to headWidth() QPointF pt; switch (sa1) { case SlurAnchor::STEM: //sc can't be null { // place slur starting point at stem end point pt = sc->stemPos() - sc->pagePos() + sc->stem()->p2(); if (useTablature) { // in tabs, stems are centred on note: pt.rx() = hw1 * 0.5 + (note1 ? note1->bboxXShift() : 0.0); // skip half notehead to touch stem, anatoly-os: incorrect. half notehead width is not always the stem position } // clear the stem (x) // allow slight overlap (y) as per Gould // don't allow overlap with hook if not disabling the autoplace checks against start/end segments in SlurSegment::layoutSegment() qreal yadj = -0.25; // sc->hook() ? 0.25 : -0.25; yadj *= _spatium * __up; pt += QPointF(0.35 * _spatium, yadj); // account for articulations pt.ry() = fixArticulations(pt.y(), sc, __up, true); sp->p1 += pt; } break; case SlurAnchor::NONE: break; } switch (sa2) { case SlurAnchor::STEM: //ec can't be null { pt = ec->stemPos() - ec->pagePos() + ec->stem()->p2(); if (useTablature) { pt.rx() = hw2 * 0.5; } // don't allow overlap with beam qreal yadj = ec->beam() ? 0.75 : -0.25; yadj *= _spatium * __up; pt += QPointF(-0.35 * _spatium, yadj); // account for articulations pt.ry() = fixArticulations(pt.y(), ec, __up, true); sp->p2 += pt; } break; case SlurAnchor::NONE: break; } // // default position: // horizontal: middle of notehead // vertical: _spatium * .4 above/below notehead // //------p1 // Compute x0, y0 and stemPos if (sa1 == SlurAnchor::NONE || sa2 == SlurAnchor::NONE) { // need stemPos if sa2 == SlurAnchor::NONE bool stemPos = false; // p1 starts at chord stem side // default positions xo = hw1 * .5 + (note1 ? note1->bboxXShift() : 0.0); if (note1) { yo = note1->pos().y(); } else if (_up) { yo = scr->bbox().top(); } else { yo = scr->bbox().top() + scr->height(); } yo += _spatium * .9 * __up; // adjustments for stem and/or beam if (stem1) { //sc not null Beam* beam1 = sc->beam(); if (beam1 && beam1->cross()) { // TODO: stem direction is not finalized, so we cannot use it here yo = fixArticulations(yo, sc, __up, false); } else if (beam1 && (beam1->elements().back() != sc) && (sc->up() == _up)) { // start chord is beamed but not the last chord of beam group // and slur direction is same as start chord (stem side) // in these cases, layout start of slur to stem qreal sh = stem1->height() + _spatium; if (_up) { yo = sc->downNote()->pos().y() - sh; } else { yo = sc->upNote()->pos().y() + sh; } xo = stem1->pos().x(); // account for articulations yo = fixArticulations(yo, sc, __up, true); // force end of slur to layout to stem as well, // if start and end chords have same stem direction stemPos = true; } else { // start chord is not beamed or is last chord of beam group // or slur direction is opposite that of start chord // at this point slur is in default position relative to note on slur side // but we may need to make further adjustments // if stem and slur are both up // we need to clear stem horizontally if (sc->up() && _up) { xo = hw1 + _spatium * .3; } // // handle case: stem up - stem down // stem down - stem up // if ((sc->up() != ecr->up()) && (sc->up() == _up)) { // start and end chord have opposite direction // and slur direction is same as start chord // (so slur starts on stem side) // float the start point along the stem to follow direction of movement // see for example Gould p. 111 // get position of note on slur side for start & end chords Note* n1 = sc->up() ? sc->upNote() : sc->downNote(); Note* n2 = 0; if (ec) { n2 = ec->up() ? ec->upNote() : ec->downNote(); } // differential in note positions qreal yd = (n2 ? n2->pos().y() : ecr->pos().y()) - n1->pos().y(); yd *= .5; // float along stem according to differential qreal sh = stem1->height(); if (_up && yd < 0.0) { yo = qMax(yo + yd, sc->downNote()->pos().y() - sh - _spatium); } else if (!_up && yd > 0.0) { yo = qMin(yo + yd, sc->upNote()->pos().y() + sh + _spatium); } // account for articulations yo = fixArticulations(yo, sc, __up, true); // we may wish to force end to align to stem as well, // if it is in same direction // (but it won't be, so this assignment should have no effect) stemPos = true; } else { // avoid articulations yo = fixArticulations(yo, sc, __up, sc->up() == _up); } } } else if (sc) { // avoid articulations yo = fixArticulations(yo, sc, __up, sc->up() == _up); } if (sa1 == SlurAnchor::NONE) { sp->p1 += QPointF(xo, yo); } //------p2 if (sa2 == SlurAnchor::NONE) { // default positions xo = hw2 * .5 + (note2 ? note2->bboxXShift() : 0.0); if (note2) { yo = note2->pos().y(); } else if (_up) { yo = endCR()->bbox().top(); } else { yo = endCR()->bbox().top() + endCR()->height(); } yo += _spatium * .9 * __up; // adjustments for stem and/or beam if (stem2) { //ec can't be null Beam* beam2 = ec->beam(); if (beam2 && beam2->cross()) { // TODO: stem direction is not finalized, so we cannot use it here yo = fixArticulations(yo, ec, __up, false); } else if ((stemPos && (scr->up() == ec->up())) || (beam2 && (!beam2->elements().empty()) && (beam2->elements().front() != ec) && (ec->up() == _up) && sc && (sc->noteType() == NoteType::NORMAL) ) ) { // slur start was laid out to stem and start and end have same direction // OR // end chord is beamed but not the first chord of beam group // and slur direction is same as end chord (stem side) // and start chordrest is not a grace chord // in these cases, layout end of slur to stem qreal sh = stem2->height() + _spatium; if (_up) { yo = ec->downNote()->pos().y() - sh; } else { yo = ec->upNote()->pos().y() + sh; } xo = stem2->pos().x(); // account for articulations yo = fixArticulations(yo, ec, __up, true); } else { // slur was not aligned to stem or start and end have different direction // AND // end chord is not beamed or is first chord of beam group // or slur direction is opposite that of end chord // if stem and slur are both down, // we need to clear stem horizontally if (!ec->up() && !_up) { xo = -_spatium * .3 + note2->x(); } // // handle case: stem up - stem down // stem down - stem up // if ((scr->up() != ec->up()) && (ec->up() == _up)) { // start and end chord have opposite direction // and slur direction is same as end chord // (so slur end on stem side) // float the end point along the stem to follow direction of movement // see for example Gould p. 111 Note* n1 = 0; if (sc) { n1 = sc->up() ? sc->upNote() : sc->downNote(); } Note* n2 = ec->up() ? ec->upNote() : ec->downNote(); qreal yd = n2->pos().y() - (n1 ? n1->pos().y() : startCR()->pos().y()); yd *= .5; qreal mh = stem2->height(); if (_up && yd > 0.0) { yo = qMax(yo - yd, ec->downNote()->pos().y() - mh - _spatium); } else if (!_up && yd < 0.0) { yo = qMin(yo - yd, ec->upNote()->pos().y() + mh + _spatium); } // account for articulations yo = fixArticulations(yo, ec, __up, true); } else { // avoid articulations yo = fixArticulations(yo, ec, __up, ec->up() == _up); } } } else if (ec) { // avoid articulations yo = fixArticulations(yo, ec, __up, ec->up() == _up); } sp->p2 += QPointF(xo, yo); } } } //--------------------------------------------------------- // Slur //--------------------------------------------------------- Slur::Slur(Score* s) : SlurTie(s) { setAnchor(Anchor::CHORD); } //--------------------------------------------------------- // write //--------------------------------------------------------- void Slur::write(XmlWriter& xml) const { if (broken()) { qDebug("broken slur not written"); return; } if (!xml.canWrite(this)) { return; } xml.stag(this); SlurTie::writeProperties(xml); xml.etag(); } //--------------------------------------------------------- // chordsHaveTie //--------------------------------------------------------- static bool chordsHaveTie(Chord* c1, Chord* c2) { size_t n = c1->notes().size(); for (size_t i1 = 0; i1 < n; ++i1) { Note* n1 = c1->notes().at(i1); size_t n2 = c2->notes().size(); for (size_t i2 = 0; i2 < n2; ++i2) { Note* n3 = c2->notes().at(i2); if (n1->tieFor() && n1->tieFor() == n3->tieBack()) { return true; } } } return false; } //--------------------------------------------------------- // directionMixture //--------------------------------------------------------- static bool isDirectionMixture(Chord* c1, Chord* c2) { bool up = c1->up(); for (Segment* seg = c1->segment(); seg; seg = seg->next(SegmentType::ChordRest)) { Element* e = seg->element(c1->track()); if (!e || !e->isChord()) { continue; } Chord* c = toChord(e); if (c->up() != up) { return true; } if (seg == c2->segment()) { break; } } return false; } //--------------------------------------------------------- // layoutSystem // layout slurSegment for system //--------------------------------------------------------- SpannerSegment* Slur::layoutSystem(System* system) { Fraction stick = system->firstMeasure()->tick(); Fraction etick = system->lastMeasure()->endTick(); SlurSegment* slurSegment = toSlurSegment(getNextLayoutSystemSegment(system, [this]() { return new SlurSegment(score()); })); SpannerSegmentType sst; if (tick() >= stick) { // // this is the first call to layoutSystem, // processing the first line segment // if (track2() == -1) { setTrack2(track()); } if (startCR() == 0 || startCR()->measure() == 0) { qDebug("Slur::layout(): track %d-%d %p - %p tick %d-%d null start anchor", track(), track2(), startCR(), endCR(), tick().ticks(), tick2().ticks()); return slurSegment; } if (endCR() == 0) { // sanity check setEndElement(startCR()); setTick2(tick()); } switch (_slurDirection) { case Direction::UP: _up = true; break; case Direction::DOWN: _up = false; break; case Direction::AUTO: { // // assumption: // slurs have only chords or rests as start/end elements // if (startCR() == 0 || endCR() == 0) { _up = true; break; } Chord* c1 = startCR()->isChord() ? toChord(startCR()) : 0; Chord* c2 = endCR()->isChord() ? toChord(endCR()) : 0; if (c1 && c1->beam() && c1->beam()->cross()) { // TODO: stem direction is not finalized, so we cannot use it here _up = true; break; } _up = !(startCR()->up()); Measure* m1 = startCR()->measure(); if ((endCR()->tick() - startCR()->tick()) > m1->ticks()) { // long slurs are always above _up = true; } else { _up = !startCR()->up(); } if (c1 && c2 && isDirectionMixture(c1, c2) && !c1->isGrace()) { // slurs go above if start and end note have different stem directions, // but grace notes are exceptions _up = true; } else if (m1->hasVoices(startCR()->staffIdx(), tick(), ticks()) && c1 && !c1->isGrace()) { // in polyphonic passage, slurs go on the stem side _up = startCR()->up(); } else if (c1 && c2 && chordsHaveTie(c1, c2)) { // could confuse slur with tie, put slur on stem side _up = startCR()->up(); } } break; } sst = tick2() < etick ? SpannerSegmentType::SINGLE : SpannerSegmentType::BEGIN; } else if (tick() < stick && tick2() >= etick) { sst = SpannerSegmentType::MIDDLE; } else { sst = SpannerSegmentType::END; } slurSegment->setSpannerSegmentType(sst); SlurPos sPos; slurPos(&sPos); switch (sst) { case SpannerSegmentType::SINGLE: slurSegment->layoutSegment(sPos.p1, sPos.p2); break; case SpannerSegmentType::BEGIN: slurSegment->layoutSegment(sPos.p1, QPointF(system->bbox().width(), sPos.p1.y())); break; case SpannerSegmentType::MIDDLE: { qreal x1 = firstNoteRestSegmentX(system); qreal x2 = system->bbox().width(); qreal y = staffIdx() > system->staves()->size() ? system->y() : system->staff(staffIdx())->y(); slurSegment->layoutSegment(QPointF(x1, y), QPointF(x2, y)); } break; case SpannerSegmentType::END: slurSegment->layoutSegment(QPointF(firstNoteRestSegmentX(system), sPos.p2.y()), sPos.p2); break; } return slurSegment; } //--------------------------------------------------------- // layout //--------------------------------------------------------- void Slur::layout() { if (track2() == -1) { setTrack2(track()); } qreal _spatium = spatium(); if (score() == gscore || tick() == Fraction(-1,1)) { // // when used in a palette, slur has no parent and // tick and tick2 has no meaning so no layout is // possible and needed // SlurSegment* s; if (spannerSegments().empty()) { s = new SlurSegment(score()); s->setTrack(track()); add(s); } else { s = frontSegment(); } s->setSpannerSegmentType(SpannerSegmentType::SINGLE); s->layoutSegment(QPointF(0, 0), QPointF(_spatium * 6, 0)); setbbox(frontSegment()->bbox()); return; } if (startCR() == 0 || startCR()->measure() == 0) { qDebug("track %d-%d %p - %p tick %d-%d null start anchor", track(), track2(), startCR(), endCR(), tick().ticks(), tick2().ticks()); return; } if (endCR() == 0) { // sanity check qDebug("no end CR for %d", (tick() + ticks()).ticks()); setEndElement(startCR()); setTick2(tick()); } switch (_slurDirection) { case Direction::UP: _up = true; break; case Direction::DOWN: _up = false; break; case Direction::AUTO: { // // assumption: // slurs have only chords or rests as start/end elements // if (startCR() == 0 || endCR() == 0) { _up = true; break; } Measure* m1 = startCR()->measure(); Chord* c1 = startCR()->isChord() ? toChord(startCR()) : 0; Chord* c2 = endCR()->isChord() ? toChord(endCR()) : 0; _up = !(startCR()->up()); if ((endCR()->tick() - startCR()->tick()) > m1->ticks()) { // long slurs are always above _up = true; } else { _up = !(startCR()->up()); } if (c1 && c2 && isDirectionMixture(c1, c2) && (c1->noteType() == NoteType::NORMAL)) { // slurs go above if start and end note have different stem directions, // but grace notes are exceptions _up = true; } else if (m1->hasVoices(startCR()->staffIdx(), tick(), ticks()) && c1 && c1->noteType() == NoteType::NORMAL) { // in polyphonic passage, slurs go on the stem side _up = startCR()->up(); } else if (c1 && c2 && chordsHaveTie(c1, c2)) { // could confuse slur with tie, put slur on stem side _up = startCR()->up(); } } break; } SlurPos sPos; slurPos(&sPos); const QList& sl = score()->systems(); ciSystem is = sl.begin(); while (is != sl.end()) { if (*is == sPos.system1) { break; } ++is; } if (is == sl.end()) { qDebug("Slur::layout first system not found"); } setPos(0, 0); //--------------------------------------------------------- // count number of segments, if no change, all // user offsets (drags) are retained //--------------------------------------------------------- unsigned nsegs = 1; for (ciSystem iis = is; iis != sl.end(); ++iis) { if ((*iis)->vbox()) { continue; } if (*iis == sPos.system2) { break; } ++nsegs; } fixupSegments(nsegs); for (int i = 0; is != sl.end(); ++i, ++is) { System* system = *is; if (system->vbox()) { --i; continue; } SlurSegment* segment = segmentAt(i); segment->setSystem(system); // case 1: one segment if (sPos.system1 == sPos.system2) { segment->setSpannerSegmentType(SpannerSegmentType::SINGLE); segment->layoutSegment(sPos.p1, sPos.p2); } // case 2: start segment else if (i == 0) { segment->setSpannerSegmentType(SpannerSegmentType::BEGIN); qreal x = system->bbox().width(); segment->layoutSegment(sPos.p1, QPointF(x, sPos.p1.y())); } // case 3: middle segment else if (i != 0 && system != sPos.system2) { segment->setSpannerSegmentType(SpannerSegmentType::MIDDLE); qreal x1 = firstNoteRestSegmentX(system); qreal x2 = system->bbox().width(); qreal y = staffIdx() > system->staves()->size() ? system->y() : system->staff(staffIdx())->y(); segment->layoutSegment(QPointF(x1, y), QPointF(x2, y)); } // case 4: end segment else { segment->setSpannerSegmentType(SpannerSegmentType::END); qreal x = firstNoteRestSegmentX(system); segment->layoutSegment(QPointF(x, sPos.p2.y()), sPos.p2); } if (system == sPos.system2) { break; } } setbbox(spannerSegments().empty() ? QRectF() : frontSegment()->bbox()); } //--------------------------------------------------------- // setTrack //--------------------------------------------------------- void Slur::setTrack(int n) { Element::setTrack(n); for (SpannerSegment* ss : spannerSegments()) { ss->setTrack(n); } } }