//| Copyright: (C) 2020-2024 Kevin Larke <contact AT larke DOT org> 
//| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
#include "cwCommon.h"
#include "cwLog.h"
#include "cwCommonImpl.h"
#include "cwTest.h"
#include "cwMem.h"
#include "cwTime.h"
#include "cwObject.h"
#include "cwText.h"
#include "cwTextBuf.h"
#include "cwThread.h"

#include "cwMidi.h"
#include "cwMidiDecls.h"
#include "cwMidiFile.h"
#include "cwMidiDevice.h"
#include <poll.h>

#include "cwMidiAlsa.h"
#include "cwMidiFileDev.h"


namespace cw
{
  namespace midi
  {
    namespace device
    {
      typedef enum {
        kStoppedStateId,
        kPausedStateId,
        kPlayingStateId
      } transportStateId_t;

      typedef struct device_str
      {
        cbFunc_t cbFunc;
        void*    cbArg;

        alsa::handle_t alsaDevH;
        unsigned       alsaPollfdN;
        struct pollfd* alsaPollfdA;
        unsigned       alsa_dev_cnt;

        file_dev::handle_t fileDevH;
        unsigned file_dev_cnt;
        
        unsigned total_dev_cnt;

        
        unsigned thread_timeout_microsecs;
        thread::handle_t threadH;

        transportStateId_t fileDevStateId;
        
        unsigned long long offset_micros;
        unsigned long long last_posn_micros;
        time::spec_t       start_time;

        ch_msg_t* buf;
        unsigned  bufN;
        std::atomic<unsigned>  buf_ii;
        std::atomic<unsigned>  buf_oi;

        bool filterRtSenseFl;
        
      } device_t;

      device_t* _handleToPtr( handle_t h )
      { return handleToPtr<handle_t,device_t>(h); }

      rc_t _validate_dev_index( device_t* p, unsigned devIdx )
      {
        rc_t rc = kOkRC;
        if( devIdx >= p->total_dev_cnt )
          rc = cwLogError(kInvalidArgRC,"Invalid MIDI device index (%i >= %i).",devIdx,p->total_dev_cnt);
          
        return rc;
      }
      
      unsigned _devIdxToAlsaDevIdx( device_t* p, unsigned devIdx )
      {
        return devIdx >= p->alsa_dev_cnt ? kInvalidIdx : devIdx;
      }

      unsigned _devIdxToFileDevIdx( device_t* p, unsigned devIdx )
      {
        return devIdx==kInvalidIdx || devIdx < p->alsa_dev_cnt ? kInvalidIdx : devIdx - p->alsa_dev_cnt;
      }

      unsigned _alsaDevIdxToDevIdx( device_t* p, unsigned alsaDevIdx )
      { return alsaDevIdx; }

      unsigned _fileDevIdxToDevIdx( device_t* p, unsigned fileDevIdx )
      { return fileDevIdx == kInvalidIdx ? kInvalidIdx : p->alsa_dev_cnt + fileDevIdx; }

      bool _isAlsaDevIdx( device_t* p, unsigned devIdx )
      { return devIdx==kInvalidIdx ? false : devIdx < p->alsa_dev_cnt; }

      bool _isFileDevIdx( device_t* p, unsigned devIdx )
      { return devIdx==kInvalidIdx ? false : (p->alsa_dev_cnt <= devIdx && devIdx < p->total_dev_cnt); }
      

      rc_t _destroy( device_t* p )
      {
        rc_t rc = kOkRC;
        
        if((rc = destroy(p->threadH)) != kOkRC )
        {
          rc = cwLogError(rc,"MIDI port thread destroy failed.");
          goto errLabel;
        }

        
        destroy(p->alsaDevH);
        destroy(p->fileDevH);

        mem::release(p->buf);
        mem::release(p);

      errLabel:
        return rc;
      }

