//=========================================================
//  MusE
//  Linux Music Editor
//  $Id: midithread.cpp,v 1.3 2003/11/05 20:45:12 lunar_shuttle Exp $
//
//  (C) Copyright 2002 Werner Schweer (ws@seh.de)
//=========================================================

#include <config.h>

#include <poll.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <sys/time.h>
#include <cmath>

#include <linux/version.h>
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0)
#include <linux/spinlock.h>
#include <linux/mc146818rtc.h>
#else
#include <linux/rtc.h>
#endif

#include <qsocketnotifier.h>
#include <qtimer.h>

#include "synth.h"
#include "alsamidi.h"
#include "midithread.h"
#include "globals.h"
#include "song.h"
#include "midictrl.h"
#include "event.h"
#include "tempo.h"
#include "audiodev.h"
#include "app.h"
#include "utils.h"
#include "drummap.h"
#include "jackaudio.h"
#include "audio.h"
#include "wave.h"
#include "sync.h"

static const unsigned char mmcStopMsg[] =         { 0x7f, 0x7f, 0x06, 0x01 };
static const unsigned char mmcDeferredPlayMsg[] = { 0x7f, 0x7f, 0x06, 0x03 };

static int TICK_SLICE;
MidiThread* midiThread;

//---------------------------------------------------------
//   SEvent
//---------------------------------------------------------

struct SEvent {
      MidiPlayEvent* ev;
      bool operator<(SEvent&);
      SEvent(MidiPlayEvent* me) { ev = me; }
      };

//---------------------------------------------------------
//   SEventList
//---------------------------------------------------------

class SEventList : public std::list<SEvent, RTalloc<SEvent> > {
   public:
      SEventList() {}
      };

typedef std::list<SEvent>::iterator iSEvent;

bool SEvent::operator<(SEvent& e)
      {
      if (ev->port() != e.ev->port())
            return ev->port() < e.ev->port();

      // play note off events first to prevent overlapping
      // notes

      if (ev->channel() == e.ev->channel())
            return ev->type() == MidiEvent::NoteOff;

      int map[16] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 10, 11, 12, 13, 14, 15 };
      return map[ev->channel()] < map[e.ev->channel()];
      }

static SEventList sevents;

//---------------------------------------------------------
//   recTimeStamp
//---------------------------------------------------------

int MidiThread::recTimeStamp() const
      {
      return _midiTick;
      }

//---------------------------------------------------------
//   heartBeat
//    (GUI context)
//---------------------------------------------------------

void MidiThread::heartBeat()
      {
      song->beat(playTickPos);
// printf("heart beat %d %d\n", _midiTick, playTickPos);
      if (controlChanged) {
            muse->ctrlChanged();
            controlChanged = false;
            }
      while (noteFifoSize) {
            int pv = recNoteFifo[noteFifoRindex];
            noteFifoRindex = (noteFifoRindex + 1) % REC_NOTE_FIFO_SIZE;
            emit midiNote((pv >> 8) & 0xff, pv & 0xff);
            --noteFifoSize;
            }
      }

//---------------------------------------------------------
//   MidiThread
//---------------------------------------------------------

MidiThread::MidiThread(int prio, const char* name)
   : QObject(0, name), Thread(prio, name)
      {
      tempoSN        = -1;
      timerFd        = -1;
      state          = IDLE;
      controlChanged = false;
      noteFifoSize   = 0;
      noteFifoWindex = 0;
      noteFifoRindex = 0;
      realRtcTicks   = rtcTicks;
      TICK_SLICE     = division/10;
      endSlice       = 0;

      startRecord.setType(Pos::TICKS);
      endRecord.setType(Pos::TICKS);

      heartBeatTimer = new QTimer(this, "timer");
      connect(heartBeatTimer, SIGNAL(timeout()), this, SLOT(heartBeat()));

      //---------------------------------------------------
      //  establish socket to gui
      //---------------------------------------------------

      int filedes[2];         // 0 - reading   1 - writing
      if (pipe(filedes) == -1) {
            perror("creating pipe1");
            exit(-1);
            }
      sigFd = filedes[1];
      QSocketNotifier* ss = new QSocketNotifier(filedes[0], QSocketNotifier::Read);
      connect(ss, SIGNAL(activated(int)), this, SLOT(seqSignal(int)));
      }

//---------------------------------------------------------
//   ~MidiThread
//---------------------------------------------------------

MidiThread::~MidiThread()
      {
      if (timerFd != -1) {
            ioctl(timerFd, RTC_PIE_OFF, 0);
            close(timerFd);
            }
      }

