//=========================================================
//  MusE
//  Linux Music Editor
//  $Id: sync.cpp,v 1.1.1.1 2003/10/29 10:05:10 wschweer Exp $
//
//  (C) Copyright 2003 Werner Schweer (ws@seh.de)
//=========================================================

#include <cmath>
#include "midithread.h"
#include "sync.h"
#include "song.h"
#include "utils.h"
#include "midiport.h"
#include "mididev.h"
#include "globals.h"

int rxSyncPort = -1;         // receive from all ports
int txSyncPort = 1;
int rxDeviceId = 0x7f;       // ´any´ device
int txDeviceId = 0x7f;       // ´any´ device

bool debugSync = false;
int mtcType     = 1;
MTC mtcOffset;
BValue extSyncFlag(0, "extSync");       // false - MASTER, true - SLAVE
bool genMTCSync = false;      // output MTC Sync
bool genMCSync  = false;      // output MidiClock Sync
bool genMMC     = false;      // output Midi Machine Control
bool acceptMTC  = false;
bool acceptMC   = true;
bool acceptMMC  = true;

static MTC mtcCurTime;
static int mtcState;    // 0-7 next expected quarter message
static bool mtcValid;
static int mtcLost;
static bool mtcSync;    // receive complete mtc frame?

static bool mcStart = false;
static int mcStartTick;

//---------------------------------------------------------
//  mmcInput
//    Midi Machine Control Input received
//---------------------------------------------------------

void MidiThread::mmcInput(const unsigned char* p, int n)
      {
      if (debugSync)
            printf("mmcInput: n:%d %02x %02x %02x %02x\n",
               n, p[2], p[3], p[4], p[5]);
      if (!(extSyncFlag.value() && acceptMMC))
            return;

      switch(p[3]) {
            case 1:
                  if (debugSync)
                        printf("  MMC: STOP\n");
                  if ((state == PLAY || state == PRECOUNT))
                        stopPlay();
                  break;
            case 2:
                  if (debugSync)
                        printf("  MMC: PLAY\n");
            case 3:
                  if (debugSync)
                        printf("  MMC: DEFERRED PLAY\n");
                  mtcState = 0;
                  mtcValid = false;
                  mtcLost  = 0;
                  mtcSync  = false;
                  startPlay(song->cpos());
                  break;

            case 4:
                  printf("MMC: FF not implemented\n");
                  break;
            case 5:
                  printf("MMC: REWIND not implemented\n");
                  break;
            case 6:
                  printf("MMC: REC STROBE not implemented\n");
                  break;
            case 7:
                  printf("MMC: REC EXIT not implemented\n");
                  break;
            case 0xd:
                  printf("MMC: RESET not implemented\n");
                  break;
            case 0x44:
                  if (p[5] == 0) {
                        printf("MMC: LOCATE IF not implemented\n");
                        break;
                        }
                  else if (p[5] == 1) {
                        MTC mtc(p[6] & 0x1f, p[7], p[8], p[9], p[10]);
                        int mmcPos = tempomap.time2tick(mtc.time());

                        seek(mmcPos);
                        if (debugSync) {
                              printf("MMC: %f %d seek ",
                                 mtc.time(), mmcPos);
                              mtc.print();
                              printf("\n");
                              }
                        write(sigFd, "G", 1);
                        break;
                        }
                  // fall through
            default:
                  printf("MMC %x %x, unknown\n", p[3], p[4]); break;
            }
      }

//---------------------------------------------------------
//   mtcInputQuarter
//    process Quarter Frame Message
//---------------------------------------------------------

void MidiThread::mtcInputQuarter(int, unsigned char c)
      {
      static int hour, min, sec, frame;

      if (!extSyncFlag.value())
            return;

      int valL = c & 0xf;
      int valH = valL << 4;

      int _state = (c & 0x70) >> 4;
      if (mtcState != _state)
            mtcLost += _state - mtcState;
      mtcState = _state + 1;

      switch(_state) {
            case 7:
                  hour  = (hour  & 0x0f) | valH;
                  break;
            case 6:
                  hour  = (hour  & 0xf0) | valL;
                  break;
            case 5:
                  min   = (min   & 0x0f) | valH;
                  break;
            case 4:
                  min   = (min   & 0xf0) | valL;
                  break;
            case 3:
                  sec   = (sec   & 0x0f) | valH;
                  break;
            case 2:
                  sec   = (sec   & 0xf0) | valL;
                  break;
            case 1:
                  frame = (frame & 0x0f) | valH;
                  break;
            case 0:  frame = (frame & 0xf0) | valL;
                  break;
            }
      frame &= 0x1f;    // 0-29
      sec   &= 0x3f;    // 0-59
      min   &= 0x3f;    // 0-59
      hour  &= 0x1f;

      if (mtcState == 8) {
            mtcValid = (mtcLost == 0);
            mtcState = 0;
            mtcLost  = 0;
            if (mtcValid) {
                  mtcCurTime.set(hour, min, sec, frame);
                  mtcSyncMsg(mtcCurTime, !mtcSync);
                  mtcSync = true;
                  }
            }
      else if (mtcValid && (mtcLost == 0)) {
            mtcCurTime.incQuarter();
            mtcSyncMsg(mtcCurTime, false);
            }
      }

//---------------------------------------------------------
//   mtcInputFull
//    process Frame Message
//---------------------------------------------------------