      bool _thread_func( void* arg )
      {
        device_t* p = (device_t*)arg;

        unsigned max_sleep_micros = p->thread_timeout_microsecs/2;
        unsigned sleep_millis = max_sleep_micros/1000;
        
        if( p->fileDevStateId == kPlayingStateId )
        {
          time::spec_t       cur_time         = time::current_time();
          unsigned           elapsed_micros   = time::elapsedMicros(p->start_time,cur_time);
          
          unsigned long long file_posn_micros = p->offset_micros + elapsed_micros;

          // Send any messages whose time has expired and get the
          // wait time for the next message.
          file_dev::exec_result_t r = exec(p->fileDevH,file_posn_micros);

          // If the file dev has no more messages to play then sleep for the maximum time.
          unsigned file_dev_sleep_micros = r.eof_fl ? max_sleep_micros : r.next_msg_wait_micros;

          // Prevent the wait time from being longer than the thread state change timeout.
          unsigned sleep_micros = std::min( max_sleep_micros, file_dev_sleep_micros );

          p->last_posn_micros = file_posn_micros + sleep_micros;

          // If the wait time is less than one millisecond then make it one millisecond.
          // (remember that we allowed the file device to go 3 milliseconds ahead and
          //  and so it is safe, and better for preventing many very short timeout's,
          // to wait at least 1 millisecond)
          sleep_millis = std::max(1U, sleep_micros/1000 );
        }
        

        // TODO: Consider replacing the poll() with epoll_wait2() is apparently
        // optimized for more accurate time outs.
        
        // Block here waiting for ALSA events or timeout when the next file msg should be sent
        int sysRC = poll( p->alsaPollfdA, p->alsaPollfdN, sleep_millis );
        
        if(sysRC == 0 )
        {
          // time-out
        }
        else
        {
          if( sysRC > 0 )
          {
            rc_t rc;
            if((rc = handleInputMsg(p->alsaDevH)) != kOkRC )
            {
              cwLogError(rc,"ALSA MIDI dev. input failed");
            }
          }
          else
          {
            cwLogSysError(kOpFailRC,sysRC,"MIDI device poll failed.");
          }
        }
        
        return true;
      }

      void _callback( void* cbArg, const packet_t* pktArray, unsigned pktCnt )
      {
        device_t* p = (device_t*)cbArg;
        
        for(unsigned i=0; i<pktCnt; ++i)
        {
          const packet_t* pkt = pktArray + i;
          if( pkt->msgArray != nullptr )
          {
            unsigned ii = p->buf_ii.load();
            unsigned oi = p->buf_oi.load();
            for(unsigned j=0;  j<pkt->msgCnt; ++j)
            {
              ch_msg_t* m = p->buf + ii;
              m->devIdx    = pkt->devIdx;
              m->portIdx   = pkt->portIdx;
              m->timeStamp = pkt->msgArray[j].timeStamp;
              m->uid       = pkt->msgArray[j].uid;
              m->ch        = pkt->msgArray[j].ch;
              m->status    = pkt->msgArray[j].status;
              m->d0        = pkt->msgArray[j].d0;
              m->d1        = pkt->msgArray[j].d1;

              ii = (ii+1 == p->bufN ? 0 : ii+1);
              if( ii == oi )
              {
                cwLogError(kBufTooSmallRC,"The MIDI device buffer is full %i.",p->bufN);
              }
            }

            p->buf_ii.store(ii);
            
          }
        }

        if( p->cbFunc != nullptr )
          p->cbFunc(p->cbArg,pktArray,pktCnt);
      }

      
    } // device
  } // midi
} // cw