//---------------------------------------------------------
//   isPlaying
//---------------------------------------------------------

bool MidiThread::isPlaying() const
      {
      return state == PLAY;
      }

//---------------------------------------------------------
//   recordStop
//    execution environment: gui thread
//---------------------------------------------------------

void MidiThread::recordStop()
      {
      TrackList* tl = song->tracks();
      int firstTick = startRecord.posTick();
      int lastTick  = endRecord.posTick();
      bool updateMixer = false;

      for (iTrack it = tl->begin(); it != tl->end(); ++it) {
            Track::TrackType type = (*it)->type();
            if (type == Track::WAVE) {
                  WaveTrack* track = (WaveTrack*)(*it);
                  if (!track->recordFlag())
                        continue;
                  song->cmdAddRecordedWave(track,
                     startRecord.posSample(),
                     endRecord.posSample());
                  track->setRecFile(0);
                  song->setRecordFlag(track, false);
                  updateMixer = true;
                  continue;
                  }
            MidiTrack* mt = (MidiTrack*)*it;
            EventList* el = mt->events();

            //---------------------------------------------------
            //    resolve NoteOff events
            //---------------------------------------------------

            for (iEvent i = el->begin(); i != el->end(); ++i) {
                  MidiEvent* ev  = (MidiEvent*)i->second;
                  if (ev->isNote()) {
                        if (ev->velo() == 0) {
                              // this is a note off event without a note on
                              // set note on to begin of recording:
                              // velocity info is lost (set to default 80)

                              ev->setPosTick(firstTick);
                              ev->setVelo(80);
                              int len = i->first - firstTick;
                              if (len <= 0)
                                    len = 1;
                              ev->setLenTick(i->first - firstTick);
                              continue;
                              }

                        iEvent k = i;
                        for (++k; k != el->end(); ++k) {
                              if (ev->isNoteOff((MidiEvent*)k->second)) {
                                    int t = k->first - i->first;
                                    if (t == 0) {
                                          t = 1;
                                          printf("note len == 0! (set to 1)\n");
                                          }
                                    ev->setLenTick(t);
                                    ev->setVeloOff(((MidiEvent*)i->second)->veloOff());
                                    el->erase(k);
                                    break;
                                    }
                              }
                        if (k == el->end()) {
                              // note off event is missing; set note end
                              // to end of recording

                              int len = lastTick - i->first;
                              if (len < 1)
                                    len = 1;
                              ev->setLenTick(len);
                              }
                        }
                  }

            //-------------------------------------------------------------
            //    assign events to parts
            //-------------------------------------------------------------

            song->cmdAddRecordedEvents(mt, el, firstTick);
            el->clear();
            }
      if (audioOutput.recordFlag()) {
            SndFile* sf = audioOutput.recFile();
            if (sf)
                  delete sf;              // close
            audioOutput.setRecFile(0);
            audioOutput.setRecordFlag1(false);
            audio->msgSetRecord(&audioOutput, false);
            }
      song->setRecord(false);
      song->update(SC_RECFLAG);
      }

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

