//=========================================================
//  MusE
//  Linux Music Editor
//  $Id: midiseq.cpp,v 1.30.2.5 2004/12/12 20:29:49 spamatica Exp $
//
//    high priority task for scheduling midi events
//
//  (C) Copyright 2003 Werner Schweer (ws@seh.de)
//=========================================================

#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <poll.h>

#include "globals.h"
#include "midi.h"
#include "midiseq.h"
#include "midiport.h"
#include "mididev.h"
#include "midictrl.h"
#include "audio.h"
#include "driver/alsamidi.h"
#include "sync.h"
#include "synth.h"
#include "song.h"
#include "gconfig.h"

MidiSeq* midiSeq;
int MidiSeq::ticker = 0;

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

static void readMsg(void* p, void*)
      {
      
      MidiSeq* at = (MidiSeq*)p;
      at->readMsg();
      }

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

void MidiSeq::processMsg(const ThreadMsg* m)
      {
      AudioMsg* msg = (AudioMsg*)m;
      switch(msg->id) {
            case MS_PROCESS:
                  audio->processMidi();
                  break;
            case SEQM_SEEK:
                  processSeek();
                  break;
            case MS_STOP:
                  processStop();
                  break;
            case MS_SET_RTC:
                  doSetuid();
                  setRtcTicks();
                  undoSetuid();
                  break;
            case MS_UPDATE_POLL_FD:
                  updatePollFd();
                  break;
            case SEQM_ADD_TRACK:
                  song->insertTrack2(msg->track, msg->ival);
                  updatePollFd();
                  break;
            case SEQM_REMOVE_TRACK:
                  song->cmdRemoveTrack(msg->track);
                  updatePollFd();
                  break;
            case SEQM_CHANGE_TRACK:
                  song->changeTrack((Track*)(msg->p1), (Track*)(msg->p2));
                  updatePollFd();
                  break;
            case SEQM_ADD_PART:
                  song->cmdAddPart((Part*)msg->p1);
                  break;
            case SEQM_REMOVE_PART:
                  song->cmdRemovePart((Part*)msg->p1);
                  break;
            case SEQM_CHANGE_PART:
                  song->cmdChangePart((Part*)msg->p1, (Part*)msg->p2);
                  break;
            case SEQM_SET_MIDI_DEVICE:
                  ((MidiPort*)(msg->p1))->setMidiDevice((MidiDevice*)(msg->p2));
                  updatePollFd();
                  break;
            case SEQM_IDLE:
                  idle = msg->a;
                  break;
            default:
                  printf("MidiSeq::processMsg() unknown id %d\n", msg->id);
                  break;
            }
      }

//---------------------------------------------------------
//   processStop
//---------------------------------------------------------

void MidiSeq::processStop()
      {
      //
      //    stop stuck notes
      //
      for (iMidiDevice id = midiDevices.begin(); id != midiDevices.end(); ++id) {
            MidiDevice* md = *id;
            if (md->midiPort() == -1)
                  continue;
            MPEventList* pel = md->playEvents();
            MPEventList* sel = md->stuckNotes();
            pel->clear();
            for (iMPEvent i = sel->begin(); i != sel->end(); ++i) {
                  MidiPlayEvent ev = *i;
                  ev.setTime(0);
                  pel->add(ev);
                  }
            sel->clear();
            md->setNextPlayEvent(pel->begin());
            }
      }

//---------------------------------------------------------
//   processSeek
//---------------------------------------------------------

void MidiSeq::processSeek()
      {
      int pos = audio->tickPos();
      if (pos == 0 && !song->record())
            audio->initDevices();

      //---------------------------------------------------
      //    set all controller
      //---------------------------------------------------

      for (iMidiDevice i = midiDevices.begin(); i != midiDevices.end(); ++i) {
            MidiDevice* dev = *i;
            int port = dev->midiPort();
            if (port == -1)
                  continue;
            MidiPort* mp = &midiPorts[port];
            MidiCtrlValListList* cll = mp->controller();

            MPEventList* el = dev->playEvents();

            if (audio->isPlaying()) {
                  // stop all notes
                  el->clear();
                  MPEventList* sel = dev->stuckNotes();
                  for (iMPEvent i = sel->begin(); i != sel->end(); ++i) {
                        MidiPlayEvent ev = *i;
                        ev.setTime(0);
                        el->add(ev);
                        }
                  sel->clear();
                  }
            else
                  el->erase(el->begin(), dev->nextPlayEvent());
            for (iMidiCtrlValList ivl = cll->begin(); ivl != cll->end(); ++ivl) {
                  MidiCtrlValList* vl = ivl->second;
                  int val = vl->value(pos);
                  if (val != CTRL_VAL_UNKNOWN) {
                        int channel = ivl->first >> 24;
                        el->add(MidiPlayEvent(0, port, channel, ME_CONTROLLER, vl->num(), val));
                        }
                  }
            dev->setNextPlayEvent(el->begin());
            }
      }

//---------------------------------------------------------
//   MidiSeq
//---------------------------------------------------------

MidiSeq::MidiSeq(int priority, const char* name)
   : Thread(priority, name)
      {
      prio = priority;
      idle = false;
      midiClock = 0;
      }

//---------------------------------------------------------
//   threadStart
//    called from loop()
//---------------------------------------------------------