cw::rc_t cw::midi::device::create( handle_t&   hRef,
                                   cbFunc_t    cbFunc,
                                   void*       cbArg,
                                   const char* filePortLabelA[],
                                   unsigned    max_file_cnt,
                                   const char* appNameStr,
                                   const char* fileDevName,
                                   unsigned    fileDevReadAheadMicros,
                                   unsigned    parserBufByteCnt,
                                   bool        enableBufFl,
                                   unsigned    bufferMsgCnt,
                                   bool        filterRtSenseFl )
{
  rc_t rc  = kOkRC;
  rc_t rc1 = kOkRC;  
  
  if((rc = destroy(hRef)) != kOkRC )
    return rc;

  device_t* p = mem::allocZ<device_t>();
  
  if((rc = create( p->alsaDevH,
                   enableBufFl ? _callback : cbFunc,
                   enableBufFl ? p         : cbArg,
                   parserBufByteCnt,
                   appNameStr,
                   filterRtSenseFl)) != kOkRC )
  {
    rc = cwLogError(rc,"ALSA MIDI device create failed.");
    goto errLabel;
  }

  p->alsa_dev_cnt = count(p->alsaDevH);

  if((rc = create( p->fileDevH,
                   enableBufFl ? _callback : cbFunc,
                   enableBufFl ? p         : cbArg,
                   p->alsa_dev_cnt,
                   filePortLabelA,
                   max_file_cnt,
                   fileDevName,
                   fileDevReadAheadMicros )) != kOkRC )
  {
    rc = cwLogError(rc,"MIDI file device create failed.");
    goto errLabel;
  }

  p->cbFunc        = cbFunc;
  p->cbArg         = cbArg;
  p->file_dev_cnt  = count(p->fileDevH);    
  p->total_dev_cnt = p->alsa_dev_cnt + p->file_dev_cnt;
  p->alsaPollfdA   = pollFdArray(p->alsaDevH,p->alsaPollfdN);
  p->fileDevStateId = kStoppedStateId;
  p->buf            = mem::allocZ<ch_msg_t>( bufferMsgCnt );
  p->bufN           = bufferMsgCnt;
  p->buf_ii.store(0);
  p->buf_oi.store(0);
  
  if((rc = thread::create(p->threadH,
                          _thread_func,
                          p,
                          "midi_dev")) != kOkRC )
  {
    rc = cwLogError(rc,"The MIDI file device thread create failed.");
    goto errLabel;
  }

  p->thread_timeout_microsecs = stateTimeOutMicros(p->threadH); 
  
  hRef.set(p);

  if((rc = unpause(p->threadH)) != kOkRC )
  {
    rc = cwLogError(rc,"Initial thread un-pause failed.");
    goto errLabel;
  }

  
errLabel:
  if(rc != kOkRC )
     rc1 = _destroy(p);

  if((rc = rcSelect(rc,rc1)) != kOkRC )
    rc = cwLogError(rc,"MIDI device mgr. create failed.");
    
  return rc;
}

cw::rc_t cw::midi::device::create( handle_t&       h,
                                   cbFunc_t        cbFunc,
                                   void*           cbArg,
                                   const object_t* args )
{
  rc_t            rc                     = kOkRC;
  const char*     appNameStr             = nullptr;
  const char*     fileDevName            = "file_dev";
  unsigned        fileDevReadAheadMicros = 3000;
  unsigned        parseBufByteCnt        = 1024;
  bool            enableBufFl            = false;
  unsigned        bufMsgCnt              = 0;
  const object_t* file_ports             = nullptr;
  const object_t* port                   = nullptr;
  bool            filterRtSenseFl        = true;;

  if((rc = args->getv("appNameStr",appNameStr,
                      "fileDevName",fileDevName,
                      "fileDevReadAheadMicros",fileDevReadAheadMicros,
                      "parseBufByteCnt",parseBufByteCnt,
                      "enableBufFl",enableBufFl,
                      "bufferMsgCnt",bufMsgCnt,
                      "file_ports",file_ports,
                      "filterRtSenseFl",filterRtSenseFl)) != kOkRC )
  {
    rc = cwLogError(rc,"MIDI port parse args. failed.");
  }
  else
  {
    unsigned fpi            = 0;
    unsigned filePortArgCnt = file_ports->child_count();
    const char* labelArray[ filePortArgCnt ];
    memset(labelArray,0,sizeof(labelArray));
    
    for(unsigned i=0; i<filePortArgCnt; ++i)
    {
      if((port = file_ports->child_ele(i)) != nullptr )
      {
        if((rc = port->getv("label",labelArray[fpi])) != kOkRC )
        {
          rc = cwLogError(rc,"MIDI file dev. port arg parse failed.");
          goto errLabel;
        }
        
        fpi += 1;
        
      }
    }
    
    rc = create(h,
                cbFunc,
                cbArg,
                labelArray,
                fpi,
                appNameStr,
                fileDevName,
                fileDevReadAheadMicros,
                parseBufByteCnt,
                enableBufFl,
                bufMsgCnt,
                filterRtSenseFl);
    
  }
  
  
errLabel:
  return rc;  
}

      
cw::rc_t cw::midi::device::destroy( handle_t& hRef)
{
  rc_t rc = kOkRC;
  if( !hRef.isValid() )
    return rc;

  device_t* p = _handleToPtr(hRef);

  if((rc = _destroy(p)) != kOkRC )
  {
    rc = cwLogError(rc,"MIDI device mgr. destroy failed.");
    goto errLabel;
  }

  hRef.clear();
errLabel:
  return rc;
}
      
