//=============================================================================
//  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
//=============================================================================

/**
 \file
 render score into event list
*/

#include <set>

#include "rendermidi.h"
#include "score.h"
#include "volta.h"
#include "note.h"
#include "glissando.h"
#include "instrument.h"
#include "part.h"
#include "chord.h"
#include "trill.h"
#include "vibrato.h"
#include "style.h"
#include "slur.h"
#include "tie.h"
#include "stafftext.h"
#include "repeat.h"
#include "articulation.h"
#include "arpeggio.h"
#include "durationtype.h"
#include "measure.h"
#include "tempo.h"
#include "repeatlist.h"
#include "velo.h"
#include "dynamic.h"
#include "navigate.h"
#include "pedal.h"
#include "staff.h"
#include "hairpin.h"
#include "bend.h"
#include "tremolo.h"
#include "noteevent.h"
#include "synthesizer/event.h"
#include "segment.h"
#include "undo.h"
#include "utils.h"
#include "sym.h"
#include "synthesizerstate.h"

namespace Ms {

    //int printNoteEventLists(NoteEventList el, int prefix, int j){
    //    int k=0;
    //    for (NoteEvent event : el) {
    //        qDebug("%d: %d: %d pitch=%d ontime=%d duration=%d",prefix, j, k, event.pitch(), event.ontime(), event.len());
    //        k++;
    //    }
    //    return 0;
    //}
    //int printNoteEventLists(QList<NoteEventList> ell, int prefix){
    //    int j=0;
    //    for (NoteEventList el : ell) {
    //        printNoteEventLists(el,prefix,j);
    //        j++;
    //    }
    //    return 0;
    //}

struct StaffRenderData {
      Fraction lastHairpinStart = Fraction(-1, 1);
      Fraction lastDynamicEnd   = Fraction(-1, 1);
      std::map<int, NPlayEvent> tempPlayEvents;
      };

bool graceNotesMerged(Chord *chord);

//---------------------------------------------------------
//   updateSwing
//---------------------------------------------------------

void Score::updateSwing()
      {
      for (Staff* s : _staves) {
            s->clearSwingList();
            }
      Measure* fm = firstMeasure();
      if (!fm)
            return;
      for (Segment* s = fm->first(SegmentType::ChordRest); s; s = s->next1(SegmentType::ChordRest)) {
            for (const Element* e : s->annotations()) {
                  if (!e->isStaffTextBase())
                        continue;
                  const StaffTextBase* st = toStaffTextBase(e);
                  if (st->xmlText().isEmpty())
                        continue;
                  Staff* staff = st->staff();
                  if (!st->swing())
                        continue;
                  SwingParameters sp;
                  sp.swingRatio = st->swingParameters()->swingRatio;
                  sp.swingUnit = st->swingParameters()->swingUnit;
                  if (st->systemFlag()) {
                        for (Staff* sta : _staves) {
                              sta->insertIntoSwingList(s->tick(),sp);
                              }
                        }
                  else
                        staff->insertIntoSwingList(s->tick(),sp);
                  }
            }
      }

//---------------------------------------------------------
//   updateCapo
//---------------------------------------------------------

void Score::updateCapo()
      {
      for (Staff* s : _staves) {
            s->clearCapoList();
            }
      Measure* fm = firstMeasure();
      if (!fm)
            return;
      for (Segment* s = fm->first(SegmentType::ChordRest); s; s = s->next1(SegmentType::ChordRest)) {
            for (const Element* e : s->annotations()) {
                  if (!e->isStaffTextBase())
                        continue;
                  const StaffTextBase* st = toStaffTextBase(e);
                  if (st->xmlText().isEmpty())
                        continue;
                  Staff* staff = st->staff();
                  if (st->capo() == 0)
                        continue;
                  staff->insertIntoCapoList(s->tick(),st->capo());
                  }
            }
      }

//---------------------------------------------------------
//   updateChannel
//---------------------------------------------------------

void Score::updateChannel()
      {
      for (Staff* s : staves()) {
            for (int i = 0; i < VOICES; ++i)
                  s->clearChannelList(i);
            }
      Measure* fm = firstMeasure();
      if (!fm)
            return;
      for (Segment* s = fm->first(SegmentType::ChordRest); s; s = s->next1(SegmentType::ChordRest)) {
            for (const Element* e : s->annotations()) {
                  if (e->isInstrumentChange()) {
                        Staff* staff = Score::staff(e->staffIdx());
                        for (int voice = 0; voice < VOICES; ++voice)
                              staff->insertIntoChannelList(voice, s->tick(), 0);
                        continue;
                        }
                  if (!e->isStaffTextBase())
                        continue;
                  const StaffTextBase* st = toStaffTextBase(e);
                  for (int voice = 0; voice < VOICES; ++voice) {
                        QString an(st->channelName(voice));
                        if (an.isEmpty())
                              continue;
                        Staff* staff = Score::staff(st->staffIdx());
                        int a = staff->part()->instrument(s->tick())->channelIdx(an);
                        if (a != -1)
                              staff->insertIntoChannelList(voice, s->tick(), a);
                        }
                  }
            }

      for (auto it = spanner().cbegin(); it != spanner().cend(); ++it) {
            Spanner* spanner = (*it).second;
            if (!spanner->isVolta())
                  continue;
            Volta* volta = toVolta(spanner);
            volta->setChannel();
            }

      for (Segment* s = fm->first(SegmentType::ChordRest); s; s = s->next1(SegmentType::ChordRest)) {
            for (Staff* st : staves()) {
                  int strack = st->idx() * VOICES;
                  int etrack = strack + VOICES;
                  for (int track = strack; track < etrack; ++track) {
                        if (!s->element(track))
                              continue;
                        Element* e = s->element(track);
                        if (e->type() != ElementType::CHORD)
                              continue;
                        Chord* c = toChord(e);
                        int channel = st->channel(c->tick(), c->voice());
                        Instrument* instr = c->part()->instrument(c->tick());
                        if (channel >= instr->channel().size()) {
                              qDebug() << "Channel " << channel << " too high. Max " << instr->channel().size();
                              channel = 0;
                              }
                        for (Note* note : c->notes()) {
                              if (note->hidden())
                                    continue;
                              if (note->tieBack())
                                    continue;
                              note->setSubchannel(channel);
                              }
                        }
                  }
            }
      }

//---------------------------------------------------------
//   playNote
//---------------------------------------------------------

static void playNote(EventMap* events, const Note* note, int channel, int pitch,
   int velo, int onTime, int offTime, int staffIdx)
      {
      if (!note->play())
            return;
      velo = note->customizeVelocity(velo);
      NPlayEvent ev(ME_NOTEON, channel, pitch, velo);
      ev.setOriginatingStaff(staffIdx);
      ev.setTuning(note->tuning());
      ev.setNote(note);
      if (offTime < onTime)
            offTime = onTime;
      events->insert(std::pair<int, NPlayEvent>(onTime, ev));
      ev.setVelo(0);
      events->insert(std::pair<int, NPlayEvent>(offTime, ev));
      }

//---------------------------------------------------------
//   collectNote
//---------------------------------------------------------

static void collectNote(EventMap* events, int channel, const Note* note, int velo, int tickOffset, int staffIdx)
      {
      if (!note->play() || note->hidden())      // do not play overlapping notes
            return;
      Chord* chord = note->chord();

      int ticks;
      int tieLen = 0;
      if (chord->isGrace()) {
            Q_ASSERT( !graceNotesMerged(chord)); // this function should not be called on a grace note if grace notes are merged
            chord = toChord(chord->parent());
            ticks = chord->actualTicks().ticks(); // ticks of the parent note
            tieLen = 0;
            }
      else {
            ticks = chord->actualTicks().ticks(); // ticks of the actual note
            // calculate additional length due to ties forward
            // taking NoteEvent length adjustments into account
            // but stopping at any note with multiple NoteEvents
            // and processing those notes recursively
            if (note->tieFor()) {
                  Note* n = note->tieFor()->endNote();
                  while (n) {
                        NoteEventList nel = n->playEvents();
                        if (nel.size() == 1) {
                              // add value of this note to main note
                              // if we wish to suppress first note of ornament,
                              // then do this regardless of number of NoteEvents
                              tieLen += (n->chord()->actualTicks().ticks() * (nel[0].len())) / 1000;
                              }
                        else {
                              // recurse
                              collectNote(events, channel, n, velo, tickOffset, staffIdx);
                              break;
                              }
                        if (n->tieFor() && n != n->tieFor()->endNote())
                              n = n->tieFor()->endNote();
                        else
                              break;
                        }
                  }
            }

      int tick1    = chord->tick().ticks() + tickOffset;
      bool tieFor  = note->tieFor();
      bool tieBack = note->tieBack();

      NoteEventList nel = note->playEvents();
      int nels = nel.size();
      for (int i = 0, pitch = note->ppitch(); i < nels; ++i) {
            const NoteEvent& e = nel[i]; // we make an explicit const ref, not a const copy.  no need to copy as we won't change the original object.

            // skip if note has a tie into it and only one NoteEvent
            // its length was already added to previous note
            // if we wish to suppress first note of ornament
            // then change "nels == 1" to "i == 0", and change "break" to "continue"
            if (tieBack && nels == 1)
                  break;
            int p = pitch + e.pitch();
            if (p < 0)
                  p = 0;
            else if (p > 127)
                  p = 127;
            int on  = tick1 + (ticks * e.ontime())/1000;
            int off = on + (ticks * e.len())/1000 - 1;
            if (tieFor && i == nels - 1)
                  off += tieLen;
            playNote(events, note, channel, p, velo, on, off, staffIdx);
            }

      // Bends
      for (Element* e : note->el()) {
            if (e == 0 || e->type() != ElementType::BEND)
                  continue;
            Bend* bend = toBend(e);
            if (!bend->playBend())
                  break;
            const QList<PitchValue>& points = bend->points();
            int pitchSize = points.size();

            double noteLen = note->playTicks();
            int lastPointTick = tick1;
            for (int pitchIndex = 0; pitchIndex < pitchSize-1; pitchIndex++) {
                  PitchValue pitchValue = points[pitchIndex];
                  PitchValue nextPitch  = points[pitchIndex+1];
                  int nextPointTick = tick1 + nextPitch.time / 60.0 * noteLen;
                  int pitch = pitchValue.pitch;

                  if (pitchIndex == 0 && (pitch == nextPitch.pitch)) {
                        int midiPitch = (pitch * 16384) / 1200 + 8192;
                        int msb = midiPitch / 128;
                        int lsb = midiPitch % 128;
                        NPlayEvent ev(ME_PITCHBEND, channel, lsb, msb);
                        ev.setOriginatingStaff(staffIdx);
                        events->insert(std::pair<int, NPlayEvent>(lastPointTick, ev));
                        lastPointTick = nextPointTick;
                        continue;
                        }
                  if (pitch == nextPitch.pitch && !(pitchIndex == 0 && pitch != 0)) {
                        lastPointTick = nextPointTick;
                        continue;
                        }

                  double pitchDelta = nextPitch.pitch - pitch;
                  double tickDelta  = nextPitch.time - pitchValue.time;
                  /*         B
                            /.                   pitch is 1/100 semitones
                    bend   / .  pitchDelta       time is in noteDuration/60
                          /  .                   midi pitch is 12/16384 semitones
                         A....
                       tickDelta   */
                  for (int i = lastPointTick; i <= nextPointTick; i += 16) {
                        double dx = ((i-lastPointTick) * 60) / noteLen;
                        int p = pitch + dx * pitchDelta / tickDelta;

                        // We don't support negative pitch, but Midi does. Let's center by adding 8192.
                        int midiPitch = (p * 16384) / 1200 + 8192;
                        // Representing pitch as two bytes
                        int msb = midiPitch / 128;
                        int lsb = midiPitch % 128;
                        NPlayEvent ev(ME_PITCHBEND, channel, lsb, msb);
                        ev.setOriginatingStaff(staffIdx);
                        events->insert(std::pair<int, NPlayEvent>(i, ev));
                        }
                  lastPointTick = nextPointTick;
                  }
            NPlayEvent ev(ME_PITCHBEND, channel, 0, 64); // 0:64 is 8192 - no pitch bend
            ev.setOriginatingStaff(staffIdx);
            events->insert(std::pair<int, NPlayEvent>(tick1+int(noteLen), ev));
            }
      }

//---------------------------------------------------------
//   aeolusSetStop
//---------------------------------------------------------

static void aeolusSetStop(int tick, int channel, int i, int k, bool val, EventMap* events)
      {
      NPlayEvent event;
      event.setType(ME_CONTROLLER);
      event.setController(98);
      if (val)
            event.setValue(0x40 + 0x20  + i);
      else
            event.setValue(0x40 + 0x10  + i);

      event.setChannel(channel);
      events->insert(std::pair<int,NPlayEvent>(tick, event));

      event.setValue(k);
      events->insert(std::pair<int,NPlayEvent>(tick, event));
//      event.setValue(0x40 + i);
//      events->insert(std::pair<int,NPlayEvent>(tick, event));
      }

//---------------------------------------------------------
//   collectProgramChanges
//---------------------------------------------------------

static void collectProgramChanges(EventMap* events, Measure* m, Staff* staff, int tickOffset)
      {
      int firstStaffIdx = staff->idx();
      int nextStaffIdx  = firstStaffIdx + 1;

      //
      // collect program changes and controller
      //
      for (Segment* s = m->first(SegmentType::ChordRest); s; s = s->next(SegmentType::ChordRest)) {
            for (Element* e : s->annotations()) {
                  if (!e->isStaffTextBase() || e->staffIdx() < firstStaffIdx || e->staffIdx() >= nextStaffIdx)
                        continue;
                  const StaffTextBase* st1 = toStaffTextBase(e);
                  Fraction tick = s->tick() + Fraction::fromTicks(tickOffset);

                  Instrument* instr = e->part()->instrument(tick);
                  for (const ChannelActions& ca : *st1->channelActions()) {
                        int channel = instr->channel().at(ca.channel)->channel();
                        for (const QString& ma : ca.midiActionNames) {
                              NamedEventList* nel = instr->midiAction(ma, ca.channel);
                              if (!nel)
                                    continue;
                              for (MidiCoreEvent event : nel->events) {
                                    event.setChannel(channel);
                                    NPlayEvent e1(event);
                                    e1.setOriginatingStaff(firstStaffIdx);
                                    if (e1.dataA() == CTRL_PROGRAM)
                                          events->insert(std::pair<int, NPlayEvent>(tick.ticks()-1, e1));
                                    else
                                          events->insert(std::pair<int, NPlayEvent>(tick.ticks(), e1));
                                    }
                              }
                        }
                  if (st1->setAeolusStops()) {
                        Staff* s1 = st1->staff();
                        int voice   = 0;
                        int channel = s1->channel(tick, voice);

                        for (int i = 0; i < 4; ++i) {
                              static int num[4] = { 12, 13, 16, 16 };
                              for (int k = 0; k < num[i]; ++k)
                                    aeolusSetStop(tick.ticks(), channel, i, k, st1->getAeolusStop(i, k), events);
                              }
                        }
                  }
            }
      }

//---------------------------------------------------------
//   getControllerFromCC
//---------------------------------------------------------

static int getControllerFromCC(int cc)
      {
      int controller = -1;

      switch (cc) {
            case 1:
                  controller = CTRL_MODULATION;
                  break;
            case 2:
                  controller = CTRL_BREATH;
                  break;
            case 4:
                  controller = CTRL_FOOT;
                  break;
            case 11:
                  controller = CTRL_EXPRESSION;
                  break;
            default:
                  break;
            }

      return controller;
      }

//---------------------------------------------------------
//   collectMeasureEventsSimple
//    the original, velocity-only method of collecting events.
//---------------------------------------------------------

static void collectMeasureEventsSimple(EventMap* events, Measure* m, Staff* staff, int tickOffset)
      {
      int firstStaffIdx = staff->idx();
      int nextStaffIdx  = firstStaffIdx + 1;

      SegmentType st = SegmentType::ChordRest;
      int strack = firstStaffIdx * VOICES;
      int etrack = nextStaffIdx * VOICES;

      for (Segment* seg = m->first(st); seg; seg = seg->next(st)) {
            int tick = seg->tick().ticks();
            for (int track = strack; track < etrack; ++track) {
                  // skip linked staves, except primary
                  if (!m->score()->staff(track / VOICES)->primaryStaff()) {
                        track += VOICES-1;
                        continue;
                        }
                  Element* cr = seg->element(track);
                  if (cr == 0 || cr->type() != ElementType::CHORD)
                        continue;

                  Chord* chord = toChord(cr);
                  Staff* st1   = chord->staff();
                  int staffIdx = st1->idx();
                  int velocity = st1->velocities().velo(seg->tick().ticks());
                  Instrument* instr = chord->part()->instrument(Fraction::fromTicks(tick));
                  int channel = instr->channel(chord->upNote()->subchannel())->channel();
                  events->registerChannel(channel);

                  for (Articulation* a : chord->articulations())
                        instr->updateVelocity(&velocity,channel, a->articulationName());

                  if ( !graceNotesMerged(chord))
                      for (Chord* c : chord->graceNotesBefore())
                          for (const Note* note : c->notes())
                              collectNote(events, channel, note, velocity, tickOffset, staffIdx);

                  for (const Note* note : chord->notes())
                        collectNote(events, channel, note, velocity, tickOffset, staffIdx);

                  if ( !graceNotesMerged(chord))
                      for (Chord* c : chord->graceNotesAfter())
                          for (const Note* note : c->notes())
                              collectNote(events, channel, note, velocity, tickOffset, staffIdx);
                 }
            }
      }

//---------------------------------------------------------
//   changeCCBetween
//    since we're adding events, pass ticks as ints rather than fractions
//---------------------------------------------------------

static void changeCCBetween(std::map<int, NPlayEvent>& tempEvents, int stick, int etick, int startExpr, int endExpr, int channel, int controller, VeloChangeMethod changeMethod, int tickOffset, int originatingStaff)
      {
      // Prevent zero-division error, but add single event
      if (startExpr == endExpr || stick == etick) {
            int tickToUse = stick + tickOffset;
            if (tempEvents.find(tickToUse) != tempEvents.end()) {
                  // Don't add a play event if it would be quieter than the current one at
                  // this tick
                  if (tempEvents[tickToUse].velo() >= abs(startExpr))
                        return;
                  }

            tempEvents[tickToUse] = NPlayEvent(ME_CONTROLLER, channel, controller, abs(startExpr));
            return;
            }

      // Ticks to change expression over
      int exprTicks = etick - stick;
      int exprDiff = endExpr - startExpr;

      // The lambdas are in the format:
      //    func(precalculated values, current tick)
      // The precalculated values are for values that stay constant, involving
      // total ticks or total expression. Although it seems messy,
      // it saves some 13% processing time.

      // NOTE:JT - hardcoded, todo change?
      int tickInc = 1;

      // See these functions graphically at: https://www.desmos.com/calculator/kk89ficmjk
      std::function<int(std::vector<double>&, int)> valueFunction;
      std::vector<double> preCalculated;
      switch (changeMethod) {
            case VeloChangeMethod::EXPONENTIAL:
                  // Due to the nth-root, exponential functions do not flip with negative values, and cause errors,
                  // so treat it as a piecewise function.
                  if (exprDiff > 0) {
                        preCalculated.push_back(
                              pow((exprDiff + 1), 1.0 / double(exprTicks))    // the exprTicks root of d+1
                              );
                        valueFunction = [](std::vector<double>& pc, int ct) { return int(
                              pow(
                                    pc[0],
                                    double(ct)        // to the power of the current tick (exponential)
                                    ) - 1
                              ); };
                        }
                  else {
                        preCalculated.push_back(
                              pow((-exprDiff + 1), 1.0 / double(exprTicks))    // the exprTicks root of 1-d
                              );
                        valueFunction = [](std::vector<double>& pc, int ct) { return -int(
                              pow(
                                    pc[0],
                                    double(ct)        // again to the power of ct
                                    ) + 1
                              ); };
                        }
                  break;
            // Uses sin x transformed, which _does_ flip with negative numbers
            case VeloChangeMethod::EASE_IN_OUT:
                  preCalculated.push_back(double(exprDiff) / 2.0);
                  preCalculated.push_back(double(M_PI / double(exprTicks)));
                  preCalculated.push_back(double(M_PI / 2.0));
                  valueFunction = [](std::vector<double>& pc, int ct) { return int(
                        pc[0] * (
                              sin(
                                    double(ct) * (
                                          pc[1]
                                          ) - pc[2]
                                    ) + 1
                              )
                        ); };
                  break;
            case VeloChangeMethod::EASE_IN:
                  preCalculated.push_back(double(exprDiff));
                  preCalculated.push_back(double(exprTicks));
                  preCalculated.push_back(double(M_PI / double(2 * exprTicks)));
                  valueFunction = [](std::vector<double>& pc, int ct) { return int(
                        pc[0] * (
                              sin(
                                    double(ct - pc[1]) * (
                                          pc[2]
                                          )
                                    ) + 1
                              )
                        ); };
                  break;
            case VeloChangeMethod::EASE_OUT:
                  preCalculated.push_back(double(exprDiff));
                  preCalculated.push_back(double(M_PI / double(2 * exprTicks)));
                  valueFunction = [](std::vector<double>& pc, int ct) { return int(
                        pc[0] * sin(
                              double(ct) * (
                                    pc[1]
                                    )
                              )
                        ); };
                  break;
            case VeloChangeMethod::NORMAL:
            default:
                  // We can calculate how to increase the ticks, since it is linear
                  tickInc = exprTicks / abs(exprDiff);
                  preCalculated.push_back(double(exprDiff));
                  preCalculated.push_back(double(exprTicks));
                  valueFunction = [](std::vector<double>& pc, int ct) { return int(pc[0] * (double(ct) / pc[1])); };
                  break;
            }

      // prevent possible infinite loop
      if (tickInc < 1)
            tickInc = 1;

      int lastVal = -1;
      for (int i = stick; i < etick; i += tickInc) {
            int valueToAdd = valueFunction(preCalculated, i - stick);
            if (lastVal == valueToAdd)
                  continue;
            
            int exprVal = startExpr + valueToAdd;
            if (tempEvents.find(i + tickOffset) != tempEvents.end()) {
                  // Don't add a play event if it would be quieter than the current one at this tick
                  if (tempEvents[i + tickOffset].velo() >= abs(exprVal))
                        continue;
                  }

            lastVal = valueToAdd;
            NPlayEvent event = NPlayEvent(ME_CONTROLLER, channel, controller, abs(exprVal));
            event.setOriginatingStaff(originatingStaff);
            tempEvents[i + tickOffset] = event;
            }
      }

//---------------------------------------------------------
//   collectMeasureEventsDefault
//    this uses only CC events to control note velocity, and sets the
//    note-on velocity to always be 127 (max). This is the method that allows
//    single note dynamics, but only works if the soundfont supports it.
//    Method is one of:
//          FIXED_MAX - default: velocity is fixed at 127
//          SEG_START - note-on velocity is the same as the start velocity of the seg
//---------------------------------------------------------

static void collectMeasureEventsDefault(EventMap* events, Measure* m, Staff* staff, StaffRenderData& renderData, int tickOffset, DynamicsRenderMethod method, int cc)
      {
      int controller = getControllerFromCC(cc);
      
      if (controller == -1) {
            qWarning("controller for CC %d not valid", cc);
            return;
            }

      int firstStaffIdx = staff->idx();
      int nextStaffIdx  = firstStaffIdx + 1;

      SegmentType st = SegmentType::ChordRest;
      int strack = firstStaffIdx * VOICES;
      int etrack = nextStaffIdx * VOICES;

      static const VeloChangeMethod defaultChangeMethod = VeloChangeMethod::NORMAL;

      int lastSubchannel = -1;
      for (Segment* seg = m->first(st); seg; seg = seg->next(st)) {
            Fraction tick = seg->tick();
            Fraction tick2 = tick + seg->ticks();

            for (int track = strack; track < etrack; ++track) {
                  // Skip linked staves, except primary
                  Staff* st1 = m->score()->staff(track / VOICES);
                  if (!st1->primaryStaff()) {
                        track += VOICES - 1;
                        continue;
                        }
                        
                  Chord* chord = nullptr;
                  Element* cr = seg->element(track);
                  if (cr != 0) {
                        if (cr->isChord())
                              chord = toChord(cr);
                        }

                  // We need to be able to add CC events even if there isn't a note,
                  // since in multi-stave scores, some segments will have no notes in
                  // them for a stave, despite a note being held. So, if there isn't
                  // a chord, at least try to add CC events.
                  if (!chord && !seg->isChordRestType())
                        continue;

                  int staffIdx = st1->idx();
                  int velocity = st1->velocities().velo(tick.ticks());

                  Instrument* instr = st1->part()->instrument(tick);
                  int channel;
                  if (chord != 0)
                        lastSubchannel = chord->upNote()->subchannel();

                  // This is a slightly hacky way of always getting a channel, no matter
                  // if there are notes or not.
                  if (lastSubchannel != -1)
                        channel = instr->channel(lastSubchannel)->channel();
                  else
                        continue;

                  events->registerChannel(channel);

                  // 
                  // Decide whether to add CC events or not
                  //

                  if (instr->singleNoteDynamics()) {
                        Fraction hairpinStartTick;
                        Fraction hairpinStopTick;
                        bool hasHairpin = false;
                        
                        bool singleNoteDynamics = false;
                        Hairpin* h = nullptr;
                        VeloChangeMethod changeMethod = VeloChangeMethod::NORMAL;

                        // This flag is used to decide whether to add a static velocity event or none at all,
                        // depending on whether we're in a hairpin/changing dynamic or not.
                        bool doAddStaticVel = true;

                        // Check for hairpin crossing segment
                        for (auto it : staff->score()->spannerMap().findOverlapping(tick.ticks(), tick2.ticks()-1)) {
                              Spanner* s = it.value;
                              if (it.stop == tick.ticks())
                                    continue;

                              // Don't playback dynamic if this hairpin started before or at the same time as the last one
                              // processed (i.e. if it is the same hairpin)
                              if (it.start + tickOffset <= renderData.lastHairpinStart.ticks()) {
                                    doAddStaticVel = false;
                                    continue;
                                    }

                              if (s->isHairpin()) {
                                    h = toHairpin(s);
                                    switch (h->dynRange()) {
                                          case Dynamic::Range::STAFF:
                                                if (h->staff() != st1)
                                                      continue;
                                                break;
                                          case Dynamic::Range::PART:
                                                if (h->part() != st1->part())
                                                      continue;
                                                break;
                                          case Dynamic::Range::SYSTEM:
                                          default:
                                                break;
                                          }

                                    singleNoteDynamics = h->singleNoteDynamics() || singleNoteDynamics;
                                    if (singleNoteDynamics) {
                                          hairpinStartTick = Fraction::fromTicks(it.start);
                                          hairpinStopTick = Fraction::fromTicks(it.stop);
                                          hasHairpin = true;
                                          changeMethod = h->veloChangeMethod();
                                          break;
                                          }
                                    }
                              }

                        // From this, work out a start and end tick to apply CC events for
                        Fraction stick;
                        Fraction etick;
                        Fraction fracTickOffset = Fraction::fromTicks(tickOffset);
                        if (hasHairpin) {
                              stick = hairpinStartTick;
                              etick = hairpinStopTick;

                              // Correct for a changing dynamic that may still be finishing
                              if (renderData.lastDynamicEnd >= etick + fracTickOffset) {
                                    singleNoteDynamics = false;
                                    }
                              else if (renderData.lastDynamicEnd > stick + fracTickOffset) {
                                    stick = renderData.lastDynamicEnd;
                                    }
                              }
                        else {
                              stick = Fraction(0, 1);
                              etick = seg->tick() + seg->ticks();
                              }

                        if (stick < seg->tick())
                              stick = seg->tick();

                        // Make sure we don't add a static dynamic event in the middle of a changing dynamic
                        if (renderData.lastDynamicEnd >= stick + fracTickOffset)
                              doAddStaticVel = false;

                        // Check if there is a fortepiano / similar dynamic
                        bool hasChangingDynamic = false;
                        Dynamic* changingDyn = nullptr;
                        if (chord != 0) {
                              for (Element* e : seg->annotations()) {
                                    if (!e)
                                          continue;
                                    if (!e->isDynamic())
                                          continue;
                                    Dynamic* d = toDynamic(e);
                                    if (d->changeInVelocity() == 0)
                                          continue;

                                    switch (d->dynRange()) {
                                          case Dynamic::Range::STAFF:
                                                if (d->staff()->idx() != staffIdx)
                                                      continue;
                                                break;
                                          case Dynamic::Range::PART:
                                                if (d->part() != chord->part())
                                                      continue;
                                                break;
                                          case Dynamic::Range::SYSTEM:
                                          default:
                                                break;
                                          }

                                    hasChangingDynamic = true;
                                    changingDyn = d;
                                    }
                              }

                        // We have a start and end tick, so get the velocities at these points
                        int velocityStart = staff->velocities().velo(stick.ticks());
                        int velocityMiddle = hasChangingDynamic ? velocityStart + changingDyn->changeInVelocity() : -1;
                        int velocityEnd = staff->velocities().velo(etick.ticks() - 1);

                        // Attempt to fix invalid hairpin
                        if (hasHairpin) {
                              int hairpinStartVel = (velocityMiddle == -1) ? velocityStart : velocityMiddle;
                              if (h->isCrescendo() && hairpinStartVel > velocityEnd)
                                    singleNoteDynamics = false;
                              else if (h->isDecrescendo() && hairpinStartVel < velocityEnd)
                                    singleNoteDynamics = false;
                              }

                        // Check for articulations
                        bool hasArticulations = false;
                        if (chord)
                              hasArticulations = chord->articulations().count() > 0;

                        //
                        // Add CC events
                        //

                        if (singleNoteDynamics || hasArticulations || hasChangingDynamic) {
                              if (chord != 0 && hasArticulations) {
                                    for (Articulation* a : chord->articulations()) {
                                          if (velocityMiddle == -1)
                                                velocityMiddle = velocityStart;
                                          instr->updateVelocity(&velocityStart, channel, a->articulationName());
                                          }
                                    }

                              if (hasHairpin && singleNoteDynamics)
                                    renderData.lastHairpinStart = hairpinStartTick + Fraction::fromTicks(tickOffset);

                              if (hasArticulations || hasChangingDynamic) {
                                    int startExpr = velocityStart;
                                    int endExpr = velocityMiddle;

                                    Fraction accentTicks = Fraction(1, 16);
                                    if (hasChangingDynamic) {
                                          accentTicks = changingDyn->velocityChangeLength();
                                          renderData.lastDynamicEnd = stick + accentTicks + fracTickOffset;
                                          }

                                    // Determine how long to 'hold' the initial velocity
                                    // This is shorter with a dynamic than an articulation
                                    // Also, since we're about to add CC events, we can use int ticks instead of fractions
                                    int stickToUse = stick.ticks() + accentTicks.ticks() / (hasChangingDynamic ? 4 : 2);
                                    int etickToUse = stick.ticks() + accentTicks.ticks();

                                    // First, add an initial accent velocity
                                    // stick is the seg start tick, stickToUse is where we should dim to the rest velocity
                                    changeCCBetween(renderData.tempPlayEvents, stick.ticks(), stickToUse, startExpr, startExpr, channel, controller, defaultChangeMethod, tickOffset, staffIdx);

                                    // Then dimenuendo back down to normal
                                    // eticktouse is the end of the dim back to normal for an accent,
                                    // but etick is the segment end tick.
                                    changeCCBetween(renderData.tempPlayEvents, stickToUse, etickToUse, startExpr, endExpr, channel, controller, defaultChangeMethod, tickOffset, staffIdx);

                                    // if there's a cresc or dim after the dynamic, apply it
                                    if (singleNoteDynamics && hasHairpin) {
                                          startExpr = velocityMiddle;
                                          endExpr = velocityEnd;

                                          stickToUse = qMin(stick.ticks() + accentTicks.ticks() + 1, etick.ticks());

                                          changeCCBetween(renderData.tempPlayEvents, stickToUse, etick.ticks(), startExpr, endExpr, channel, controller, changeMethod, tickOffset, staffIdx);
                                          }
                                    }
                              else {
                                    int startExpr = velocityStart;
                                    int endExpr = velocityEnd;
                                    changeCCBetween(renderData.tempPlayEvents, stick.ticks(), etick.ticks(), startExpr, endExpr, channel, controller, changeMethod, tickOffset, staffIdx);
                                    }
                              }
                        else if (doAddStaticVel) {
                              // Add a single expression value to match the velocity, since there is no hairpin
                              int exprVal = velocityStart;
                              int staticTick = seg->tick().ticks();
                              changeCCBetween(renderData.tempPlayEvents, staticTick, staticTick, exprVal, exprVal, channel, controller, defaultChangeMethod, tickOffset, staffIdx);
                              }
                        velocity = velocityStart; // update the velocity value that will be used in note events
                        } // if instr->singleNoteDynamics()
                  else {
                        if (chord != 0) {
                              for (Articulation* a : chord->articulations())
                                    instr->updateVelocity(&velocity, channel, a->articulationName());
                              }
                        // Add a single expression value to match the velocity, since this instrument should
                        // not use single note dynamics.
                        int staticTick = seg->tick().ticks();
                        changeCCBetween(renderData.tempPlayEvents, staticTick, staticTick, velocity, velocity, channel, controller, defaultChangeMethod, tickOffset, staffIdx);
                        }

                  //
                  // Add normal note events
                  //

                  if (chord != 0) {
                        int velocityToUse = 0;
                        switch (method) {
                              case DynamicsRenderMethod::FIXED_MAX:
                                    velocityToUse = 127;
                                    break;
                              case DynamicsRenderMethod::SEG_START:
                              default:
                                    velocityToUse = velocity;
                                    break;
                              }

                        if (!graceNotesMerged(chord))
                              for (Chord* c : chord->graceNotesBefore())
                                    for (const Note* note : c->notes())
                                          collectNote(events, channel, note, velocityToUse, tickOffset, staffIdx);

                        for (const Note* note : chord->notes())
                              collectNote(events, channel, note, velocityToUse, tickOffset, staffIdx);

                        if (!graceNotesMerged(chord))
                              for (Chord* c : chord->graceNotesAfter())
                                    for (const Note* note : c->notes())
                                          collectNote(events, channel, note, velocityToUse, tickOffset, staffIdx);
                        }
                  }
            }
      }

//---------------------------------------------------------
//   collectMeasureEvents
//    redirects to the correct function based on the passed method
//---------------------------------------------------------

static void collectMeasureEvents(EventMap* events, Measure* m, Staff* staff, StaffRenderData& renderData, int tickOffset, DynamicsRenderMethod method, int cc)
      {
      switch (method) {
            case DynamicsRenderMethod::SIMPLE:
                  collectMeasureEventsSimple(events, m, staff, tickOffset);
                  break;
            case DynamicsRenderMethod::SEG_START:
            case DynamicsRenderMethod::FIXED_MAX:
                  collectMeasureEventsDefault(events, m, staff, renderData, tickOffset, method, cc);
                  break;
            default:
                  qWarning("Unrecognized dynamics method: %d", int(method));
                  break;
            }
      
      collectProgramChanges(events, m, staff, tickOffset);
      }

//---------------------------------------------------------
//   updateHairpin
//---------------------------------------------------------

void Score::updateHairpin(Hairpin* h)
      {
      Staff* st = h->staff();
      Fraction tick  = h->tick();

      // Find any changing dynamics
      // If there are any, then start the hairpin from after them
      Segment* seg   = h->startSegment();
      if (seg) {
            for (Element* e : seg->annotations()) {
                  if (!e)
                        continue;
                  if (!e->isDynamic())
                        continue;
                  Dynamic* d = toDynamic(e);
                  if (d->changeInVelocity() == 0)
                        continue;

                  switch (d->dynRange()) {
                        case Dynamic::Range::STAFF:
                              if (d->staff()->idx() != st->idx())
                                    continue;
                              break;
                        case Dynamic::Range::PART:
                              if (d->part() != h->part())
                                    continue;
                              break;
                        case Dynamic::Range::SYSTEM:
                        default:
                              break;
                        }

                  // start hairpin after the dynamic stops
                  tick = seg->tick() + d->velocityChangeLength();
                  break;
                  }
            }

      int velo  = st->velocities().velo(tick.ticks());
      int incr  = h->veloChange();
      Fraction tick2 = h->tick2();
      if (tick > tick2)
            tick = tick2;

      //
      // If velocity increase/decrease is zero, then assume
      // the end velocity is taken from the next velocity
      // event (the next dynamics symbol after the hairpin).
      //

      int endVelo = velo;
      if (h->isCrescendo()) {
            if (incr == 0 && velo < st->velocities().nextVelo(tick2.ticks()-1))
                  endVelo = st->velocities().nextVelo(tick2.ticks()-1);
            else
                  endVelo += incr;
            }
      else {
            if (incr == 0 && velo > st->velocities().nextVelo(tick2.ticks()-1))
                  endVelo = st->velocities().nextVelo(tick2.ticks()-1);
            else
                  endVelo -= incr;
            }

      if (endVelo > 127)
            endVelo = 127;
      else if (endVelo < 1)
            endVelo = 1;

      switch (h->dynRange()) {
            case Dynamic::Range::STAFF:
                  st->velocities().setVelo(tick.ticks(),  VeloEvent(VeloType::RAMP, velo));
                  st->velocities().setVelo(tick2.ticks()-1, VeloEvent(VeloType::FIX, endVelo));
                  break;
            case Dynamic::Range::PART:
                  for (Staff* s : *st->part()->staves()) {
                        s->velocities().setVelo(tick.ticks(),  VeloEvent(VeloType::RAMP, velo));
                        s->velocities().setVelo(tick2.ticks()-1, VeloEvent(VeloType::FIX, endVelo));
                        }
                  break;
            case Dynamic::Range::SYSTEM:
                  for (Staff* s : _staves) {
                        s->velocities().setVelo(tick.ticks(),  VeloEvent(VeloType::RAMP, velo));
                        s->velocities().setVelo(tick2.ticks()-1, VeloEvent(VeloType::FIX, endVelo));
                        }
                  break;
            }
      }

//---------------------------------------------------------
//   removeHairpin
//---------------------------------------------------------

void Score::removeHairpin(Hairpin* h)
      {
      Staff* st = h->staff();
      int tick  = h->tick().ticks();
      int tick2 = h->tick2().ticks() - 1;

      switch (h->dynRange()) {
            case Dynamic::Range::STAFF:
                  st->velocities().remove(tick);
                  st->velocities().remove(tick2);
                  break;
            case Dynamic::Range::PART:
                  for (Staff* s : *st->part()->staves()) {
                        s->velocities().remove(tick);
                        s->velocities().remove(tick2);
                        }
                  break;
            case Dynamic::Range::SYSTEM:
                  for (Staff* s : _staves) {
                        s->velocities().remove(tick);
                        s->velocities().remove(tick2);
                        }
                  break;
            }
      }

//---------------------------------------------------------
//   updateVelo
//    calculate velocity for all notes
//---------------------------------------------------------

void Score::updateVelo()
      {
      //
      //    collect Dynamics
      //
      if (!firstMeasure())
            return;

      for (Staff* st : _staves) {
            VeloList& velo = st->velocities();
            velo.clear();
            velo.setVelo(0, 80);
            }
      for (int staffIdx = 0; staffIdx < nstaves(); ++staffIdx) {
            Staff* st      = staff(staffIdx);
            VeloList& velo = st->velocities();
            Part* prt      = st->part();
            int partStaves = prt->nstaves();
            int partStaff  = Score::staffIdx(prt);

            for (Segment* s = firstMeasure()->first(); s; s = s->next1()) {
                  Fraction tick = s->tick();
                  for (const Element* e : s->annotations()) {
                        if (e->staffIdx() != staffIdx)
                              continue;
                        if (e->type() != ElementType::DYNAMIC)
                              continue;
                        const Dynamic* d = toDynamic(e);
                        int v            = d->velocity();

                        // treat an invalid dynamic as no change, i.e. a dynamic set to 0
                        if (v < 1)
                              continue;

                        v = qBound(1, v, 127);     //  illegal values

                        // If a dynamic has 'velocity change' update its ending
                        int v2 = 0;
                        if (d->changeInVelocity() != 0) {
                              v2 = d->velocity() + d->changeInVelocity();
                              v2 = qBound(1, v2, 127);     //  illegal values
                              }

                        int dStaffIdx = d->staffIdx();
                        switch(d->dynRange()) {
                              case Dynamic::Range::STAFF:
                                    if (dStaffIdx == staffIdx) {
                                          velo.setVelo(tick.ticks(), v);

                                          // Set the second dynamic point of changing dynamic immediately after
                                          // to make sure that hairpins start from the correct dynamic if they
                                          // overlap the dynamic velocity change
                                          if (v2 > 0)
                                                velo.setVelo(tick.ticks() + 1, v2);
                                          }
                                    break;
                              case Dynamic::Range::PART:
                                    if (dStaffIdx >= partStaff && dStaffIdx < partStaff+partStaves) {
                                          for (int i = partStaff; i < partStaff+partStaves; ++i) {
                                                staff(i)->velocities().setVelo(tick.ticks(), v);
                                                if (v2 > 0)
                                                      staff(i)->velocities().setVelo(tick.ticks() + 1, v2);
                                                }
                                          }
                                    break;
                              case Dynamic::Range::SYSTEM:
                                    for (int i = 0; i < nstaves(); ++i) {
                                          staff(i)->velocities().setVelo(tick.ticks(), v);
                                          if (v2 > 0)
                                                staff(i)->velocities().setVelo(tick.ticks() + 1, v2);
                                          }
                                    break;
                              }
                        }
                  }
            for (const auto& sp : _spanner.map()) {
                  Spanner* s = sp.second;
                  if (s->type() != ElementType::HAIRPIN || sp.second->staffIdx() != staffIdx)
                        continue;
                  Hairpin* h = toHairpin(s);
                  updateHairpin(h);
                  }
            }

      for (auto it = spanner().cbegin(); it != spanner().cend(); ++it) {
            Spanner* spanner = (*it).second;
            if (!spanner->isVolta())
                  continue;
            Volta* volta = toVolta(spanner);
            volta->setVelocity();
            }
      }

//---------------------------------------------------------
//   renderStaffSegment
//---------------------------------------------------------

void MidiRenderer::renderStaffChunk(const Chunk& chunk, EventMap* events, Staff* staff, DynamicsRenderMethod method, int cc)
      {
      Measure* start = chunk.startMeasure();
      Measure* end = chunk.endMeasure();
      const int tickOffset = chunk.tickOffset();

      Measure* lastMeasure = start->prevMeasure();
      StaffRenderData renderData;

      for (Measure* m = start; m != end; m = m->nextMeasure()) {
            if (lastMeasure && m->isRepeatMeasure(staff)) {
                  int offset = (m->tick() - lastMeasure->tick()).ticks();
                  collectMeasureEvents(events, lastMeasure, staff, renderData, tickOffset + offset, method, cc);
                  }
            else {
                  lastMeasure = m;
                  collectMeasureEvents(events, lastMeasure, staff, renderData, tickOffset, method, cc);
                  }
            }

      events->insert(renderData.tempPlayEvents.begin(), renderData.tempPlayEvents.end());
      }

//---------------------------------------------------------
//   renderSpanners
//---------------------------------------------------------

void MidiRenderer::renderSpanners(const Chunk& chunk, EventMap* events)
      {
      const int tickOffset = chunk.tickOffset();
      const int tick1 = chunk.tick1();
      const int tick2 = chunk.tick2();

      std::map<int, std::vector<std::pair<int, std::pair<bool, int>>>> channelPedalEvents;
      for (const auto& sp : score->spannerMap().map()) {
            Spanner* s = sp.second;

            int staff = s->staffIdx();
            int idx = s->staff()->channel(s->tick(), 0);
            int channel = s->part()->instrument(s->tick())->channel(idx)->channel();

            if (s->isPedal() || s->isLetRing()) {
                  channelPedalEvents.insert({channel, std::vector<std::pair<int, std::pair<bool, int>>>()});
                  std::vector<std::pair<int, std::pair<bool, int>>> pedalEventList = channelPedalEvents.at(channel);
                  std::pair<int, std::pair<bool, int>> lastEvent;

                  if (!pedalEventList.empty())
                        lastEvent = pedalEventList.back();
                  else
                        lastEvent = std::pair<int, std::pair<bool, int>>(0, std::pair<bool, int>(true, staff));

                  int st = s->tick().ticks();
                  if (st >= tick1 && st < tick2) {
                        // Handle "overlapping" pedal segments (usual case for connected pedal line)
                        if (lastEvent.second.first == false && lastEvent.first >= (st + tickOffset + 2)) {
                              channelPedalEvents.at(channel).pop_back();
                              channelPedalEvents.at(channel).push_back(std::pair<int, std::pair<bool, int>>(st + tickOffset + 1, std::pair<bool, int>(false, staff)));
                              }
                        int a = st + tickOffset + 2;
                        channelPedalEvents.at(channel).push_back(std::pair<int, std::pair<bool, int>>(a, std::pair<bool, int>(true, staff)));
                        }
                  if (s->tick2().ticks() >= tick1 && s->tick2().ticks() <= tick2) {
                        int t = s->tick2().ticks() + tickOffset + 1;
                        const RepeatSegment& lastRepeat = *score->repeatList().back();
                        if (t > lastRepeat.utick + lastRepeat.len())
                              t = lastRepeat.utick + lastRepeat.len();
                        channelPedalEvents.at(channel).push_back(std::pair<int, std::pair<bool, int>>(t, std::pair<bool, int>(false, staff)));
                        }
                  }
            else if (s->isVibrato()) {
                  int stick = s->tick().ticks();
                  int etick = s->tick2().ticks();
                  if (stick >= tick2 || etick < tick1)
                        continue;

                  if (stick < tick1)
                        stick = tick1;
                  if (etick > tick2)
                        etick = tick2;

                  // from start to end of trill, send bend events at regular interval
                  Vibrato* t = toVibrato(s);
                  // guitar vibrato, up only
                  int spitch = 0; // 1/8 (100 is a semitone)
                  int epitch = 12;
                  if (t->vibratoType() == Vibrato::Type::GUITAR_VIBRATO_WIDE) {
                        spitch = 0; // 1/4
                        epitch = 25;
                        }
                  // vibrato with whammy bar up and down
                  else if (t->vibratoType() == Vibrato::Type::VIBRATO_SAWTOOTH_WIDE) {
                        spitch = 25; // 1/16
                        epitch = -25;
                        }
                  else if (t->vibratoType() == Vibrato::Type::VIBRATO_SAWTOOTH) {
                        spitch = 12;
                        epitch = -12;
                        }

                  int j = 0;
                  int delta = MScore::division / 8; // 1/8 note
                  int lastPointTick = stick;
                  while (lastPointTick < etick) {
                        int pitch = (j % 4 < 2) ? spitch : epitch;
                        int nextPitch = ((j+1) % 4 < 2) ? spitch : epitch;
                        int nextPointTick = lastPointTick + delta;
                        for (int i = lastPointTick; i <= nextPointTick; i += 16) {
                              double dx = ((i - lastPointTick) * 60) / delta;
                              int p = pitch + dx * (nextPitch - pitch) / delta;
                              int midiPitch = (p * 16384) / 1200 + 8192;
                              int msb = midiPitch / 128;
                              int lsb = midiPitch % 128;
                              NPlayEvent ev(ME_PITCHBEND, channel, lsb, msb);
                              ev.setOriginatingStaff(staff);
                              events->insert(std::pair<int, NPlayEvent>(i + tickOffset, ev));
                              }
                        lastPointTick = nextPointTick;
                        j++;
                        }
                  NPlayEvent ev(ME_PITCHBEND, channel, 0, 64); // no pitch bend
                  ev.setOriginatingStaff(staff);
                  events->insert(std::pair<int, NPlayEvent>(etick + tickOffset, ev));
                  }
            else
                  continue;
            }

      for (const auto& pedalEvents : channelPedalEvents) {
            int channel = pedalEvents.first;
            for (const auto& pe : pedalEvents.second) {
                  NPlayEvent event;
                  if (pe.second.first == true)
                        event = NPlayEvent(ME_CONTROLLER, channel, CTRL_SUSTAIN, 127);
                  else
                        event = NPlayEvent(ME_CONTROLLER, channel, CTRL_SUSTAIN, 0);
                  event.setOriginatingStaff(pe.second.second);
                  events->insert(std::pair<int,NPlayEvent>(pe.first, event));
                  }
            }
      }

//--------------------------------------------------------
//   swingAdjustParams
//--------------------------------------------------------

void Score::swingAdjustParams(Chord* chord, int& gateTime, int& ontime, int swingUnit, int swingRatio)
      {
      Fraction tick = chord->rtick();
      // adjust for anacrusis
      Measure* cm     = chord->measure();
      MeasureBase* pm = cm->prev();
      ElementType pt  = pm ? pm->type() : ElementType::INVALID;
      if (!pm || pm->lineBreak() || pm->pageBreak() || pm->sectionBreak()
         || pt == ElementType::VBOX || pt == ElementType::HBOX
         || pt == ElementType::FBOX || pt == ElementType::TBOX) {
            Fraction offset = cm->timesig() - cm->ticks();
            if (offset > Fraction(0,1)) {
                  tick += offset;
                  }
            }

      int swingBeat           = swingUnit * 2;
      qreal ticksDuration     = (qreal)chord->actualTicks().ticks();
      qreal swingTickAdjust   = ((qreal)swingBeat) * (((qreal)(swingRatio-50))/100.0);
      qreal swingActualAdjust = (swingTickAdjust/ticksDuration) * 1000.0;
      ChordRest *ncr          = nextChordRest(chord);

      //Check the position of the chord to apply changes accordingly
      if (tick.ticks() % swingBeat == swingUnit) {
            if (!isSubdivided(chord,swingUnit)) {
                  ontime = ontime + swingActualAdjust;
                  }
            }
      int endTick = tick.ticks() + ticksDuration;
      if ((endTick % swingBeat == swingUnit) && (!isSubdivided(ncr,swingUnit))) {
            gateTime = gateTime + (swingActualAdjust/10);
            }
      }

//---------------------------------------------------------
//   isSubdivided
//   Check for subdivided beat
//---------------------------------------------------------

bool Score::isSubdivided(ChordRest* chord, int swingUnit)
      {
      if (!chord)
            return false;
      ChordRest* prev = prevChordRest(chord);
      if (chord->actualTicks().ticks() < swingUnit || (prev && prev->actualTicks().ticks() < swingUnit))
            return true;
      else
            return false;
      }

const Drumset* getDrumset(const Chord* chord)
      {
      if (chord->staff() && chord->staff()->isDrumStaff(chord->tick())) {
            const Drumset* ds = chord->staff()->part()->instrument(chord->tick())->drumset();
            return ds;
            }
      return nullptr;
      }

//---------------------------------------------------------
//   renderTremolo
//---------------------------------------------------------

void renderTremolo(Chord* chord, QList<NoteEventList>& ell)
      {
      Segment* seg = chord->segment();
      Tremolo* tremolo = chord->tremolo();
      int notes = int(chord->notes().size());

      // check if tremolo was rendered before for drum staff
      const Drumset* ds = getDrumset(chord);
      if (ds) {
            for (Note* n : chord->notes()) {
                  DrumInstrumentVariant div = ds->findVariant(n->pitch(), chord->articulations(), chord->tremolo());
                  if (div.pitch != INVALID_PITCH && div.tremolo == tremolo->tremoloType())
                        return; // already rendered
                  }
            }

      // we cannot render buzz roll with MIDI events only
      if (tremolo->tremoloType() == TremoloType::BUZZ_ROLL)
            return;

      // render tremolo with multiple events
      if (chord->tremoloChordType() == TremoloChordType::TremoloFirstNote) {
            int t = MScore::division / (1 << (tremolo->lines() + chord->durationType().hooks()));
            SegmentType st = SegmentType::ChordRest;
            Segment* seg2 = seg->next(st);
            int track = chord->track();
            while (seg2 && !seg2->element(track))
                  seg2 = seg2->next(st);

            if (!seg2)
                  return;

            Element* s2El = seg2->element(track);
            if (s2El) {
                  if (!s2El->isChord())
                        return;
                  }
            else
                  return;

            Chord* c2 = toChord(s2El);
            if (c2->type() == ElementType::CHORD) {
                  int notes2 = int(c2->notes().size());
                  int tnotes = qMax(notes, notes2);
                  int tticks = chord->actualTicks().ticks() * 2; // use twice the size
                  int n = tticks / t;
                  n /= 2;
                  int l = 2000 * t / tticks;
                  for (int k = 0; k < tnotes; ++k) {
                        NoteEventList* events;
                        if (k < notes) {
                              // first chord has note
                              events = &ell[k];
                              events->clear();
                              }
                        else {
                              // otherwise reuse note 0
                              events = &ell[0];
                              }
                        if (k < notes && k < notes2) {
                              // both chords have note
                              int p1 = chord->notes()[k]->pitch();
                              int p2 = c2->notes()[k]->pitch();
                              int dpitch = p2 - p1;
                              for (int i = 0; i < n; ++i) {
                                    events->append(NoteEvent(0, l * i * 2, l));
                                    events->append(NoteEvent(dpitch, l * i * 2 + l, l));
                                    }
                              }
                        else if (k < notes) {
                              // only first chord has note
                              for (int i = 0; i < n; ++i)
                                    events->append(NoteEvent(0, l * i * 2, l));
                              }
                        else {
                              // only second chord has note
                              // reuse note 0 of first chord
                              int p1 = chord->notes()[0]->pitch();
                              int p2 = c2->notes()[k]->pitch();
                              int dpitch = p2-p1;
                              for (int i = 0; i < n; ++i)
                                    events->append(NoteEvent(dpitch, l * i * 2 + l, l));
                              }
                        }
                  }
            else
                  qDebug("Chord::renderTremolo: cannot find 2. chord");
            }
      else if (chord->tremoloChordType() == TremoloChordType::TremoloSecondNote) {
            for (int k = 0; k < notes; ++k) {
                  NoteEventList* events = &(ell)[k];
                  events->clear();
                  }
            }
      else if (chord->tremoloChordType() == TremoloChordType::TremoloSingle) {
            int t = MScore::division / (1 << (tremolo->lines() + chord->durationType().hooks()));
            if (t == 0) // avoid crash on very short tremolo
                  t = 1;
            int n = chord->ticks().ticks() / t;
            int l = 1000 / n;
            for (int k = 0; k < notes; ++k) {
                  NoteEventList* events = &(ell)[k];
                  events->clear();
                  for (int i = 0; i < n; ++i)
                        events->append(NoteEvent(0, l * i, l));
                  }
            }
      }

//---------------------------------------------------------
//   renderArpeggio
//---------------------------------------------------------

void renderArpeggio(Chord *chord, QList<NoteEventList> & ell)
      {
      int notes = int(chord->notes().size());
      int l = 64;
      while (l && (l * notes > chord->upNote()->playTicks()))
            l = 2*l / 3;
      int start, end, step;
      bool up = chord->arpeggio()->arpeggioType() != ArpeggioType::DOWN && chord->arpeggio()->arpeggioType() != ArpeggioType::DOWN_STRAIGHT;
      if (up) {
            start = 0;
            end   = notes;
            step  = 1;
            }
      else {
            start = notes - 1;
            end   = -1;
            step  = -1;
            }
      int j = 0;
      for (int i = start; i != end; i += step) {
            NoteEventList* events = &(ell)[i];
            events->clear();

            auto tempoRatio = chord->score()->tempomap()->tempo(chord->tick().ticks()) / Score::defaultTempo();
            int ot = (l * j * 1000) / chord->upNote()->playTicks() *
                        tempoRatio * chord->arpeggio()->Stretch();

            events->append(NoteEvent(0, ot, 1000 - ot));
            j++;
            }
      }

//---------------------------------------------------------
//   convertLine
// find the line in clefF corresponding to lineL2 in clefR
//---------------------------------------------------------

int convertLine (int lineL2, ClefType clefL, ClefType clefR) {
      int lineR2 = lineL2;
      int goalpitch = line2pitch(lineL2, clefL, Key::C);
      int p;
      while ( (p = line2pitch(lineR2, clefR, Key::C)) > goalpitch && p < 127)
            lineR2++;
      while ( (p = line2pitch(lineR2, clefR, Key::C)) < goalpitch &&  p > 0)
            lineR2--;
      return lineR2;
      }

//---------------------------------------------------------
//   convertLine
// find the line in clef for NoteL corresponding to lineL2 in clef for noteR
// for example middle C is line 10 in Treble clef, but is line -2 in Bass clef.
//---------------------------------------------------------

int convertLine(int lineL2, Note *noteL, Note *noteR)
      {
      return convertLine(lineL2,
         noteL->chord()->staff()->clef(noteL->chord()->tick()),
         noteR->chord()->staff()->clef(noteR->chord()->tick()));
      }

//---------------------------------------------------------
//   articulationExcursion -- an articulation such as a trill, or modant consists of several notes
// played in succession.  The pitch offsets of each such note in the sequence can be represented either
// as a number of steps in the diatonic scale, or in half steps as on a piano keyboard.
// this function, articulationExcursion, takes deltastep indicating the number of steps in the
// diatonic scale, and calculates (and returns) the number of half steps, taking several things into account.
// E.g., the key signature, a trill from e to f, is to be understood as a trill between E and F# if we are
// in the key of G.
// E.g., if previously (looking backward in time) in the same measure there is another note on the same
// staff line/space, and that note has an accidental (sharp,flat,natural,etc), then we want to match that
// tone exactly.
// E.g., If there are multiple notes on the same line/space, then we only consider the most
// recent one, but avoid looking forward in time after the current note.
// E.g., Also if there is an accidental     // on a note one (or more) octaves above or below we
// observe its accidental as well.
// E.g., Still another case is that if two staffs are involved (such as a glissando between two
// notes on different staffs) then we have to search both staffs for the most recent accidental.
//
// noteL is the note to measure the deltastep from, i.e., ornaments are w.r.t. this note
// noteR is the note to search backward from to find accidentals.
//    for ornament calculation noteL and noteR are the same, but for glissando they are
//     the start end end note of glissando.
// deltastep is the desired number of diatonic steps between the base note and this articulation step.
//---------------------------------------------------------

int articulationExcursion(Note *noteL, Note *noteR, int deltastep)
      {
      if (0 == deltastep)
            return 0;
      Chord *chordL = noteL->chord();
      Chord *chordR = noteR->chord();
      int epitchL = noteL->epitch();
      Fraction tickL = chordL->tick();
      // we canot use staffL = chord->staff() because that won't correspond to the noteL->line()
      //   in the case the user has pressed Shift-Cmd->Up or Shift-Cmd-Down.
      //   Therefore we have to take staffMove() into account using vStaffIdx().
      Staff * staffL = noteL->score()->staff(chordL->vStaffIdx());
      ClefType clefL = staffL->clef(tickL);
      // line represents the ledger line of the staff.  0 is the top line, 1, is the space between the top 2 lines,
      //  ... 8 is the bottom line.
      int lineL     = noteL->line();
      // we use line - deltastep, because lines are oriented from top to bottom, while step is oriented from bottom to top.
      int lineL2    = lineL - deltastep;
      Measure* measureR = chordR->segment()->measure();

      Segment* segment = noteL->chord()->segment();
      int lineR2 = convertLine(lineL2, noteL, noteR);
      // is there another note in this segment on the same line?
      // if so, use its pitch exactly.
      int halfsteps = 0;
      int staffIdx = noteL->chord()->staff()->idx(); // cannot use staffL->idx() because of staffMove()
      int startTrack = staffIdx * VOICES;
      int endTrack   = startTrack + VOICES;
      bool done = false;
      for (int track = startTrack; track < endTrack; ++track) {
            Element *e = segment->element(track);
            if (!e || e->type() != ElementType::CHORD)
                  continue;
            Chord* chord = toChord(e);
            if (chord->vStaffIdx() != chordL->vStaffIdx())
                  continue;
            for (Note* note : chord->notes()) {
                  if (note->tieBack())
                        continue;
                  int pc = (note->line() + 700) % 7;
                  int pc2 = (lineL2 + 700) % 7;
                  if (pc2 == pc) {
                        // e.g., if there is an F# note at this staff/tick, then force every F to be F#.
                        int octaves = (note->line() - lineL2) / 7;
                        halfsteps = note->epitch() + 12 * octaves - epitchL;
                        done = true;
                        break;
                        }
                  }
            if (!done) {
                  if (staffL->isPitchedStaff(segment->tick())) {
                        bool error = false;
                        AccidentalVal acciv2 = measureR->findAccidental(chordR->segment(), chordR->vStaffIdx(), lineR2, error);
                        int acci2 = int(acciv2);
                        // epitch (effective pitch) is a visible pitch so line2pitch returns exactly that.
                        halfsteps = line2pitch(lineL-deltastep, clefL, Key::C) + acci2 - epitchL;
                        }
                  else {
                        // cannot rely on accidentals or key signatures
                        halfsteps = deltastep;
                        }
                  }
            }
      return halfsteps;
      }

//---------------------------------------------------------
// totalTiedNoteTicks
//      return the total of the actualTicks of the given note plus
//      the chain of zero or more notes tied to it to the right.
//---------------------------------------------------------

int totalTiedNoteTicks(Note* note)
      {
      Fraction total = note->chord()->actualTicks();
      while (note->tieFor() && note->tieFor()->endNote() && (note->chord()->tick() < note->tieFor()->endNote()->chord()->tick())) {
            note = note->tieFor()->endNote();
            total += note->chord()->actualTicks();
            }
      return total.ticks();
      }

//---------------------------------------------------------
//   renderNoteArticulation
// prefix, vector of int, normally something like {0,-1,0,1} modeling the prefix of tremblement relative to the base note
// body, vector of int, normally something like {0,-1,0,1} modeling the possibly repeated tremblement relative to the base note
// tickspernote, number of ticks, either _16h or _32nd, i.e., MScore::division/4 or MScore::division/8
// repeatp, true means repeat the body as many times as possible to fill the time slice.
// sustainp, true means the last note of the body is sustained to fill remaining time slice
//---------------------------------------------------------

bool renderNoteArticulation(NoteEventList* events, Note* note, bool chromatic, int requestedTicksPerNote,
   const vector<int>& prefix, const vector<int>& body,
   bool repeatp, bool sustainp, const vector<int>& suffix,
   int fastestFreq=64, int slowestFreq=8 // 64 Hz and 8 Hz
   )
      {
      events->clear();
      Chord *chord = note->chord();
      int maxticks = totalTiedNoteTicks(note);
      int space = 1000 * maxticks;
      int numrepeat = 1;
      int sustain   = 0;
      int ontime    = 0;

      int gnb = note->chord()->graceNotesBefore().size();
      int p = int(prefix.size());
      int b = int(body.size());
      int s = int(suffix.size());
      int gna = note->chord()->graceNotesAfter().size();

      int ticksPerNote = 0;

      if (gnb + p + b + s + gna <= 0 )
            return false;

      Fraction tick = chord->tick();
      qreal tempo = chord->score()->tempo(tick);
      int ticksPerSecond = tempo * MScore::division;

      int minTicksPerNote = int(ticksPerSecond / fastestFreq);
      int maxTicksPerNote = (0 == slowestFreq) ? 0 : int(ticksPerSecond / slowestFreq);

      // for fast tempos, we have to slow down the tremblement frequency, i.e., increase the ticks per note
      if (requestedTicksPerNote >= minTicksPerNote)
            ;
      else { // try to divide the requested frequency by a power of 2 if possible, if not, use the maximum frequency, ie., minTicksPerNote
            ticksPerNote = requestedTicksPerNote;
            while (ticksPerNote < minTicksPerNote) {
                  ticksPerNote *= 2; // decrease the tremblement frequency
                  }
            if (ticksPerNote > maxTicksPerNote)
                  ticksPerNote = minTicksPerNote;
            }

      ticksPerNote = max(requestedTicksPerNote, minTicksPerNote);

      if (slowestFreq <= 0) // no slowest freq given such as something silly like glissando with 4 notes over 8 counts.
            ;
      else if (ticksPerNote <= maxTicksPerNote) // in a good range, so we don't need to adjust ticksPerNote
            ;
      else {
            // for slow tempos, such as adagio, we may need to speed up the tremblement frequency, i.e., decrease the ticks per note, to make it sound reasonable.
            ticksPerNote = requestedTicksPerNote ;
            while (ticksPerNote > maxTicksPerNote) {
                  ticksPerNote /= 2;
                  }
            if (ticksPerNote < minTicksPerNote)
                  ticksPerNote = minTicksPerNote;
            }
      // calculate whether to shorten the duration value.
      if ( ticksPerNote*(gnb + p + b + s + gna) <= maxticks )
            ; // plenty of space to play the notes without changing the requested trill note duration
      else if ( ticksPerNote == minTicksPerNote )
            return false; // the ornament is impossible to implement respecting the minimum duration and all the notes it contains
      else {
            ticksPerNote = maxticks / (gnb + p + b + s + gna);  // integer division ignoring remainder
            if ( slowestFreq <= 0 )
                  ;
            else if ( ticksPerNote < minTicksPerNote )
                  return false;
            }

      int millespernote = space * ticksPerNote  / maxticks;  // rescale duration into per mille

      // local function:
      // look ahead in the given vector to see if the current note is the same pitch as the next note or next several notes.
      // If so, increment the duration by the appropriate note duration, and increment the index, j, to the next note index
      // of a different pitch.
      // The total duration of the tied note is returned, and the index is modified.
      auto tieForward = [millespernote] (int & j, const vector<int> & vec) {
            int size = int(vec.size());
            int duration = millespernote;
            while ( j < size-1 && vec[j] == vec[j+1] ) {
                  duration += millespernote;
                  j++;
                  }
            return duration;
            };

      // local function:
      //   append a NoteEvent either by calculating an articulationExcursion or by
      //   the given chromatic relative pitch.
      //   RETURNS the new ontime value.  The caller is expected to assign this value.
      auto makeEvent = [note,chord,chromatic,events] (int pitch, int ontime, int duration) {
            events->append( NoteEvent(chromatic ? pitch : articulationExcursion(note,note,pitch),
               ontime/chord->actualTicks().ticks(),
               duration/chord->actualTicks().ticks()));
            return ontime + duration;
            };

      // local function:
      //    Given a chord from a grace note, (normally the chord contains a single note) and create
      //    a NoteEvent as if the grace note were part of the articulation (such as trill).  This
      //    local function works for the graceNotesBefore() and also graceNotesAfter().
      //    If the grace note has play=false, then it will sound as a rest, but the other grace
      //    notes will still play.  This means graceExtend simply omits the call to append( NoteEvent(...))
      //    but still updates ontime +=millespernote.
      //    RETURNS the new value of ontime, so caller must make an assignment to the return value.
      auto graceExtend = [millespernote,chord,events] (int notePitch, QVector<Chord*> graceNotes, int ontime) {
            for (Chord* c : graceNotes) {
                  for (Note* n : c->notes()) {
                        // NoteEvent takes relative pitch as first argument.
                        // The pitch is relative to the pitch of the note, the event is rendering
                        if (n->play())
                              events->append( NoteEvent(n->pitch() - notePitch,
                                 ontime/chord->actualTicks().ticks(),
                                 millespernote/chord->actualTicks().ticks()));
                        }
                  ontime += millespernote;
                  }
            return ontime;
            };

      // calculate the number of times to repeat the body, and sustain the last note of the body
      // 1000 = P + numrepeat*B+sustain + S
      if (repeatp)
            numrepeat = (space - millespernote*(gnb + p + s + gna)) / (millespernote * b);
      if (sustainp)
            sustain   = space - millespernote*(gnb + p + numrepeat * b + s + gna);
      // render the graceNotesBefore
      ontime = graceExtend(note->pitch(),note->chord()->graceNotesBefore(), ontime);

      // render the prefix
      for (int j=0; j < p; j++)
            ontime = makeEvent(prefix[j], ontime, tieForward(j,prefix));

      if (b > 0) {
            // render the body, but not the final repetition
            for (int r = 0; r < numrepeat-1; r++) {
                  for (int j=0; j < b; j++)
                        ontime = makeEvent(body[j], ontime, millespernote);
                  }
            // render the final repetition of body, but not the final note of the repition
            for (int j = 0; j < b - 1; j++)
                  ontime = makeEvent(body[j], ontime, millespernote);
            // render the final note of the final repeat of body
            ontime = makeEvent(body[b-1], ontime, millespernote+sustain);
            }
      // render the suffix
      for (int j = 0; j < s; j++)
            ontime = makeEvent(suffix[j], ontime, tieForward(j,suffix));
      // render graceNotesAfter
      graceExtend(note->pitch(), note->chord()->graceNotesAfter(), ontime);
      return true;
      }

// This struct specifies how to render an articulation.
//   atype - the articulation type to implement, such as SymId::ornamentTurn
//   ostyles - the actual ornament has a property called ornamentStyle whose value is
//             a value of type MScore::OrnamentStyle.  This ostyles field indicates the
//             the set of ornamentStyles which apply to this rendition.
//   duration - the default duration for each note in the rendition, the final duration
//            rendered might be less than this if an articulation is attached to a note of
//            short duration.
//   prefix - vector of integers. indicating which notes to play at the beginning of rendering the
//            articulation.  0 represents the principle note, 1==> the note diatonically 1 above
//            -1 ==> the note diatonically 1 below.  E.g., in the key of G, if a turn articulation
//            occurs above the note F#, then 0==>F#, 1==>G, -1==>E.
//            These integers indicate which notes actual notes to play when rendering the ornamented
//            note.   However, if the same integer appears several times adjacently such as {0,0,0,1}
//            That means play the notes tied.  e.g., F# followed by G, but the duration of F# is 3x the
//            duration of the G.
//    body   - notes to play comprising the body of the rendered ornament.
//            The body differs from the prefix and suffix in several ways.
//            * body does not support tied notes: {0,0,0,1} means play 4 distinct notes (not tied).
//            * if there is sufficient duration in the principle note, AND repeatep is true, then body
//               will be rendered multiple times, as the duration allows.
//            * to avoid a time gap (or rest) in rendering the articulation, if sustainp is true,
//               then the final note of the body will be sustained to fill the left-over time.
//    suffix - similar to prefix but played once at the end of the rendered ornament.
//    repeatp  - whether the body is repeatable in its entirety.
//    sustainp - whether the final note of the body should be sustained to fill the remaining duration.

struct OrnamentExcursion {
      SymId atype;
      set<MScore::OrnamentStyle> ostyles;
      int duration;
      vector<int> prefix;
      vector<int> body;
      bool repeatp;
      bool sustainp;
      vector<int> suffix;
      };

set<MScore::OrnamentStyle> baroque  = {MScore::OrnamentStyle::BAROQUE};
set<MScore::OrnamentStyle> defstyle = {MScore::OrnamentStyle::DEFAULT};
set<MScore::OrnamentStyle> any; // empty set has the special meaning of any-style, rather than no-styles.
int _16th = MScore::division / 4;
int _32nd = _16th / 2;

vector<OrnamentExcursion> excursions = {
      //  articulation type            set of  duration       body         repeatp      suffix
      //                               styles          prefix                    sustainp
      { SymId::ornamentTurn,                any, _32nd, {},    {1,0,-1,0},   false, true, {}}
      ,{SymId::ornamentTurnInverted,        any, _32nd, {},    {-1,0,1,0},   false, true, {}}
      ,{SymId::ornamentTrill,           baroque, _32nd, {1,0}, {1,0},        true,  true, {}}
      ,{SymId::ornamentTrill,          defstyle, _32nd, {0,1}, {0,1},        true,  true, {}}
      ,{SymId::brassMuteClosed,         baroque, _32nd, {0,-1},{0, -1},      true,  true, {}}
      ,{SymId::ornamentMordentInverted,     any, _32nd, {},    {0,-1,0},     false, true, {}}
      ,{SymId::ornamentMordent,        defstyle, _32nd, {},    {0,1,0},      false, true, {}} // inverted mordent
      ,{SymId::ornamentMordent,         baroque, _32nd, {1,0,1},{0},         false, true, {}} // short trill
      ,{SymId::ornamentTremblement,         any, _32nd, {1,0}, {1,0},        false, true, {}}
      ,{SymId::ornamentPrallMordent,        any, _32nd, {},    {1,0,-1,0},   false, true, {}}
      ,{SymId::ornamentLinePrall,           any, _32nd, {2,2,2},{1,0},       true,  true, {}}
      ,{SymId::ornamentUpPrall,             any, _16th, {-1,0},{1,0},        true,  true, {1,0}} // p 144 Ex 152 [1]
      ,{SymId::ornamentUpMordent,           any, _16th, {-1,0},{1,0},        true,  true, {-1,0}} // p 144 Ex 152 [1]

      ,{SymId::ornamentPrecompMordentUpperPrefix, any, _16th, {1,1,1,0}, {1,0},    true,  true, {}} // p136 Cadence Appuyee [1] [2]
      ,{SymId::ornamentDownMordent,         any, _16th, {1,1,1,0}, {1,0},    true,  true, {-1, 0}} // p136 Cadence Appuyee + mordent [1] [2]
      ,{SymId::ornamentPrallUp,             any, _16th, {1,0}, {1,0},        true,  true, {-1,0}} // p136 Double Cadence [1]
      ,{SymId::ornamentPrallDown,           any, _16th, {1,0}, {1,0},        true,  true, {-1,0,0,0}} // p144 ex 153 [1]
      ,{SymId::ornamentPrecompSlide,        any, _32nd, {},    {0},          false, true, {}}

      // [1] Some of the articulations/ornaments in the excursions table above come from
      // Baroque Music, Style and Performance A Handbook, by Robert Donington,(c) 1982
      // ISBN 0-393-30052-8, W. W. Norton & Company, Inc.

      // [2] In some cases, the example from [1] does not preserve the timing.
      // For example, illustrates 2+1/4 counts per half note.
      };

//---------------------------------------------------------
//   renderNoteArticulation
//---------------------------------------------------------

bool renderNoteArticulation(NoteEventList* events, Note * note, bool chromatic, SymId articulationType, MScore::OrnamentStyle ornamentStyle)
      {
      if (!note->staff()->isPitchedStaff(note->tick())) // not enough info in tab staff
            return false;

      vector<int> emptypattern = {};
      for (auto& oe : excursions) {
            if (oe.atype == articulationType && ( 0 == oe.ostyles.size()
                  || oe.ostyles.end() != oe.ostyles.find(ornamentStyle))) {
                     return renderNoteArticulation(events, note, chromatic, oe.duration,
                                                   oe.prefix, oe.body, oe.repeatp, oe.sustainp, oe.suffix);
                     }
            }
      return false;
      }

//---------------------------------------------------------
//   renderNoteArticulation
//---------------------------------------------------------

bool renderNoteArticulation(NoteEventList* events, Note * note, bool chromatic, Trill::Type trillType, MScore::OrnamentStyle ornamentStyle)
      {
      map<Trill::Type,SymId> articulationMap = {
            {Trill::Type::TRILL_LINE,      SymId::ornamentTrill      }
           ,{Trill::Type::UPPRALL_LINE,    SymId::ornamentUpPrall    }
           ,{Trill::Type::DOWNPRALL_LINE,  SymId::ornamentPrecompMordentUpperPrefix  }
           ,{Trill::Type::PRALLPRALL_LINE, SymId::ornamentTrill      }
            };
      auto it = articulationMap.find(trillType);
      if (it == articulationMap.cend())
            return false;
      else
            return renderNoteArticulation(events, note, chromatic, it->second, ornamentStyle);
      }

//---------------------------------------------------------
//   noteHasGlissando
// true if note is the end of a glissando
//---------------------------------------------------------

bool noteHasGlissando(Note *note)
      {
      for (Spanner* spanner : note->spannerFor()) {
            if ((spanner->type() == ElementType::GLISSANDO)
               && spanner->endElement()
               && (ElementType::NOTE == spanner->endElement()->type()))
                  return true;
            }
      return false;
      }

//---------------------------------------------------------
//   renderGlissando
//---------------------------------------------------------

void renderGlissando(NoteEventList* events, Note *notestart)
      {
      vector<int> empty = {};
      int Cnote = 60; // pitch of middle C
      int pitchstart = notestart->ppitch();
      int linestart = notestart->line();

      set<int> blacknotes = {  1,  3,    6, 8, 10};
      set<int> whitenotes = {0,  2, 4, 5, 7,  9, 11};

      for (Spanner* spanner : notestart->spannerFor()) {
            if (spanner->type() == ElementType::GLISSANDO) {
                  Glissando *glissando = toGlissando(spanner);
                  GlissandoStyle glissandoStyle = glissando->glissandoStyle();
                  Element* ee = spanner->endElement();
                  // only consider glissando connected to NOTE.
                  if (glissando->playGlissando() && ElementType::NOTE == ee->type()) {
                        vector<int> body;
                        Note *noteend  = toNote(ee);
                        int pitchend   = noteend->ppitch();
                        bool direction = pitchend >  pitchstart;
                        if (pitchend == pitchstart)
                              continue; // next spanner
                        if (glissandoStyle == GlissandoStyle::DIATONIC) { // scale obeying accidentals
                              int line;
                              int p = pitchstart;
                              // iterate as long as we haven't past the pitchend.
                              for (line = linestart; (direction) ? (p<pitchend) : (p>pitchend);
                                 (direction) ? line-- : line++) {
                                    int halfsteps = articulationExcursion(notestart, noteend, linestart - line);
                                    p = pitchstart + halfsteps;
                                    if (direction ? p < pitchend : p > pitchend)
                                          body.push_back(halfsteps);
                                    }
                              }
                        else {
                              for (int p = pitchstart; direction ? p < pitchend : p > pitchend; p += (direction ? 1 : -1)) {
                                    bool choose = false;
                                    int mod = ((p - Cnote) + 1200) % 12;
                                    switch (glissandoStyle) {
                                          case GlissandoStyle::CHROMATIC:
                                                choose = true;
                                                break;
                                          case GlissandoStyle::WHITE_KEYS: // white note
                                                choose = (whitenotes.find(mod) != whitenotes.end());
                                                break;
                                          case GlissandoStyle::BLACK_KEYS: // black note
                                                choose =  (blacknotes.find(mod) != blacknotes.end());
                                                break;
                                          default:
                                                choose = false;
                                          }
                                    if (choose)
                                          body.push_back(p - pitchstart);
                                    }
                              }
                        renderNoteArticulation(events, notestart, true, MScore::division, empty, body, false, true, empty, 16, 0);
                        }
                  }
            }
      }



//---------------------------------------------------------
// findFirstTrill
//  search the spanners in the score, finding the first one
//  which overlaps this chord and is of type ElementType::TRILL
//---------------------------------------------------------

Trill* findFirstTrill(Chord *chord)
      {
      auto spanners = chord->score()->spannerMap().findOverlapping(1+chord->tick().ticks(),
         chord->tick().ticks() + chord->actualTicks().ticks() - 1);
      for (auto i : spanners) {
            if (i.value->type() != ElementType::TRILL)
                  continue;
            if (i.value->track() != chord->track())
                  continue;
            Trill *trill = toTrill (i.value);
            if (trill->playArticulation() == false)
                  continue;
            return trill;
            }
      return nullptr;
      }

// In the case that graceNotesBefore or graceNotesAfter are attached to a note
// with an articulation such as a trill, then the grace notes are/will-be/have-been
// already merged into the articulation.
// So this predicate, graceNotesMerged, checks for this condition to avoid calling
// functions which would re-emit the grace notes by a different algorithm.

bool graceNotesMerged(Chord* chord)
      {
      if (findFirstTrill(chord))
            return true;
      for (Articulation* a : chord->articulations())
            for (auto& oe : excursions)
                  if ( oe.atype == a->symId() )
                        return true;
      return false;
      }

//---------------------------------------------------------
//   renderChordArticulation
//---------------------------------------------------------

void renderChordArticulation(Chord* chord, QList<NoteEventList> & ell, int & gateTime)
      {
      Segment* seg = chord->segment();
      Instrument* instr = chord->part()->instrument(seg->tick());
      int channel  = 0;  // note->subchannel();

      for (unsigned k = 0; k < chord->notes().size(); ++k) {
            NoteEventList* events = &ell[k];
            Note *note = chord->notes()[k];
            Trill *trill;

            if (noteHasGlissando(note))
                  renderGlissando(events, note);
            else if (chord->staff()->isPitchedStaff(chord->tick())  && (trill = findFirstTrill(chord)) != nullptr) {
                  renderNoteArticulation(events, note, false, trill->trillType(), trill->ornamentStyle());
                  }
            else {
                  for (Articulation* a : chord->articulations()) {
                        if (!a->playArticulation())
                              continue;
                        if (!renderNoteArticulation(events, note, false, a->symId(), a->ornamentStyle()))
                              instr->updateGateTime(&gateTime, channel, a->articulationName());
                        }
                  }
            }
      }

//---------------------------------------------------------
//   shouldRenderNote
//---------------------------------------------------------

static bool shouldRenderNote(Note* n)
      {
      int dist = 0;
      while (n->tieBack()) {
            n = n->tieBack()->startNote();
            ++dist;
            if (n && n->playEvents().offtime() > (dist * NoteEvent::NOTE_LENGTH)) {
                  // The previous tied note probably has events for this note too.
                  // That is, we don't need to render this note separately.
                  return false;
                  }
            }
      return true;
      }

//---------------------------------------------------------
//   renderChord
//    ontime and trailtime in 1/1000 of duration
//    ontime signifies how much gap to leave, i.e., how late the note should start because of graceNotesBefore which have already been rendered
//    trailtime signifies how much gap to leave after the note to allow for graceNotesAfter to be rendered
//---------------------------------------------------------

static QList<NoteEventList> renderChord(Chord* chord, int gateTime, int ontime, int trailtime)
      {
      QList<NoteEventList> ell;
      if (chord->notes().empty())
            return ell;

      size_t notes = chord->notes().size();
      for (size_t i = 0; i < notes; ++i)
            ell.append(NoteEventList());

      bool arpeggio = false;
      if (chord->tremolo()) {
            renderTremolo(chord, ell);
            }
      else if (chord->arpeggio() && chord->arpeggio()->playArpeggio()) {
            renderArpeggio(chord, ell);
            arpeggio = true;
            }
      else
            renderChordArticulation(chord, ell, gateTime);

      // Check each note and apply gateTime
      for (int i = 0; i < int(notes); ++i) {
            NoteEventList* el = &ell[i];
            if (!shouldRenderNote(chord->notes()[i])) {
                  el->clear();
                  continue;
                  }
            if (arpeggio)
                  continue; // don't add extra events and apply gateTime to arpeggio

            // If we are here then we still need to render the note.
            // Render its body if necessary and apply gateTime.
            if (el->size() == 0 && chord->tremoloChordType() != TremoloChordType::TremoloSecondNote) {
                  el->append(NoteEvent(0, ontime, 1000 - ontime - trailtime));
                  }
            if (trailtime == 0) // if trailtime is non-zero that means we have graceNotesAfter, so we don't need additional gate time.
                for (NoteEvent& e : ell[i])
                      e.setLen(e.len() * gateTime / 100);
            }
      return ell;
      }

//---------------------------------------------------------
//   createGraceNotesPlayEvent
// as a side effect of createGraceNotesPlayEvents, ontime and trailtime (passed by ref)
// are modified.  ontime reflects the time needed to play the grace-notes-before, and
// trailtime reflects the time for the grace-notes-after.  These are used by the caller
// to effect the on/off time of the main note
//---------------------------------------------------------

void Score::createGraceNotesPlayEvents(const Fraction& tick, Chord* chord, int& ontime, int& trailtime)
      {
      QVector<Chord*> gnb = chord->graceNotesBefore();
      QVector<Chord*> gna = chord->graceNotesAfter();
      int nb = gnb.size();
      int na = gna.size();
      if (0 == nb + na){
            return; // return immediately if no grace notes to deal with
            }
      // return immediately if the chord has a trill or articulation which effectively plays the graces notes.
      if (graceNotesMerged(chord)) {
            return;
            }
      // if there are graceNotesBefore and also graceNotesAfter, and the before grace notes are
      // not ACCIACCATURA, then the total time of all of them will be 50% of the time of the main note.
      // if the before grace notes are ACCIACCATURA then the grace notes after (if there are any).
      // get 50% of the time of the main note.
      // this is achieved by the two floating point weights: weighta and weightb whose total is 1.0
      // assuring that all the grace notes get the same duration, and their total is 50%.
      // exception is if the note is dotted or double-dotted; see below.
      float weighta = float(na) / (nb+na);
      float weightb = float(nb) / (nb+na);

      int graceDuration = 0;
      bool drumset = (getDrumset(chord) != nullptr);
      const qreal ticksPerSecond = tempo(tick) * MScore::division;
      const qreal chordTimeMS = (chord->actualTicks().ticks() / ticksPerSecond) * 1000;
      if (drumset) {
            int flamDuration = 15; //ms
            graceDuration = flamDuration / chordTimeMS * 1000; //ratio 1/1000 from the main note length
            ontime = graceDuration * nb;
            }
      else if (nb) {
            //
            //  render grace notes:
            //  simplified implementation:
            //  - grace notes start on the beat of the main note
            //  - duration: appoggiatura: 0.5  * duration of main note (2/3 for dotted notes, 4/7 for double-dotted)
            //              acciacatura: min of 0.5 * duration or 65ms fixed (independent of duration or tempo)
            //  - for appoggiaturas, the duration is divided by the number of grace notes
            //  - the grace note duration as notated does not matter
            //
            Chord* graceChord = gnb[0];
            if (graceChord->noteType() ==  NoteType::ACCIACCATURA) {
                  int graceTimeMS = 65 * nb;     // value determined empirically (TODO: make instrument-specific, like articulations)
                  // 1000 occurs below as a unit for ontime
                  ontime = qMin(500, static_cast<int>((graceTimeMS / chordTimeMS) * 1000));
                  weightb = 0.0;
                  weighta = 1.0;
                  }
            else if (chord->dots() == 1)
                  ontime = floor(667 * weightb);
            else if (chord->dots() == 2)
                  ontime = floor(571 * weightb);
            else
                  ontime = floor(500 * weightb);

            graceDuration = ontime / nb;
            }

      for (int i = 0, on = 0; i < nb; ++i) {
            QList<NoteEventList> el;
            Chord* gc = gnb.at(i);
            size_t nn = gc->notes().size();
            for (size_t ii = 0; ii < nn; ++ii) {
                  NoteEventList nel;
                  nel.append(NoteEvent(0, on, graceDuration));
                  el.append(nel);
                  }

            if (gc->playEventType() == PlayEventType::InvalidUser)
                  gc->score()->undo(new ChangeEventList(gc, el));
            else if (gc->playEventType() == PlayEventType::Auto) {
                  for (int ii = 0; ii < int(nn); ++ii)
                        gc->notes()[ii]->setPlayEvents(el[ii]);
                  }
            on += graceDuration;
            }
      if (na) {
            if (chord->dots() == 1)
                  trailtime = floor(667 * weighta);
            else if (chord->dots() == 2)
                  trailtime = floor(571 * weighta);
            else
                  trailtime = floor(500 * weighta);
            int graceDuration1 = trailtime / na;
            int on = 1000 - trailtime;
            for (int i = 0; i < na; ++i) {
                  QList<NoteEventList> el;
                  Chord* gc = gna.at(i);
                  size_t nn = gc->notes().size();
                  for (size_t ii = 0; ii < nn; ++ii) {
                        NoteEventList nel;
                        nel.append(NoteEvent(0, on, graceDuration1)); // NoteEvent(pitch,ontime,len)
                        el.append(nel);
                        }

                  if (gc->playEventType() == PlayEventType::InvalidUser)
                        gc->score()->undo(new ChangeEventList(gc, el));
                  else if (gc->playEventType() == PlayEventType::Auto) {
                        for (int ii = 0; ii < int(nn); ++ii)
                              gc->notes()[ii]->setPlayEvents(el[ii]);
                        }
                  on += graceDuration1;
                  }
            }
      }

//---------------------------------------------------------
//   createPlayEvents
//    create default play events
//---------------------------------------------------------

void Score::createPlayEvents(Chord* chord)
      {
      int gateTime = 100;

      Fraction tick = chord->tick();
      Slur* slur = 0;
      for (auto sp : _spanner.map()) {
            if (!sp.second->isSlur() || sp.second->staffIdx() != chord->staffIdx())
                  continue;
            Slur* s = toSlur(sp.second);
            if (tick >= s->tick() && tick < s->tick2()) {
                  slur = s;
                  break;
                  }
            }
      // gateTime is 100% for slured notes
      if (!slur) {
            Instrument* instr = chord->part()->instrument(tick);
            instr->updateGateTime(&gateTime, 0, "");
            }

      int ontime    = 0;
      int trailtime = 0;
      createGraceNotesPlayEvents(tick, chord, ontime, trailtime); // ontime and trailtime are modified by this call depending on grace notes before and after

      SwingParameters st = chord->staff()->swing(tick);
      int unit           = st.swingUnit;
      int ratio          = st.swingRatio;
      // Check if swing needs to be applied
      if (unit && !chord->tuplet()) {
            swingAdjustParams(chord, gateTime, ontime, unit, ratio);
            }
      //
      //    render normal (and articulated) chords
      //
      QList<NoteEventList> el = renderChord(chord, gateTime, ontime, trailtime);
      if (chord->playEventType() == PlayEventType::InvalidUser) {
            chord->score()->undo(new ChangeEventList(chord, el));
            }
      else if (chord->playEventType() == PlayEventType::Auto) {
            int n = int(chord->notes().size());
            for (int i = 0; i < n; ++i)
                  chord->notes()[i]->setPlayEvents(el[i]);
            }
      // don’t change event list if type is PlayEventType::User
      }

void Score::createPlayEvents(Measure* start, Measure* end)
      {
      if (!start)
            start = firstMeasure();

      int etrack = nstaves() * VOICES;
      for (int track = 0; track < etrack; ++track) {
            bool rangeEnded = false;
            for (Measure* m = start; m; m = m->nextMeasure()) {
                  constexpr SegmentType st = SegmentType::ChordRest;

                  if (m == end)
                        rangeEnded = true;
                  if (rangeEnded) {
                        // The range has ended, but we should collect events
                        // for tied notes. So we'll check if this is the case.
                        const Segment* seg = m->first(st);
                        const Element* e = seg->element(track);
                        bool tie = false;
                        if (e && e->isChord()) {
                              for (const Note* n : toChord(e)->notes()) {
                                    if (n->tieBack()) {
                                          tie = true;
                                          break;
                                          }
                                    }
                              }
                        if (!tie)
                              break;
                        }

                  // skip linked staves, except primary
                  if (!m->score()->staff(track / VOICES)->primaryStaff())
                        continue;
                  for (Segment* seg = m->first(st); seg; seg = seg->next(st)) {
                        Element* e = seg->element(track);
                        if (e == 0 || !e->isChord())
                              continue;
                        createPlayEvents(toChord(e));
                        }
                  }
            }
      }

//---------------------------------------------------------
//   renderMetronome
///   add metronome tick events
//---------------------------------------------------------

void MidiRenderer::renderMetronome(const Chunk& chunk, EventMap* events)
      {
      const int tickOffset = chunk.tickOffset();
      Measure* start = chunk.startMeasure();
      Measure* end = chunk.endMeasure();

      for (Measure* m = start; m != end; m = m->nextMeasure())
            renderMetronome(events, m, Fraction::fromTicks(tickOffset));
      }

//---------------------------------------------------------
//   renderMetronome
///   add metronome tick events
//---------------------------------------------------------

void MidiRenderer::renderMetronome(EventMap* events, Measure* m, const Fraction& tickOffset)
      {
      int msrTick         = m->tick().ticks();
      qreal tempo         = score->tempomap()->tempo(msrTick);
      TimeSigFrac timeSig = score->sigmap()->timesig(msrTick).nominal();

      int clickTicks      = timeSig.isBeatedCompound(tempo) ? timeSig.beatTicks() : timeSig.dUnitTicks();
      int endTick         = m->endTick().ticks();

      int rtick;

      if (m->isAnacrusis()) {
            int rem = m->ticks().ticks() % clickTicks;
            msrTick += rem;
            rtick = rem + timeSig.ticksPerMeasure() - m->ticks().ticks();
            }
      else
            rtick = 0;

      for (int tick = msrTick; tick < endTick; tick += clickTicks, rtick += clickTicks)
            events->insert(std::pair<int,NPlayEvent>(tick + tickOffset.ticks(), NPlayEvent(timeSig.rtick2beatType(rtick))));
      }

//---------------------------------------------------------
//   renderMidi
//    export score to event list
//---------------------------------------------------------

void Score::renderMidi(EventMap* events, const SynthesizerState& synthState)
      {
      renderMidi(events, true, MScore::playRepeats, synthState);
      }

void Score::renderMidi(EventMap* events, bool metronome, bool expandRepeats, const SynthesizerState& synthState)
      {
      masterScore()->setExpandRepeats(expandRepeats);
      MidiRenderer(this).renderScore(events, synthState, metronome);
      }

void MidiRenderer::renderScore(EventMap* events, const SynthesizerState& synthState, bool metronome)
      {
      updateState();
      for (const Chunk& chunk : chunks) {
            renderChunk(chunk, events, synthState, metronome);
            }
      }

void MidiRenderer::renderChunk(const Chunk& chunk, EventMap* events, const SynthesizerState& synthState, bool metronome)
      {
      // TODO: avoid doing it multiple times for the same measures
      score->createPlayEvents(chunk.startMeasure(), chunk.endMeasure());

      score->updateChannel();
      score->updateVelo();

      SynthesizerState s = score->synthesizerState();
      int method = s.method();
      int cc = s.ccToUse();

      // check if the score synth settings are actually set
      // if not, use the global synth state
      if (method == -1) {
            method = synthState.method();
            cc = synthState.ccToUse();

            if (method == -1) {
                  // fall back to defaults - this may be needed to pass tests,
                  // since sometimes the synth state is not init
                  method = 1;
                  cc = 2;
                  qWarning("Had to fall back to defaults to render measure");
                  }
            }

      DynamicsRenderMethod renderMethod = DynamicsRenderMethod::SIMPLE;
      switch (method) {
            case 0:
                  renderMethod = DynamicsRenderMethod::SIMPLE;
                  break;
            case 1:
                  renderMethod = DynamicsRenderMethod::SEG_START;
                  break;
            case 2:
                  renderMethod = DynamicsRenderMethod::FIXED_MAX;
                  break;
            default:
                  qWarning("Unrecognized dynamics method: %d", method);
                  break;
            }

      // create note & other events
      for (Staff* st : score->staves())
            renderStaffChunk(chunk, events, st, renderMethod, cc);
      events->fixupMIDI();

      // create sustain pedal events
      renderSpanners(chunk, events);

      if (metronome)
            renderMetronome(chunk, events);
      }

//---------------------------------------------------------
//   MidiRenderer::updateState
//---------------------------------------------------------

void MidiRenderer::updateState()
      {
      if (needUpdate) {
            // Update the related structures inside score
            // to avoid doing it multiple times on chunks rendering
            score->updateSwing();
            score->updateCapo();

            updateChunksPartition();

            needUpdate = false;
            }
      }

//---------------------------------------------------------
//   MidiRenderer::canBreakChunk
///   Helper function for updateChunksPartition
///   Determines whether it is allowed to break MIDI
///   rendering chunk at given measure.
//---------------------------------------------------------

bool MidiRenderer::canBreakChunk(const Measure* last)
      {
      Score* score = last->score();

      // Check for hairpins that overlap measure end:
      // hairpins should be inside one chunk, if possible
      const int endTick = last->endTick().ticks();
      const auto& spanners = score->spannerMap().findOverlapping(endTick - 1, endTick);
      for (const auto& interval : spanners) {
            const Spanner* sp = interval.value;
            if (sp->isHairpin() && sp->tick2().ticks() > endTick)
                  return false;
            }

      // Repeat measures rely on the previous measure
      // being properly rendered, disallow breaking
      // chunk at repeat measure.
      if (const Measure* next = last->nextMeasure())
            for (const Staff* staff : score->staves()) {
                  if (next->isRepeatMeasure(staff))
                        return false;
            }

      return true;
      }

//---------------------------------------------------------
//   MidiRenderer::updateChunksPartition
//---------------------------------------------------------

void MidiRenderer::updateChunksPartition()
      {
      chunks.clear();

      const RepeatList& repeatList = score->repeatList();

      for (const RepeatSegment* rs : repeatList) {
            const int tickOffset = rs->utick - rs->tick;

            if (!minChunkSize) {
                  // just make chunks corresponding to repeat segments
                  chunks.emplace_back(tickOffset, rs->firstMeasure(), rs->lastMeasure());
                  continue;
                  }

            Measure* end = rs->lastMeasure()->nextMeasure();
            int count = 0;
            bool needBreak = false;
            Measure* chunkStart = nullptr;
            for (Measure* m = rs->firstMeasure(); m != end; m = m->nextMeasure()) {
                  if (!chunkStart)
                        chunkStart = m;
                  if ((++count) >= minChunkSize)
                        needBreak = true;
                  if (needBreak && canBreakChunk(m)) {
                        chunks.emplace_back(tickOffset, chunkStart, m);
                        chunkStart = nullptr;
                        needBreak = false;
                        count = 0;
                        }
                  }
            if (chunkStart) // last measures did not get added to chunk list
                  chunks.emplace_back(tickOffset, chunkStart, rs->lastMeasure());
            }

      if (score != repeatList.score()) {
            // Repeat list may belong to another linked score (e.g. MasterScore).
            // Update chunks to make them contain measures from the currently
            // rendered score.
            for (Chunk& ch : chunks) {
                  Measure* first = score->tick2measure(ch.startMeasure()->tick());
                  Measure* last = score->tick2measure(ch.lastMeasure()->tick());
                  ch = Chunk(ch.tickOffset(), first, last);
                  }
            }
      }

//---------------------------------------------------------
//   MidiRenderer::getChunkAt
//---------------------------------------------------------

MidiRenderer::Chunk MidiRenderer::getChunkAt(int utick)
      {
      updateState();

      auto it = std::upper_bound(chunks.begin(), chunks.end(), utick, [](int utick, const Chunk& ch) { return utick < ch.utick1(); });
      if (it == chunks.begin())
            return Chunk();
      --it;
      const Chunk& ch = *it;
      if (ch.utick2() <= utick)
            return Chunk();
      return ch;
      }

//---------------------------------------------------------
//   RangeMap::setOccupied
//---------------------------------------------------------

void RangeMap::setOccupied(int tick1, int tick2)
      {
      auto it1 = status.upper_bound(tick1);
      const bool beforeBegin = (it1 == status.begin());
      if (beforeBegin || (--it1)->second != Range::BEGIN) {
            if (!beforeBegin && it1->first == tick1)
                  status.erase(it1);
            else
                  status.insert(std::make_pair(tick1, Range::BEGIN));
            }

      const auto it2 = status.lower_bound(tick2);
      const bool afterEnd = (it2 == status.end());
      if (afterEnd || it2->second != Range::END) {
            if (!afterEnd && it2->first == tick2)
                  status.erase(it2);
            else
                  status.insert(std::make_pair(tick2, Range::END));
            }
      }

//---------------------------------------------------------
//   RangeMap::occupiedRangeEnd
//---------------------------------------------------------

int RangeMap::occupiedRangeEnd(int tick) const
      {
      const auto it = status.upper_bound(tick);
      if (it == status.begin())
            return tick;
      const int rangeEnd = (it == status.end()) ? tick : it->first;
      if (it->second == Range::END)
            return rangeEnd;
      return tick;
      }
}