void MidiSeq::threadStart(void*)
      {
      struct sched_param rt_param;
      memset(&rt_param, 0, sizeof(rt_param));
      int prio_min = sched_get_priority_min(SCHED_FIFO);
      int prio_max = sched_get_priority_max(SCHED_FIFO);

      if (prio < prio_min) prio = prio_min;
      else if (prio > prio_max) prio = prio_max;      

      rt_param.sched_priority = prio;
      int rv = pthread_setschedparam(pthread_self(), SCHED_FIFO, &rt_param);
      if (rv != 0)
            perror("set realtime scheduler");

      int policy;
      if ((policy = sched_getscheduler (0)) < 0) {
            printf("Cannot get current client scheduler: %s\n", strerror(errno));
            }
      if (policy != SCHED_FIFO)
            printf("midi thread %d _NOT_ running SCHED_FIFO\n", getpid());
      updatePollFd();
      }

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

static void alsaMidiRead(void*, void*)
      {
      // calls itself midiDevice->recordEvent(MidiRecordEvent):
      alsaProcessMidiInput();
      }

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

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

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

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

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

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

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

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

      clearPollFd();
      addPollFd(timerFd, POLLIN, midiTick, this, 0);

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

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

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

      for (iMidiDevice imd = midiDevices.begin(); imd != midiDevices.end(); ++imd) {
            MidiDevice* dev = *imd;
            int port = dev->midiPort();
            if (port == -1)
                  continue;
            if ((dev->rwFlags()&0x2) || (extSyncFlag.value()
               && (rxSyncPort == port || rxSyncPort == -1))) {
                  addPollFd(dev->selectRfd(), POLLIN, ::midiRead, this, dev);
                  }
            if (dev->bytesToWrite())
                  addPollFd(dev->selectWfd(), POLLOUT, ::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)

      addPollFd(alsaSelectRfd(), POLLIN, ::alsaMidiRead, this, 0);
      }

//---------------------------------------------------------
//   threadStop
//    called from loop()
//---------------------------------------------------------

void MidiSeq::threadStop()
      {
      timer.stopTimer();
      }

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

bool MidiSeq::setRtcTicks()
      {
      
      timer.setTimerTicks(config.rtcTicks);  
      timer.startTimer();      
      realRtcTicks = config.rtcTicks;
      return true;
      }

//---------------------------------------------------------
//   start
//    return true on error
//---------------------------------------------------------

bool MidiSeq::start()
      {
      timerFd = -1;

      doSetuid();
      
      timerFd = timer.initTimer();
      setRtcTicks();
      undoSetuid();
      Thread::start();
      return false;
      }

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

void MidiSeq::processMidiClock()
      {
      if (genMCSync)
            midiPorts[txSyncPort].sendClock();
/*      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();

            startRecordPos.setPosTick(playTickPos);
            }
*/
      midiClock += config.division/24;
      }

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

void MidiSeq::midiTick(void* p, void*)
      {
      MidiSeq* at = (MidiSeq*)p;
      at->processTimerTick();
      if (TIMER_DEBUG)
      {
        if(MidiSeq::ticker++ > 100000)
          {
          printf("tick!\n");
          MidiSeq::ticker=0;
          }
        }
      }

//---------------------------------------------------------
//   processTimerTick
//---------------------------------------------------------

void MidiSeq::processTimerTick()
      {
      extern int watchMidi;
      ++watchMidi;      // make a simple watchdog happy

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

      unsigned long nn;
      if (timerFd != -1) {
            nn = timer.getTimerTicks();
            nn >>= 8;
            }

      if (idle)
            return;

      unsigned curFrame = audio->curFrame();

      if (!extSyncFlag.value()) {
            int curTick = tempomap.frame2tick(curFrame);
            if (curTick >= midiClock)
                  processMidiClock();
            }

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

      //
      // play all events upto curFrame
      //
      for (iMidiDevice id = midiDevices.begin(); id != midiDevices.end(); ++id) {
            MidiDevice* md = *id;
            int port = md->midiPort();
            MidiPort* mp = port != -1 ? &midiPorts[port] : 0;
            if (md->isSynti())      // syntis are handled by audio thread
                  continue;
            MPEventList* el = md->playEvents();
            if (el->empty())
                  continue;
            iMPEvent i = md->nextPlayEvent();
            for (; i != el->end(); ++i) {
/*                  if (i->time() > curFrame) {
                        printf("  curT %d  frame %d\n", i->time(), curFrame);
                        break;
                        }
 */
                  // break if event cannot be delivered
                  if (mp) {
                        if (mp->sendEvent(*i))
                              break;
                        }
                  else {
                        if (md->putEvent(*i))
                              break;
                        }
                  }
            md->setNextPlayEvent(i);
            }
      }

//---------------------------------------------------------
//   msgMsg
//---------------------------------------------------------

void MidiSeq::msgMsg(int id)
      {
      AudioMsg msg;
      msg.id = id;
      Thread::sendMsg(&msg);
      }

//---------------------------------------------------------
//   msgSetMidiDevice
//    to avoid timeouts in the RT-thread, setMidiDevice
//    is done in GUI context after setting the midi thread
//    into idle mode
//---------------------------------------------------------

void MidiSeq::msgSetMidiDevice(MidiPort* port, MidiDevice* device)
      {
      AudioMsg msg;
      msg.id = SEQM_IDLE;
      msg.a  = true;
      Thread::sendMsg(&msg);

      port->setMidiDevice(device);

      msg.id = SEQM_IDLE;
      msg.a  = false;
      Thread::sendMsg(&msg);
      }

void MidiSeq::msgProcess()      { msgMsg(MS_PROCESS); }
void MidiSeq::msgSeek()         { msgMsg(SEQM_SEEK); }
void MidiSeq::msgStop()         { msgMsg(MS_STOP); }
void MidiSeq::msgSetRtc()       { msgMsg(MS_SET_RTC); }
void MidiSeq::msgUpdatePollFd() { msgMsg(MS_UPDATE_POLL_FD); }