bool cw::midi::device::isInitialized( handle_t h )
{ return h.isValid(); }

unsigned    cw::midi::device::count( handle_t h )
{
  device_t* p = _handleToPtr(h);
  return p->total_dev_cnt;
}
      
const char* cw::midi::device::name( handle_t h, unsigned devIdx )
{
  device_t*   p          = _handleToPtr(h);
  const char* ret_name   = nullptr;
  unsigned    alsaDevIdx = kInvalidIdx;
  unsigned    fileDevIdx = kInvalidIdx;
  
  if((alsaDevIdx = _devIdxToAlsaDevIdx(p,devIdx)) != kInvalidIdx )
    ret_name                                       = name(p->alsaDevH,alsaDevIdx);
  else
  {
    if((fileDevIdx = _devIdxToFileDevIdx(p,devIdx)) != kInvalidIdx)
      ret_name = name(p->fileDevH,fileDevIdx);
    else
      cwLogError(kInvalidArgRC,"%i is an invalid device index.",devIdx);    
  }

  if( ret_name == nullptr )
    cwLogError(kOpFailRC,"The name of device index %i could not be found.",devIdx);

  return ret_name;
}
      
unsigned    cw::midi::device::nameToIndex(handle_t h, const char* deviceName)
{
  device_t* p = _handleToPtr(h);
  unsigned  devIdx = kInvalidIdx;

  if((devIdx = nameToIndex(p->alsaDevH,deviceName)) != kInvalidIdx )
    devIdx   = _alsaDevIdxToDevIdx(p,devIdx);
  else
  {
    if((devIdx = nameToIndex(p->fileDevH,deviceName)) != kInvalidIdx )
      devIdx   = _fileDevIdxToDevIdx(p,devIdx);
  }

  if( devIdx == kInvalidIdx )
    cwLogError(kOpFailRC,"MIDI device name to index failed on '%s'.",cwStringNullGuard(deviceName));

  return devIdx;
}

unsigned cw::midi::device::portNameToIndex( handle_t h, unsigned devIdx, unsigned flags, const char* portNameStr )
{
  device_t* p          = _handleToPtr(h);
  unsigned  alsaDevIdx = kInvalidIdx;
  unsigned  fileDevIdx = kInvalidIdx;
  unsigned  portIdx    = kInvalidIdx;
  
  if((alsaDevIdx   = _devIdxToAlsaDevIdx(p,devIdx)) != kInvalidIdx )
    portIdx = portNameToIndex(p->alsaDevH,alsaDevIdx,flags,portNameStr);
  else
    if((fileDevIdx = _devIdxToFileDevIdx(p,devIdx)) != kInvalidIdx )
      portIdx = portNameToIndex(p->fileDevH,fileDevIdx,flags,portNameStr);
  
  if( portIdx == kInvalidIdx )
    cwLogError(kInvalidArgRC,"The MIDI port name '%s' could not be found.",cwStringNullGuard(portNameStr));
               
  return portIdx;
}

