//=============================================================================
//  MuseScore
//  Music Composition & Notation
//
//  Copyright (C) 2002-2012 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 "beam.h"
#include "segment.h"
#include "score.h"
#include "chord.h"
#include "sig.h"
#include "style.h"
#include "note.h"
#include "tuplet.h"
#include "system.h"
#include "tremolo.h"
#include "measure.h"
#include "undo.h"
#include "staff.h"
#include "stafftype.h"
#include "stem.h"
#include "hook.h"
#include "mscore.h"
#include "icon.h"
#include "stemslash.h"
#include "groups.h"
#include "xml.h"
#include "spanner.h"

namespace Ms {

static const ElementStyle beamStyle {
      { Sid::beamNoSlope,                        Pid::BEAM_NO_SLOPE           },
      };

//---------------------------------------------------------
//   BeamFragment
//    position of primary beam
//    idx 0 - Direction::AUTO or Direction::DOWN
//        1 - Direction::UP
//---------------------------------------------------------

struct BeamFragment {
      qreal py1[2];
      qreal py2[2];
      };

//---------------------------------------------------------
//   Beam
//---------------------------------------------------------

Beam::Beam(Score* s)
   : Element(s)
      {
      initElementStyle(&beamStyle);
      _direction       = Direction::AUTO;
      _up              = true;
      _distribute      = false;
      _userModified[0] = false;
      _userModified[1] = false;
      _grow1           = 1.0;
      _grow2           = 1.0;
      _isGrace         = false;
      _cross           = false;
      }

//---------------------------------------------------------
//   Beam
//---------------------------------------------------------

Beam::Beam(const Beam& b)
   : Element(b)
      {
      _elements     = b._elements;
      _id           = b._id;
      for (const QLineF* bs : b.beamSegments)
            beamSegments.append(new QLineF(*bs));
      _direction       = b._direction;
      _up              = b._up;
      _distribute      = b._distribute;
      _userModified[0] = b._userModified[0];
      _userModified[1] = b._userModified[1];
      _grow1           = b._grow1;
      _grow2           = b._grow2;
      for (const BeamFragment* f : b.fragments)
            fragments.append(new BeamFragment(*f));
      minMove          = b.minMove;
      maxMove          = b.maxMove;
      _isGrace         = b._isGrace;
      _cross           = b._cross;
      maxDuration      = b.maxDuration;
      slope            = b.slope;
      _noSlope         = b._noSlope;
      }

//---------------------------------------------------------
//   Beam
//---------------------------------------------------------

Beam::~Beam()
      {
      //
      // delete all references from chords
      //
      for (ChordRest* cr : _elements)
            cr->setBeam(0);
      qDeleteAll(beamSegments);
      qDeleteAll(fragments);
      }

//---------------------------------------------------------
//   pagePos
//---------------------------------------------------------

QPointF Beam::pagePos() const
      {
      System* s = system();
      if (s == 0)
            return pos();
      qreal yp = y() + s->staff(staffIdx())->y() + s->y();
      return QPointF(pageX(), yp);
      }

//---------------------------------------------------------
//   canvasPos
//---------------------------------------------------------

QPointF Beam::canvasPos() const
      {
      QPointF p(pagePos());
      if (system() && system()->parent())
            p += system()->parent()->pos();
      return p;
      }

//---------------------------------------------------------
//   add
//---------------------------------------------------------

void Beam::add(Element* e)
      {
      if (e->isChordRest())
            addChordRest(toChordRest(e));
      }

//---------------------------------------------------------
//   remove
//---------------------------------------------------------

void Beam::remove(Element* e)
      {
      if (e->isChordRest())
            removeChordRest(toChordRest(e));
      }

//---------------------------------------------------------
//   addChordRest
//---------------------------------------------------------

void Beam::addChordRest(ChordRest* a)
      {
      a->setBeam(this);
      if (!_elements.contains(a)) {
            //
            // insert element in same order as it appears
            // in the score
            //
            if (a->segment() && !_elements.empty()) {
                  for (int i = 0; i < _elements.size(); ++i) {
                        Segment* s = _elements[i]->segment();
                        if ((s->tick() > a->segment()->tick())
                           || ((s->tick() == a->segment()->tick()) && (a->segment()->next(SegmentType::ChordRest) == s))
                           )  {
                              _elements.insert(i, a);
                              return;
                              }
                        }
                  }
            _elements.append(a);
            }
      }

//---------------------------------------------------------
//   removeChordRest
//---------------------------------------------------------

void Beam::removeChordRest(ChordRest* a)
      {
      if (!_elements.removeOne(a))
            qDebug("Beam::remove(): cannot find ChordRest");
      a->setBeam(0);
      }

//---------------------------------------------------------
//   draw
//---------------------------------------------------------

void Beam::draw(QPainter* painter) const
      {
      if (beamSegments.empty())
            return;
      painter->setBrush(QBrush(curColor()));
      painter->setPen(Qt::NoPen);
      qreal lw2 = score()->styleP(Sid::beamWidth) * .5 * mag();

      // make beam thickness independent of slant
      // (expression can be simplified?)

      const QLineF* bs = beamSegments.front();
      double d  = (qAbs(bs->y2() - bs->y1())) / (bs->x2() - bs->x1());
      if (beamSegments.size() > 1 && d > M_PI/6.0)
            d = M_PI/6.0;
      double ww = lw2 / sin(M_PI_2 - atan(d));

      for (const QLineF* bs1 : beamSegments) {
            painter->drawPolygon(
               QPolygonF({
                  QPointF(bs1->x1(), bs1->y1() - ww),
                  QPointF(bs1->x2(), bs1->y2() - ww),
                  QPointF(bs1->x2(), bs1->y2() + ww),
                  QPointF(bs1->x1(), bs1->y1() + ww),
                  }),
            Qt::OddEvenFill);
            }
      }

//---------------------------------------------------------
//   move
//---------------------------------------------------------

void Beam::move(const QPointF& offset)
      {
      Element::move(offset);
      for (QLineF* bs : beamSegments)
            bs->translate(offset);
      }

//---------------------------------------------------------
//   twoBeamedNotes
//    calculate stem direction of two beamed notes
//    return true if two beamed notes found
//---------------------------------------------------------

bool Beam::twoBeamedNotes()
      {
      // if not two elements or elements are not chords or chords have more than 1 note, return failure
      if ((_elements.size() != 2)
         || !_elements[0]->isChord()
         || !_elements[1]->isChord()) {
            return false;
            }
      const Chord* c1 = toChord(_elements[0]);
      const Chord* c2 = toChord(_elements[1]);
      if (c1->notes().size() != 1 || c2->notes().size() != 1)
            return false;

      int upDnLimit = staff()->lines(0) - 1;           // was '4' hard-coded in the next 2 lines
      int dist1     = c1->upLine() - upDnLimit;
      int dist2     = c2->upLine() - upDnLimit;
      if ((dist1 == -dist2) || (-dist1 == dist2)) {
            _up = false;
            Segment* s = c1->segment();
            s = s->prev1(SegmentType::ChordRest);
            if (s) {
                  Element* e = s->element(c1->track());
                  if (e && e->isChord()) {
                        Chord* c = toChord(e);
                        if (c->beam())
                              _up = c->beam()->up();
                        }
                  }
            }
      else if (qAbs(dist1) > qAbs(dist2))
            _up = dist1 > 0;
      else
            _up = dist2 > 0;
      return true;
      }

//---------------------------------------------------------
//   layout1
//    - remove beam segments
//    - calculate stem direction and set chord
//---------------------------------------------------------

void Beam::layout1()
      {
      //delete old segments
      qDeleteAll(beamSegments);
      beamSegments.clear();

      maxDuration.setType(TDuration::DurationType::V_INVALID);
      Chord* c1 = 0;
      Chord* c2 = 0;

      // TAB's with stem beside staves have special layout
      if (staff()->isTabStaff(0) && !staff()->staffType(0)->stemThrough()) {
            //TABULATURES: all beams (and related chords) are:
            //    UP or DOWN according to TAB duration position
            //    slope 0
            _up   = !staff()->staffType(0)->stemsDown();
            slope   = 0.0;
            _cross  = false;
            minMove = maxMove = 0;              // no cross-beaming in TAB's!
            for (ChordRest* cr : _elements) {
                  if (cr->isChord()) {
                        // set members maxDuration, c1, c2
                        if (!maxDuration.isValid() || (maxDuration < cr->durationType()))
                              maxDuration = cr->durationType();
                        c2 = toChord(cr);
                        if (c1 == 0)
                              c1 = c2;
                        }
                  }
            }
      else if (staff()->isDrumStaff(0)) {
            if (_direction != Direction::AUTO)
                  _up = _direction == Direction::UP;
            else {
                  for (ChordRest* cr :_elements) {
                        if (cr->isChord()) {
                              c2 = toChord(cr);
                              _up = c2->up();
                              break;
                              }
                        }
                  }
            for (ChordRest* cr : _elements)
                  cr->setUp(_up);
            }
      else {
            //PITCHED STAVES (and TAB's with stems through staves)
            minMove = 1000;
            maxMove = -1000;
            _isGrace = false;
            qreal mag = 0.0;

            int mUp     = 0;
            int mDown   = 0;
            int upDnLimit = staff()->lines(0) - 1;           // was '4' hard-coded in following code

            int staffIdx = -1;
            for (ChordRest* cr : _elements) {
                  qreal m = cr->small() ? score()->styleD(Sid::smallNoteMag) : 1.0;
                  mag     = qMax(mag, m);
                  if (cr->isChord()) {
                        c2 = toChord(cr);
                        staffIdx = c2->vStaffIdx();
                        if (c1 == 0)
                              c1 = c2;
                        int i = c2->staffMove();
                        if (i < minMove)
                              minMove = i;
                        if (i > maxMove)
                              maxMove = i;
                        int line = c2->upLine();
                        if ((upDnLimit - line) > mUp)
                              mUp = upDnLimit - line;
                        line = c2->downLine();
                        if (line - upDnLimit > mDown)
                              mDown = line - upDnLimit;
                        }
                  if (!maxDuration.isValid() || (maxDuration < cr->durationType()))
                        maxDuration = cr->durationType();
                  }
            setMag(mag);
            //
            // determine beam stem direction
            //
            if (_direction != Direction::AUTO) {
                  _up = _direction == Direction::UP;
                  }
            else {
                  if (maxMove > 0)            // cross staff beaming down
                        _up = true;
                  else if (minMove < 0)
                        _up = false;
                  else if (c1) {
                        Measure* m = c1->measure();
                        if (c1->stemDirection() != Direction::AUTO)
                              _up = c1->stemDirection() == Direction::UP;
                        else if (m->hasVoices(c1->staffIdx()))
                              _up = !(c1->voice() % 2);
                        else if (!twoBeamedNotes()) {
                              // highest or lowest note determines stem direction
                              // interval higher is bigger -> downstem
                              // interval lower is  bigger -> upstem
                              // down-stems is preferred if equal
                              _up = mUp < mDown;
                              }
                        }
                  else
                        _up = true;
                  }

            _cross = minMove < maxMove;
            if (minMove == 1 && maxMove == 1)
                  setTrack(staffIdx * VOICES + voice());

            // int idx = (_direction == Direction::AUTO || _direction == Direction::DOWN) ? 0 : 1;
            slope = 0.0;

            // leave initial guess alone for moved chords within a beam that crosses staves
            // otherwise, assume beam direction is stem direction

            for (ChordRest* cr : _elements) {
                  if (!cr->isChord())
                        continue;
                  Chord* chord = toChord(cr);
                  if (!(_cross || chord->staffMove())) {
                        if (chord->up() != _up) {
                              chord->setUp(_up);
                              chord->layoutStem1();
                              }
                        }
                  }
            }     // end of if/else(tablature)
      }

//---------------------------------------------------------
//   layoutGraceNotes
//---------------------------------------------------------

void Beam::layoutGraceNotes()
      {
      //delete old segments
      qDeleteAll(beamSegments);
      beamSegments.clear();

      maxDuration.setType(TDuration::DurationType::V_INVALID);
      Chord* c1 = 0;
      Chord* c2 = 0;

      //PITCHED STAVES (and TAB's with stems through staves)
      minMove = 1000;
      maxMove = -1000;
      _isGrace = true;
      qreal graceMag   = score()->styleD(Sid::graceNoteMag);
      setMag(graceMag);

      for (ChordRest* cr : _elements) {
            c2 = toChord(cr);
            if (c1 == 0)
                  c1 = c2;
            int i = c2->staffMove();
            if (i < minMove)
                  minMove = i;
            if (i > maxMove)
                  maxMove = i;
            if (!maxDuration.isValid() || (maxDuration < cr->durationType()))
                  maxDuration = cr->durationType();
            }
      //
      // determine beam stem direction
      //
      if (staff()->isTabStaff(0)) {
            //TABULATURES: all beams (and related chords) are:
            //    UP or DOWN according to TAB duration position
            //    slope 0
            _up   = !staff()->staffType(0)->stemsDown();
            }
      else {
            if (_direction != Direction::AUTO)
                  _up = _direction == Direction::UP;
            else {
                  ChordRest* cr = _elements[0];

                  Measure* m = cr->measure();
                  if (m->hasVoices(cr->staffIdx()))
                        _up = !(cr->voice() % 2);
                  else
                        _up = true;
                  }
            }

      int idx = (_direction == Direction::AUTO || _direction == Direction::DOWN) ? 0 : 1;
      slope   = 0.0;

      if (!_userModified[idx]) {
            for (ChordRest* cr : _elements) {
                  cr->setUp(_up);
                  if (cr->isChord())
                        toChord(cr)->layoutStem1();            /* create stems needed to calculate horizontal spacing */
                  }
            }
      }

//---------------------------------------------------------
//   layout
//---------------------------------------------------------

void Beam::layout()
      {
      System* system = _elements.front()->measure()->system();
      setParent(system);

      std::vector<ChordRest*> crl;

      int n = 0;
      for (ChordRest* cr : _elements) {
            if (cr->measure()->system() != system) {
                  SpannerSegmentType st;
                  if (n == 0)
                        st = SpannerSegmentType::BEGIN;
                  else
                        st = SpannerSegmentType::MIDDLE;
                  ++n;
                  if (fragments.size() < n)
                        fragments.append(new BeamFragment);
                  layout2(crl, st, n-1);
                  crl.clear();
                  system = cr->measure()->system();
                  }
            crl.push_back(cr);
            }
      setbbox(QRectF());
      if (!crl.empty()) {
            SpannerSegmentType st;
            if (n == 0)
                  st = SpannerSegmentType::SINGLE;
            else
                  st = SpannerSegmentType::END;
            if (fragments.size() < (n+1))
                  fragments.append(new BeamFragment);
            layout2(crl, st, n);

            qreal lw2      = score()->styleP(Sid::beamWidth) * .5 * mag();
//            ChordRest* cr  = crl.front();
//            Shape& s       = cr->segment()->shape(staffIdx());
//            QPointF offset = cr->pos() + cr->segment()->pos() + cr->segment()->measure()->pos();

            for (const QLineF* bs : beamSegments) {
                  QPolygonF a(4);
                  a[0] = QPointF(bs->x1(), bs->y1());
                  a[1] = QPointF(bs->x2(), bs->y2());
                  a[2] = QPointF(bs->x2(), bs->y2());
                  a[3] = QPointF(bs->x1(), bs->y1());
                  QRectF r(a.boundingRect().adjusted(0.0, -lw2, 0.0, lw2));
//TODO                  s.add(r.translated(-offset));
                  addbbox(r);
                  }
            }
      }

//---------------------------------------------------------
//   absLimit
//---------------------------------------------------------

inline qreal absLimit(qreal val, qreal limit)
      {
      if (val > limit)
            return limit;
      if (val < -limit)
            return -limit;
      return val;
      }

//---------------------------------------------------------
//   hasNoSlope
//---------------------------------------------------------

bool Beam::hasNoSlope()
      {
      int idx = (_direction == Direction::AUTO || _direction == Direction::DOWN) ? 0 : 1;
      return _noSlope && !_userModified[idx];
      }

//---------------------------------------------------------
//   slopeZero
//---------------------------------------------------------

bool Beam::slopeZero(const std::vector<ChordRest*>& cl)
      {
      if (hasNoSlope() || cl.size() < 2)
            return true;

      //
      // return true if beam spans a rest
      //
      if (cl.size() == 2 && (cl.front()->isRest() || cl.back()->isRest()))
            return true;

//      for (const ChordRest* cr : cl) {
//            if (!cr->isChord())
//                  return true;
//            }
      int l1 = cl.front()->line();
      int le = cl.back()->line();

      // look for some pattern
      if (cl.size() == 4) {
            int l2 = cl[1]->line();
            int l3 = cl[2]->line();

            if ((l1 < le) && (l2 > l1) && (l2 > l3) && (l3 > le))
                  return true;
            if ((l1 == l3) && (l2 == le))
                  return true;
            }
      else if (cl.size() == 6) {
            int l2 = cl[1]->line();
            int l3 = cl[2]->line();
            int l4 = cl[3]->line();
            int l5 = cl[4]->line();
            if ((l2 > l1) && (l3 > l2) && (l1 == l4) && (l2 == l5) && (l3 == le))
                  return true;
            }
      //
      //    concave beams have a slope of 0.0
      //
      bool sameLine = true;

      slope = 0.0;
      if (cl.size() >= 3) {
            int l4 = cl[1]->line(_up);
            for (unsigned  i = 1; i < cl.size()-1; ++i) {
                  // Don't consider interior rests
                  if (!cl[i]->isChord())
                        continue;
                  int l3 = cl[i]->line(_up);
                  if (_up) {
                        if (l3 < l1 && l3 < le)
                              return true;
                        }
                  else {
                        if (l3 > l1 && l3 > le)
                              return true;
                        }
                  sameLine = l3 == l4;
                  }
            if (sameLine && (l1 == l4 || le == l4) && cl[1]->isChord()) {
                  if (_up) {
                        if (l1 == l4 && l1 < le)
                              return true;
                        if (le == l4 && le < l1)
                              return true;
                        }
                  else {
                        if (l1 == l4 && l1 > le)
                              return true;
                        else if (le == l4 && le > l1)
                              return true;
                        }
                  }
            }
      return l1 == le;
      }

//---------------------------------------------------------
//   BeamMetric
//---------------------------------------------------------

struct Bm {
      signed char l;     // stem len   in 1/4 spatium units
      signed char s;     // beam slant in 1/4 spatium units
      Bm() : l(0), s(0) {}
      Bm(signed char a, signed char b) : l(a), s(b) {}
      static int key(int a, int b, int c) { return ((a & 0xff) << 16) | ((b & 0xff) << 8) | (c & 0xff); }
      };

static QHash<int, Bm> bMetrics;

//---------------------------------------------------------
//   initBeamMetrics
//---------------------------------------------------------

#define B(a,b,c,d,e) bMetrics[Bm::key(a, b, c)] = Bm(d, e);

static void initBeamMetrics()
      {
      // up  step1 step2 stemLen1 slant
      //                 (- up)   (- up)
      // =================================== C
      B(1,  10, 10, -12,  0);
      B(0,   3,  3,  11,  0);
      B(1,   3,  3, -11,  0);

      B(1,  10,  9, -12, -1);
      B(1,  10,  8, -12, -4);
      B(1,  10,  7, -12, -5);
      B(1,  10,  6, -15, -5);
      B(1,  10,  5, -16, -5);
      B(1,  10,  4, -20, -4);
      B(1,  10,  3, -20, -5);

      B(1,  10,  11, -12, 1);
      B(1,  10,  12, -13, 2);      // F
      B(1,  10,  13, -13, 2);
      B(1,  10,  14, -13, 2);
      B(1,  10,  15, -13, 2);

      B(1,  3,  4, -11, 1);
      B(1,  3,  5, -11, 2);
      B(1,  3,  6, -11, 4);
      B(1,  3,  7, -11, 5);
      B(1,  3,  8, -11, 5);
      B(1,  3,  9, -11, 5);
      B(1,  3, 10, -11, 5);

      B(0, -4, -3,  15, 1);
      B(0, -4, -2,  15, 2);
      B(0, -4, -1,  15, 2);
      B(0, -4,  0,  15, 5);
      B(0, -4,  1,  16, 5);
      B(0, -4,  2,  20, 4);
      B(0, -4,  3,  20, 5);

      B(0,  3,  4,  13, 1);
      B(0,  3,  5,  13, 2);
      B(0,  3,  6,  14, 4);
      B(0,  3,  7,  14, 4);
      B(0,  3,  8,  14, 6);

      B(0,  3,  2,  11, -1);
      B(0,  3,  1,  11, -2);
      B(0,  3,  0,  11, -5);
      B(0,  3, -1,  11, -5);
      B(0,  3, -2,  11, -5);
      B(0,  3, -3,  11, -5);
      B(0,  3, -4,  11, -5);

      // =================================== D
      B(1,  9,  9,  -13, 0);
      B(0,  2,  2,   12, 0);
      B(1,  2,  2,  -11, 0);

      B(1,  9,  8,  -13, -1);
      B(1,  9,  7,  -13, -2);
      B(1,  9,  6,  -13, -5);
      B(1,  9,  5,  -14, -5);
      B(1,  9,  4,  -16, -6);
      B(1,  9,  3,  -17, -5);
      B(1,  9,  2,  -17, -8);

      B(1,  9, 10,  -11, 1);
      B(1,  9, 11,  -11, 2);
      B(1,  9, 12,  -11, 2);
      B(1,  9, 13,  -11, 2);
      B(1,  9, 14,  -11, 2);
      B(1,  9, 15,  -11, 2);

      B(1,  2, 3,   -12, 1);
      B(1,  2, 4,   -12, 2);
      B(1,  2, 5,   -12, 4);
      B(1,  2, 6,   -12, 5);
      B(1,  2, 7,   -11, 5);
      B(1,  2, 8,   -12, 5);
      B(1,  2, 9,   -12, 8);

      B(0, -5,-4,   16, 2);
      B(0, -5,-3,   16, 2);
      B(0, -5,-2,   17, 2);
      B(0, -5,-1,   17, 2);
      B(0, -5, 0,   18, 4);
      B(0, -5, 1,   18, 5);
      B(0, -5, 2,   21, 5);

      B(0,  2, 3,   12, 1);
      B(0,  2, 4,   12, 4);
      B(0,  2, 5,   13, 4);  // F
      B(0,  2, 6,   15, 5);
      B(0,  2, 7,   15, 6);
      B(0,  2, 8,   16, 8);
      B(0,  2, 9,   16, 8);

      B(0,  2,  1,   12, -1);
      B(0,  2,  0,   12, -4);
      B(0,  2, -1,   12, -5);
      B(0,  2, -2,   12, -5);
      B(0,  2, -3,   12, -4);
      B(0,  2, -4,   12, -4);
      B(0,  2, -5,   12, -5);

      // =================================== E
      B(1, 8, 8,  -12, 0);
      B(0, 1, 1,   13, 0);
      B(1, 1, 1,  -12, 0);

      B(1, 8, 7, -12, -1);
      B(1, 8, 6, -12, -4);
      B(1, 8, 5, -12, -5);
      B(1, 8, 4, -15, -5);
      B(1, 8, 3, -16, -5);
      B(1, 8, 2, -17, -6);
      B(1, 8, 1, -19, -6);

      B(1, 15, 11, -21, -1);
      B(1, 15, 10, -21, -1);
      B(1, 15,  9, -21, -4);
      B(1, 15,  8, -21, -5);

      B(1,  1,  8, -11,  6);
      B(1,  1,  7, -11,  6);
      B(1,  1,  6, -12,  6);

      B(1,  8,  9, -12,  1);
      B(1,  8, 10, -12,  4);
      B(1,  8, 11, -12,  5);
      B(1,  8, 12, -12,  5);
      B(1,  8, 13, -12,  4);
      B(1,  8, 14, -12,  5);
      B(1,  8, 15, -12,  5);

      B(0,  1,  0, 11,  -1);
      B(0,  1, -1, 11,  -2);
      B(0,  1, -2, 11,  -5);
      B(0,  1, -3, 11,  -5);
      B(0,  1, -4, 11,  -5);
      B(0,  1, -5, 11,  -5);
      B(0,  1, -6, 11,  -5);

      B(0, 1, 2, 13, 1);
      B(0, 1, 3, 13, 2);
      B(0, 1, 4, 13, 5);
      B(0, 1, 5, 14, 5);
      B(0, 1, 6, 15, 5);
      B(0, 1, 7, 17, 5);
      B(0, 1, 8, 17, 8);

      B(0, -6, -2,  19, 2);
      B(0, -6, -1,  19, 4);
      B(0, -6,  0,  20, 4);
      B(0, -6,  1,  20, 5);

      B(0, 8, 3, 9,  -6);
      B(0, 8, 2, 12, -8);
      B(0, 8, 1, 12, -8);

      // =================================== F
      B(1, 7, 7,-13, 0);      //F
      B(0, 0, 0, 12, 0);
      B(0, 7, 7, 12, 0);

      B(1, 7, 6, -13, -1);
      B(1, 7, 5, -13, -2);
      B(1, 7, 4, -13, -5);
      B(1, 7, 3, -14, -5);
      B(1, 7, 2, -15, -6);
      B(1, 7, 1, -17, -6);
      B(1, 7, 0, -18, -8);

      B(1, 14, 10, -19, -2);
      B(1, 14,  9, -19, -2);
      B(1, 14,  8, -20, -4);
      B(1, 14,  7, -20, -5);

      B(1,  0,  5,  -9, 6);
      B(1,  0,  6, -12, 8);
      B(1,  0,  7, -12, 8);

      B(1, 7,  8, -11, 1);
      B(1, 7,  9, -11, 2);
      B(1, 7, 10, -11, 5);
      B(1, 7, 11, -11, 5);
      B(1, 7, 12, -11, 5);
      B(1, 7, 13, -11, 5);
      B(1, 7, 14, -11, 5);

      B(0, 0, -1, 12, -1);
      B(0, 0, -2, 12, -4);
      B(0, 0, -3, 12, -5);
      B(0, 0, -4, 12, -5);
      B(0, 0, -5, 12, -4);
      B(0, 0, -6, 12, -4);
      B(0, 0, -7, 12, -4);

      B(0, 0, 1, 12, 1);
      B(0, 0, 2, 12, 4);
      B(0, 0, 3, 12, 5);
      B(0, 0, 4, 15, 5);
      B(0, 0, 5, 16, 5);
      B(0, 0, 6, 17, 5);
      B(0, 0, 7, 19, 6);

      B(0, -7, -3, 21, 2);
      B(0, -7, -2, 21, 2);
      B(0, -7, -1, 21, 2);
      B(0, -7,  0, 22, 4);

      B(0, 7, 2, 12, -6);
      B(0, 7, 1, 11, -6);
      B(0, 7, 0, 11, -6);

      // =================================== G
      B(1,  6,  6, -12, 0);
      B(0, -1, -1,  13, 0);
      B(0,  6,  6,  11, 0);

      B(1, 6,  5, -12, -1);
      B(1, 6,  4, -12, -4);
      B(1, 6,  3, -13, -4);
      B(1, 6,  2, -15, -5);
      B(1, 6,  1, -13, -7);
      B(1, 6,  0, -16, -8);
      B(1, 6, -1, -16, -8);

      B(1, 13, 10, -17, -2);
      B(1, 13,  9, -17, -2);
      B(1, 13,  8, -18, -4);
      B(1, 13,  7, -18, -5);
      B(1, 13,  6, -21, -5);

      B(1, -1, 6, -10, 8);

      B(1, 6,  7, -12, 1);
      B(1, 6,  8, -12, 4);
      B(1, 6,  9, -12, 5);
      B(1, 6, 10, -12, 5);
      B(1, 6, 11, -12, 4);
      B(1, 6, 12, -12, 5);
      B(1, 6, 13, -12, 5);

      B(0, -1, -2, 11, -1);
      B(0, -1, -3, 11, -2);
      B(0, -1, -4, 11, -2);
      B(0, -1, -5, 11, -2);
      B(0, -1, -6, 11, -2);
      B(0, -1, -7, 11, -2);

      B(0, -1,  0, 13, 1);
      B(0, -1,  1, 13, 2);
      B(0, -1,  2, 13, 5);
      B(0, -1,  3, 14, 5);
      B(0, -1,  4, 17, 6);
      B(0, -1,  5, 18, 5);
      B(0, -1,  6, 18, 8);

      B(0,  6,  5, 12, -4);
      B(0,  6,  4, 12, -4);
      B(0,  6,  3, 12, -4);
      B(0,  6,  2, 12, -6);
      B(0,  6,  1, 11, -6);
      B(0,  6,  0, 12, -7);
      B(0,  6, -1, 12, -8);

      // =================================== A
      B(1,  5,  5, -11, 0);
      B(0, -2, -2,  12, 0);
      B(0,  5,  5,  11, 0);

      B(1,  5,  4, -13, -1);
      B(1,  5,  3, -13, -2);
      B(1,  5,  2, -14, -4);
      B(1,  5,  1, -15, -4);
      B(1,  5,  0, -15, -6);

      B(1, 12, 11, -15, -1);
      B(1, 12, 10, -15, -2);
      B(1, 12,  9, -15, -2);
      B(1, 12,  8, -15, -5);
      B(1, 12,  7, -16, -5);
      B(1, 12,  6, -20, -4);
      B(1, 12,  5, -20, -5);

      B(1,  5,  6, -11,  1);
      B(1,  5,  7, -11,  2);
      B(1,  5,  8, -11,  5);
      B(1,  5,  9, -11,  5);
      B(1,  5, 10, -11,  5);
      B(1,  5, 11, -11,  5);
      B(1,  5, 12, -11,  5);

      B(0, -2, -1, 12, 1);
      B(0, -2,  0, 12, 4);
      B(0, -2,  1, 12, 5);
      B(0, -2,  2, 15, 5);
      B(0, -2,  3, 16, 5);
      B(0, -2,  4, 20, 4);
      B(0, -2,  5, 20, 5);

      B(0, -2, -3, 12, -1);
      B(0, -2, -4, 13, -2);
      B(0, -2, -5, 13, -2);
      B(0, -2, -6, 13, -2);
      B(0, -2, -7, 13, -2);

      B(0,  5,  4, 11, -1);
      B(0,  5,  3, 11, -2);
      B(0,  5,  2, 11, -4);
      B(0,  5,  1, 11, -5);
      B(0,  5,  0, 11, -5);
      B(0,  5, -1, 11, -5);
      B(0,  5, -2, 11, -5);

      // =================================== B
      B(1,  4,  4, -12, 0);
      B(1, 11, 11, -13, 0);
      B(0,  4,  4,  12, 0);
      B(0, -3, -3,  13, 0);

      B(1, 11, 10, -13, -1);
      B(1, 11,  9, -13, -2);
      B(1, 11,  8, -13, -5);
      B(1, 11,  7, -14, -5);
      B(1, 11,  6, -18, -4);
      B(1, 11,  5, -18, -5);
      B(1, 11,  4, -21, -5);

      B(1,  4,  3, -12, -1);
      B(1,  4,  2, -12, -4);
      B(1,  4,  1, -14, -4);
      B(1,  4,  0, -16, -4);

      B(1, 11, 12, -14, 1);
      B(1, 11, 13, -14, 1);
      B(1, 11, 14, -14, 1);
      B(1, 11, 15, -15, 2);
      B(1, 11, 16, -15, 2);

      B(1,  4,  5, -12, 1);
      B(1,  4,  6, -12, 4);
      B(1,  4,  7, -12, 5);
      B(1,  4,  8, -12, 5);
      B(1,  4,  9, -13, 6);
      B(1,  4, 10, -12, 4);
      B(1,  4, 11, -12, 5);

      B(0,  4,  3, 12, -1);
      B(0,  4,  2, 12, -4);
      B(0,  4,  1, 12, -5);
      B(0,  4,  0, 12, -5);
      B(0,  4, -1, 13, -6);
      B(0,  4, -2, 12, -4);
      B(0,  4, -3, 12, -5);

      B(0,  4,  5, 12, 1);
      B(0,  4,  6, 12, 4);

      B(0, -3, -4, 14, -1);
      B(0, -3, -5, 14, -1);
      B(0, -3, -6, 14, -1);
      B(0, -3, -7, 15, -2);
      B(0, -3, -8, 15, -2);
      B(0, -3, -9, 15, -2);

      B(0, -3, -2, 13, 1);
      B(0, -3, -1, 13, 2);
      B(0, -3,  0, 13, 5);
      B(0, -3,  1, 14, 5);
      B(0, -3,  2, 18, 4);
      B(0, -3,  3, 18, 5);
      B(0, -3,  4, 21, 5);
      }

//---------------------------------------------------------
//   beamMetric1
//    table driven
//---------------------------------------------------------

static Bm beamMetric1(bool up, char l1, char l2)
      {
      static int initialized = false;
      if (!initialized) {
            initBeamMetrics();
            initialized = true;
            }
      return bMetrics[Bm::key(up, l1, l2)];
      }

//---------------------------------------------------------
//   adjust
//    adjust stem len for notes between start-end
//    return 1/4 spatium units
//---------------------------------------------------------

static int adjust(qreal _spatium4, int slant, const std::vector<ChordRest*>& cl)
      {
      int n = cl.size();
      const ChordRest* c1 = cl[0];
      const ChordRest* c2 = cl[n-1];

      QPointF p1(c1->stemPosBeam());   // canvas coordinates
      qreal slope = (slant * _spatium4) / (c2->stemPosBeam().x() - p1.x());
      int ml = -1000;
      if (c1->up()) {
            for (int i = 1; i < n; ++i) {
                  QPointF p3(cl[i]->stemPosBeam());
                  qreal yUp   = p1.y() + (p3.x() - p1.x()) * slope;
                  int l       = lrint((yUp - p3.y()) / (_spatium4));
                  ml          = qMax(ml, l);
                  }
            }
      else {
            for (int i = 1; i < n; ++i) {
                  const ChordRest* c = cl[i];
                  QPointF p3(c->stemPosBeam());
                  qreal yUp   = p1.y() + (p3.x() - p1.x()) * slope;
                  int l       = lrint((p3.y() - yUp) / (_spatium4));
                  ml          = qMax(ml, l);
                  }
            }
      // on tab staff, reduce a bit the stems (value 4 is experimental)
      // TODO : proper fix should adapt all the numeric vaues used in Beam::computeStemLen() below
      // to variable line distance
      if (c1->staff() && c1->staff()->isTabStaff(0)) {
            ml = (ml != 0) ? ml - 4 : 0;
            return ml;
            }
      return (ml > 0) ? ml : 0;
      }

//---------------------------------------------------------
//   adjust2
//    adjust stem position for single beams
//---------------------------------------------------------

static void adjust2(Bm& bm, const ChordRest* c1)
      {
      static const int dd[4][4] = {
            // St   H  --   S
            {0,  0,  1,  0},     // St
            {0,  0, -1,  0},     // S
            {1,  1,  1, -1},     // --
            {0,  0, -1,  0}      // H
            };
      int ys = bm.l + c1->line() * 2;
      int e1 = qAbs((ys  + 1000) % 4);
      int e2 = qAbs((ys + 1000 + bm.s) % 4);
      bm.l  -= dd[e1][e2];
      }

//---------------------------------------------------------
//   minSlant
//---------------------------------------------------------

static int minSlant(uint interval)
      {
      static const int minSlantTable[] = { 0, 1, 2, 4, 5 };
      if (interval > 4)
            return 5;
      return minSlantTable[interval];
      }

//---------------------------------------------------------
//   maxSlant
//---------------------------------------------------------

static int maxSlant(uint interval)
      {
      static const int maxSlantTable[] = { 0, 1, 4, 5, 5, 6, 7, 8 };
      if (interval > 7)
            return 8;
      return maxSlantTable[interval];
      }

//---------------------------------------------------------
//   slantTable
//---------------------------------------------------------

static int* slantTable(uint interval)
      {
      static int t[8][5] = {
            { 0, -1,  0,  0,  0 },
            { 1, -1,  0,  0,  0 },
            { 3,  4,  2, -1,  0 },
            { 4,  5, -1,  0,  0 },
            { 5, -1,  0,  0,  0 },
            { 5,  6, -1,  0,  0 },
            { 6,  5,  7, -1,  0 },
            { 6,  7,  5,  8, -1 },
            };
      if (interval > 7)
            interval = 7;
      return &t[interval][0] ;
      }

//---------------------------------------------------------
//   computeStemLen
//---------------------------------------------------------

void Beam::computeStemLen(const std::vector<ChordRest*>& cl, qreal& py1, int beamLevels)
      {
      qreal _spatium      = spatium();
      qreal _spatium4     = _spatium * .25;
      // TAB: scale to staff line distance for vert. pos. within a staff
      qreal _spStaff4     = staff()->isTabStaff(0) ? _spatium4 * staff()->lineDistance(0) : _spatium4;
      const ChordRest* c1 = cl.front();
      const ChordRest* c2 = cl.back();
      qreal dx            = c2->pagePos().x() - c1->pagePos().x();
      bool zeroSlant      = slopeZero(cl);

      int l1 = c1->line() * 2;
      int l2 = c2->line() * 2;

      Bm bm;

      // shorten stem length if grace notes beam is under main notes beam.
      // Value 4 estimated. Desired: to find a good formula.

      int graceStemLengthCorrection;
      if (_isGrace)
            graceStemLengthCorrection = static_cast<const Chord*>(c1)->underBeam() ? 4 : 3;
      else
            graceStemLengthCorrection = 0;

      if (beamLevels == 1) {
            bm = beamMetric1(_up, l1 / 2, l2 / 2);
            if (hasNoSlope())
                  bm.s = 0.0;

            // special case for two beamed notes: flatten to max of 1sp
            static int maxShortSlant = 4;
            if (bm.l && elements().size() == 2) {
                  //qDebug("computeStemLen: l = %d, s = %d", (int)bm.l, (int)bm.s);
                  if (bm.s > maxShortSlant) {
                        // slant downward
                        // lengthen first stem if down
                        if (bm.l > 0)
                              bm.l += bm.s - maxShortSlant;
                        // flatten beam
                        bm.s = maxShortSlant;
                        }
                  else if (bm.s < -maxShortSlant) {
                        // slant upward
                        // lengthen first stem if up
                        if (bm.l < 0)
                              bm.l -= -maxShortSlant - bm.s;
                        // flatten beam
                        bm.s = -maxShortSlant;
                        }
                  }

            if (bm.l) {
                  if (bm.l > 0)
                        bm.l -= graceStemLengthCorrection;
                  else
                        bm.l += graceStemLengthCorrection;
                  }

            if (bm.l && !(zeroSlant && cl.size() > 2)) {
                  if (cl.size() > 2) {
                        if (_up)
                              bm.l = -12 - adjust(_spStaff4, bm.s, cl);
                        else
                              bm.l = 12 + adjust(_spStaff4, bm.s, cl);
                        adjust2(bm, c1);
                        }
                  }
            else {
                  int* st = slantTable(zeroSlant ? 0 : qAbs((l2 - l1) / 2));
                  int ll1;
                  if (_up) {
                        ll1 = l1 - ((l1 & 3) ? 11 : 12);
                        int ll1m = l1 - 10;
                        int rll1 = ll1;
                        if ((l1 > 20) && (l2 > 20)) {
                              st = slantTable(zeroSlant ? 0 : 1);
                              rll1 = (zeroSlant || (l2 < l1)) ? 9 : 8;
                              }
                        for (int n = 0; ; ll1--) {
                              int i;
                              for (i = 0; st[i] != -1; ++i) {
                                    int slant = (l2 > l1) ? st[i] : -st[i];
                                    int lll1  = qMin(rll1, ll1m - n - adjust(_spStaff4, slant, cl));
                                    int ll2   = lll1 + slant;
                                    static bool ba[4][4] = {
                                          { true,  true,  false, true },
                                          { true,  true,  false, true },
                                          { false, false, false, true },
                                          { true,  true,  false, true }
                                          };
                                    if (ba[lll1 & 3][ll2 & 3]) {
                                          ll1 = lll1;
                                          bm.s = slant;
                                          break;
                                          }
                                    }
                              if (st[i] != -1)
                                    break;
                              if (++n > 4) {
                                    qDebug("beam note not found 1");
                                    break;
                                    }
                              }
                        }
                  else {
                        ll1 = ((l1 & 3) ? 11 : 12) + l1;
                        int rll1 = ll1;
                        if ((l1 < -4) && (l2 < -4)) {
                              // extend to middle line, slant is always 0 <= 1
                              st = slantTable(zeroSlant ? 0 : 1);
                              rll1 = (zeroSlant || (l2 > l1)) ? 7 : 8;
                              }
                        for (int n = 0;;ll1++) {
                              int i;
                              for (i = 0; st[i] != -1; ++i) {
                                    int slant = (l2 > l1) ? st[i] : -st[i];
                                    int lll1  = qMax(rll1, ll1 + adjust(_spStaff4, slant, cl));
                                    int e1    = lll1 & 3;
                                    int ll2   = lll1 + slant;
                                    int e2    = ll2 & 3;
                                    static bool ba[4][4] = {
                                          { true,  true,  false, true },
                                          { true,  true,  false, true },
                                          { false, false, false, true },
                                          { true,  true,  false, true }
                                          };
                                    if (ba[e1][e2]) {
                                          ll1 = lll1;
                                          bm.s = slant;
                                          break;
                                          }
                                    }
                              if (st[i] != -1)
                                    break;
                              if (++n > 4) {
                                    qDebug("beam not found 2");
                                    break;
                                    }
                              }
                        }
                  bm.l = ll1 - l1;
                  }
            }
      else if (beamLevels == 2) {
            int minS, maxS;
            if (zeroSlant)
                  minS = maxS = 0;
            else {
                  uint interval = qAbs((l2 - l1) / 2);
                  minS          = minSlant(interval);
                  maxS          = maxSlant(interval);
                  if (elements().size() == 2) {
                        minS = qMin(minS, 2);
                        maxS = qMin(maxS, 4);
                        }
                  }
            int ll1;
            if (_up) {
                  ll1 = l1 - 12;     // sp minimum to primary beam
                  int rll1 = ll1;
                  if ((l1 > 20) && (l2 > 20)) {
                        minS = zeroSlant ? 0 : 1;
                        maxS = minS;
                        rll1 = (zeroSlant || (l2 < l1)) ? 9 : 8;
                        }
                  for (int n = 0; ; ll1--) {
                        int i;
                        for (i = minS; i <= maxS; ++i) {
                              int slant = (l2 > l1) ? i : -i;
                              int lll1  = qMin(rll1, ll1 - adjust(_spStaff4, slant, cl));
                              int ll2   = lll1 + slant;
                              static bool ba[4][4] = {
                                    { true,  true,  false, false  },
                                    { true,  true,  false, false },
                                    { false, false, false, false },
                                    { false, false, false, false }
                                    };
                              if (ba[lll1 & 3][ll2 & 3]) {
                                    ll1 = lll1;
                                    break;
                                    }
                              }
                        if (i <= maxS) {
                              bm.s = l2 > l1 ? i : -i;
                              break;
                              }
                        if (++n > 4) {
                              qDebug("beam note not found 1 %d-%d", minS, maxS);
                              break;
                              }
                        }
                  }
            else {
                  ll1       = 12 + l1;
                  int rll1  = ll1;
                  bool down = l2 > l1;
                  if ((l1 < -4) && (l2 < -4)) {
                        // extend to middle line, slant is always 0 <= 1
                        minS = zeroSlant ? 0 : 1;
                        maxS = minS;
                        rll1 = (zeroSlant || down) ? 7 : 8;
                        }
                  for (int n = 0;;ll1++) {
                        int i;
                        for (i = minS; i <= maxS; ++i) {
                              int slant = down ? i : -i;
                              int lll1  = qMax(rll1, ll1 + adjust(_spStaff4, slant, cl));
                              int ll2   = lll1 + slant;
                              static bool ba[4][4] = {
                                    { true,  false, false, true  },
                                    { false, false, false, false },
                                    { false, false, false, false },
                                    { true,  false, false, true }
                                    };
                              if (ba[lll1 & 3][ll2 & 3]) {
                                    ll1 = lll1;
                                    bm.s = slant;
                                    break;
                                    }
                              }
                        if (i <= maxS)
                              break;
                        if (++n > 4) {
                              qDebug("beam not found 2");
                              break;
                              }
                        }
                  }
            bm.l = ll1 - l1;
            }
      else if (beamLevels == 3) {
            int slant;
            bool outside;
            if (zeroSlant) {
                  outside = (_up && qMin(l1, l2) <= 10) ||
                     (!_up && qMax(l1, l2) >= 6);
                  slant = 0;
                  }
            else {
                  outside = (_up && (l1 <= 10) && (l2 <= 10)) ||
                     (!_up && (l1 >= 6) && (l2 >= 6));
                  if (outside)
                        slant = *slantTable(qAbs(l1-l2) / 2);
                  else
                        slant = 4;
                  if (l1 > l2)
                        slant = -slant;
                  }
            int ll1;
            if (_up) {
                  static const int t[4] = { 3, 0, 1, 2 };
                  ll1 = l1 - 15 - adjust(_spStaff4, slant, cl);
                  ll1 = qMin(ll1, 5);
                  if (!outside)
                        ll1 -= t[ll1 & 3];      // extend to sit on line
                  }
            else {
                  ll1 = 15 + l1 + adjust(_spStaff4, slant, cl);
                  ll1 = qMax(ll1, 11);
                  if (!outside)
                        ll1 += 3 - (ll1 & 3);   // extend to hang on line
                  }
            bm.s = slant;
            bm.l = ll1 - l1;
            }
      else if (beamLevels == 4) {
            int slant = zeroSlant ? 0 : (l2 > l1 ? 4 : -4);
            int ll1;
            if (_up) {
                  ll1 = l1 - 17 - adjust(_spStaff4, slant, cl);
                  ll1 = qMin(ll1, 1);
                  static const int t[4] = { 3, 0, 1, 2 };
                  ll1 -= t[ll1 & 3];      // extend to sit on line
                  }
            else {
                  ll1 = 17 + l1 + adjust(_spStaff4, slant, cl);
                  ll1 = qMax(ll1, 15);
                  ll1 += 3 - (ll1 & 3);   // extend to hang on line
                  }
            bm.s = slant;
            bm.l = ll1 - l1;
            }
      else { // if (beamLevels > 4) {
            static const int t[] = { 0, 0, 4, 4, 8, 12, 16 }; // spatium4 added to stem len
            int n = t[beamLevels] + 12;
            bm.s = 0;
            if (_up) {
                  bm.l = -n;
                  bm.l -= adjust(_spStaff4, bm.s, cl);
                  }
            else {
                  bm.l += n;
                  bm.l += adjust(_spStaff4, bm.s, cl);
                  }
            }
      if (_isGrace && (beamLevels > 1) && bm.l) {
            if (bm.l > 0)
                  bm.l -= graceStemLengthCorrection;
            else
                  bm.l += graceStemLengthCorrection;
            }
      if (dx == 0.0)
            slope = 0.0;
      else
            slope   = (bm.s * _spatium4) / dx;
      int dy = (c1->line(_up) - c1->line(!_up)) * 2;

      py1 += (dy + bm.l) * _spStaff4;
      }

//---------------------------------------------------------
//   layout2
//---------------------------------------------------------

void Beam::layout2(std::vector<ChordRest*>crl, SpannerSegmentType, int frag)
      {
      if (_distribute)
            score()->respace(&crl);       // fix horizontal spacing of stems

      if (crl.empty())                  // no beamed Elements
            return;
      const ChordRest* c1 = crl.front();       // first chord/rest in beam
      const ChordRest* c2 = crl.back();        // last chord/rest in beam

      int beamLevels = 1;
      for (const ChordRest* c : crl)
            beamLevels = qMax(beamLevels, c->durationType().hooks());

      BeamFragment* f = fragments[frag];
      int dIdx        = (_direction == Direction::AUTO || _direction == Direction::DOWN) ? 0 : 1;
      qreal& py1      = f->py1[dIdx];
      qreal& py2      = f->py2[dIdx];

      qreal _spatium   = spatium();
      QPointF _pagePos(pagePos());
      qreal beamMinLen = score()->styleP(Sid::beamMinLen) * mag();

      if (beamLevels == 4)
            _beamDist = score()->styleP(Sid::beamWidth) * (1 + score()->styleD(Sid::beamDistance)*4/3);
      else
            _beamDist = score()->styleP(Sid::beamWidth) * (1 + score()->styleD(Sid::beamDistance));

      _beamDist *= mag();
      _beamDist *= c1->staff()->mag(c1->tick());
      int n = crl.size();

      StaffType* tab = 0;
      if (staff()->isTabStaff(0) )
            tab = staff()->staffType(0);
      if (tab && !tab->stemThrough()) {
            //
            // TAB STAVES with stems beside staves: beam position is fixed depending on TAB parameters and chordrest up/down
            // (all the chordrests of a beam have the same up/down, as it depends on TAB parameters if there are no voices
            // or from the voice the beam belongs to if there are voices; then, it is enough to check only the first chordrest)
            _up = c1->up();
            // compute vert. pos. of beam, relative to staff (top line = 0)
            qreal y = tab->chordRestStemPosY(c1) + (_up ? - STAFFTYPE_TAB_DEFAULTSTEMLEN_UP : STAFFTYPE_TAB_DEFAULTSTEMLEN_DN);
            y *= _spatium;
            py1 = py2 = y;          // in this case, beams are always horizontal: py1 = py2
            }
      else {
            //
            // PITCHED STAVES (or TAB with stems through staves)
            //
            qreal px1 = c1->stemPosX() + c1->pageX();
            qreal px2 = c2->stemPosX() + c2->pageX();

            if (_userModified[dIdx]) {
                  py1 += _pagePos.y();
                  py2 += _pagePos.y();

                  qreal beamY = py1;
                  slope       = (py2 - py1) / (px2 - px1);
                  //
                  // set stem direction for every chord
                  //
                  bool relayoutGrace = false;
                  for (int i = 0; i < n; ++i) {
                        Chord* c = toChord(crl.at(i));
                        if (c->isRest())
                              continue;
                        QPointF p = c->upNote()->pagePos();
                        qreal y1  = beamY + (p.x() - px1) * slope;
                        bool nup  = y1 < p.y();
                        if (c->up() != nup) {
                              c->setUp(nup);
                              // guess was wrong, have to relayout
                              if (!_isGrace) {
                                    score()->layoutChords1(c->segment(), c->staffIdx());
                                    // DEBUG: attempting to layout during beam edit causes crash
                                    // probably because ledger lines are deleted and added back
                                    // if (editFragment == -1)
                                          c->layout();
                                    }
                              else {
                                    relayoutGrace = true;
                                    score()->layoutChords3(c->notes(), c->staff(), 0);
                                    }
                              }
                        }
                  _up = crl.front()->up();
                  if (relayoutGrace)
                        c1->parent()->layout();
                  }
            else if (_cross) {
                  qreal beamY   = 0.0;  // y position of main beam start
                  qreal y1   = -200000;
                  qreal y2   = 200000;
                  for (int i = 0; i < n; ++i) {
                        Chord* c = toChord(crl.at(i));
                        qreal y;
                        if (c->isRest())
                              continue;   //y = c->pagePos().y();
                        else
                              y  = c->upNote()->pagePos().y();
                        y1 = qMax(y1, y);
                        y2 = qMin(y2, y);
                        }
                  if (y1 > y2)
                        beamY = y2 + (y1 - y2) * .5;
                  else
                        beamY = _up ? y2 : y1;
                  py1 = beamY;

                  //
                  // set stem direction for every chord
                  //
                  for (ChordRest* cr : crl) {
                        if (!cr->isChord())
                              continue;
                        Chord* c = toChord(cr);
                        qreal y  = c->upNote()->pagePos().y();
                        bool nup = beamY < y;
                        if (c->up() != nup) {
                              c->setUp(nup);
                              // guess was wrong, have to relayout
                              score()->layoutChords1(c->segment(), c->staffIdx());
                              c->layout();
                              // TODO: this might affect chord space, which might affect segment position
                              // we should relayout entire measure
                              // this probably means starting over for beam as well
                              // see https://musescore.org/en/node/71901
                              }
                        }

                  qreal yDownMax = -300000;
                  qreal yUpMin   = 300000;

                  for (ChordRest* cr : crl) {
                        if (!cr->isChord())
                              continue;
                        Chord* c = toChord(cr);
                        bool _up1 = c->up();
                        qreal y = (_up1 ? c->upNote() : c->downNote())->pagePos().y();
                        if (_up1)
                              yUpMin = qMin(y, yUpMin);
                        else
                              yDownMax = qMax(y, yDownMax);
                        }
                  qreal slant = hasNoSlope() ? 0 : _spatium;
                  if (crl.front()->up())
                        slant = -slant;
                  py1   = yUpMin + (yDownMax - yUpMin) * .5 - slant * .5;
                  slope = slant / (px2 - px1);
                  if (_direction == Direction::AUTO)
                        _up = crl.front()->up();
                  }
            else {
                  py1 = c1->stemPos().y();
                  py2 = c2->stemPos().y();      // for debug
                  computeStemLen(crl, py1, beamLevels);
                  }
            py2  = (px2 - px1) * slope + py1;   // for debug
            py2 -= _pagePos.y();
            py1 -= _pagePos.y();
            }

      //---------------------------------------------
      //   create beam segments
      //---------------------------------------------

      qreal x1 = crl[0]->stemPosX() + crl[0]->pageX() - pageX();

      int baseLevel = 0;      // beam level that covers all notes of beam
#if (!defined (_MSCVER) && !defined (_MSC_VER))
      int crBase[n];          // offset of beam level 0 for each chord
#else
      // MSVC does not support VLA. Replace with std::vector. If profiling determines that the
      //    heap allocation is slow, an optimization might be used.
      std::vector<int> crBase(n);
#endif
      bool growDown = _up;

      for (int beamLevel = 0; beamLevel < beamLevels; ++beamLevel) {

            // loop through the different groups for this beam level
            // inner loop will advance through chordrests within each group
            for (int i = 0; i < n;) {
                  ChordRest* cr1 = crl[i];
                  int l1 = cr1->durationType().hooks() - 1;

                  if ((cr1->type() == ElementType::REST && i) || l1 < beamLevel) {
                        ++i;
                        continue;
                        }

                  // at the beginning of a group
                  // loop through chordrests looking for end
                  int currentChordRestIndex = i;
                  ++i;
                  bool b32 = false, b64 = false;
                  for (; i < n; ++i) {
                        ChordRest* c = crl[i];
                        ChordRest* p = i ? crl[i - 1] : 0;
                        int l = c->durationType().hooks() - 1;

                        Mode bm = Groups::endBeam(c, p);
                        b32 = (beamLevel >= 1) && (bm == Mode::BEGIN32);
                        b64 = (beamLevel >= 2) && (bm == Mode::BEGIN64);

                        if ((l >= beamLevel && (b32 || b64)) || (l < beamLevel)) {
                              if (i > 1 && crl[i-1]->type() == ElementType::REST) {
                                    --i;
                                    }
                              break;
                              }
                        }

                  // found end of group
                  int chordRestEndGroupIndex = i;
                  ChordRest* cr2 = crl[chordRestEndGroupIndex - 1];

                  // if group covers whole beam, we are still at base level
                  if (currentChordRestIndex == 0 && chordRestEndGroupIndex == n)
                        baseLevel = beamLevel;

                  // default assumption - everything grows in same direction
                  int bl = growDown ? beamLevel : -beamLevel;
                  bool growDownGroup = growDown;

                  // calculate direction for this group
                  if (beamLevel > baseLevel) {

                        if ((currentChordRestIndex && (cr1->up() == cr2->up()))
                            || ((chordRestEndGroupIndex == n) && (cr1->up() != cr2->up()))) {
                              // matching direction for outer stems, not first group
                              // or, opposing direction for outer stems, last group
                              // recalculate beam for this group based on its *first* cr
                              growDownGroup = cr1->up();
                              }

                        else if (!currentChordRestIndex && (chordRestEndGroupIndex < n) && (cr1->up() != cr2->up())) {
                              // opposing directions for outer stems, first (but not only) group
                              // recalculate beam for this group if necessary based on its *last* cr
                              growDownGroup = cr2->up();
                              }

                        // recalculate segment offset bl
                        int base = crBase[currentChordRestIndex];
                        if (growDownGroup && base <= 0)
                              bl = base + beamLevel;
                        else if (growDownGroup)
                              bl = base + 1;
                        else if (!growDownGroup && base >= 0)
                              bl = base - beamLevel;
                        else if (!growDownGroup)
                              bl = base - 1;

                        }

                  // if there are more beam levels,
                  // record current beam offsets for all notes of this group for re-use
                  if (beamLevel < beamLevels - 1) {
                        for (int i1 = currentChordRestIndex; i1 < chordRestEndGroupIndex; ++i1)
                              crBase[i1] = bl;
                        }

                  qreal stemWidth  = score()->styleP(Sid::stemWidth);
                  qreal x2         = cr1->stemPosX() + cr1->pageX() - _pagePos.x();
                  qreal x3;

                  if ((chordRestEndGroupIndex - currentChordRestIndex) > 1) {
                        ChordRest* chordRest2 = crl[chordRestEndGroupIndex-1];
                        // create segment
                        x3 = chordRest2->stemPosX() + chordRest2->pageX() - _pagePos.x();

                        if (tab) {
                              x2 -= stemWidth * 0.5;
                              x3 += stemWidth * 0.5;
                              }
                        else {
                              if (cr1->up())
                                    x2 -= stemWidth;
                              if (!chordRest2->up())
                                    x3 += stemWidth;
                              }
                        }
                  else {
                        // create broken segment
                        if (cr1->type() == ElementType::REST)
                              continue;

                        int sizeChordRests = crl.size();
                        qreal len = beamMinLen;
                        //
                        // find direction (by default, segment points to right)
                        //
                        // if first or last of group (including tuplet groups)
                        // unconditionally set beam at right or left side
                        Tuplet* tuplet = cr1->tuplet();
                        if (currentChordRestIndex == 0)
                              ;
                        else if (currentChordRestIndex == sizeChordRests - 1)
                              len = -len;
                        else if (tuplet && cr1 == tuplet->elements().front())
                              ;
                        else if (tuplet && cr1 == tuplet->elements().back())
                              len = -len;
                        else if (b32 || b64)          // end of a sub-beam group
                              len = -len;
                        else if (!(cr1->isGrace())) {
                              // inside group - here it gets more complex
                              // see http://musescore.org/en/node/42856, http://musescore.org/en/node/40806
                              // our strategy:
                              // decide if we have reached the end of a "logical" grouping
                              // even if we are not literally at the end of a beam group
                              // we do this two ways:
                              // 1) see if beam groups would have indicated a break or sub-beam if the next chord were same length as this
                              // 2) see if next note is on a "sub-beat" as defined by 2 * current note duration
                              // in either case, broken segment should point left; otherwise right
                              // however, we should try to be careful to avoid "floating" segments
                              // caused by mismatches between number of incoming versus outgoing beams
                              // so, we favor the side with more beams (to the extent we can count reliably)
                              // if there is a corner case missed, this would probably be where
                              ChordRest* prevCR = crl[currentChordRestIndex-1];
                              ChordRest* nextCR = crl[currentChordRestIndex+1];
                              TDuration currentDuration = cr1->durationType();
                              int currentHooks = currentDuration.hooks();

                              // since we have already established that we are not at end of sub-beam,
                              // outgoing beams should always be # hooks of next chord
                              int beamsOut = nextCR->durationType().hooks();

                              // incoming beams is normally # hooks of previous chord
                              // unless this is start of sub-beam
                              const Groups& g = cr1->staff()->group(cr1->measure()->tick());
                              Fraction stretch = cr1->staff()->timeStretch(cr1->measure()->tick());
                              int currentTick = (cr1->rtick() * stretch.numerator()) / stretch.denominator();
                              Beam::Mode bm = g.beamMode(currentTick, currentDuration.type());
                              int beamsIn;
                              if (bm == Beam::Mode::BEGIN32)
                                    beamsIn = 1;
                              else if (bm == Beam::Mode::BEGIN64)
                                    beamsIn = 2;
                              else
                                    beamsIn = prevCR->durationType().hooks();

                              // remember, we are checking whether nextCR would have started sub-beam *if* same duration as this
                              int nextTick = (nextCR->rtick() * stretch.numerator()) / stretch.denominator();
                              bm = g.beamMode(nextTick, currentDuration.type());

                              if (currentHooks - beamsOut > 1 && beamsIn > beamsOut && currentHooks > beamsIn) {
                                    // point left to avoid floating segment
                                    len = -len;
                                    }
                              else if (beamsIn < beamsOut) {
                                    // point right to avoid floating segment
                                    ;
                                    }
                              else if (bm != Beam::Mode::AUTO) {
                                    // beam group info suggests this is a logical group end as per 1) above
                                    len = -len;
                                    }
                              else {
                                    // determine if this is a logical group end as per 2) above

                                    int baseTick = tuplet ? tuplet->tick() : cr1->measure()->tick();
                                    int tickNext = nextCR->tick() - baseTick;
                                    if (tuplet) {
                                          // for tuplets with odd ratios, apply ratio
                                          // thus, we are performing calculation relative to apparent rather than actual beat
                                          // for tuplets with even ratios, use actual beat
                                          // see https://musescore.org/en/node/58061
                                          Fraction r = tuplet->ratio();
                                          if (r.numerator() & 1)
                                                tickNext = (tickNext * r.numerator()) / r.denominator();
                                          }

                                    // determine the tick length of a chord with one beam level less than this
                                    // (i.e. twice the ticks of this)

                                    int tickMod  = cr1->duration().ticks() * 2;     // (tickNext - (crl[c1]->tick() - baseTick)) * 2;

                                    // if this completes, within the measure or tuplet, a unit of tickMod length, flip beam to left
                                    // (allow some tolerance for tick rounding in tuplets
                                    // without tuplet tolerance, could be simplified)

                                    static const int BEAM_TUPLET_TOLERANCE = 6;
                                    int mod = tickNext % tickMod;
                                    if (mod <= BEAM_TUPLET_TOLERANCE || (tickMod - mod) <= BEAM_TUPLET_TOLERANCE)
                                          len = -len;
                                    }
                              }
                        if (tab) {
                              if (len > 0)
                                    x2 -= stemWidth * 0.5;
                              else
                                    x2 += stemWidth * 0.5;
                              }
                        else {
                              bool stemUp = cr1->up();
                              if (stemUp && len > 0)
                                    x2 -= stemWidth;
                              else if (!stemUp && len < 0)
                                    x2 += stemWidth;
                              }
                        x3 = x2 + len;
                        }
                  //feathered beams
                  qreal yo   = py1 + bl * _beamDist * _grow1;
                  qreal yoo  = py1 + bl * _beamDist * _grow2;
                  qreal ly1  = (x2 - x1) * slope + yo;
                  qreal ly2  = (x3 - x1) * slope + yoo;

                  if (!qIsFinite(x2) || !qIsFinite(ly1)
                     || !qIsFinite(x3) || !qIsFinite(ly2)) {
                        qDebug("bad beam segment: slope %f", slope);
                        }
                  else {
                        beamSegments.push_back(new QLineF(x2, ly1, x3, ly2));
                        }
                  }
            }

      //
      //  calculate stem length
      //
      for (ChordRest* cr : crl) {
            if (!cr->isChord())
                  continue;
            Chord* c = toChord(cr);
            if (c->hook())
                  score()->undoRemoveElement(c->hook());

            QPointF stemPos(c->stemPosX() + c->pagePos().x(), c->stemPos().y());
            qreal x2   = stemPos.x() - _pagePos.x();
            qreal y1   = (x2 - x1) * slope + py1 + _pagePos.y();
            qreal y2   = stemPos.y();
            // qreal fuzz = _spatium * .1;
            qreal fuzz = _spatium * .4;   // something is wrong

            qreal by = y2 < y1 ? -1000000 : 1000000;
            for (const QLineF* l : beamSegments) {
                  if ((x2+fuzz) >= l->x1() && (x2-fuzz) <= l->x2()) {
                        qreal y = (x2 - l->x1()) * slope + l->y1();
                        by = y2 < y1 ? qMax(by, y) : qMin(by, y);
                        }
                  }
            if (by == -1000000 || by == 1000000) {
                  if (beamSegments.empty())
                        qDebug("no BeamSegments");
                  else {
                        qDebug("BeamSegment not found: x %f  %f-%f",
                           x2, beamSegments.front()->x1(),
                           beamSegments.back()->x2());
                        }
                  by = 0;
                  }

            Stem* stem = c->stem();
            if (stem) {
                  qreal sw2  = stem->lineWidth() * .5;
                  if (c->up())
                        sw2 = -sw2;
                  stem->rxpos() = c->stemPosX() + sw2;
                  qreal l       = y2 - (by + _pagePos.y());
                  stem->setLen(l);

                  StemSlash* stemSlash = c->stemSlash();
                  if (stemSlash)
                        stemSlash->layout();
                  Tremolo* tremolo = c->tremolo();
                  if (tremolo)
                        tremolo->layout();
                  c->segment()->createShape(c->vStaffIdx());      // recreate shape
                  }
            }
      }

//---------------------------------------------------------
//   spatiumChanged
//---------------------------------------------------------

void Beam::spatiumChanged(qreal oldValue, qreal newValue)
      {
      int idx = (_direction == Direction::AUTO || _direction == Direction::DOWN) ? 0 : 1;
      if (_userModified[idx]) {
            qreal diff = newValue / oldValue;
            for (BeamFragment* f : fragments) {
                  f->py1[idx] = f->py1[idx] * diff;
                  f->py2[idx] = f->py2[idx] * diff;
                  }
            }
      }

//---------------------------------------------------------
//   write
//---------------------------------------------------------

void Beam::write(XmlWriter& xml) const
      {
      if (_elements.empty())
            return;
      xml.stag(name());
      Element::writeProperties(xml);

      writeProperty(xml, Pid::STEM_DIRECTION);
      writeProperty(xml, Pid::DISTRIBUTE);
      writeProperty(xml, Pid::BEAM_NO_SLOPE);
      writeProperty(xml, Pid::GROW_LEFT);
      writeProperty(xml, Pid::GROW_RIGHT);

      int idx = (_direction == Direction::AUTO || _direction == Direction::DOWN) ? 0 : 1;
      if (_userModified[idx]) {
            qreal _spatium = spatium();
            for (BeamFragment* f : fragments) {
                  xml.stag("Fragment");
                  xml.tag("y1", f->py1[idx] / _spatium);
                  xml.tag("y2", f->py2[idx] / _spatium);
                  xml.etag();
                  }
            }

      // this info is used for regression testing
      // l1/l2 is the beam position of the layout engine
      if (MScore::testMode) {
            qreal _spatium4 = spatium() * .25;
            for (BeamFragment* f : fragments) {
                  xml.tag("l1", int(lrint(f->py1[idx] / _spatium4)));
                  xml.tag("l2", int(lrint(f->py2[idx] / _spatium4)));
                  }
            }

      xml.etag();
      }

//---------------------------------------------------------
//   read
//---------------------------------------------------------

void Beam::read(XmlReader& e)
      {
      QPointF p1, p2;
      qreal _spatium = spatium();
      while (e.readNextStartElement()) {
            const QStringRef& tag(e.name());
            if (tag == "StemDirection") {
                  setProperty(Pid::STEM_DIRECTION, Ms::getProperty(Pid::STEM_DIRECTION, e));
                  e.readNext();
                  }
            else if (tag == "distribute")
                  setDistribute(e.readInt());
            else if (readStyledProperty(e, tag))
                  ;
            else if (tag == "growLeft")
                  setGrowLeft(e.readDouble());
            else if (tag == "growRight")
                  setGrowRight(e.readDouble());
            else if (tag == "y1") {
                  if (fragments.empty())
                        fragments.append(new BeamFragment);
                  BeamFragment* f = fragments.back();
                  int idx = (_direction == Direction::AUTO || _direction == Direction::DOWN) ? 0 : 1;
                  _userModified[idx] = true;
                  f->py1[idx] = e.readDouble() * _spatium;
                  }
            else if (tag == "y2") {
                  if (fragments.empty())
                        fragments.append(new BeamFragment);
                  BeamFragment* f = fragments.back();
                  int idx = (_direction == Direction::AUTO || _direction == Direction::DOWN) ? 0 : 1;
                  _userModified[idx] = true;
                  f->py2[idx] = e.readDouble() * _spatium;
                  }
            else if (tag == "Fragment") {
                  BeamFragment* f = new BeamFragment;
                  int idx = (_direction == Direction::AUTO || _direction == Direction::DOWN) ? 0 : 1;
                  _userModified[idx] = true;
                  qreal _spatium1 = spatium();

                  while (e.readNextStartElement()) {
                        const QStringRef& tag1(e.name());
                        if (tag1 == "y1")
                              f->py1[idx] = e.readDouble() * _spatium1;
                        else if (tag1 == "y2")
                              f->py2[idx] = e.readDouble() * _spatium1;
                        else
                              e.unknown();
                        }
                  fragments.append(f);
                  }
            else if (tag == "l1" || tag == "l2")      // ignore
                  e.skipCurrentElement();
            else if (tag == "subtype")          // obsolete
                  e.skipCurrentElement();
            else if (!Element::readProperties(e))
                  e.unknown();
            }
      }

//---------------------------------------------------------
//   Beam::read300
//---------------------------------------------------------

void Beam::read300(XmlReader& e)
      {
      QPointF p1, p2;
      qreal _spatium = spatium();
      _id = e.intAttribute("id");
      while (e.readNextStartElement()) {
            const QStringRef& tag(e.name());
            if (tag == "StemDirection") {
                  setProperty(Pid::STEM_DIRECTION, Ms::getProperty(Pid::STEM_DIRECTION, e));
                  e.readNext();
                  }
            else if (tag == "distribute")
                  setDistribute(e.readInt());
            else if (readStyledProperty(e, tag))
                  ;
            else if (tag == "growLeft")
                  setGrowLeft(e.readDouble());
            else if (tag == "growRight")
                  setGrowRight(e.readDouble());
            else if (tag == "y1") {
                  if (fragments.empty())
                        fragments.append(new BeamFragment);
                  BeamFragment* f = fragments.back();
                  int idx = (_direction == Direction::AUTO || _direction == Direction::DOWN) ? 0 : 1;
                  _userModified[idx] = true;
                  f->py1[idx] = e.readDouble() * _spatium;
                  }
            else if (tag == "y2") {
                  if (fragments.empty())
                        fragments.append(new BeamFragment);
                  BeamFragment* f = fragments.back();
                  int idx = (_direction == Direction::AUTO || _direction == Direction::DOWN) ? 0 : 1;
                  _userModified[idx] = true;
                  f->py2[idx] = e.readDouble() * _spatium;
                  }
            else if (tag == "Fragment") {
                  BeamFragment* f = new BeamFragment;
                  int idx = (_direction == Direction::AUTO || _direction == Direction::DOWN) ? 0 : 1;
                  _userModified[idx] = true;
                  qreal _spatium = spatium();

                  while (e.readNextStartElement()) {
                        const QStringRef& tag(e.name());
                        if (tag == "y1")
                              f->py1[idx] = e.readDouble() * _spatium;
                        else if (tag == "y2")
                              f->py2[idx] = e.readDouble() * _spatium;
                        else
                              e.unknown();
                        }
                  fragments.append(f);
                  }
            else if (tag == "l1" || tag == "l2")      // ignore
                  e.skipCurrentElement();
            else if (tag == "subtype")          // obsolete
                  e.skipCurrentElement();
            else if (!Element::readProperties300(e))
                  e.unknown();
            }
      }

//---------------------------------------------------------
//   BeamEditData
//---------------------------------------------------------

class BeamEditData : public ElementEditData {
   public:
      int editFragment;
      };

//---------------------------------------------------------
//   editDrag
//---------------------------------------------------------

void Beam::editDrag(EditData& ed)
      {
      int idx  = (_direction == Direction::AUTO || _direction == Direction::DOWN) ? 0 : 1;
      qreal dy = ed.delta.y();
      BeamEditData* bed = static_cast<BeamEditData*>(ed.getData(this));
      BeamFragment* f = fragments[bed->editFragment];
      qreal y1 = f->py1[idx];
      qreal y2 = f->py2[idx] + dy;
      if (ed.curGrip == Grip::START)
            y1 += dy;

      qreal _spatium = spatium();
      undoChangeProperty(Pid::BEAM_POS, QPointF(y1 / _spatium, y2 / _spatium));
      undoChangeProperty(Pid::USER_MODIFIED, true);
      undoChangeProperty(Pid::GENERATED, false);

      triggerLayout();
      }

//---------------------------------------------------------
//   updateGrips
//---------------------------------------------------------

void Beam::updateGrips(EditData& ed) const
      {
      int idx = (_direction == Direction::AUTO || _direction == Direction::DOWN) ? 0 : 1;
      BeamEditData* bed = static_cast<BeamEditData*>(ed.getData(this));
      BeamFragment* f = fragments[bed->editFragment];

      ChordRest* c1 = nullptr;
      ChordRest* c2 = nullptr;
      int n = _elements.size();
      for (int i = 0; i < n; ++i) {
            if (_elements[i]->isChordRest()) {
                  c1 = toChordRest(_elements[i]);
                  break;
                  }
            }
      for (int i = n-1; i >= 0; --i) {
            if (_elements[i]->isChordRest()) {
                  c2 = toChordRest(_elements[i]);
                  break;
                  }
            }

      int y = pagePos().y();
      ed.grip[0].translate(QPointF(c1->stemPosX() + c1->pageX(), f->py1[idx] + y));
      ed.grip[1].translate(QPointF(c2->stemPosX() + c2->pageX(), f->py2[idx] + y));
      }

//---------------------------------------------------------
//   setBeamDirection
//---------------------------------------------------------

void Beam::setBeamDirection(Direction d)
      {
      _direction = d;
      if (d != Direction::AUTO)
            _up = d == Direction::UP;
      }

//---------------------------------------------------------
//   reset
//---------------------------------------------------------

void Beam::reset()
      {
      if (distribute())
            undoChangeProperty(Pid::DISTRIBUTE, false);
      if (growLeft() != 1.0)
            undoChangeProperty(Pid::GROW_LEFT, 1.0);
      if (growRight() != 1.0)
            undoChangeProperty(Pid::GROW_RIGHT, 1.0);
      if (userModified()) {
            undoChangeProperty(Pid::BEAM_POS, QVariant(beamPos()));
            undoChangeProperty(Pid::USER_MODIFIED, false);
            }
      undoChangeProperty(Pid::STEM_DIRECTION, QVariant::fromValue<Direction>(Direction::AUTO));
      resetProperty(Pid::BEAM_NO_SLOPE);
      setGenerated(true);
      }

//---------------------------------------------------------
//   startEdit
//---------------------------------------------------------

void Beam::startEdit(EditData& ed)
      {
      ed.grips   = 2;
      ed.curGrip = Grip::END;
      BeamEditData* bed = new BeamEditData();
      bed->e    = this;
      bed->editFragment = 0;
      ed.addData(bed);

      QPointF pt(ed.startMove - pagePos());
      qreal ydiff = 100000000.0;
      int idx = (_direction == Direction::AUTO || _direction == Direction::DOWN) ? 0 : 1;
      int i = 0;
      for (BeamFragment* f : fragments) {
            qreal d = fabs(f->py1[idx] - pt.y());
            if (d < ydiff) {
                  ydiff = d;
                  bed->editFragment = i;
                  }
            ++i;
            }
      }

//---------------------------------------------------------
//   endEdit
//---------------------------------------------------------

void Beam::endEdit(EditData& ed)
      {
      Element::endEdit(ed);
      }

//---------------------------------------------------------
//   triggerLayout
//---------------------------------------------------------

void Beam::triggerLayout() const
      {
      if (!_elements.empty()) {
            _elements.front()->triggerLayout();
            _elements.back()->triggerLayout();
            }
      }

//---------------------------------------------------------
//   acceptDrop
//---------------------------------------------------------

bool Beam::acceptDrop(EditData& data) const
      {
      return (data.element->type() == ElementType::ICON)
         && ((toIcon(data.element)->iconType() == IconType::FBEAM1)
         || (toIcon(data.element)->iconType() == IconType::FBEAM2));
      }

//---------------------------------------------------------
//   drop
//---------------------------------------------------------

Element* Beam::drop(EditData& data)
      {
      if (!data.element->isIcon())
            return 0;
      Icon* e = toIcon(data.element);
      qreal g1;
      qreal g2;

      if (e->iconType() == IconType::FBEAM1) {
            g1 = 1.0;
            g2 = 0.0;
            }
      else if (e->iconType() == IconType::FBEAM2) {
            g1 = 0.0;
            g2 = 1.0;
            }
      else
            return 0;
      if (g1 != growLeft())
            undoChangeProperty(Pid::GROW_LEFT, g1);
      if (g2 != growRight())
            undoChangeProperty(Pid::GROW_RIGHT, g2);
      return 0;
      }

//---------------------------------------------------------
//   beamPos
//    misuse QPointF for y1-y2 real values
//---------------------------------------------------------

QPointF Beam::beamPos() const
      {
      if (fragments.empty())
            return QPointF(0.0, 0.0);
      BeamFragment* f = fragments.back();
      int idx = (_direction == Direction::AUTO || _direction == Direction::DOWN) ? 0 : 1;
      qreal _spatium = spatium();
      return QPointF(f->py1[idx] / _spatium, f->py2[idx] / _spatium);
      }

//---------------------------------------------------------
//   setBeamPos
//---------------------------------------------------------

void Beam::setBeamPos(const QPointF& bp)
      {
      if (fragments.empty())
            fragments.append(new BeamFragment);
      BeamFragment* f = fragments.back();
      int idx = (_direction == Direction::AUTO || _direction == Direction::DOWN) ? 0 : 1;
      _userModified[idx] = true;
      setGenerated(false);
      qreal _spatium = spatium();
      f->py1[idx] = bp.x() * _spatium;
      f->py2[idx] = bp.y() * _spatium;
      }

//---------------------------------------------------------
//   userModified
//---------------------------------------------------------

bool Beam::userModified() const
      {
      int idx = (_direction == Direction::AUTO || _direction == Direction::DOWN) ? 0 : 1;
      return _userModified[idx];
      }

//---------------------------------------------------------
//   setUserModified
//---------------------------------------------------------

void Beam::setUserModified(bool val)
      {
      int idx = (_direction == Direction::AUTO || _direction == Direction::DOWN) ? 0 : 1;
      _userModified[idx] = val;
      }

//---------------------------------------------------------
//   getProperty
//---------------------------------------------------------

QVariant Beam::getProperty(Pid propertyId) const
      {
      switch (propertyId) {
            case Pid::STEM_DIRECTION: return QVariant::fromValue<Direction>(beamDirection());
            case Pid::DISTRIBUTE:     return distribute();
            case Pid::GROW_LEFT:      return growLeft();
            case Pid::GROW_RIGHT:     return growRight();
            case Pid::USER_MODIFIED:  return userModified();
            case Pid::BEAM_POS:       return beamPos();
            case Pid::BEAM_NO_SLOPE:  return noSlope();
            default:
                  return Element::getProperty(propertyId);
            }
      }

//---------------------------------------------------------
//   setProperty
//---------------------------------------------------------

bool Beam::setProperty(Pid propertyId, const QVariant& v)
      {
      switch (propertyId) {
            case Pid::STEM_DIRECTION:
                  setBeamDirection(v.value<Direction>());
                  break;
            case Pid::DISTRIBUTE:
                  setDistribute(v.toBool());
                  break;
            case Pid::GROW_LEFT:
                  setGrowLeft(v.toDouble());
                  break;
            case Pid::GROW_RIGHT:
                  setGrowRight(v.toDouble());
                  break;
            case Pid::USER_MODIFIED:
                  setUserModified(v.toBool());
                  break;
            case Pid::BEAM_POS:
                  if (userModified())
                        setBeamPos(v.toPointF());
                  break;
            case Pid::BEAM_NO_SLOPE:
                  setNoSlope(v.toBool());
                  break;
            default:
                  if (!Element::setProperty(propertyId, v))
                        return false;
                  break;
            }
      if (!_elements.empty()) {
            score()->setLayout(_elements.front()->tick());
            score()->setLayout(_elements.back()->tick());
            }
      setGenerated(false);
      return true;
      }

//---------------------------------------------------------
//   propertyDefault
//---------------------------------------------------------

QVariant Beam::propertyDefault(Pid id) const
      {
      switch (id) {
//            case Pid::SUB_STYLE:      return int(Tid::BEAM);
            case Pid::STEM_DIRECTION: return QVariant::fromValue<Direction>(Direction::AUTO);
            case Pid::DISTRIBUTE:     return false;
            case Pid::GROW_LEFT:      return 1.0;
            case Pid::GROW_RIGHT:     return 1.0;
            case Pid::USER_MODIFIED:  return false;
            case Pid::BEAM_POS:       return beamPos();
            default:                   return Element::propertyDefault(id);
            }
      }

//---------------------------------------------------------
//   shape
//---------------------------------------------------------

Shape Beam::shape() const
      {
      qreal lw2 = score()->styleP(Sid::beamWidth) * .5 * mag();
      const QLineF* bs = beamSegments.front();
      double d  = (qAbs(bs->y2() - bs->y1())) / (bs->x2() - bs->x1());
      if (beamSegments.size() > 1 && d > M_PI/6.0)
            d = M_PI/6.0;
      double ww      = lw2 / sin(M_PI_2 - atan(d));
      qreal _spatium = spatium();

      Shape shape;
      for (const QLineF* beamSegment : beamSegments) {
            qreal x = beamSegment->x1();
            qreal y = beamSegment->y1();
            qreal w = beamSegment->x2() - x;
            int n   = int(ceil(w / _spatium));
            qreal s = (beamSegment->y2() - y) / w;
            w /= n;
            for (int i = 1; i < n; ++i) {
                  qreal xx = beamSegment->x1() + i * w;
                  qreal yy = beamSegment->y1() + i * w * s;
                  if (yy > y)
                        shape.add(QRectF(x, y-ww, w, yy - y + ww*2));
                  else
                        shape.add(QRectF(x, yy-ww, w, y - yy + ww*2));
                  x = xx;
                  y = yy;
                  }
            if (y > beamSegment->y2())
                  shape.add(QRectF(x, beamSegment->y2()-ww, w, y - beamSegment->y2() + ww*2));
            else
                  shape.add(QRectF(x, y-ww, w, beamSegment->y2() - y + ww*2));
            }
      return shape;
      }

//---------------------------------------------------------
//   tick
//---------------------------------------------------------

int Beam::tick() const
      {
      return _elements.empty() ? 0 : _elements.front()->tick();
      }

//---------------------------------------------------------
//   rtick
//---------------------------------------------------------

int Beam::rtick() const
      {
      return _elements.empty() ? 0 : _elements.front()->rtick();
      }
}