void MidiThread::mtcInputFull(const unsigned char* p, int n)
      {
      if (debugSync)
            printf("mtcInputFull\n");
      if (!extSyncFlag.value())
            return;

      if (p[3] != 1) {
            if (p[3] != 2) {   // silently ignore user bits
                  printf("unknown mtc msg subtype 0x%02x\n", p[3]);
                  dump(p, n);
                  }
            return;
            }
      int hour  = p[4];
      int min   = p[5];
      int sec   = p[6];
      int frame = p[7];

      frame &= 0x1f;    // 0-29
      sec   &= 0x3f;    // 0-59
      min   &= 0x3f;    // 0-59
//      int type = (hour >> 5) & 3;
      hour &= 0x1f;

      mtcCurTime.set(hour, min, sec, frame);
      mtcState = 0;
      mtcValid = true;
      mtcLost  = 0;
      }

//---------------------------------------------------------
//   nonRealtimeSystemSysex
//---------------------------------------------------------

void MidiThread::nonRealtimeSystemSysex(const unsigned char* p, int n)
      {
//      int chan = p[2];
      switch(p[3]) {
            case 4:
                  printf("NRT Setup\n");
                  break;
            default:
                  printf("unknown NRT Msg 0x%02x\n", p[3]);
                  dump(p, n);
                  break;
           }
      }

//---------------------------------------------------------
//   setSongPosition
//    MidiBeat is a 14 Bit value. Each MidiBeat spans
//    6 MIDI Clocks. Inother words, each MIDI Beat is a
//    16th note (since there are 24 MIDI Clocks in a
//    quarter note).
//---------------------------------------------------------

void MidiThread::setSongPosition(int port, int midiBeat)
      {
      if (midiInputTrace)
            printf("set song position port:%d %d\n", port, midiBeat);
      if (!extSyncFlag.value())
            return;
      playTickPos = midiClick = (division * midiBeat) / 4;
      if (debugSync)
            printf("setSongPosition %d\n", playTickPos);
      write(sigFd, "G", 1);
      }

//---------------------------------------------------------
//   realtimeSystemInput
//    real time message received
//---------------------------------------------------------

void MidiThread::realtimeSystemInput(int port, int c)
      {
      if (midiInputTrace)
            printf("realtimeSystemInput port:%d 0x%x\n", port+1, c);

      if (midiInputTrace && (rxSyncPort != port) && rxSyncPort != -1) {
            if (debugSync)
                  printf("rxSyncPort configured as %d; received sync from port %d\n",
                     rxSyncPort, port);
            return;
            }
      if (!extSyncFlag.value())
            return;
      switch(c) {
            case 0xf8:  // midi clock (24 ticks / quarter note)
                  {
                  double mclock0 = curTime();
                  processMidiClock();
                  if (state == PLAY) {
                        recTick  += division / 24;
                        int diff  = recTick - _midiTick;
                        int tempo = tempomap.tempo(0);
                        tempo    -= diff*5;
                        tempomap.setTempo(0, tempo);
                        _midiTick = recTick;  // brutal
                        break;
                        }
                  double tdiff0   = mclock0 - mclock1;
                  double tdiff1   = mclock1 - mclock2;
                  if (mclock1 == 0.0)
                        midiPorts[port].device()->discardInput();
                  if ((mclock2 != 0.0) && (tdiff0 > 0.0)) {
                        int tempo0 = int(24000000.0 * tdiff0 + .5);
                        int tempo1 = int(24000000.0 * tdiff1 + .5);
                        int tempo = tempomap.tempo(0);

                        int diff0 = tempo0 - tempo;
                        int diff1 = tempo1 - tempo0;
                        if (diff0) {
                              int newTempo = tempo + diff0/8 + diff1/16;
                              tempomap.setTempo(0, newTempo);
                              }
                        }
                  mclock2 = mclock1;
                  mclock1 = mclock0;
                  }
                  break;
            case 0xf9:  // midi tick  (every 10 msec)
                  if (mcStart) {
                        song->setPos(0, mcStartTick);
                        mcStart = false;
                        return;
                        }
                  break;
            case 0xfa:  // start
                  if (debugSync)
                        printf("   start\n");
                  if (state == IDLE) {
                        seek(0);
                        startPlay(song->cpos());
                        }
                  break;
            case 0xfb:  // continue
                  if (debugSync)
                        printf("   continue\n");
                  if (state == IDLE) {
                        startPlay(song->cpos());
                        }
                  break;
            case 0xfc:  // stop
                  if (debugSync)
                        printf("   stop\n");
                  if (state == PLAY)
                        stopPlay();
                  break;
            case 0xfd:  // unknown
            case 0xfe:  // active sensing
            case 0xff:  // system reset
                  break;
            }
      }

//---------------------------------------------------------
//   mtcSyncMsg
//    process received mtc Sync
//    seekFlag - first complete mtc frame received after
//                start
//---------------------------------------------------------

void MidiThread::mtcSyncMsg(const MTC& mtc, bool seekFlag)
      {
      double time = mtc.time();
      if (debugSync)
            printf("mtcSyncMsg: time %f\n", time);

      if (seekFlag && state == START_PLAY) {
//            int tick = tempomap.time2tick(time);
            state = PLAY;
            write(sigFd, "1", 1);  // say PLAY to gui
            return;
            }
      // double curT = curTime();

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

      //
      // diff is the time in sec MusE is out of sync
      //
      double diff = time - (double(samplePosStart)/double(sampleRate));
      if (debugSync)
            printf("   state %d diff %f\n", mtcState, diff);
      }