cw::rc_t cw::midi::device::portEnable( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx, bool enableFl )
{
  rc_t      rc         = kOkRC;
  device_t* p          = _handleToPtr(h);
  unsigned  alsaDevIdx = kInvalidIdx;
  unsigned  fileDevIdx = kInvalidIdx;
  
  if((alsaDevIdx   = _devIdxToAlsaDevIdx(p,devIdx)) != kInvalidIdx )
    rc = portEnable(p->alsaDevH,alsaDevIdx,flags,portIdx,enableFl);
  else
    if((fileDevIdx = _devIdxToFileDevIdx(p,devIdx)) != kInvalidIdx )
      rc = portEnable(p->fileDevH,fileDevIdx,flags,portIdx,enableFl);
  
  if( rc != kOkRC )
    rc = cwLogError(rc,"The MIDI port %s failed on dev '%s'  port '%s'.",enableFl ? "enable" : "disable", cwStringNullGuard(name(h,devIdx)), cwStringNullGuard(portName(h,devIdx,flags,portIdx)));
               
  return rc;
  
}
      
unsigned cw::midi::device::portCount(  handle_t h, unsigned devIdx, unsigned flags )
{
  device_t* p          = _handleToPtr(h);
  unsigned  alsaDevIdx = kInvalidIdx;
  unsigned  fileDevIdx = kInvalidIdx;
  unsigned  portCnt = 0;
  
  if((alsaDevIdx = _devIdxToAlsaDevIdx(p,devIdx)) != kInvalidIdx )
    portCnt      = portCount(p->alsaDevH,alsaDevIdx,flags);
  else
  {
    if((fileDevIdx = _devIdxToFileDevIdx(p,devIdx)) != kInvalidIdx )
      portCnt      = portCount(p->fileDevH,fileDevIdx,flags);
    else
      cwLogError(kInvalidArgRC,"The device index %i is not valid. Port count access failed.",devIdx);
  }
  
  return portCnt;   
}
      
const char* cw::midi::device::portName(   handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx )
{
  device_t*   p          = _handleToPtr(h);
  unsigned    alsaDevIdx = kInvalidIdx;
  unsigned    fileDevIdx = kInvalidIdx;
  const char* name       = nullptr;

  if((alsaDevIdx   = _devIdxToAlsaDevIdx(p,devIdx)) != kInvalidIdx )
    name = portName(p->alsaDevH,alsaDevIdx,flags,portIdx);
  else
    if((fileDevIdx = _devIdxToFileDevIdx(p,devIdx)) != kInvalidIdx )
      name = portName(p->fileDevH,fileDevIdx,flags,portIdx);
    else
      cwLogError(kInvalidArgRC,"The device index %i is not valid.");

  if( name == nullptr )
    cwLogError(kOpFailRC,"The access to %s port name on device index %i port index %i failed.",flags & kInMpFl ? "input" : "output", devIdx,portIdx);

  return name;  
}
      
      
cw::rc_t cw::midi::device::send(       handle_t h, unsigned devIdx, unsigned portIdx, uint8_t st, uint8_t d0, uint8_t d1 )
{
  rc_t      rc         = kOkRC;
  device_t* p          = _handleToPtr(h);
  unsigned  alsaDevIdx = kInvalidIdx;
  unsigned  fileDevIdx = kInvalidIdx;

  if((alsaDevIdx = _devIdxToAlsaDevIdx(p,devIdx)) != kInvalidIdx )
    rc = send(p->alsaDevH,alsaDevIdx,portIdx,st,d0,d1);
  else
  {
    if((fileDevIdx = _devIdxToFileDevIdx(p,devIdx)) != kInvalidIdx )
      rc                                             = send(p->fileDevH,fileDevIdx,portIdx,st,d0,d1);
    else
      rc = cwLogError(kInvalidArgRC,"The device %i is not valid.",devIdx);
  }

  if( rc != kOkRC )
    rc = cwLogError(rc,"The MIDI msg (0x%x %i %i) transmit failed.",st,d0,d1);

  return rc;
}
      
