    //=============================================================================
    //  MuseScore
    //  Linux Music Score Editor
    //  $Id: seq.cpp 5660 2012-05-22 14:17:39Z wschweer $
    //
    //  Copyright (C) 2002-2011 Werner Schweer and others
    //
    //  This program is free software; you can redistribute it and/or modify
    //  it under the terms of the GNU General Public License version 2.
    //
    //  This program is distributed in the hope that it will be useful,
    //  but WITHOUT ANY WARRANTY; without even the implied warranty of
    //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    //  GNU General Public License for more details.
    //
    //  You should have received a copy of the GNU General Public License
    //  along with this program; if not, write to the Free Software
    //  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
    //=============================================================================

    #include "config.h"
    #include "seq.h"
    #include "musescore.h"

    #include "synthesizer/msynthesizer.h"
    #include "libmscore/slur.h"
    #include "libmscore/tie.h"
    #include "libmscore/score.h"
    #include "libmscore/segment.h"
    #include "libmscore/note.h"
    #include "libmscore/chord.h"
    #include "libmscore/tempo.h"
    #include "scoreview.h"
    #include "playpanel.h"
    #include "libmscore/staff.h"
    #include "libmscore/measure.h"
    #include "preferences.h"
    #include "libmscore/part.h"
    #include "libmscore/ottava.h"
    #include "libmscore/utils.h"
    #include "libmscore/repeatlist.h"
    #include "libmscore/audio.h"
    #include "synthcontrol.h"
    #include "pianoroll.h"
    #include "pianotools.h"

    #include "click.h"

    #include <vorbis/vorbisfile.h>

    #if defined(Q_OS_MAC) || defined(Q_OS_WIN)
      #include "portmidi/porttime/porttime.h"
    #else
      #include <porttime.h>
    #endif

    namespace Ms {

    Seq* seq;

    static const int guiRefresh   = 10;       // Hz
    static const int peakHoldTime = 1400;     // msec
    static const int peakHold     = (peakHoldTime * guiRefresh) / 1000;
    static OggVorbis_File vf;

    #if 0 // yet(?) unused
    static const int AUDIO_BUFFER_SIZE = 1024 * 512;  // 2 MB
    #endif

    //---------------------------------------------------------
    //   VorbisData
    //---------------------------------------------------------

    struct VorbisData {
          int pos;          // current position in audio->data()
          QByteArray data;
          };

    static VorbisData vorbisData;

    static size_t ovRead(void* ptr, size_t size, size_t nmemb, void* datasource);
    static int ovSeek(void* datasource, ogg_int64_t offset, int whence);
    static long ovTell(void* datasource);

    static ov_callbacks ovCallbacks = {
          ovRead, ovSeek, 0, ovTell
          };

    //---------------------------------------------------------
    //   ovRead
    //---------------------------------------------------------

    static size_t ovRead(void* ptr, size_t size, size_t nmemb, void* datasource)
          {
          VorbisData* vd = (VorbisData*)datasource;
          size_t n = size * nmemb;
          if (vd->data.size() < int(vd->pos + n))
                n = vd->data.size() - vd->pos;
          if (n) {
                const char* src = vd->data.data() + vd->pos;
                memcpy(ptr, src, n);
                vd->pos += n;
                }
          return n;
          }

    //---------------------------------------------------------
    //   ovSeek
    //---------------------------------------------------------

    static int ovSeek(void* datasource, ogg_int64_t offset, int whence)
          {
          VorbisData* vd = (VorbisData*)datasource;
          switch(whence) {
                case SEEK_SET:
                      vd->pos = offset;
                      break;
                case SEEK_CUR:
                      vd->pos += offset;
                      break;
                case SEEK_END:
                      vd->pos = vd->data.size() - offset;
                      break;
                }
          return 0;
          }

    //---------------------------------------------------------
    //   ovTell
    //---------------------------------------------------------

    static long ovTell(void* datasource)
          {
          VorbisData* vd = (VorbisData*)datasource;
          return vd->pos;
          }

    //---------------------------------------------------------
    //   Seq
    //---------------------------------------------------------

    Seq::Seq()
          {
          running         = false;
          playlistChanged = false;
          cs              = 0;
          cv              = 0;
          tackRemain        = 0;
          tickRemain        = 0;
          maxMidiOutPort  = 0;

          endUTick  = 0;
          state    = Transport::STOP;
          oggInit  = false;
          _driver  = 0;
          playPos  = events.cbegin();
          playFrame  = 0;
          metronomeVolume = 0.3;
          useJackTransportSavedFlag = false;

          inCountIn         = false;
          countInPlayPos    = countInEvents.cbegin();
          countInPlayFrame  = 0;

          meterValue[0]     = 0.0;
          meterValue[1]     = 0.0;
          meterPeakValue[0] = 0.0;
          meterPeakValue[1] = 0.0;
          peakTimer[0]       = 0;
          peakTimer[1]       = 0;

          heartBeatTimer = new QTimer(this);
          connect(heartBeatTimer, SIGNAL(timeout()), this, SLOT(heartBeatTimeout()));

          noteTimer = new QTimer(this);
          noteTimer->setSingleShot(true);
          connect(noteTimer, SIGNAL(timeout()), this, SLOT(stopNotes()));
          noteTimer->stop();

          connect(this, SIGNAL(toGui(int, int)), this, SLOT(seqMessage(int, int)), Qt::QueuedConnection);

          prevTimeSig.setNumerator(0);
          prevTempo = 0;
          connect(this, SIGNAL(timeSigChanged()),this,SLOT(handleTimeSigTempoChanged()));
          connect(this, SIGNAL(tempoChanged()),this,SLOT(handleTimeSigTempoChanged()));

          initialMillisecondTimestampWithLatency = 0;
          }

    //---------------------------------------------------------
    //   Seq
    //---------------------------------------------------------

    Seq::~Seq()
          {
          delete _driver;
          }

    //---------------------------------------------------------
    //   setScoreView
    //---------------------------------------------------------

    void Seq::setScoreView(ScoreView* v)
          {
          if (oggInit) {
                ov_clear(&vf);
                oggInit = false;
                }
          if (cv !=v && cs) {
                unmarkNotes();
                stopWait();
                }
          cv = v;
          if (cs)
                disconnect(cs, SIGNAL(playlistChanged()), this, SLOT(setPlaylistChanged()));
          cs = cv ? cv->score()->masterScore() : 0;

          if (!heartBeatTimer->isActive())
                heartBeatTimer->start(20);    // msec

          playlistChanged = true;
          _synti->reset();
          if (cs) {
                initInstruments();
                connect(cs, SIGNAL(playlistChanged()), this, SLOT(setPlaylistChanged()));
                }
          }

    //---------------------------------------------------------
    //   init
    //    return false on error
    //---------------------------------------------------------

    bool Seq::init(bool hotPlug)
          {
          if (!_driver || !_driver->start(hotPlug)) {
                qDebug("Cannot start I/O");
                running = false;
                return false;
                }
          running = true;
          return true;
          }

    //---------------------------------------------------------
    //   exit
    //---------------------------------------------------------

    void Seq::exit()
          {
          if (_driver) {
                if (MScore::debugMode)
                      qDebug("Stop I/O");
                stopWait();
                delete _driver;
                _driver = 0;
                }
          }

    //---------------------------------------------------------
    //   rewindStart
    //---------------------------------------------------------

    void Seq::rewindStart()
          {
          seek(0);
          }

    //---------------------------------------------------------
    //   loopStart
    //---------------------------------------------------------

    void Seq::loopStart()
          {
          start();
    //      qDebug("LoopStart. playPos = %d", playPos);
          }

    //---------------------------------------------------------
    //   canStart
    //    return true if sequencer can be started
    //---------------------------------------------------------

    bool Seq::canStart()
          {
          if (!_driver)
                return false;
          if (playlistChanged)
                collectEvents();
          return (!events.empty() && endUTick != 0);
          }

    //---------------------------------------------------------
    //   start
    //    called from gui thread
    //---------------------------------------------------------

    void Seq::start()
          {
          if (!_driver) {
                qDebug("No driver!");
                return;
                }

          if (playlistChanged)
                collectEvents();
          if (cs->playMode() == PlayMode::AUDIO) {
                if (!oggInit) {
                      vorbisData.pos  = 0;
                      vorbisData.data = cs->audio()->data();
                      int n = ov_open_callbacks(&vorbisData, &vf, 0, 0, ovCallbacks);
                      if (n < 0) {
                            qDebug("ogg open failed: %d", n);
                            }
                      oggInit = true;
                      }
                }
          if ((mscore->loop())) {
                if (cs->selection().isRange())
                      setLoopSelection();
                if (!preferences.useJackTransport || (preferences.useJackTransport && state == Transport::STOP))
                      seek(cs->repeatList()->tick2utick(cs->loopInTick()));
                }
          else {
                if (!preferences.useJackTransport || (preferences.useJackTransport && state == Transport::STOP))
                      seek(cs->repeatList()->tick2utick(cs->playPos()));
                }
          if (preferences.useJackTransport && mscore->countIn() && state == Transport::STOP) {
                // Ready to start playing count in, switching to fake transport
                // to prevent playing in other applications with our ticks simultaneously
                useJackTransportSavedFlag    = true;
                preferences.useJackTransport = false;
                }
          _driver->startTransport();
          }

    //---------------------------------------------------------
    //   stop
    //    called from gui thread
    //---------------------------------------------------------

    void Seq::stop()
          {
          if (state == Transport::STOP)
                return;

          if (oggInit) {
                ov_clear(&vf);
                oggInit = false;
                }
          if (!_driver)
                return;
          if (!preferences.useJackTransport || (preferences.useJackTransport && _driver->getState() == Transport::PLAY))
                _driver->stopTransport();
          if (cv)
                cv->setCursorOn(false);
          if (cs) {
    //??            cs->setLayoutAll();
                cs->setUpdateAll();
                cs->update();
                }
          }

    //---------------------------------------------------------
    //   stopWait
    //---------------------------------------------------------

    void Seq::stopWait()
          {
          stop();
          QWaitCondition sleep;
          int idx = 0;
          while (state != Transport::STOP) {
                qDebug("State %d", (int)state);
                mutex.lock();
                sleep.wait(&mutex, 100);
                mutex.unlock();
                ++idx;
                Q_ASSERT(idx <= 10);
                }
          }

    //---------------------------------------------------------
    //   seqStarted
    //---------------------------------------------------------

    void MuseScore::seqStarted()
          {
          if (cv)
                cv->setCursorOn(true);
          if (cs)
                cs->update();
          }

    //---------------------------------------------------------
    //   seqStopped
    //    JACK has stopped
    //    executed in gui environment
    //---------------------------------------------------------

    void MuseScore::seqStopped()
          {
          cv->setCursorOn(false);
          }

    //---------------------------------------------------------
    //   unmarkNotes
    //---------------------------------------------------------

    void Seq::unmarkNotes()
          {
          foreach(const Note* n, markedNotes) {
                n->setMark(false);
                cs->addRefresh(n->canvasBoundingRect());
                }
          markedNotes.clear();
          PianoTools* piano = mscore->pianoTools();
          if (piano && piano->isVisible())
                piano->heartBeat(markedNotes);
          }

    //---------------------------------------------------------
    //   guiStop
    //---------------------------------------------------------

    void Seq::guiStop()
          {
          QAction* a = getAction("play");
          a->setChecked(false);

          unmarkNotes();
          if (!cs)
                return;

          cs->setPlayPos(cs->repeatList()->utick2tick(cs->utime2utick(qreal(playFrame) / qreal(MScore::sampleRate))));
          cs->update();
          emit stopped();
          }

    //---------------------------------------------------------
    //   seqSignal
    //    sequencer message to GUI
    //    execution environment: gui thread
    //---------------------------------------------------------

    void Seq::seqMessage(int msg, int arg)
          {
          switch(msg) {
                case '5': {
                      // Update the screen after seeking from the realtime thread
                      Segment* seg = cs->tick2segment(arg);
                      if (seg)
                            mscore->currentScoreView()->moveCursor(seg->tick());
                      cs->setPlayPos(arg);
                      cs->update();
                      break;
                      }
                case '4':   // Restart the playback at the end of the score
                      loopStart();
                      break;
                case '3':   // Loop restart while playing
                      seek(cs->repeatList()->tick2utick(cs->loopInTick()));
                      break;
                case '2':
                      guiStop();
    //                  heartBeatTimer->stop();
                      if (_driver && mscore->getSynthControl()) {
                            meterValue[0]     = .0f;
                            meterValue[1]     = .0f;
                            meterPeakValue[0] = .0f;
                            meterPeakValue[1] = .0f;
                            peakTimer[0]       = 0;
                            peakTimer[1]       = 0;
                            mscore->getSynthControl()->setMeter(0.0, 0.0, 0.0, 0.0);
                            }
                      seek(0);
                      break;
                case '0':         // STOP
                      guiStop();
    //                  heartBeatTimer->stop();
                      if (_driver && mscore->getSynthControl()) {
                            meterValue[0]     = .0f;
                            meterValue[1]     = .0f;
                            meterPeakValue[0] = .0f;
                            meterPeakValue[1] = .0f;
                            peakTimer[0]       = 0;
                            peakTimer[1]       = 0;
                            mscore->getSynthControl()->setMeter(0.0, 0.0, 0.0, 0.0);
                            }
                      break;

                case '1':         // PLAY
                      emit started();
    //                  heartBeatTimer->start(1000/guiRefresh);
                      break;

                default:
                      qDebug("MScore::Seq:: unknown seq msg %d", msg);
                      break;
                }
          }

    //---------------------------------------------------------
    //   playEvent
    //    send one event to the synthesizer
    //---------------------------------------------------------

    void Seq::playEvent(const NPlayEvent& event, unsigned framePos)
          {
          int type = event.type();
          if (type == ME_NOTEON) {
                bool mute = false;
                const Note* note = event.note();

                if (note) {
                      Staff* staff      = note->staff();
                      Instrument* instr = staff->part()->instrument(note->chord()->tick());
                      const Channel* a = instr->channel(note->subchannel());
                      mute = a->mute || a->soloMute || !staff->playbackVoice(note->voice());
                      }
                if (!mute)
                      putEvent(event, framePos);
                }
          else if (type == ME_CONTROLLER || type == ME_PITCHBEND)
                putEvent(event, framePos);
          }

    //---------------------------------------------------------
    //   recomputeMaxMidiOutPort
    //   Computes the maximum number of midi out ports
    //   in all opened scores
    //---------------------------------------------------------

    void Seq::recomputeMaxMidiOutPort()
          {
          if (!(preferences.useJackMidi || preferences.useAlsaAudio))
                return;
          int max = 0;
          for (Score * s : MuseScoreCore::mscoreCore->scores()) {
                if (s->masterScore()->midiPortCount() > max)
                      max = s->masterScore()->midiPortCount();
                }
          maxMidiOutPort = max;
          }

    //---------------------------------------------------------
    //   processMessages
    //   from gui to process thread
    //---------------------------------------------------------

    void Seq::processMessages()
          {
          for (;;) {
                if (toSeq.empty())
                      break;
                SeqMsg msg = toSeq.dequeue();
                switch(msg.id) {
                      case SeqMsgId::TEMPO_CHANGE:
                            {
                            if (!cs)
                                  continue;
                            if (playFrame != 0) {
                                  int utick = cs->utime2utick(qreal(playFrame) / qreal(MScore::sampleRate));
                                  cs->tempomap()->setRelTempo(msg.realVal);
                                  playFrame = cs->utick2utime(utick) * MScore::sampleRate;
                                  if (preferences.jackTimebaseMaster && preferences.useJackTransport)
                                        _driver->seekTransport(utick + 2 * cs->utime2utick(qreal((_driver->bufferSize()) + 1) / qreal(MScore::sampleRate)));
                                  }
                            else
                                  cs->tempomap()->setRelTempo(msg.realVal);
                            cs->repeatList()->update();
                            prevTempo = curTempo();
                            emit tempoChanged();
                            }
                            break;
                      case SeqMsgId::PLAY:
                            putEvent(msg.event);
                            break;
                      case SeqMsgId::SEEK:
                            setPos(msg.intVal);
                            break;
                      default:
                            break;
                      }
                }
          }

    //---------------------------------------------------------
    //   metronome
    //---------------------------------------------------------

    void Seq::metronome(unsigned n, float* p, bool force)
          {
          if (!mscore->metronome() && !force) {
                tickRemain = 0;
                tackRemain = 0;
                return;
                }
          if (tickRemain) {
                tackRemain = 0;
                int idx = tickLength - tickRemain;
                int nn = n < tickRemain ? n : tickRemain;
                for (int i = 0; i < nn; ++i) {
                      qreal v = tick[idx] * tickVolume * metronomeVolume;
                      *p++ += v;
                      *p++ += v;
                      ++idx;
                      }
                tickRemain -= nn;
                }
          if (tackRemain) {
                int idx = tackLength - tackRemain;
                int nn = n < tackRemain ? n : tackRemain;
                for (int i = 0; i < nn; ++i) {
                      qreal v = tack[idx] * tackVolume * metronomeVolume;
                      *p++ += v;
                      *p++ += v;
                      ++idx;
                      }
                tackRemain -= nn;
                }
          }

    //---------------------------------------------------------
    //   addCountInClicks
    //---------------------------------------------------------

    void Seq::addCountInClicks()
          {
          const int   plPos       = cs->playPos();
          Measure*    m           = cs->tick2measure(plPos);
          const int   msrTick     = m->tick();
          const qreal tempo       = cs->tempomap()->tempo(msrTick);
          TimeSigFrac timeSig     = cs->sigmap()->timesig(msrTick).nominal();

          const int clickTicks = timeSig.isBeatedCompound(tempo) ? timeSig.beatTicks() : timeSig.dUnitTicks();

          // add at least one full measure of just clicks.
          int endTick = timeSig.ticksPerMeasure();

          // add extra clicks if...
          endTick += plPos - msrTick;   // ...not starting playback at beginning of measure

          if (m->isAnacrusis())         // ...measure is incomplete (anacrusis)
                endTick += timeSig.ticksPerMeasure() - m->ticks();

          for (int tick = 0; tick < endTick; tick += clickTicks) {
                const int rtick = tick % timeSig.ticksPerMeasure();
                countInEvents.insert(std::pair<int,NPlayEvent>(tick, NPlayEvent(timeSig.rtick2beatType(rtick))));
                }

          NPlayEvent event;
          event.setType(ME_INVALID);
          event.setPitch(0);
          countInEvents.insert( std::pair<int,NPlayEvent>(endTick, event));
          // initialize play parameters to count-in events
          countInPlayPos  = countInEvents.cbegin();
          countInPlayFrame = 0;
          }

    //-------------------------------------------------------------------
    //   process
    //    This function is called in a realtime context. This
    //    means that no blocking operations are allowed which
    //    includes memory allocation. The usual thread synchronisation
    //    methods like semaphores can also not be used.
    //-------------------------------------------------------------------

    void Seq::process(unsigned framesPerPeriod, float* buffer)
          {
          unsigned framesRemain = framesPerPeriod; // the number of frames remaining to be processed by this call to Seq::process
          Transport driverState = _driver->getState();
          // Checking for the reposition from JACK Transport
          _driver->checkTransportSeek(playFrame, framesRemain, inCountIn);

          if (driverState != state) {
                // Got a message from JACK Transport panel: Play
                if (state == Transport::STOP && driverState == Transport::PLAY) {
                      if ((preferences.useJackMidi || preferences.useJackAudio) && !getAction("play")->isChecked()) {
                            // Do not play while editing elements
                            if (mscore->state() != STATE_NORMAL || !isRunning() || !canStart())
                                  return;
                            getAction("play")->setChecked(true);
                            getAction("play")->triggered(true);

                            // If we just launch MuseScore and press "Play" on JACK Transport with time 0:00
                            // MuseScore doesn't seek to 0 and guiPos is uninitialized, so let's make it manually
                            if (preferences.useJackTransport && getCurTick() == 0)
                                  seekRT(0);

                            // Switching to fake transport while playing count in
                            // to prevent playing in other applications with our ticks simultaneously
                            if (preferences.useJackTransport && mscore->countIn()) {
                                  // Stopping real JACK Transport
                                  _driver->stopTransport();
                                  // Starting fake transport
                                  useJackTransportSavedFlag = preferences.useJackTransport;
                                  preferences.useJackTransport = false;
                                  _driver->startTransport();
                                  }
                            }
                      // Initializing instruments every time we start playback.
                      // External synth can have wrong values, for example
                      // if we switch between scores
                      initInstruments(true);
                      // Need to change state after calling collectEvents()
                      state = Transport::PLAY;
                      if (mscore->countIn() && cs->playMode() == PlayMode::SYNTHESIZER) {
                            countInEvents.clear();
                            inCountIn = true;
                            }
                      emit toGui('1');
                      }
                // Got a message from JACK Transport panel: Stop
                else if (state == Transport::PLAY && driverState == Transport::STOP) {
                      state = Transport::STOP;
                      // Muting all notes
                      stopNotes(-1, true);
                      initInstruments(true);
                      if (playPos == events.cend()) {
                            if (mscore->loop()) {
                                  qDebug("Seq.cpp - Process - Loop whole score. playPos = %d, cs->pos() = %d", playPos->first, cs->pos());
                                  emit toGui('4');
                                  return;
                                  }
                            else {
                                  emit toGui('2');
                                  }
                            }
                      else {
                         emit toGui('0');
                         }
                      }
                else if (state != driverState)
                      qDebug("Seq: state transition %d -> %d ?",
                         (int)state, (int)driverState);
                }

          memset(buffer, 0, sizeof(float) * framesPerPeriod * 2); // assume two channels
          float* p = buffer;

          processMessages();

          if (state == Transport::PLAY) {
                if (!cs)
                      return;

                // if currently in count-in, these pointers will reference data in the count-in
                EventMap::const_iterator* pPlayPos   = &playPos;
                EventMap*                 pEvents    = &events;
                int*                      pPlayFrame = &playFrame;
                if (inCountIn) {
                      if (countInEvents.size() == 0)
                            addCountInClicks();
                      pEvents    = &countInEvents;
                      pPlayPos   = &countInPlayPos;
                      pPlayFrame = &countInPlayFrame;
                      }

                //
                // play events for one segment
                //
                unsigned framePos = 0; // frame currently being processed relative to the first frame of this call to Seq::process
                int periodEndFrame = *pPlayFrame + framesPerPeriod; // the ending frame (relative to start of playback) of the period being processed by this call to Seq::process
                int scoreEndUTick = cs->repeatList()->tick2utick(cs->lastMeasure()->endTick());
                while (*pPlayPos != pEvents->cend()) {
                      int playPosUTick = (*pPlayPos)->first;
                      int n; // current frame (relative to start of playback) that is being synthesized

                      if (inCountIn) {
                            qreal beatsPerSecond = curTempo() * cs->tempomap()->relTempo(); // relTempo needed here to ensure that bps changes as we slide the tempo bar
                            qreal ticksPerSecond = beatsPerSecond * MScore::division;
                            qreal playPosSeconds = playPosUTick / ticksPerSecond;
                            int playPosFrame = playPosSeconds * MScore::sampleRate;
                            if (playPosFrame >= periodEndFrame)
                                  break;
                            n = playPosFrame - *pPlayFrame;
                            if (n < 0) {
                                  qDebug("Count-in: playPosUTick %d: n = %d - %d", playPosUTick, playPosFrame, *pPlayFrame);
                                  n = 0;
                                  }
                            }
                      else {
                            qreal playPosSeconds = cs->utick2utime(playPosUTick);
                            int playPosFrame = playPosSeconds * MScore::sampleRate;
                            if (playPosFrame >= periodEndFrame)
                                  break;
                            n = playPosFrame - *pPlayFrame;
                            if (n < 0) {
                                  qDebug("%d:  %d - %d", playPosUTick, playPosFrame, *pPlayFrame);
                                  n = 0;
                                  }
                            if (mscore->loop()) {
                                  int loopOutUTick = cs->repeatList()->tick2utick(cs->loopOutTick());
                                  if (loopOutUTick < scoreEndUTick) {
                                        // Also make sure we are not "before" the loop
                                        if (playPosUTick >= loopOutUTick || cs->repeatList()->utick2tick(playPosUTick) < cs->loopInTick()) {
                                              qDebug ("Process: playPosUTick = %d, cs->loopInTick() = %d, cs->loopOutTick() = %d, getCurTick() = %d, loopOutUTick = %d, playFrame = %d",
                                                                playPosUTick,      cs->loopInTick(),      cs->loopOutTick(),      getCurTick(),      loopOutUTick,    *pPlayFrame);
                                              if (preferences.useJackTransport) {
                                                    int loopInUTick = cs->repeatList()->tick2utick(cs->loopInTick());
                                                    _driver->seekTransport(loopInUTick);
                                                    if (loopInUTick != 0) {
                                                          int seekto = loopInUTick - 2 * cs->utime2utick((qreal)_driver->bufferSize() / MScore::sampleRate);
                                                          seekRT((seekto > 0) ? seekto : 0 );
                                                          }
                                                    }
                                              else {
                                                    emit toGui('3');
                                                    }
                                              // Exit this function to avoid segmentation fault in Scoreview
                                              return;
                                              }
                                        }
                                  }
                            }
                      if (n) {
                            if (cs->playMode() == PlayMode::SYNTHESIZER) {
                                  metronome(n, p, inCountIn);
                                  _synti->process(n, p);
                                  p += n * 2;
                                  *pPlayFrame  += n;
                                  framesRemain -= n;
                                  framePos     += n;
                                  }
                            else {
                                  while (n > 0) {
                                        int section;
                                        float** pcm;
                                        long rn = ov_read_float(&vf, &pcm, n, &section);
                                        if (rn == 0)
                                              break;
                                        for (int i = 0; i < rn; ++i) {
                                              *p++ = pcm[0][i];
                                              *p++ = pcm[1][i];
                                              }
                                        *pPlayFrame  += rn;
                                        framesRemain -= rn;
                                        framePos     += rn;
                                        n            -= rn;
                                        }
                                  }
                            }
                      const NPlayEvent& event = (*pPlayPos)->second;
                      playEvent(event, framePos);
                      if (event.type() == ME_TICK1) {
                            tickRemain = tickLength;
                            tickVolume = event.velo() ? qreal(event.value()) / 127.0 : 1.0;
                            }
                      else if (event.type() == ME_TICK2) {
                            tackRemain = tackLength;
                            tackVolume = event.velo() ? qreal(event.value()) / 127.0 : 1.0;
                            }
                      mutex.lock();
                      ++(*pPlayPos);
                      mutex.unlock();
                      }
                if (framesRemain) {
                      if (cs->playMode() == PlayMode::SYNTHESIZER) {
                            metronome(framesRemain, p, inCountIn);
                            _synti->process(framesRemain, p);
                            *pPlayFrame += framesRemain;
                            }
                      else {
                            int n = framesRemain;
                            while (n > 0) {
                                  int section;
                                  float** pcm;
                                  long rn = ov_read_float(&vf, &pcm, n, &section);
                                  if (rn == 0)
                                        break;
                                  for (int i = 0; i < rn; ++i) {
                                        *p++ = pcm[0][i];
                                        *p++ = pcm[1][i];
                                        }
                                  *pPlayFrame  += rn;
                                  framesRemain -= rn;
                                  framePos     += rn;
                                  n            -= rn;
                                  }
                            }
                      }
                if (*pPlayPos == pEvents->cend()) {
                      if (inCountIn) {
                            inCountIn = false;
                            // Connecting to JACK Transport if MuseScore was temporarily disconnected from it
                            if (useJackTransportSavedFlag) {
                                  // Stopping fake driver
                                  _driver->stopTransport();
                                  preferences.useJackTransport = true;
                                  // Starting the real JACK Transport. All applications play in sync now
                                  _driver->startTransport();
                                  }
                            }
                      else
                            _driver->stopTransport();
                      }
                }
          else {
                // Outside of playback mode
                while (!liveEventQueue()->empty()) {
                      const NPlayEvent& event = liveEventQueue()->dequeue();
                      if (event.type() == ME_TICK1) {
                            tickRemain = tickLength;
                            tickVolume = event.velo() ? qreal(event.value()) / 127.0 : 1.0;
                            }
                      else if (event.type() == ME_TICK2) {
                            tackRemain = tackLength;
                            tackVolume = event.velo() ? qreal(event.value()) / 127.0 : 1.0;
                            }
                      }
                if (framesRemain) {
                      metronome(framesRemain, p, true);
                      _synti->process(framesRemain, p);
                      }
                }
          //
          // metering / master gain
          //
          qreal lv = 0.0f;
          qreal rv = 0.0f;
          p = buffer;
          for (unsigned i = 0; i < framesRemain; ++i) {
                qreal val = *p;
                lv = qMax(lv, qAbs(val));
                p++;

                val = *p;
                rv = qMax(rv, qAbs(val));
                p++;
                }
          meterValue[0] = lv;
          meterValue[1] = rv;
          if (meterPeakValue[0] < lv) {
                meterPeakValue[0] = lv;
                peakTimer[0] = 0;
                }
          if (meterPeakValue[1] < rv) {
                meterPeakValue[1] = rv;
                peakTimer[1] = 0;
                }
          }

    //---------------------------------------------------------
    //   initInstruments
    //---------------------------------------------------------

    void Seq::initInstruments(bool realTime)
          {
          // Add midi out ports if necessary
          if (cs && (preferences.useJackMidi || preferences.useAlsaAudio)) {
                // Increase the maximum number of midi ports if user adds staves/instruments
                int scoreMaxMidiPort = cs->masterScore()->midiPortCount();
                if (maxMidiOutPort < scoreMaxMidiPort)
                      maxMidiOutPort = scoreMaxMidiPort;
                // if maxMidiOutPort is equal to existing ports number, it will do nothing
                if (_driver)
                      _driver->updateOutPortCount(maxMidiOutPort + 1);
                }

          foreach(const MidiMapping& mm, *cs->midiMapping()) {
                Channel* channel = mm.articulation;
                foreach(const MidiCoreEvent& e, channel->init) {
                      if (e.type() == ME_INVALID)
                            continue;
                      NPlayEvent event(e.type(), channel->channel, e.dataA(), e.dataB());
                      if (realTime)
                            putEvent(event);
                      else
                            sendEvent(event);
                      }
                // Setting pitch bend sensitivity to 12 semitones for external synthesizers
                if ((preferences.useJackMidi || preferences.useAlsaAudio) && mm.channel != 9) {
                      if (realTime) {
                            putEvent(NPlayEvent(ME_CONTROLLER, channel->channel, CTRL_LRPN, 0));
                            putEvent(NPlayEvent(ME_CONTROLLER, channel->channel, CTRL_HRPN, 0));
                            putEvent(NPlayEvent(ME_CONTROLLER, channel->channel, CTRL_HDATA,12));
                            putEvent(NPlayEvent(ME_CONTROLLER, channel->channel, CTRL_LRPN, 127));
                            putEvent(NPlayEvent(ME_CONTROLLER, channel->channel, CTRL_HRPN, 127));
                            }
                      else {
                            sendEvent(NPlayEvent(ME_CONTROLLER, channel->channel, CTRL_LRPN, 0));
                            sendEvent(NPlayEvent(ME_CONTROLLER, channel->channel, CTRL_HRPN, 0));
                            sendEvent(NPlayEvent(ME_CONTROLLER, channel->channel, CTRL_HDATA,12));
                            sendEvent(NPlayEvent(ME_CONTROLLER, channel->channel, CTRL_LRPN, 127));
                            sendEvent(NPlayEvent(ME_CONTROLLER, channel->channel, CTRL_HRPN, 127));
                            }
                      }
                }
          }

    //---------------------------------------------------------
    //   collectEvents
    //---------------------------------------------------------

    void Seq::collectEvents()
          {
          //do not collect even while playing
          if (state ==  Transport::PLAY)
                return;
          mutex.lock();
          events.clear();

          cs->renderMidi(&events);
          endUTick = 0;

          if (!events.empty()) {
                auto e = events.cend();
                --e;
                endUTick = e->first;
                }
          playPos  = events.cbegin();
          mutex.unlock();

          playlistChanged = false;
          }

    //---------------------------------------------------------
    //   getCurTick
    //---------------------------------------------------------

    int Seq::getCurTick()
          {
          return cs->utime2utick(qreal(playFrame) / qreal(MScore::sampleRate));
          }

    //---------------------------------------------------------
    //   setRelTempo
    //    relTempo = 1.0 = normal tempo
    //---------------------------------------------------------

    void Seq::setRelTempo(double relTempo)
          {
          guiToSeq(SeqMsg(SeqMsgId::TEMPO_CHANGE, relTempo));
          }

    //---------------------------------------------------------
    //   setPos
    //    seek
    //    realtime environment
    //---------------------------------------------------------

    void Seq::setPos(int utick)
          {
          if (cs == 0)
                return;
          stopNotes(-1, true);

          int ucur;
          mutex.lock();
          if (playPos != events.end())
                ucur = cs->repeatList()->utick2tick(playPos->first);
          else
                ucur = utick - 1;
          if (utick != ucur)
                updateSynthesizerState(ucur, utick);

          playFrame = cs->utick2utime(utick) * MScore::sampleRate;
          playPos   = events.lower_bound(utick);
          mutex.unlock();
          }

    //---------------------------------------------------------
    //   seekCommon
    //   a common part of seek() and seekRT(), contains code
    //   that could be safely called from any thread.
    //   Do not use explicitly, use seek() or seekRT()
    //---------------------------------------------------------

    void Seq::seekCommon(int utick)
          {
          if (cs == 0)
                return;

          if (playlistChanged)
                collectEvents();

          if (cs->playMode() == PlayMode::AUDIO) {
                ogg_int64_t sp = cs->utick2utime(utick) * MScore::sampleRate;
                ov_pcm_seek(&vf, sp);
                }

          guiPos = events.lower_bound(utick);
          mscore->setPos(cs->repeatList()->utick2tick(utick));
          unmarkNotes();
          }

    //---------------------------------------------------------
    //   seek
    //   send seek message to sequencer
    //   gui thread
    //---------------------------------------------------------

    void Seq::seek(int utick)
          {
          if (preferences.useJackTransport) {
                if (utick > endUTick)
                      utick = 0;
                _driver->seekTransport(utick);
                if (utick != 0)
                      return;
                }
          seekCommon(utick);

          int tick = cs->repeatList()->utick2tick(utick);
          Segment* seg = cs->tick2segment(tick);
          if (seg)
                mscore->currentScoreView()->moveCursor(seg->tick());
          cs->setPlayPos(tick);
          cs->update();
          guiToSeq(SeqMsg(SeqMsgId::SEEK, utick));
          }

    //---------------------------------------------------------
    //   seekRT
    //   realtime thread
    //---------------------------------------------------------

    void Seq::seekRT(int utick)
          {
          if (preferences.useJackTransport && utick > endUTick)
                      utick = 0;
          seekCommon(utick);
          setPos(utick);
          // Update the screen in GUI thread
          emit toGui('5', cs->repeatList()->utick2tick(utick));
          }

    //---------------------------------------------------------
    //   startNote
    //---------------------------------------------------------

    void Seq::startNote(int channel, int pitch, int velo, double nt)
          {
          if (state != Transport::STOP)
                return;
          NPlayEvent ev(ME_NOTEON, channel, pitch, velo);
          ev.setTuning(nt);
          sendEvent(ev);
          }

    void Seq::startNote(int channel, int pitch, int velo, int duration, double nt)
          {
          stopNotes();
          startNote(channel, pitch, velo, nt);
          startNoteTimer(duration);
          }

    //---------------------------------------------------------
    //   playMetronomeBeat
    //---------------------------------------------------------

    void Seq::playMetronomeBeat(BeatType type)
          {
          if (state != Transport::STOP)
                return;
          liveEventQueue()->enqueue(NPlayEvent(type));
          }

    //---------------------------------------------------------
    //   startNoteTimer
    //---------------------------------------------------------

    void Seq::startNoteTimer(int duration)
          {
          if (duration) {
                noteTimer->setInterval(duration);
                noteTimer->start();
                }
          }
    //---------------------------------------------------------
    //   stopNoteTimer
    //---------------------------------------------------------

    void Seq::stopNoteTimer()
          {
          if (noteTimer->isActive()) {
                noteTimer->stop();
                stopNotes();
                }
          }

    //---------------------------------------------------------
    //   stopNotes
    //---------------------------------------------------------

    void Seq::stopNotes(int channel, bool realTime)
          {
          auto send = [this, realTime](const NPlayEvent& event) {
                if (realTime)
                      putEvent(event);
                else
                      sendEvent(event);
          };
          // Stop notes in all channels
          if (channel == -1) {
                for(int ch = 0; ch < cs->midiMapping()->size(); ch++) {
                      send(NPlayEvent(ME_CONTROLLER, ch, CTRL_SUSTAIN, 0));
                      send(NPlayEvent(ME_CONTROLLER, ch, CTRL_ALL_NOTES_OFF, 0));
                      if (cs->midiChannel(ch) != 9)
                            send(NPlayEvent(ME_PITCHBEND,  ch, 0, 64));
                      }
                }
          else {
                send(NPlayEvent(ME_CONTROLLER, channel, CTRL_SUSTAIN, 0));
                send(NPlayEvent(ME_CONTROLLER, channel, CTRL_ALL_NOTES_OFF, 0));
                if (cs->midiChannel(channel) != 9)
                      send(NPlayEvent(ME_PITCHBEND,  channel, 0, 64));
                }
          if (preferences.useAlsaAudio || preferences.useJackAudio || preferences.usePulseAudio || preferences.usePortaudioAudio)
                _synti->allNotesOff(channel);
          }

    //---------------------------------------------------------
    //   setController
    //---------------------------------------------------------

    void Seq::setController(int channel, int ctrl, int data)
          {
          NPlayEvent event(ME_CONTROLLER, channel, ctrl, data);
          sendEvent(event);
          }

    //---------------------------------------------------------
    //   sendEvent
    //    called from GUI context to send a midi event to
    //    midi out or synthesizer
    //---------------------------------------------------------

    void Seq::sendEvent(const NPlayEvent& ev)
          {
          guiToSeq(SeqMsg(SeqMsgId::PLAY, ev));
          }

    //---------------------------------------------------------
    //   nextMeasure
    //---------------------------------------------------------

    void Seq::nextMeasure()
          {
          Measure* m = cs->tick2measure(guiPos->first);
          if (m) {
                if (m->nextMeasure())
                      m = m->nextMeasure();
                seek(m->tick());
                }
          }

    //---------------------------------------------------------
    //   nextChord
    //---------------------------------------------------------

    void Seq::nextChord()
          {
          int tick = guiPos->first;
          for (auto i = guiPos; i != events.cend(); ++i) {
                if (i->second.type() == ME_NOTEON && i->first > tick && i->second.velo()) {
                      seek(i->first);
                      break;
                      }
                }
          }

    //---------------------------------------------------------
    //   prevMeasure
    //---------------------------------------------------------

    void Seq::prevMeasure()
          {
          auto i = guiPos;
          if (i == events.begin())
                return;
          --i;
          Measure* m = cs->tick2measure(i->first);
          if (m) {
                if ((i->first == m->tick()) && m->prevMeasure())
                      m = m->prevMeasure();
                seek(m->tick());
                }
          }

    //---------------------------------------------------------
    //   prevChord
    //---------------------------------------------------------

    void Seq::prevChord()
          {
          int tick  = playPos->first;
          //find the chord just before playpos
          EventMap::const_iterator i = events.upper_bound(cs->repeatList()->tick2utick(tick));
          for (;;) {
                if (i->second.type() == ME_NOTEON) {
                      const NPlayEvent& n = i->second;
                      if (i->first < tick && n.velo()) {
                            tick = i->first;
                            break;
                            }
                      }
                if (i == events.cbegin())
                      break;
                --i;
                }
          //go the previous chord
          if (i != events.cbegin()) {
                i = playPos;
                for (;;) {
                      if (i->second.type() == ME_NOTEON) {
                            const NPlayEvent& n = i->second;
                            if (i->first < tick && n.velo()) {
                                  seek(i->first);
                                  break;
                                  }
                            }
                      if (i == events.cbegin())
                            break;
                      --i;
                      }
                }
          }

    //---------------------------------------------------------
    //   seekEnd
    //---------------------------------------------------------

    void Seq::seekEnd()
          {
          qDebug("seek to end");
          }

    //---------------------------------------------------------
    //   guiToSeq
    //---------------------------------------------------------

    void Seq::guiToSeq(const SeqMsg& msg)
          {
          if (!_driver || !running)
                return;
          toSeq.enqueue(msg);
          }

    //---------------------------------------------------------
    //   eventToGui
    //---------------------------------------------------------

    void Seq::eventToGui(NPlayEvent e)
          {
          fromSeq.enqueue(SeqMsg(SeqMsgId::MIDI_INPUT_EVENT, e));
          }

    //---------------------------------------------------------
    //   midiInputReady
    //---------------------------------------------------------

    void Seq::midiInputReady()
          {
          if (_driver)
                _driver->midiRead();
          }

    //---------------------------------------------------------
    //   SeqMsgFifo
    //---------------------------------------------------------

    SeqMsgFifo::SeqMsgFifo()
          {
          maxCount = SEQ_MSG_FIFO_SIZE;
          clear();
          }

    //---------------------------------------------------------
    //   enqueue
    //---------------------------------------------------------

    void SeqMsgFifo::enqueue(const SeqMsg& msg)
          {
          int i = 0;
          int n = 50;

          QMutex mutex;
          QWaitCondition qwc;
          mutex.lock();
          for (; i < n; ++i) {
                if (!isFull())
                      break;
                qwc.wait(&mutex,100);
                }
          mutex.unlock();
          if (i == n) {
                qDebug("===SeqMsgFifo: overflow");
                return;
                }
          messages[widx] = msg;
          push();
          }

    //---------------------------------------------------------
    //   dequeue
    //---------------------------------------------------------

    SeqMsg SeqMsgFifo::dequeue()
          {
          SeqMsg msg = messages[ridx];
          pop();
          return msg;
          }

    //---------------------------------------------------------
    //   putEvent
    //---------------------------------------------------------

    void Seq::putEvent(const NPlayEvent& event, unsigned framePos)
          {
          if (!cs)
                return;
          int channel = event.channel();
          if (channel >= cs->midiMapping()->size()) {
                qDebug("bad channel value %d >= %d", channel, cs->midiMapping()->size());
                return;
                }

          // audio
          int syntiIdx= _synti->index(cs->midiMapping(channel)->articulation->synti);
          _synti->play(event, syntiIdx);

          // midi
          if (_driver != 0 && (preferences.useJackMidi || preferences.useAlsaAudio || preferences.usePortaudioAudio))
                _driver->putEvent(event, framePos);
          }

    //---------------------------------------------------------
    //   heartBeat
    //    update GUI
    //---------------------------------------------------------

    void Seq::heartBeatTimeout()
          {
          SynthControl* sc = mscore->getSynthControl();
          if (sc && _driver) {
                if (++peakTimer[0] >= peakHold)
                      meterPeakValue[0] *= .7f;
                if (++peakTimer[1] >= peakHold)
                      meterPeakValue[1] *= .7f;
                sc->setMeter(meterValue[0], meterValue[1], meterPeakValue[0], meterPeakValue[1]);
                }

          while (!fromSeq.empty()) {
                SeqMsg msg = fromSeq.dequeue();
                if (msg.id == SeqMsgId::MIDI_INPUT_EVENT) {
                      int type = msg.event.type();
                      if (type == ME_NOTEON)
                            mscore->midiNoteReceived(msg.event.channel(), msg.event.pitch(), msg.event.velo());
                      else if (type == ME_NOTEOFF)
                            mscore->midiNoteReceived(msg.event.channel(), msg.event.pitch(), 0);
                      else if (type == ME_CONTROLLER)
                            mscore->midiCtrlReceived(msg.event.controller(), msg.event.value());
                      }
                }

          if (state != Transport::PLAY || inCountIn)
                return;

          int endFrame = playFrame;

          mutex.lock();
          auto ppos = playPos;
          if (ppos != events.cbegin())
                --ppos;
          mutex.unlock();

          if (cs && cs->sigmap()->timesig(getCurTick()).nominal()!=prevTimeSig) {
                prevTimeSig = cs->sigmap()->timesig(getCurTick()).nominal();
                emit timeSigChanged();
                }
          if (cs && curTempo()!=prevTempo) {
                prevTempo = curTempo();
                emit tempoChanged();
                }

          QRectF r;
          for (;guiPos != events.cend(); ++guiPos) {
                if (guiPos->first > ppos->first)
                      break;
                if (mscore->loop())
                      if (guiPos->first >= cs->repeatList()->tick2utick(cs->loopOutTick()))
                            break;
                const NPlayEvent& n = guiPos->second;
                if (n.type() == ME_NOTEON) {
                      const Note* note1 = n.note();
                      if (n.velo()) {
                            while (note1) {
                                  note1->setMark(true);
                                  markedNotes.append(note1);
                                  r |= note1->canvasBoundingRect();
                                  note1 = note1->tieFor() ? note1->tieFor()->endNote() : 0;
                                  }
                            }
                      else {
                            while (note1) {
                                  note1->setMark(false);
                                  r |= note1->canvasBoundingRect();
                                  markedNotes.removeOne(note1);
                                  note1 = note1->tieFor() ? note1->tieFor()->endNote() : 0;
                                  }
                            }
                      }
                }
          int utick = ppos->first;
          int tick = cs->repeatList()->utick2tick(utick);
          mscore->currentScoreView()->moveCursor(tick);
          mscore->setPos(tick);

          emit(heartBeat(tick, utick, endFrame));

          PianorollEditor* pre = mscore->getPianorollEditor();
          if (pre && pre->isVisible())
                pre->heartBeat(this);

          PianoTools* piano = mscore->pianoTools();
          if (piano && piano->isVisible())
                piano->heartBeat(markedNotes);

          cv->update(cv->toPhysical(r));
          }

    //---------------------------------------------------------
    //   updateSynthesizerState
    //    collect all controller events between tick1 and tick2
    //    and send them to the synthesizer
    //    Called from RT thread
    //---------------------------------------------------------

    void Seq::updateSynthesizerState(int tick1, int tick2)
          {
          if (tick1 > tick2)
                tick1 = 0;
          // Making a local copy of events to avoid touching it
          // from different threads at the same time
          EventMap ev = events;
          EventMap::const_iterator i1 = ev.lower_bound(tick1);
          EventMap::const_iterator i2 = ev.upper_bound(tick2);

          for (; i1 != i2; ++i1) {
                if (i1->second.type() == ME_CONTROLLER)
                      playEvent(i1->second, 0);
                }
          }

    //---------------------------------------------------------
    //   curTempo
    //---------------------------------------------------------

    double Seq::curTempo() const
          {
          return cs->tempomap()->tempo(playPos->first);
          }

    //---------------------------------------------------------
    //   set Loop in position
    //---------------------------------------------------------

    void Seq::setLoopIn()
          {
          int tick;
          if (state == Transport::PLAY) {      // If in playback mode, set the In position where note is being played
                auto ppos = playPos;
                if (ppos != events.cbegin())
                      --ppos;                 // We have to go back one pos to get the correct note that has just been played
                tick = cs->repeatList()->utick2tick(ppos->first);
                }
          else
                tick = cs->pos();             // Otherwise, use the selected note.
          if (tick >= cs->loopOutTick())   // If In pos >= Out pos, reset Out pos to end of score
                cs->setPos(POS::RIGHT, cs->lastMeasure()->endTick());
          cs->setPos(POS::LEFT, tick);
          }

    //---------------------------------------------------------
    //   set Loop Out position
    //---------------------------------------------------------

    void Seq::setLoopOut()
          {
          int tick;
          if (state == Transport::PLAY) {    // If in playback mode, set the Out position where note is being played
                tick = cs->repeatList()->utick2tick(playPos->first);
                }
          else
                tick = cs->pos() + cs->inputState().ticks();   // Otherwise, use the selected note.
          if (tick <= cs->loopInTick())   // If Out pos <= In pos, reset In pos to beginning of score
                cs->setPos(POS::LEFT, 0);
          else
              if (tick > cs->lastMeasure()->endTick())
                  tick = cs->lastMeasure()->endTick();
          cs->setPos(POS::RIGHT, tick);
          if (state == Transport::PLAY)
                guiToSeq(SeqMsg(SeqMsgId::SEEK, tick));
          }

    void Seq::setPos(POS, unsigned tick)
          {
          qDebug("seq: setPos %d", tick);
          }

    //---------------------------------------------------------
    //   set Loop In/Out position based on the selection
    //---------------------------------------------------------

    void Seq::setLoopSelection()
          {
          cs->setLoopInTick(cs->selection().tickStart());
          cs->setLoopOutTick(cs->selection().tickEnd());
          }

    //---------------------------------------------------------
    //   Called after tempo or time signature
    //   changed while playback
    //---------------------------------------------------------

    void Seq::handleTimeSigTempoChanged()
          {
          _driver->handleTimeSigTempoChanged();
          }

    //---------------------------------------------------------
    //  setInitialMillisecondTimestampWithLatency
    //   Called whenever seq->process() starts.
    //   Sets a starting reference time for which subsequent PortMidi events will be offset from.
    //   Time is relative to the start of PortMidi's initialization.
    //---------------------------------------------------------

    void Seq::setInitialMillisecondTimestampWithLatency()
          {
         #ifdef USE_PORTMIDI
               initialMillisecondTimestampWithLatency = Pt_Time() + preferences.portMidiOutputLatencyMilliseconds;
               //qDebug("PortMidi initialMillisecondTimestampWithLatency: %d = %d + %d", initialMillisecondTimestampWithLatency, unsigned(Pt_Time()), preferences.portMidiOutputLatencyMilliseconds);
         #endif
         }

    //---------------------------------------------------------
    //  getCurrentMillisecondTimestampWithLatency
    //   Called when midi messages are sent to PortMidi device.
    //   Returns the time in milliseconds of the current play cursor.
    //   Time is relative to the start of PortMidi's initialization.
    //---------------------------------------------------------

    unsigned Seq::getCurrentMillisecondTimestampWithLatency(unsigned framePos) const
          {
    #ifdef USE_PORTMIDI
          unsigned playTimeMilliseconds = unsigned(framePos * 1000) / unsigned(MScore::sampleRate);
          //qDebug("PortMidi timestamp = %d + %d", initialMillisecondTimestampWithLatency, playTimeMilliseconds);
          return initialMillisecondTimestampWithLatency + playTimeMilliseconds;
    #else
          qDebug("Shouldn't be using this function if not using PortMidi");
          return 0;
    #endif
          }
    }

↑ V728 An excessive check 'preferences.useJackTransport' can be simplified. The '||' operator is surrounded by opposite expressions.

↑ V728 An excessive check 'preferences.useJackTransport' can be simplified. The '||' operator is surrounded by opposite expressions.

↑ V728 An excessive check 'preferences.useJackTransport' can be simplified. The '||' operator is surrounded by opposite expressions.

↑ V595 The 'cs' pointer was utilized before it was verified against nullptr. Check lines: 722, 745.