void MidiThread::seqSignal(int fd)
      {
      char buffer[16];

      int n = ::read(fd, buffer, 16);
      if (n < 0) {
            printf("MidiThread: seqSignal: READ PIPE failed: %s\n",
               strerror(errno));
            return;
            }
      for (int i = 0; i < n; ++i) {
            switch(buffer[i]) {
                  case '0':         // STOP
                        if (song->record())
                              recordStop();
                        song->setStopPlay(false);
                        break;
                  case '1':
                        song->setStopPlay(true);
                        break;
                  case '2':   // record
                        song->setRecord(true);
                  case 'P':   // alsa ports changed
                        song->rescanAlsaPorts();
                        break;
//                case 'I':   // event received
//                      emit midiNote(curPitch, curVelo);
//                      break;
                  case 'G':
                        song->setPos(0, playTickPos, true, true, true);
                        break;
                  default:
                        printf("unknown Seq Signal <%c>\n", buffer[i]);
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   readMsg
//---------------------------------------------------------

static void readMsg(void* p, void*)
      {
// printf("midithread: readMsg\n");
      MidiThread* at = (MidiThread*)p;
      at->readMsg();
      }

//---------------------------------------------------------
//   midiTick
//---------------------------------------------------------

void MidiThread::midiTick(void* p, void*)
      {
      MidiThread* at = (MidiThread*)p;
      at->processTimerTick();
      }

//---------------------------------------------------------
//   alsaMidiRead
//---------------------------------------------------------

static void alsaMidiRead(void*, void*)
      {
      alsaProcessMidiInput();
      }

//---------------------------------------------------------
//   midiRead
//---------------------------------------------------------

static void midiRead(void*, void* d)
      {
      MidiDevice* dev = (MidiDevice*) d;
      dev->processInput();
      }

//---------------------------------------------------------
//   synthIRead
//---------------------------------------------------------

static void synthIRead(void*, void* d)
      {
      SynthI* syn = (SynthI*) d;
      syn->processInput();
      }

//---------------------------------------------------------
//   midiWrite
//---------------------------------------------------------

static void midiWrite(void*, void* d)
      {
      MidiDevice* dev = (MidiDevice*) d;
      dev->flush();
      }

//---------------------------------------------------------
//   setHeartBeat
//---------------------------------------------------------

void MidiThread::setHeartBeat()
      {
      heartBeatTimer->start(1000/guiRefresh);
      }

//---------------------------------------------------------
//   start
//---------------------------------------------------------

void MidiThread::start()
      {
      tempoSN      = -1;
      timerFd      = -1;
      state        = IDLE;
      controlChanged = false;

      realRtcTicks = rtcTicks;
      TICK_SLICE         = division/10;
      endSlice     = 0;
      //---------------------------------------------

      setHeartBeat();

      //---------------------------------------------------
      // initialize timer
      //---------------------------------------------------

      doSetuid();
      timerFd = ::open("/dev/rtc", O_RDWR);
      if (timerFd == -1) {
            perror("cannot open rtc clock /dev/rtc");
            if (!debugMode)
                  exit(-1);
            }
      if (setRtcTicks()) {
            if (ioctl(timerFd, RTC_PIE_ON, 0) == -1) {
                  perror("MidiThread: start: RTC_PIE_ON failed");
                  if (!debugMode)
                        exit(-1);
                  }
            }
      undoSetuid();
      Thread::start();
      }

//---------------------------------------------------------
//   threadStart
//---------------------------------------------------------

void MidiThread::threadStart(void*)
      {
      _midiTick     = 0;
      rtcTick      = 0;
      midiClock    = 0;
      playTickPos  = 0;
      updatePollFd();
      }

//---------------------------------------------------------
//   threadStop
//---------------------------------------------------------

void MidiThread::threadStop()
      {
      if (timerFd != -1) {
            ioctl(timerFd, RTC_PIE_OFF, 0);
            ::close(timerFd);
            timerFd = -1;
            }
      }

//---------------------------------------------------------
//   updatePollFd
//---------------------------------------------------------

void MidiThread::updatePollFd()
      {
      if (!isRunning())
            return;

      clearPollFd();

      if (timerFd != -1) {
            addPollFd(timerFd, POLLIN, midiTick, this, 0);
            }
      else {
            fprintf(stderr, "updatePollFd: no timer fd\n");
            if (!debugMode)
                  exit(-1);
            }

      addPollFd(toThreadFdr, POLLIN, ::readMsg, this, 0);

      //---------------------------------------------------
      //  midi ports
      //---------------------------------------------------

      for (int i = 0; i < MIDI_PORTS; ++i) {
            MidiPort* mp    = &midiPorts[i];
            MidiDevice* dev = mp->device();
            if (dev) {
                  if ((mp->rwFlags()&0x2) || (extSyncFlag.value()
                     && (rxSyncPort == i || rxSyncPort == -1))) {
                        int fd = dev->selectRfd();
                        if (fd != -1)
                              addPollFd(fd, POLLIN, ::midiRead, this, dev);
                        }
                  if (dev->bytesToWrite()) {
                        int fd = dev->selectWfd();
                        if (fd != -1)
                              addPollFd(fd, POLLIN, ::midiWrite, this, dev);
                        }
                  }
            }
      // special handling for alsa midi:
      // (one fd for all devices)
      //    this allows for processing of some alsa events
      //    even if no alsa driver is active (assigned to a port)
      int fd = alsaSelectRfd();
      if (fd != -1) {
            addPollFd(fd, POLLIN, ::alsaMidiRead, this, 0);
            }

      //---------------------------------------------------
      //   connect Synthi instances (GUI)
      //---------------------------------------------------

      for (iSynthI i = synthiInstances.begin(); i != synthiInstances.end(); ++i) {
            int fd = (*i)->readFd();
            if (fd != -1)
                  addPollFd(fd, POLLIN, ::synthIRead, this, *i);
            }
      }

//---------------------------------------------------------
//   setRtcTicks
//    return true on success
//---------------------------------------------------------

bool MidiThread::setRtcTicks()
      {
      if (timerFd == -1) {
            if (!debugMode) {
                  fprintf(stderr, "setRtcTicks: not rtc timer avail.");
                  exit(-1);
                  }
            setPollWait(10);  // 10 msec
            realRtcTicks = 100;
            return false;
            }
      int rc = ioctl(timerFd, RTC_IRQP_SET, rtcTicks);
      if (rc == -1) {
            fprintf(stderr, "cannot set tick on /dev/rtc: %s\n",
               strerror(errno));
            fprintf(stderr, "precise timer not available\n");
            close(timerFd);
            if (!debugMode)
                  exit(-1);
            timerFd = -1;
            setPollWait(10);  // 10 msec
            realRtcTicks = 100;
            return false;
            }
      realRtcTicks = rtcTicks;
      return true;
      }

//---------------------------------------------------------
//   processMsg
//---------------------------------------------------------

void MidiThread::processMsg(const ThreadMsg* m)
      {
      const MidiMsg* msg = (MidiMsg*)m;

      switch (msg->id) {
            case SEQM_RESET_DEVICES:
                  for (int i = 0; i < MIDI_PORTS; ++i)
                        midiPorts[i].instrument()->reset(i, song->mtype());
                  break;
            case SEQM_INIT_DEVICES:
                  initDevices();
                  break;
            case SEQM_MIDI_LOCAL_OFF:
                  sendLocalOff();
                  break;
            case SEQM_PANIC:
                  panic();
                  break;
            case SEQM_MIDI_CTRL:
                  midiPorts[msg->port].setCtrl(msg->channel, msg->ctrl, msg->a);
                  break;
            case SEQM_MIDI_MASTER_VOLUME:
                  for (int i = 0; i < MIDI_PORTS; ++i)
                        midiPorts[i].setMasterVol(msg->b);
                  break;
            case SEQM_SET_MIDI_DEVICE:
                  ((MidiPort*)(msg->p1))->setMidiDevice((MidiDevice*)(msg->p2));
                  updatePollFd();
                  break;
            case SEQM_PLAY_MIDI_EVENT:
                  {
                  MidiPlayEvent* ev = (MidiPlayEvent*)(msg->p1);
                  midiPorts[ev->port()].putEvent(ev);
                  }
                  break;
            case SEQM_SET_RTC_TICKS:
                  doSetuid();
                  setRtcTicks();
                  undoSetuid();
                  break;
            case SEQM_SEEK:
                  if (state == PLAY)
                        seek(msg->a);
                  break;
            case SEQM_SCAN_ALSA_MIDI_PORTS:
                  alsaScanMidiPorts();
                  break;
            case SEQM_PLAY:
                  if (state == IDLE && msg->a)
                        startPlay(song->cpos());
                  else if ((state == PLAY || state == PRECOUNT) && !msg->a)
                        stopPlay();
                  break;
            case MIDI_ADD_SYNTHI:
                  updatePollFd();
                  break;
            case MIDI_SHOW_INSTR_GUI:
//                  ((MidiInstrument*)(msg->p1))->showGui(msg->a);
                  updatePollFd();
                  break;
            default:
                  song->processMidiMsg(msg);
                  break;
            }
      }

//---------------------------------------------------------
//   stop
//---------------------------------------------------------

void MidiThread::stopPlay()
      {
      endRecord.setPosTick(playTickPos);
      audio->msgPlay(false);

      //---------------------------------------------------
      //    end all notes
      //---------------------------------------------------

      // printf("stopPlay: stuck notes %d\n", stuckNotes.size());

//      midiOutputTrace = true;
      for (iMPEvent i = stuckNotes.begin(); i != stuckNotes.end(); ++i)
            playEvent(&i->second);
      stuckNotes.clear();
//      midiOutputTrace = false;

      //---------------------------------------------------
      //    reset sustain
      //---------------------------------------------------

      for (int i = 0; i < MIDI_PORTS; ++i) {
            for (int ch = 0; ch < MIDI_CHANNELS; ++ch) {
                  if (midiPorts[i].cState(ch)->controller[CTRL_SUSTAIN] != -1)
                        midiPorts[i].setCtrl(ch, CTRL_SUSTAIN, 0);
                  }
            }

      //---------------------------------------------------
      //    clear track activity display
      //---------------------------------------------------

      TrackList* tl = song->tracks();
      for (iTrack i = tl->begin(); i != tl->end(); ++i) {
            SNode* ss = (*i)->node();
            // ss->resetMeter();
            ss->setActivity(0);
            }

      if (genMMC) {
            unsigned char mmcPos[] = {
                  0x7f, 0x7f, 0x06, 0x44, 0x06, 0x01,
                  0, 0, 0, 0, 0
                  };
            MTC mtc(tempomap.tick2time(playTickPos));
            mmcPos[6] = mtc.h() | (mtcType << 5);
            mmcPos[7] = mtc.m();
            mmcPos[8] = mtc.s();
            mmcPos[9] = mtc.f();
            mmcPos[10] = mtc.sf();
            midiPorts[txSyncPort].sysex(mmcStopMsg, sizeof(mmcStopMsg));
            midiPorts[txSyncPort].sysex(mmcPos, sizeof(mmcPos));
            }

      MidiDevice* syncDev = midiPorts[txSyncPort].device();
      if (genMCSync && syncDev) {         // Midi Clock
            // send STOP and
            // "set song position pointer"
            syncDev->putStop();
            int beat = playTickPos * 4 / division;
            syncDev->putSongpos(beat);
            }
      state = IDLE;
      write(sigFd, "0", 1);   // STOP
      }

//---------------------------------------------------------
//   defaultTick
//---------------------------------------------------------

void MidiThread::defaultTick()
      {
      processTimerTick();
      }

//---------------------------------------------------------
//   midiPortsChanged
//---------------------------------------------------------

void MidiThread::midiPortsChanged()
      {
      write(sigFd, "P", 1);
      }

//---------------------------------------------------------
//   sendLocalOff
//---------------------------------------------------------

void MidiThread::sendLocalOff()
      {
      for (int k = 0; k < MIDI_PORTS; ++k) {
            for (int i = 0; i < 16; ++i)
                  midiPorts[k].setCtrl(i, 122, 0);
            }
      }

//---------------------------------------------------------
//   panic
//---------------------------------------------------------

void MidiThread::panic()
      {
      MidiPlayEvent ev;
      ev.setType(0xb0);
      for (int i = 0; i < MIDI_PORTS; ++i) {
            MidiPort* port = &midiPorts[i];
            if (port == 0)
                  continue;
            ev.setPort(i);
            for (int chan = 0; chan < MIDI_CHANNELS; ++chan) {
                  ev.setChannel(chan);
                  ev.setA(120);          // all sound off
                  ev.setB(0);
                  port->putEvent(&ev);

                  ev.setA(121);          // reset all controller
                  ev.setB(0);
                  port->putEvent(&ev);
                  }
            }
      }

//---------------------------------------------------------
//   initDevices
//---------------------------------------------------------

void MidiThread::initDevices()
      {
      //
      // mark all used devices in song as active
      //
      bool activeDevices[MIDI_PORTS];
      for (int i = 0; i < MIDI_PORTS; ++i)
            activeDevices[i] = false;

      TrackList* tracks = song->tracks();
      for (iTrack it = tracks->begin(); it != tracks->end(); ++it) {
            Track* t = *it;
            if (t->type() != Track::MIDI && t->type() != Track::DRUM)
                  continue;
            MidiTrack* track = (MidiTrack*)t;
            activeDevices[track->outPort()] = true;
            }


      //
      // test for explicit instrument initialization
      //

      for (int i = 0; i < MIDI_PORTS; ++i) {
            if (!activeDevices[i])
                  continue;
            MidiPort* port = &midiPorts[i];
            MidiInstrument* instr = port->instrument();
            if (instr) {
                  EventList* events = instr->midiInit();
                  if (events->empty())
                        continue;
                  MPEventList el;
                  for (iEvent ie = events->begin(); ie != events->end(); ++ie)
                        el.add(0, (MidiEvent*)(ie->second), i, 0);
                  el.play();
                  activeDevices[i] = false;  // no standard initialization
                  }
            }
      //
      // damit Midi-Devices, die mehrere Ports besitzen, wie z.B.
      // das Korg NS5R, nicht mehrmals zwischen GM und XG/GS hin und
      // hergeschaltet werden, wird zunächst auf allen Ports GM
      // initialisiert, und dann erst XG/GS
      //
      for (int i = 0; i < MIDI_PORTS; ++i) {
            if (!activeDevices[i])
                  continue;
            MidiPort* port = &midiPorts[i];
            switch(song->mtype()) {
                  case MT_GS:
                  case MT_UNKNOWN:
                        break;
                  case MT_GM:
                  case MT_XG:
                        port->gmOn();
                        usleep(200000);   // wait 200ms
                        break;
                  }
            }
      for (int i = 0; i < MIDI_PORTS; ++i) {
            if (!activeDevices[i])
                  continue;
            MidiPort* port = &midiPorts[i];
            switch(song->mtype()) {
                  case MT_UNKNOWN:
                        break;
                  case MT_GM:
                        break;
                  case MT_GS:
                        port->gsOn();
                        usleep(50000);    // wait 50 ms
                        break;
                  case MT_XG:
                        port->xgOn();
                        usleep(50000);    // wait 50 ms
                        break;
                  }
            }

      for (int i = 0; i < MIDI_PORTS; ++i) {
            if (!activeDevices[i])
                  continue;
            MidiPort* port = &midiPorts[i];
            //
            // alle channel-states resetten wg. gmOn
            //
            for (int chan = 0; chan < MIDI_CHANNELS; ++chan) {
                  ChannelState* istate = port->iState(chan);
                  //
                  //  Drum- und Percussion Channel bei GM nicht switchen!
                  //    mein Korg NS5R schalten dann auf Voice um!
                  //
                  if (song->mtype() == MT_GM) {
                        ChannelState* cstate = port->cState(chan);
                        cstate->controller[CTRL_HBANK] = istate->controller[CTRL_HBANK];
                        cstate->controller[CTRL_LBANK] = istate->controller[CTRL_LBANK];
                        }
                  for (int k = 0; k < 128; ++k) {
                        int val = istate->controller[k];
                        if ((val != -1) && (val != port->cState(chan)->controller[k])) {
//DEBUG
//printf("init port %d dev %d ctrl %d val %d\n",
//   i, chan, k, val);
                              port->setCtrl(chan, k, val);
                              }
                        }

                  if (!((chan == 9) && (song->mtype() == MT_GM))) {
                        int val = istate->program;
                        if (val != -1) {
                              port->programChange(chan, val);
                              }
                        }
                  else {
                        // GM-File tries to change drum channel
//                        printf("bad GM\n");
                        }
                  // RPN's
                  // NRPN's
                  }
            }
      }

//---------------------------------------------------------
//   startPlay
//---------------------------------------------------------

void MidiThread::startPlay(int st)
      {
      //
      // reset runstate of midi devices for more
      // robust handling
      //
      for (int i = 0; i < MIDI_PORTS; ++i)
            midiPorts[i].resetRunstate();
      if (st == 0 && !song->record())
            initDevices();

      seek(st);
      if (genMMC)
            midiPorts[txSyncPort].sysex(mmcDeferredPlayMsg, sizeof(mmcDeferredPlayMsg));
      if (genMCSync) {
            if (playTickPos)
                  midiPorts[txSyncPort].putContinue();
            else
                  midiPorts[txSyncPort].putStart();
            }
      if (precountEnableFlag
         && song->click()
         && !extSyncFlag.value()
         && song->record()) {
            state = PRECOUNT;
            int z, n;
            if (precountFromMastertrackFlag)
                  sigmap.timesig(playTickPos, z, n);
            else {
                  z = precountSigZ;
                  n = precountSigN;
                  }
            clickno       = z * preMeasures;
            clicksMeasure = z;
            ticksBeat     = (division * 4)/n;
            midiClick     = _midiTick;         // start now
            }
      else {
            int bar, beat, tick;
            sigmap.tickValues(playTickPos, &bar, &beat, &tick);
            if (tick)
                  beat += 1;
            midiClick = sigmap.bar2tick(bar, beat, 0);
            }
      // start real play on next midiClock
      // current state is START_PLAY (set in seek)
      }

//---------------------------------------------------------
//   seek
//---------------------------------------------------------

void MidiThread::seek(int pos)
      {
      state       = START_PLAY;
      playTickPos = pos;
      midiClick   = pos;      // first click on loop start

      //---------------------------------------------------
      //    end all notes
      //---------------------------------------------------

      for (iMPEvent i = stuckNotes.begin(); i != stuckNotes.end(); ++i)
            playEvent(&i->second);
      stuckNotes.clear();

      MidiDevice* syncDev = midiPorts[txSyncPort].device();
      if (genMCSync && syncDev) {
            int beat = (playTickPos * 4) / division;
            syncDev->putStop();           // ??
            syncDev->putSongpos(beat);
            syncDev->putContinue();       // ??
            }
      audio->msgSeek(playTickPos);
      loopPassed = true;   // for record loop mode
      playEvents.clear();
      }

//---------------------------------------------------------
//   playEvent
//---------------------------------------------------------

void MidiThread::playEvent(const MidiPlayEvent* event)
      {
      MidiPort* port = &midiPorts[event->port()];
      port->putEvent(event);
      if (port->instrument()->hasGui())
            port->instrument()->writeToGui(event);
      }

//---------------------------------------------------------
//   nextEvents
//    collects all events between stick - etick
//---------------------------------------------------------

void MidiThread::nextEvents(int stick, int etick)
      {
      for (iTrack t = song->tracks()->begin(); t != song->tracks()->end(); ++t) {
            if ((*t)->type() != Track::MIDI && (*t)->type() != Track::DRUM)
                  continue;
            MidiTrack* track = (MidiTrack*)(*t);
            if (track->node()->isMute())
                  continue;
            PartList* pl = track->parts();
            for (iPart p = pl->begin(); p != pl->end(); ++p) {
                  MidiPart* part    = (MidiPart*)(p->second);
                  // dont play muted parts
                  if (part->mute())
                        continue;
                  EventList* events = part->events();

                  int partTick = part->posTick();
                  int delay    = track->delay;
                  iEvent ie    = events->lower_bound(stick - delay - partTick);
                  iEvent iend  = events->lower_bound(etick - delay - partTick);

                  for (; ie != iend; ++ie) {
                        MidiEvent* ev = (MidiEvent*)ie->second;
                        //
                        //  don´t play any meta events
                        //
                        if (ev->type() == MidiEvent::Meta)
                              continue;
                        if (track->type() == Track::DRUM) {
                              int instr = drumInmap[ev->pitch()];
                              if (ev->isNote() && drumMap[instr].mute) //Ignore muted drums
                                    continue;
                              }
                        int tick    = ev->posTick() + delay + part->posTick();
                        int port    = track->outPort();
                        int channel =  track->outChannel();
                        switch (ev->type()) {
                              case MidiEvent::Note:
                              case MidiEvent::NoteOff:
                                    {
                                    int len   = ev->lenTick();
                                    int pitch = ev->pitch();
                                    if (track->type() == Track::DRUM)  { //Map drum-notes to the drum-map values
                                          int instr = drumInmap[ev->pitch()];
                                          pitch = drumMap[instr].anote;
                                          port = drumMap[instr].port;
                                          channel = drumMap[instr].channel;
                                          }
                                    else
                                          pitch+= (track->transposition + song->globalPitchShift()); //Transpose non-drum notes


                                    int velo  = ev->velo();
                                    if (pitch > 127)
                                          pitch = 127;
                                    if (pitch < 0)
                                          pitch = 0;
                                    velo += track->velocity;
                                    velo = (velo * track->compression) / 100;
                                    if (velo > 127)
                                          velo = 127;
                                    if (velo < 1)           // no off event
                                          velo = 1;
                                    len = (len *  track->len) / 100;
                                    if (len <= 0)     // don´t allow zero length
                                          len = 1;
                                    int veloOff = ev->veloOff();

                                    if (ev->type() == MidiEvent::Note)
                                          playEvents.add(tick,
                                             port, channel, 0x90, pitch, velo);
                                    else
                                          playEvents.add(tick,
                                             port, channel, 0x80, pitch, veloOff);
                                    if (ev->type() == MidiEvent::Note) {
                                          stuckNotes.add(tick + len,
                                             port, channel,
                                             veloOff ? 0x80 : 0x90, pitch, veloOff);
                                          track->addActivity(velo);
                                          }
                                    }
                                    break;

                              case MidiEvent::Ctrl7:
                              case MidiEvent::Ctrl14:
                                    controlChanged = true;
                              default:
                                    playEvents.add(tick, ev, port, channel);
                                    break;
                              } //end switch 
                        }//end for 
                  }
            }
      }

//---------------------------------------------------------
//   processMidiClock
//---------------------------------------------------------

void MidiThread::processMidiClock()
      {
      if (genMCSync)
            midiPorts[txSyncPort].clock();
      if (state == START_PLAY) {
            // start play on sync
            state         = PLAY;
            _midiTick      = playTickPos;
            midiClock     = playTickPos;

            int bar, beat, tick;
            sigmap.tickValues(_midiTick, &bar, &beat, &tick);
            midiClick      = sigmap.bar2tick(bar, beat+1, 0);

            double cpos    = tempomap.tick2time(playTickPos);
            samplePosStart = samplePos - lrint(cpos * sampleRate);
            rtcTickStart   = rtcTick - lrint(cpos * realRtcTicks);

            endSlice       = playTickPos;
            recTick        = playTickPos;
            lastTickPos    = playTickPos;

            tempoSN = tempomap.tempoSN();

            startRecord.setPosTick(playTickPos);
            audio->msgPlay(true);
            }
      midiClock += division/24;
      }

//---------------------------------------------------------
//   processTimerTick
//    fast hardware timer tick interrupt at
//    rtcTicks rate
//---------------------------------------------------------

void MidiThread::processTimerTick()
      {
      extern int watchMidi;
      ++watchMidi;      // make watchdog happy

      //---------------------------------------------------
      //    if tempomap changed, fix
      //    samplePosStart and rtcTickStart
      //---------------------------------------------------

      if (tempoSN != tempomap.tempoSN()) {
            double cpos    = tempomap.tick2time(_midiTick);
            samplePosStart = samplePos - lrint(cpos * sampleRate);
            rtcTickStart   = rtcTick - lrint(cpos * realRtcTicks);
            tempoSN        = tempomap.tempoSN();
            }

      //---------------------------------------------------
      //    read elapsed rtc timer ticks
      //---------------------------------------------------

      int nn = 1;
      if (timerFd != -1) {
            unsigned long nn;
            if (read(timerFd, &nn, sizeof(unsigned long)) != sizeof(unsigned long)) {
                  perror("rtc timer read error");
                  exit(-1);
                  }
            nn >>= 8;
            }
      rtcTick += nn;

      //---------------------------------------------------
      // compute current midiTick value
      //---------------------------------------------------

      if (audioTimebase) {
            samplePos = audio->curPlayPos();
            _midiTick  = tempomap.time2tick(
               double(samplePos - samplePosStart) / double(sampleRate));
            }
      else {
            _midiTick = tempomap.time2tick(
               double(rtcTick - rtcTickStart) / double(realRtcTicks)
               );
            }

      if (!extSyncFlag.value() && _midiTick >= midiClock)
            processMidiClock();

      if (genMTCSync) {
            // printf("Midi Time Code Sync generation not impl.\n");
            }

      if (state == PLAY) {
            playTickPos = _midiTick;
            // start loop division/24-1 ticks earlier
            if (song->loop() && !extSyncFlag.value() && (playTickPos >= song->rpos()-division/24+1)) {
                  seek(song->lpos());
                  // loop on next midiClock
                  return;
                  }
            if (playTickPos >= endSlice) {
                  int next = endSlice + TICK_SLICE;
                  nextEvents(endSlice, next);
                  endSlice = next;
                  }
            if (playTickPos >= song->len() && (!song->record() || song->bounceTrack)
               && !song->loop()) {
                  stopPlay();
                  }
            }
      if ((state == PRECOUNT || (song->click() && (state == PLAY)))
         && (_midiTick >= midiClick)) {
            int bar, beat, tick;
            bool isMeasure = false;
            switch (state) {
                  case PRECOUNT:
                        isMeasure = (clickno % clicksMeasure) == 0;
                        midiClick += ticksBeat;
                        if (clickno)
                              --clickno;
                        else
                              state = START_PLAY;
                        break;

                  case PLAY:
                        sigmap.tickValues(_midiTick, &bar, &beat, &tick);
                        midiClick = sigmap.bar2tick(bar, beat+1, 0);
                        isMeasure = beat == 0;
                        break;
                  default:
                        break;
                  }

            if (isMeasure) {
                  playEvents.add(_midiTick, clickPort, clickChan, 0x90,
                    measureClickNote, measureClickVelo);
                  stuckNotes.add(_midiTick+10, clickPort, clickChan, 0x90,
                    measureClickNote, 0);
                  }
            else {
                  playEvents.add(_midiTick, clickPort, clickChan, 0x90,
                    beatClickNote, beatClickVelo);
                  stuckNotes.add(_midiTick+10, clickPort, clickChan, 0x90,
                    beatClickNote, 0);
                  }
            }
      //
      //  play all event upto/including _midiTick
      //
      sevents.clear();

      //   collect stuck notes
      MPEventRange range1 = stuckNotes.equal_range(_midiTick);
      for (iMPEvent k = stuckNotes.begin(); k != range1.second; ++k)
            sevents.push_back(SEvent(&k->second));

      //   collect play notes
      MPEventRange range2 = playEvents.equal_range(_midiTick);
      for (iMPEvent k = playEvents.begin(); k != range2.second; ++k)
            sevents.push_back(SEvent(&k->second));

      if (!sevents.empty()) {
            sevents.sort();
            for (iSEvent sk = sevents.begin(); sk != sevents.end(); ++sk)
                  playEvent(sk->ev);
            //
            // why recompute range1?
            MPEventRange range1 = stuckNotes.equal_range(_midiTick);
            stuckNotes.erase(stuckNotes.begin(), range1.second);
            playEvents.erase(playEvents.begin(), range2.second);
            }
      }