cw::rc_t cw::midi::device::sendData(   handle_t h, unsigned devIdx, unsigned portIdx, const uint8_t* dataPtr, unsigned byteCnt )
{
  rc_t      rc         = kOkRC;
  device_t* p          = _handleToPtr(h);
  unsigned  alsaDevIdx = kInvalidIdx;
  unsigned  fileDevIdx = kInvalidIdx;

  if((alsaDevIdx = _devIdxToAlsaDevIdx(p,devIdx)) != kInvalidIdx )
    rc = sendData(p->alsaDevH,alsaDevIdx,portIdx,dataPtr,byteCnt);
  else
  {
    if((fileDevIdx = _devIdxToFileDevIdx(p,devIdx)) != kInvalidIdx )
      rc = sendData(p->fileDevH,fileDevIdx,portIdx,dataPtr,byteCnt);
    else
      rc = cwLogError(kInvalidArgRC,"The device %i is not valid.",devIdx);
  }

  if( rc != kOkRC )
    rc = cwLogError(rc,"The MIDI msg transmit data failed.");

  return rc;
}

cw::rc_t cw::midi::device::openMidiFile( handle_t h, unsigned devIdx, unsigned portIdx, const char* fname )
{
  rc_t      rc = kOkRC;
  device_t* p          = _handleToPtr(h);

  if( _devIdxToFileDevIdx(p,devIdx) == kInvalidIdx )
  {
    cwLogError(kInvalidArgRC,"The device index %i does not identify a valid file device.",devIdx);
    goto errLabel;
  }

  if((rc = open_midi_file( p->fileDevH, portIdx, fname)) != kOkRC )
    goto errLabel;
  

errLabel:
  return rc;
}

cw::rc_t cw::midi::device::loadMsgPacket( handle_t h, const packet_t& pkt )
{
  rc_t rc = kOkRC;
  device_t* p  = _handleToPtr(h);

  if( _devIdxToFileDevIdx(p,pkt.devIdx) == kInvalidIdx )
  {
    cwLogError(kInvalidArgRC,"The device index %i does not identify a valid file device.",pkt.devIdx);
    goto errLabel;
  }

  if((rc = load_messages( p->fileDevH, pkt.portIdx, pkt.msgArray, pkt.msgCnt)) != kOkRC )
    goto errLabel;

errLabel:
  return rc;
}

unsigned cw::midi::device::msgCount( handle_t h, unsigned devIdx, unsigned portIdx )
{
  device_t* p  = _handleToPtr(h);
  
  if(_devIdxToFileDevIdx(p,devIdx) == kInvalidIdx )
  {
    cwLogError(kInvalidArgRC,"The device index %i does not identify a valid file device.",devIdx);
    goto errLabel;
  }

  return msg_count( p->fileDevH, portIdx);
  
errLabel:
  return 0;
}

cw::rc_t cw::midi::device::seekToMsg( handle_t h, unsigned devIdx, unsigned portIdx, unsigned msgIdx )
{
  rc_t      rc = kOkRC;
  device_t* p  = _handleToPtr(h);
  
  if(_devIdxToFileDevIdx(p,devIdx) == kInvalidIdx )
  {
    cwLogError(kInvalidArgRC,"The device index %i does not identify a valid file device.",devIdx);
    goto errLabel;
  }

  if((rc = seek_to_msg_index( p->fileDevH, portIdx, msgIdx)) != kOkRC )
    goto errLabel;
  
  
errLabel:
  return rc;

}

cw::rc_t cw::midi::device::setEndMsg( handle_t h, unsigned devIdx, unsigned portIdx, unsigned msgIdx )
{
  rc_t      rc = kOkRC;
  device_t* p  = _handleToPtr(h);
  
  if(_devIdxToFileDevIdx(p,devIdx) == kInvalidIdx )
  {
    cwLogError(kInvalidArgRC,"The device index %i does not identify a valid file device.",devIdx);
    goto errLabel;
  }

  if((rc = set_end_msg_index( p->fileDevH, portIdx, msgIdx)) != kOkRC )
    goto errLabel;
  
  
errLabel:
  return rc;

}


unsigned cw::midi::device::maxBufferMsgCount( handle_t h )
{
  device_t* p  = _handleToPtr(h);
  return p->bufN;
}

const cw::midi::ch_msg_t* cw::midi::device::getBuffer(   handle_t h, unsigned& msgCntRef )
{
  device_t* p  = _handleToPtr(h);
  unsigned  ii = p->buf_ii.load();
  unsigned  oi = p->buf_oi.load();
  ch_msg_t* m  = nullptr;
  
  msgCntRef  = ii >= oi ? ii-oi : p->bufN - oi;
  
  if( msgCntRef > 0 )
    m = p->buf + oi;
  
  return m;
}

cw::rc_t            cw::midi::device::clearBuffer( handle_t h, unsigned msgCnt )
{
  if( msgCnt > 0 )
  {
    device_t* p  = _handleToPtr(h);
    unsigned oi = p->buf_oi.load();
    
    oi = (oi + msgCnt) % p->bufN;
  
    p->buf_oi.store(oi);
  }
  
  return kOkRC;
}


cw::rc_t cw::midi::device::start( handle_t h )
{
  rc_t rc = kOkRC;
  device_t* p = _handleToPtr(h);

  if( p->fileDevStateId != kPlayingStateId )
  {

    if((rc = rewind(p->fileDevH)) != kOkRC )
    {
      rc = cwLogError(rc,"Rewind failed on MIDI file device.");
      goto errLabel;
    }

    p->start_time       = time::current_time();
    p->offset_micros    = 0;
    p->last_posn_micros = 0; 
    p->fileDevStateId   = kPlayingStateId;
  }
errLabel:

  if( rc != kOkRC )
    rc = cwLogError(rc,"MIDI port start failed.");
  
  return rc;
}
  
cw::rc_t cw::midi::device::stop( handle_t h )
{
  device_t* p = _handleToPtr(h);
  
  p->fileDevStateId = kStoppedStateId;
  
  return kOkRC;
}

cw::rc_t cw::midi::device::pause( handle_t h, bool pause_fl )
{
  rc_t rc = kOkRC;
  device_t* p = _handleToPtr(h);
  
  switch( p->fileDevStateId )
  {
    case kStoppedStateId:
      // unpausing does nothing from a 'stopped' state
      break;
      
    case kPausedStateId:
      if( !pause_fl )
      {
        p->start_time = time::current_time();
        p->fileDevStateId = kPlayingStateId;    
      }
      break;
      
    case kPlayingStateId:
      if( pause_fl )
      {
        p->offset_micros  = p->last_posn_micros;
        p->fileDevStateId = kPausedStateId;
      }
      break;
  }

  return rc;
}


cw::rc_t cw::midi::device::report( handle_t h )
{
  rc_t rc = kOkRC;
  textBuf::handle_t tbH;
  
  if((rc = textBuf::create(tbH)) != kOkRC )
    goto errLabel;

  report(h,tbH);

  printf("%s\n",text(tbH));
  
errLabel:
  destroy(tbH);
  return rc;
}

void cw::midi::device::report( handle_t h, textBuf::handle_t tbH)
{
  device_t* p = _handleToPtr(h);
  report(p->alsaDevH,tbH);
  report(p->fileDevH,tbH);  
}


void cw::midi::device::latency_measure_reset(handle_t h)
{
  device_t* p = _handleToPtr(h);
  latency_measure_reset(p->alsaDevH);
  latency_measure_reset(p->fileDevH);
}

cw::midi::device::latency_meas_combined_result_t cw::midi::device::latency_measure_result(handle_t h)
{
  device_t* p = _handleToPtr(h);
  latency_meas_combined_result_t r;
  r.alsa_dev = latency_measure_result(p->alsaDevH);
  r.file_dev = latency_measure_result(p->fileDevH);
  return r;
}



