//| 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 "cwObject.h"
#include "cwText.h"
#include "cwTextBuf.h"

#include "cwThread.h"
#include "cwIo.h"

#include "cwMidi.h"
#include "cwMidiDevice.h"


#include "cwObject.h"

#include "cwThreadMach.h"
#include "cwMutex.h"

#include "cwSerialPort.h"
#include "cwSerialPortSrv.h"

#include "cwAudioDevice.h"
#include "cwAudioBuf.h"
#include "cwAudioDeviceAlsa.h"
#include "cwAudioDeviceFile.h"

#include "cwSocket.h"

#include "cwWebSock.h"
#include "cwUi.h"

#include "cwVectOps.h"

namespace cw
{
  namespace io
  {

    struct io_str;

    typedef struct thread_str
    {
      unsigned           id;
      void*              arg;
      bool               asyncFl;
      struct io_str*     p;
      struct thread_str* link;
    } thread_t;
    
    typedef struct timer_str
    {
      struct io_str*    io;
      bool              deletedFl;
      bool              startedFl;
      char*             label;
      unsigned          id;
      unsigned          index;
      unsigned          periodMicroSec;
      bool              asyncFl;
      time::spec_t      nextTime;
    } timer_t;
    
    typedef struct serialPort_str
    {
      char*                   label;    
      unsigned                userId;
      bool                    asyncFl;
      char*                   device;
      unsigned                baudRate;
      unsigned                flags;
      serialPortSrv::handle_t serialH;
    } serialPort_t;

    typedef struct audioGroup_str
    {
      bool                   enableFl;
      bool                   asyncFl;
      audio_msg_t            msg;
      mutex::handle_t        mutexH;
      unsigned               threadTimeOutMs;
      struct io_str*         p;
    } audioGroup_t;

    typedef struct audioDev_str
    {
      bool               activeFl; // True if this device was enabled by the user
      bool               meterFl;  // True if meters are enabled on this device
      const char*        label;    // User label
      unsigned           userId;   // User id
      char*              devName;  // System device name
      unsigned           devIdx;   // AudioDevice interface device index
      audioGroup_t*      iGroup;   // Audio group pointers for this device
      audioGroup_t*      oGroup;   //
      audio_group_dev_t* iagd;     // Audio group device record assoc'd with this device
      audio_group_dev_t* oagd;     //
      unsigned           cycleCnt;
      unsigned           framesPerCycle;
      
      struct audioDev_str* clockInList;  // List of devices sync'd to this devices input clock
      struct audioDev_str* clockOutList; // List of devices sync'd to this devices output clock
      struct audioDev_str* clockLink;   // links used by clockIn/OutList
      
    } audioDev_t;

    typedef struct socket_str
    {
      bool     enableFl;
      bool     asyncFl;
      char*    label;
      unsigned sockA_index;
      unsigned userId;      
    } socket_t;
    
    typedef struct thread_once_str
    {
      struct io_str*          p;
      thread::handle_t        threadH;
      void*                   arg;
      thread_once_func_t      cbFunc;
      unsigned                id;
      struct thread_once_str* link;
      bool                    asyncFl;
      bool                    doneFl;
    } thread_once_t;

    typedef struct io_str
    {
      std::atomic<bool>             quitFl;
      std::atomic<bool>             startedFl;

      time::spec_t                  t0;
      
      cbFunc_t                      cbFunc;
      void*                         cbArg;

      mutex::handle_t               cbMutexH;
      unsigned                      cbMutexTimeOutMs;
      
      thread_mach::handle_t         threadMachH;
      
      object_t*                     cfg;

      thread_t*                     threadL;
      
      timer_t*                      timerA;
      unsigned                      timerN;
      
      serialPort_t*                 serialA;
      unsigned                      serialN;
      serialPortSrv::handle_t       serialPortSrvH;
      
      midi::device::handle_t        midiH;
      bool                          midiAsyncFl;

      socket_t*                     sockA;
      unsigned                      sockN;
      sock::handle_t                sockH;
      unsigned                      sockThreadTimeOutMs;
      
      
      audio::device::handle_t       audioH;
      audio::device::alsa::handle_t alsaH;
      audio::device::file::handle_t audioDevFileH;
      audio::buf::handle_t          audioBufH;
      unsigned                      audioThreadTimeOutMs;
      unsigned                      audioMeterDevEnabledN;
      unsigned                      audioMeterCbPeriodMs;
      time::spec_t                  audioMeterNextTime;
      
      audioDev_t*                   audioDevA;
      unsigned                      audioDevN;
      
      audioGroup_t*                 audioGroupA;
      unsigned                      audioGroupN;
      
      ui::ws::handle_t              wsUiH;       // ui::ws handle (invalid if no UI was specified)
      ui::appIdMap_t*               uiMapA;      // Application supplied id's for the UI resource supplied with the cfg script via 'uiCfgFn'.
      unsigned                      uiMapN;      //
      bool                          uiAsyncFl;

      sample_t                      latency_meas_thresh_db;
      sample_t                      latency_meas_thresh_lin;
      bool                          latency_meas_enable_fl;
      latency_meas_result_t         latency_meas_result;

      thread_once_t*                threadOnceList;
    } io_t;
  

    //----------------------------------------------------------------------------------------------------------
    //
    // io
    //
    
    io_t* _handleToPtr( handle_t h )
    { return handleToPtr<handle_t,io_t>(h); }


    // All callbacks to the application occur through this function
    rc_t _ioCallback( io_t* p, bool asyncFl, const msg_t* m, rc_t* app_rc_ref=nullptr )
    {
      rc_t rc       = kOkRC;
      bool unlockFl = false;
      bool isSynchronousFl = !asyncFl;
      bool isStartedFl = p->startedFl.load();

      if( isStartedFl )
      {

        // if this is a synchronous callback then lock the mutex
        if( isSynchronousFl )
        {
          switch(rc = mutex::lock(p->cbMutexH,p->cbMutexTimeOutMs))
          {
            case kOkRC:
              unlockFl = true;
              break;
            
            case kTimeOutRC:
              rc = cwLogError(rc,"io mutex callback mutex lock timed out.");
              break;
            
            default:
              rc = cwLogError(rc,"io mutex callback mutex lock failed.");
          }
           
        }

        // make the callback to the client
        if( rc == kOkRC )
        {
        
          rc_t app_rc = p->cbFunc( p->cbArg, m );
          if( app_rc_ref != nullptr )
            *app_rc_ref = app_rc;
        }

        // if the mutex is locked
        if( unlockFl )
        {
          if((rc = mutex::unlock(p->cbMutexH)) != kOkRC )
          {
            // Time out is not a failure
            if( rc == kTimeOutRC )
              rc = kOkRC;
            else
              rc = cwLogError(rc,"io mutex callback mutex unlock failed.");          
          }
        }
      }
      return rc;
    }

    rc_t _ioParse( io_t* p, const object_t* cfg )
    {
      rc_t rc = kOkRC;
      const object_t* ioCfg;
      if((ioCfg = cfg->find("io")) == nullptr )
      {
        cwLogError(kInvalidArgRC,"The 'io' configuration block could not be found.");
        goto errLabel;
      }

      if((rc = ioCfg->getv("callbackMutexTimeOutMs",p->cbMutexTimeOutMs)) != kOkRC )
      {
        cwLogError(rc,"Parsing of 'io' block configuration failed.");
        goto errLabel;
      }

    errLabel:
      return rc;
    }
    
    //----------------------------------------------------------------------------------------------------------
    //
    // Thread
    //
    bool _threadFunc( void* arg )
    {
      rc_t        rc  = kOkRC;
      thread_t*    t  = (thread_t*)arg;
      thread_msg_t tm = { .id=t->id, .arg=t->arg, .rc=kOkRC };
      msg_t        m;

      m.tid       = kThreadTId;
      m.u.thread = &tm;
    
      if((rc = _ioCallback( t->p, t->asyncFl, &m )) != kOkRC )
        cwLogError(rc,"Thread app callback failed.");

      return true;
    }

    void _threadRelease( io_t* p )
    {
      thread_t* t0 = p->threadL;
      for(; t0!=nullptr; t0=t0->link)
      {
        thread_t* t1 = t0->link;
        mem::release(t0);
        t0 = t1;
      }
    }

    bool  _thread_once_func( void* arg )
    {
      rc_t           rc = kOkRC;
      thread_once_t* t  = (thread_once_t*)arg;

      rc = t->cbFunc(t->arg);

      thread_msg_t tm = { .id=t->id, .arg=t->arg, .rc=rc };
      msg_t        m;

      m.tid      = kThreadTId;
      m.u.thread = &tm;
    
      if((rc = _ioCallback( t->p, t->asyncFl, &m )) != kOkRC )
        cwLogError(rc,"Thread app callback failed.");

      t->doneFl = true;
      
      return false;
    }

    void _thread_once_cleanup(  io_t* p, bool force_clean_fl=false )
    {
      thread_once_t* t0 = nullptr;
      thread_once_t* t = p->threadOnceList;
      while( t != nullptr )
      {
        thread_once_t* t_next = t->link;
        
        if( t->doneFl || force_clean_fl )
        {
          thread_once_t* tt = t;
          
          // point the previous link around this link
          if( t0 != nullptr )
          {
            t0->link = t_next;
          }
          else
          { // first link is being removed
            p->threadOnceList = t_next;
          }

          thread::destroy(tt->threadH);
          mem::release(tt);

        }
        else
        {
          t0 = t;
        }

        t = t_next;
      }
    }


    //----------------------------------------------------------------------------------------------------------
    //
    // Timer
    //
    bool _timerThreadCb( void* arg )
    {
      timer_t* t = (timer_t*)arg;

      time::spec_t t0;
      time::get(t0);

      int usec = time::diffMicros(t0,t->nextTime);

      if( usec > 0 )
        sleepUs(usec);

      time::advanceMicros(t->nextTime,t->periodMicroSec);

      
      if( t->startedFl && !t->deletedFl )
      {
        rc_t        rc = kOkRC;
        msg_t       m;        
        timer_msg_t tm;
        
        tm.id     = t->id;
        tm.index  = t->index;
        m.tid     = kTimerTId;
        m.u.timer = &tm;
        
        if((rc = _ioCallback( t->io, t->asyncFl, &m )) != kOkRC )
          cwLogError(rc,"Timer app callback failed.");
      }

      return !t->deletedFl;
    }

    rc_t _timerCreate( io_t* p, const char* label, unsigned id, unsigned periodMicroSec, bool asyncFl )
    {
      rc_t     rc = kOkRC;
      timer_t* t  = nullptr;
      unsigned timer_idx = kInvalidIdx;
      
      // look for a deleted timer
      for(unsigned i=0; i<p->timerN; ++i)
        if( p->timerA[i].deletedFl )
        {
          t = p->timerA + i;
          timer_idx = i;
          break;
        }

      // if no deleted timer was found
      if( t == nullptr )
      {
        // reallocate the timer array with an additional slot
        timer_t* tA = mem::allocZ< timer_t >( p->timerN + 1 );
        for(unsigned i=0; i<p->timerN; ++i)
          tA[i] = p->timerA[i];

        // keep a pointer to the empty slot
        t = tA + p->timerN;
        timer_idx = p->timerN;

        // update the timer array
        mem::release( p->timerA );
        p->timerA = tA;
        p->timerN = p->timerN + 1;
      }
        
      assert( t != nullptr );

      t->io             = p;
      t->label          = mem::duplStr(label);
      t->id             = id;
      t->index          = timer_idx;
      t->asyncFl        = asyncFl;
      t->periodMicroSec = periodMicroSec;


      time::get(t->nextTime);
            
      if((rc = thread_mach::add(p->threadMachH,_timerThreadCb,t, label)) != kOkRC )
      {
        rc = cwLogError(rc,"Timer thread assignment failed.");        
      }

      return rc;
    }

    timer_t* _timerIndexToPtr( io_t* p, unsigned timerIdx )
    {
      if( timerIdx >= p->timerN || p->timerA[ timerIdx ].deletedFl == true )
      {
        cwLogError(kInvalidIdRC,"The timer index '%i' is invalid.", timerIdx );
        return nullptr;
      }

      return p->timerA + timerIdx;
    }

    rc_t    _timerStart( io_t* p, unsigned timerIdx, bool startFl )
    {
      rc_t     rc = kOkRC;
      timer_t* t  = _timerIndexToPtr(p,timerIdx);

      if( t == nullptr )
        rc = kInvalidIdRC;
      else
        p->timerA[ timerIdx ].startedFl = startFl;
      return rc;
    }
    
    
    //----------------------------------------------------------------------------------------------------------
    //
    // Serial
    //    
  
    void _serialPortCb( void* arg, unsigned serialCfgIdx, const void* byteA, unsigned byteN )
    {
      io_t* p = (io_t*)arg;

      if( serialCfgIdx > p->serialN )
        cwLogError(kAssertFailRC,"The serial cfg index %i is out of range %i in serial port callback.", serialCfgIdx, p->serialN );
      else
      {
        rc_t rc = kOkRC;
        const serialPort_t* sp = p->serialA + serialCfgIdx;
        msg_t m;
        serial_msg_t sm;
        sm.label  = sp->label;
        sm.userId = sp->userId;
        sm.dataA  = byteA;
        sm.byteN  = byteN;
        
        m.tid      = kSerialTId;
        m.u.serial = &sm;

        if((rc = _ioCallback( p, sp->asyncFl,  &m )) != kOkRC )
          cwLogError(rc,"Serial port app callback failed.");
      }
      
    }


    rc_t _serialPortParseCfg( const object_t& e, serialPort_t* port, bool& enableFlRef )
    {
      rc_t        rc          = kOkRC;
      char*       parityLabel = nullptr;
      unsigned    bits        = 8;
      unsigned    stop        = 1;
      
      idLabelPair_t parityA[] =
        {
         { serialPort::kEvenParityFl, "even" },
         { serialPort::kOddParityFl,  "odd"  },
         { serialPort::kNoParityFl,   "no"   },
         { 0, nullptr }
        };
             
      if((rc = e.getv(
                      "enableFl",     enableFlRef,
                      "asyncFl",      port->asyncFl,
                      "label",        port->label,
                      "device",       port->device,
                      "baud",         port->baudRate,
                      "bits",         bits,
                      "stop",         stop,
                      "parity",       parityLabel )) != kOkRC )
      {
        rc = cwLogError(kSyntaxErrorRC,"Serial configuration parse failed.");
      }

      switch( bits )
      {
        case 5:  port->flags |= serialPort::kDataBits5Fl; break;
        case 6:  port->flags |= serialPort::kDataBits6Fl; break;
        case 7:  port->flags |= serialPort::kDataBits7Fl; break;
        case 8:  port->flags |= serialPort::kDataBits8Fl; break;
        default:
          rc = cwLogError(kSyntaxErrorRC,"Invalid serial data bits cfg:%i.",bits);
      }

      switch( stop )
      {
        case 1: port->flags |= serialPort::k1StopBitFl; break;
        case 2: port->flags |= serialPort::k2StopBitFl; break;
        default:
          rc = cwLogError(kSyntaxErrorRC,"Invalid serial stop bits cfg:%i.",stop);
      }

      unsigned i;
      for(i=0; parityA[i].label != nullptr; ++i)
        if( textCompare(parityLabel,parityA[i].label) == 0 )
        {
          port->flags |= parityA[i].id;
          break;
        }

      if( parityA[i].label == nullptr )
        rc = cwLogError(kSyntaxErrorRC,"Invalid parity cfg:'%s'.",cwStringNullGuard(parityLabel));
      

      return rc;
    }
        
    rc_t _serialPortCreate( io_t* p, const object_t* c )
    {
      rc_t            rc           = kOkRC;
      const object_t* cfg          = nullptr;
      const object_t* port_array   = nullptr;
      unsigned        pollPeriodMs = 50;
      unsigned        recvBufByteN = 512;
      bool            enableFl     = false;

      // get the serial port list node
      if((cfg = c->find("serial")) == nullptr)
      {
        cwLogWarning("No 'serial' configuration.");
        return kOkRC;
      }

      // the serial header values
      if((rc = cfg->getv("enableFl",enableFl,
                         "pollPeriodMs", pollPeriodMs,
                         "recvBufByteN", recvBufByteN,
                         "array",        port_array)) != kOkRC )
      {
        rc = cwLogError(kSyntaxErrorRC,"Serial cfg header parse failed.");
        goto errLabel;
      }

      p->serialN = port_array->child_count();
      
      if( enableFl==false || p->serialN == 0 )
      {
        p->serialN = 0;
        cwLogInfo("Serial port sub-system disabled.");
        goto errLabel;
      }

      p->serialA = mem::allocZ<serialPort_t>(p->serialN);

      
      // create the serial server
      if((rc = serialPortSrv::create(p->serialPortSrvH,pollPeriodMs,recvBufByteN)) != kOkRC )
      {
        rc = cwLogError(kSyntaxErrorRC,"Serial port server failed.");
        goto errLabel;
      }
      
      // for each serial port cfg
      for(unsigned i=0; i<p->serialN; ++i)
      {
        const object_t* e = port_array->child_ele(i);
        serialPort_t*   r = p->serialA + i;
        
        if( e == nullptr )
        {
          rc = cwLogError(kSyntaxErrorRC,"Unable to access a 'serial' port configuration record at index:%i.",i);
          break;
        }
        else
        {
          bool enableFl = false;
          
          // parse the cfg record
          if((rc = _serialPortParseCfg(*e,r,enableFl)) != kOkRC )
          {
            rc = cwLogError(rc,"Serial configuration parse failed on record index:%i.", i );
            break;
          }

          if( enableFl )
          {
            r->userId = i; // default the serial port userId to the index into serialA[]

            serialPort::handle_t spH = serialPortSrv::serialHandle(p->serialPortSrvH);

          
            // create the serial port object
            if((rc = serialPort::createPort( spH, i, r->device, r->baudRate, r->flags, _serialPortCb, p )) != kOkRC )
            {
              rc = cwLogError(rc,"Serial port create failed on record index:%i.", i );
              break;
            }
          }
        }
      }

    errLabel:
      return rc;
    }

    void _serialPortDestroy( io_t* p )
    {
      serialPortSrv::destroy(p->serialPortSrvH);
                            
      mem::release(p->serialA);
      p->serialN = 0;
      
    }

    rc_t _serialPortStart( io_t* p )
    {
      rc_t rc = kOkRC;

      if( p->serialPortSrvH.isValid() ) 
        // the service is only started if at least one serial port is enabled
        if( serialPort::portCount( serialPortSrv::serialHandle(p->serialPortSrvH) ) > 0 )      
          if((rc =serialPortSrv::start( p->serialPortSrvH )) != kOkRC )
            rc = cwLogError(rc,"The serial port server start failed.");
      
      return rc;
    }


    //----------------------------------------------------------------------------------------------------------
    //
    // MIDI
    //
    void _midiCallback( void* cbArg, const midi::packet_t* pktArray, unsigned pktCnt )
    {
      unsigned i;
      for(i=0; i<pktCnt; ++i)
      {
        msg_t                 m;
        midi_msg_t            mm;
        const midi::packet_t* pkt = pktArray + i;        
        io_t*                 p   = reinterpret_cast<io_t*>(cbArg);
        rc_t                  rc  = kOkRC;

        
        mm.pkt   = pkt;
        m.tid    = kMidiTId;
        m.u.midi = &mm;

        if((rc = _ioCallback( p, p->midiAsyncFl, &m )) !=kOkRC )
          cwLogError(rc,"MIDI app callback failed: async fl:%i",p->midiAsyncFl);

        /*
        for(unsigned j=0; j<pkt->msgCnt; ++j)
          if( pkt->msgArray != NULL )
            printf("io midi cb: %ld %ld 0x%x %i %i\n", pkt->msgArray[j].timeStamp.tv_sec, pkt->msgArray[j].timeStamp.tv_nsec, pkt->msgArray[j].status,pkt->msgArray[j].d0, pkt->msgArray[j].d1);
          else
            printf("io midi cb: 0x%x ",pkt->sysExMsg[j]);
        */
      }
    }
    
    rc_t _midiPortCreate( io_t* p, const object_t* c )
    {
      rc_t            rc       = kOkRC;
      const object_t* cfg      = nullptr;
      bool            enableFl = false;
      
      // get the MIDI port cfg
      if((cfg = c->find("midi")) == nullptr)
      {
        cwLogWarning("No 'MIDI' configuration.");
        return kOkRC;
      }
      
      if((rc = cfg->getv( "enableFl", enableFl, 
                          "asyncFl", p->midiAsyncFl )) != kOkRC )
      {
        rc = cwLogError(kSyntaxErrorRC,"MIDI configuration parse failed.");
      }

      if( !enableFl )
        cwLogInfo("MIDI device system disabled.");
      else
      {
        if((rc = create(p->midiH, _midiCallback, p, cfg)) != kOkRC )
          return rc;
      }
      
      return rc;
    }

    //------------------------------------------------------------------------------------------------
    //
    // Socket
    //

    socket_t* _socketIndexToRecd( io_t* p, unsigned sockIdx )
    {
      if( sockIdx >= p->sockN )
        cwLogError(kInvalidArgRC,"Invalid socket index (%i >= %i)", sockIdx, p->sockN);
      return p->sockA + sockIdx;
    }
    
    bool _socketThreadFunc( void* arg )
    {
      rc_t     rc        = kOkRC;
      io_t*    p         = reinterpret_cast<io_t*>(arg);
      unsigned readByteN = 0;
      
      if((rc = receive_all(p->sockH, p->sockThreadTimeOutMs, readByteN)) != kOkRC )
      {
        if( rc != kTimeOutRC )
          cwLogWarning("Socket receive_all() failed.");
      }
      

      return true;
    }
    
    void _socketCallback( void* cbArg, sock::cbOpId_t cbId, unsigned sockArray_index, unsigned connId, const void* byteA, unsigned byteN, const struct sockaddr_in* srcAddr )
    {
      io_t* p = reinterpret_cast<io_t*>(cbArg);

      if( sockArray_index >= p->sockN )
        cwLogError(kInvalidArgRC,"The socket index '%i' outside range (0-%i).", sockArray_index, p->sockN );
      else
      {
        socket_msg_t sm;
        sm.cbId    = cbId;
        sm.sockIdx = sockArray_index,
        sm.userId  = p->sockA[ sockArray_index ].userId;
        sm.connId  = connId;
        sm.byteA   = byteA;
        sm.byteN   = byteN;
        sm.srcAddr = srcAddr;

        msg_t m;
        m.tid    = kSockTId;
        m.u.sock = &sm;

        rc_t rc;
        
        if((rc = _ioCallback( p, p->sockA[ sockArray_index ].asyncFl, &m )) != kOkRC )
          cwLogError(rc,"Socket app callback failed.");
      }
    }
    
    
    rc_t _socketParseAttrs( const object_t* attrL, unsigned& flagsRef )
    {
      rc_t            rc    = kOkRC;
      const object_t* node  = nullptr;

      idLabelPair_t attrA[] =
        {
          { .id=sock::kNonBlockingFl,   .label="non_blocking"   }, // Create a non-blocking socket.
          { .id=sock::kBlockingFl,      .label="blocking"       }, // Create a blocking socket.
          { .id=sock::kTcpFl,           .label="tcp"            }, // Create a TCP socket rather than a UDP socket.
          { .id=0,                      .label="udp"            }, //
          { .id=sock::kBroadcastFl,     .label="broadcast"      }, //
          { .id=sock::kReuseAddrFl,     .label="reuse_addr"     }, //
          { .id=sock::kReusePortFl,     .label="reuse_port"     }, //
          { .id=sock::kMultiCastTtlFl,  .label="multicast_ttl"  }, //
          { .id=sock::kMultiCastLoopFl, .label="multicast_loop" }, //
          { .id=sock::kListenFl,        .label="listen"         }, // Use this socket to listen for incoming connections
          { .id=sock::kStreamFl,        .label="stream"         }, // Connected stream (vs. Datagram)
          { .id=sock::kTcpNoDelayFl,    .label="tcp_no_delay"   }, // Implements TCP_NODELAY
          { .id=0,                      .label=nullptr,         }
        };

      flagsRef = 0;
      
      for(unsigned j=0; j<attrL->child_count(); ++j)
        if((node = attrL->child_ele(j)) != nullptr && node->is_type( kStringTId ) )
        {
          unsigned k;
          for(k=0; attrA[k].label != nullptr; ++k)
            if( textCompare(attrA[k].label, node->u.str) == 0 )
            {
              flagsRef += attrA[k].id;
              break;
            }

          if( attrA[k].label == nullptr )
          {
            rc = cwLogError(kInvalidArgRC,"The attribute label '%s'.",cwStringNullGuard(node->u.str));
          }
        }

      return rc;
    }

    rc_t _socketParseConfig( io_t* p, const object_t* cfg )
    {
      const object_t* node           = nullptr;
      rc_t            rc             = kOkRC;
      unsigned        maxSocketCnt   = 10;
      unsigned        recvBufByteCnt = 4096;
      const object_t* socketL        = nullptr;
      bool            enableFl       = false;
      
      // get the socket configuration node
      if((node = cfg->find("socket")) == nullptr )
      {
        cwLogWarning("No 'socket' configuration node.");
        return kOkRC;
      }

      // get the required socket arguments
      if(( rc = node->getv(
             "enableFl",           enableFl,
             "maxSocketCnt",       maxSocketCnt,
             "recvBufByteCnt",     recvBufByteCnt,
             "threadTimeOutMs",    p->sockThreadTimeOutMs,
             "socketL",            socketL )) != kOkRC )
      {
        rc = cwLogError(kSyntaxErrorRC,"Unable to parse the 'socket' configuration node.");
        goto errLabel;
      }

      // THe max socket count must be at least as large as the number of defined sockets
      maxSocketCnt = std::max(p->sockN,maxSocketCnt);

      // create the socket control array
      p->sockN = socketL->child_count();

      if( enableFl == false || p->sockN == 0 )
      {
        p->sockN = 0;
        cwLogInfo("Socket system disabled.");
        goto errLabel;
      }
      
      p->sockA = mem::allocZ<socket_t>(p->sockN);

      // create the socket manager
      if((rc = sock::createMgr( p->sockH, recvBufByteCnt, maxSocketCnt )) != kOkRC )
      {
        rc = cwLogError(rc,"Socket manager creation failed.");
        goto errLabel;
      }

      // parse each socket configuration
      for(unsigned i=0; i<p->sockN; ++i)
      {
        if((node = socketL->child_ele(i)) != nullptr )
        {
          unsigned        port       = sock::kInvalidPortNumber;
          unsigned        timeOutMs  = 50; 
          const object_t* attrL      = nullptr;
          char*           remoteAddr = nullptr;
          unsigned        remotePort = sock::kInvalidPortNumber;
          char*           localAddr  = nullptr;
          unsigned        flags      = 0;

          // parse the required arguments
          if(( rc = node->getv(
                "enableFl",  p->sockA[i].enableFl,
                "asyncFl",   p->sockA[i].asyncFl,
                "label",     p->sockA[i].label,
                "port",      port,
                "timeOutMs", timeOutMs,
                "attrL",     attrL )) != kOkRC )
          {
            rc = cwLogError(rc,"Error parsing required socket cfg record at index:%i",i);
            goto errLabel;
          }

          // parse the optional arguments
          if((rc = node->getv_opt(
                "userId",     p->sockA[i].userId,
                "remoteAddr", remoteAddr,
                "remotePort", remotePort,
                "localAddr",  localAddr)) != kOkRC )
          {
            rc = cwLogError(rc,"Error parsing optional socket cfg record at index:%i",i);
            goto errLabel;
          }

          // parse the socket attribute list
          if((rc = _socketParseAttrs( attrL, flags )) != kOkRC )
            goto errLabel;

          // create the socket object
          if((rc = create( p->sockH, i, port, flags, timeOutMs, _socketCallback, p, remoteAddr, remotePort, localAddr )) != kOkRC )
          {
            rc = cwLogError(rc,"Socket create failed.");
            goto errLabel;
          }
          
        }
      }

      // create the socket thread
      if( p->sockN > 0 )
        if((rc = thread_mach::add(p->threadMachH,_socketThreadFunc,p,"io_socket")) != kOkRC )
        {
          rc = cwLogError(rc,"Error creating socket thread.");
          goto errLabel;
        }
      

    errLabel:
      return rc;
    }
    

    
    //----------------------------------------------------------------------------------------------------------
    //
    // Audio
    //

    
    // Start or stop all the audio devices in p->audioDevA[]
    rc_t _audioDeviceStartStop( io_t* p, bool startFl )
    {
      rc_t rc = kOkRC;
      rc_t rc1 = kOkRC;
      
      for(unsigned i=0; i<p->audioDevN; ++i)
        if( p->audioDevA[i].activeFl )
        {
          rc_t rc0 = kOkRC;
          if( startFl )
            rc0 = audio::device::start( p->audioH, p->audioDevA[i].devIdx );
          else
            rc0 = audio::device::stop( p->audioH, p->audioDevA[i].devIdx );

          if(rc0 != kOkRC )
            rc = cwLogError(rc0,"The audio device: %s failed to %s.", cwStringNullGuard(p->audioDevA[i].devName), startFl ? "start" : "stop");

        }

      if( p->audioDevFileH.isValid() )
      {    
        if( startFl )
          rc1 = audio::device::file::start( p->audioDevFileH );
        else
          rc1 = audio::device::file::stop( p->audioDevFileH );

        if( rc1 != kOkRC )
          rc1 = cwLogError(rc1,"Audio device file sub-system %s failed.", startFl ? "start" : "stop");
        
      }
      
       
      return rcSelect(rc,rc1);
    }


    // Release all resource associated with a group-device record.
    void _audioGroupDestroyDevs( audio_group_dev_t* agd )
    {
      while( agd != nullptr )
      {
        audio_group_dev_t* agd0 = agd->link;
        mem::release(agd->meterA);
        mem::release(agd);
        agd = agd0;
      }
    }

    // Release all resource associated with all audio group records
    rc_t _audioGroupDestroyAll( io_t* p )
    {
      rc_t rc = kOkRC;

      for(unsigned i=0; i<p->audioGroupN; ++i)
      {
        audioGroup_t* ag = p->audioGroupA + i;
        _audioGroupDestroyDevs( ag->msg.iDevL );
        _audioGroupDestroyDevs( ag->msg.oDevL );
        mem::release(ag->msg.iBufArray);
        mem::release(ag->msg.oBufArray);

        mutex::unlock( ag->mutexH );  // the mutex is expected to be locked at this point
        mutex::destroy( ag->mutexH );
      }

      mem::release(p->audioGroupA);
      p->audioGroupN = 0;
      return rc;
    }

    rc_t _audioDestroy( io_t* p )
    {
      rc_t rc = kOkRC;

      // stop each device - this will stop the callbacks to _audioDeviceCallback()
      if((rc = _audioDeviceStartStop(p,false)) != kOkRC )
      {
        rc = cwLogError(rc,"Audio device stop failed.");
        goto errLabel;
      }

      if((rc = audio::device::alsa::destroy(p->alsaH)) != kOkRC )
      {
        rc = cwLogError(rc,"ALSA sub-system shutdown failed.");
        goto errLabel;
      }
      
      if((rc = audio::device::file::destroy(p->audioDevFileH)) != kOkRC )
      {
        rc = cwLogError(rc,"Audio device file sub-system shutdown failed.");
        goto errLabel;
      }
            
      if((rc = audio::device::destroy(p->audioH)) != kOkRC )
      {
        rc = cwLogError(rc,"Audio device sub-system shutdown failed.");
        goto errLabel;
      }
      
      if((rc = audio::buf::destroy(p->audioBufH)) != kOkRC )
      {
        rc = cwLogError(rc,"Audio buffer release failed.");
        goto errLabel;
      }

      if((rc = _audioGroupDestroyAll(p)) != kOkRC )
      {
        rc = cwLogError(rc,"Audio group release failed.");        
        goto errLabel;
      }

      for(unsigned i=0; i<p->audioDevN; ++i)
        mem::release( p->audioDevA[i].devName );
      
      mem::free(p->audioDevA);
      p->audioDevN = 0;

    errLabel:

      if(rc != kOkRC )
        rc = cwLogError(rc,"Audio sub-system shutdown failed.");
      
      return rc;
      
    }

    
    // Are either input or output meter enabled on this audio devices.
    // Set flags to kInFl or kOutput to select inut or output meters.
    // Set both flags to check if either input or output meters are enabled.
    bool _audioDeviceIsMeterEnabled( audioDev_t* ad, unsigned flags )
    {
      return (cwIsFlag(flags,kInFl ) && ad->iagd!=nullptr && cwIsFlag(ad->iagd->flags,kMeterFl))
        ||   (cwIsFlag(flags,kOutFl) && ad->oagd!=nullptr && cwIsFlag(ad->oagd->flags,kMeterFl));
    }

    
    rc_t _audioGroupDeviceProcessMeter( io_t* p, audio_group_dev_t* agd, audioGroup_t* ag, unsigned audioBufFlags )
    {
      rc_t rc = kOkRC;
      if( agd != nullptr && ag != nullptr && cwIsFlag(agd->flags,kMeterFl))
      {
        rc_t app_rc = kOkRC;
        msg_t m;
        m.tid             = kAudioMeterTId;
        m.u.audioGroupDev = agd;

        // get the current meter values from the audioBuf
        audio::buf::meter(p->audioBufH, agd->devIdx, audioBufFlags, agd->meterA, agd->chCnt );

        // callback the application with the current meter values
        if((rc = _ioCallback( p, ag->asyncFl, &m, &app_rc )) != kOkRC )
          cwLogError(rc,"Audio meter app callback failed.");
        
        rc = app_rc;
      }

      return rc;
    }
    
    rc_t _audioDeviceProcessMeters( io_t* p )
    {
      rc_t rc = kOkRC;

      // if it is time to execute the next meter update
      if( time::isGTE(p->t0,p->audioMeterNextTime) )
      {
        for(unsigned i=0; i<p->audioDevN; ++i)
          if( p->audioDevA[i].activeFl )
          {
            audioDev_t* ad = p->audioDevA + i;
            rc_t rc0;

            // update the input meters
            if((rc0 = _audioGroupDeviceProcessMeter( p, ad->iagd,  ad->iGroup, audio::buf::kInFl )) != kOkRC )
              rc = rc0;

            // update the output meters
            if((rc0 = _audioGroupDeviceProcessMeter( p, ad->oagd,  ad->oGroup, audio::buf::kOutFl )) != kOkRC )
              rc = rc0;          
          }


        // schedule the next meter update
        p->audioMeterNextTime = p->t0;
        time::advanceMs(p->audioMeterNextTime,p->audioMeterCbPeriodMs);
      }
      
      return rc;
    }
    

    bool _audioGroupBufIsReady( io_t* p, audioGroup_t* ag, bool inputFl )
    {
      audio_group_dev_t* agd =  inputFl ? ag->msg.iDevL : ag->msg.oDevL;      
      for(; agd!=nullptr; agd=agd->link)
        if( !audio::buf::isDeviceReady( p->audioBufH, agd->devIdx, inputFl ? audio::buf::kInFl : audio::buf::kOutFl ) )
          return false;

      return true;          
    }

    // Get sample pointers or advance the pointers on the buffer associates with each device.
    enum { kAudioGroupGetBuf, kAudioGroupAdvBuf };
    void _audioGroupProcSampleBufs( io_t* p, audioGroup_t* ag, unsigned processTypeId, unsigned inputFl  )
    {
      sample_t** bufArray      = inputFl ? ag->msg.iBufArray   : ag->msg.oBufArray;
      unsigned   bufArrayChCnt = inputFl ? ag->msg.iBufChCnt   : ag->msg.oBufChCnt;
      audio_group_dev_t* agd   = inputFl ? ag->msg.iDevL       : ag->msg.oDevL;
      unsigned   audioBufFlags = inputFl ? audio::buf::kInFl : audio::buf::kOutFl;
      
      unsigned chIdx = 0;
      for(; agd!=nullptr && chIdx < bufArrayChCnt; agd=agd->link)
      {
        switch( processTypeId )
        {
          case kAudioGroupGetBuf:
            audio::buf::get( p->audioBufH, agd->devIdx, audioBufFlags, bufArray+chIdx, agd->chCnt );
            break;
            
          case kAudioGroupAdvBuf:
            audio::buf::advance( p->audioBufH, agd->devIdx, audioBufFlags );
            break;

          default:
            assert(0);
        }
        
        chIdx += agd->chCnt;
      }
    }

    void _audio_latency_measure( io_t* p, const audio_msg_t& msg )
    {
      if( p->latency_meas_enable_fl )
      {
        
        if( msg.iBufChCnt && p->latency_meas_result.audio_in_ts.tv_nsec == 0 )
        {
          sample_t rms = vop::rms(msg.iBufArray[0], msg.dspFrameCnt );
          
          if( rms > p->latency_meas_thresh_lin  )
            time::get(p->latency_meas_result.audio_in_ts);
          
          if( rms > p->latency_meas_result.audio_in_rms_max )
            p->latency_meas_result.audio_in_rms_max = rms;
        }
        
        if( msg.oBufChCnt && p->latency_meas_result.audio_out_ts.tv_nsec == 0 )
        {
          sample_t rms = vop::rms(msg.oBufArray[0], msg.dspFrameCnt );
          
          if( rms > p->latency_meas_thresh_lin  )
            time::get(p->latency_meas_result.audio_out_ts);
          
          if( rms > p->latency_meas_result.audio_out_rms_max )
            p->latency_meas_result.audio_out_rms_max = rms;
        }
        
        p->latency_meas_enable_fl = p->latency_meas_result.audio_in_ts.tv_nsec == 0 ||
                                    p->latency_meas_result.audio_out_ts.tv_nsec == 0;
          
      }
    }

    // This is the audio processing thread function. Block on the audio group condition var
    // which is triggered when all the devices in the group are ready by _audioGroupNotifyIfReady().    
    bool _audioGroupThreadFunc( void* arg )
    {
      rc_t rc = kOkRC;

      audioGroup_t* ag = reinterpret_cast<audioGroup_t*>(arg);

      // block on the cond. var
      if((rc = mutex::waitOnCondVar(ag->mutexH,false,ag->threadTimeOutMs)) != kOkRC )
      {
        if( rc != kTimeOutRC )
        {
          cwLogError(rc,"Audio thread Wait-on-condition-var failed.");
          return false;
        }
      }

      // if the cond. var was signaled and ag->mutexH is locked
      if( rc == kOkRC )
      {
        msg_t msg;
        msg.tid     = kAudioTId;
        msg.u.audio = &ag->msg;

        // While the all audio devices for this group are ready 
        while( _audioGroupBufIsReady( ag->p, ag, true ) && _audioGroupBufIsReady( ag->p, ag, false) )
        {
          _audioGroupProcSampleBufs( ag->p, ag, kAudioGroupGetBuf, true );
          _audioGroupProcSampleBufs( ag->p, ag, kAudioGroupGetBuf, false );

          if((rc = _ioCallback( ag->p, ag->asyncFl, &msg)) != kOkRC )
            cwLogError(rc,"Audio app callback failed %i.",ag->asyncFl);

          _audio_latency_measure( ag->p, ag->msg );
          
          _audioGroupProcSampleBufs( ag->p, ag, kAudioGroupAdvBuf, true );
          _audioGroupProcSampleBufs( ag->p, ag, kAudioGroupAdvBuf, false );
        }
      }
     
      return true;
    }
    
    // Given a device index return the associated audioDev_t record.
    audioDev_t* _audioDeviceIndexToRecd( io_t* p, unsigned devIdx, bool reportMissingFl=true )
    {
      for(unsigned i=0; i<p->audioDevN; ++i)
        if( p->audioDevA[i].devIdx == devIdx )
          return p->audioDevA + i;

      if( reportMissingFl)
        cwLogError(kInvalidArgRC,"A device with index %i could not be found.",devIdx);
      
      return nullptr;
    }

    // Given a device name return the associated audioDev_t record.
    audioDev_t* _audioDeviceNameToRecd( io_t* p, const char* devName, bool reportMissingFl=true)
    {
      for(unsigned i=0; i<p->audioDevN; ++i)
        if( textCompare(p->audioDevA[i].devName, devName ) == 0 )
          return p->audioDevA + i;

      if( reportMissingFl )
        cwLogError(kInvalidArgRC,"A device named '%s' was not found.", cwStringNullGuard(devName) );
      
      return nullptr;
    }

    // Given a user label return the associated audioDev_t record.
    audioDev_t* _audioDeviceLabelToRecd( io_t* p, const char* label, bool reportMissingFl=true )
    {
      for(unsigned i=0; i<p->audioDevN; ++i)
        if( textCompare(label,p->audioDevA[i].label) == 0 )
          return p->audioDevA + i;

      if( reportMissingFl )
        cwLogError(kInvalidArgRC,"An audio device with label '%s' could not be found.",cwStringNullGuard(label));

      return nullptr;
    }

    rc_t _audioDeviceParams( io_t* p, unsigned devIdx, unsigned flags, audioDev_t*& adRef, unsigned& audioBufFlagsRef )
    {
      rc_t rc = kOkRC;
  
      if((adRef = _audioDeviceIndexToRecd(p,devIdx)) == nullptr )
        rc = kInvalidArgRC;
      else
      {
        audioBufFlagsRef = 0;
        if( cwIsFlag(flags,kInFl) )
          audioBufFlagsRef += audio::buf::kInFl;
    
        if( cwIsFlag(flags,kOutFl) )
          audioBufFlagsRef += audio::buf::kOutFl;

        if( cwIsFlag(flags,kEnableFl) )
          audioBufFlagsRef += audio::buf::kEnableFl;
    
      }

      return rc;
    }

    rc_t _audioDeviceEnableMeter( io_t* p, unsigned devIdx, unsigned inOutEnaFlags )
    {
      rc_t        rc            = kOkRC;
      audioDev_t* ad            = nullptr;
      unsigned    audioBufFlags = 0;
  
      if((rc = _audioDeviceParams( p, devIdx, inOutEnaFlags, ad, audioBufFlags )) != kOkRC )
        rc = cwLogError(rc,"Audio device to buffer parameter translation failed.");
      else    
      {
        bool     enaFl       = inOutEnaFlags & kEnableFl;
        bool     enaState0Fl = _audioDeviceIsMeterEnabled(ad, kInFl | kOutFl);
    
        audioBufFlags += audio::buf::kMeterFl;

        if( ad->iagd != nullptr && (inOutEnaFlags & kInFl) )
          ad->iagd->flags = cwEnaFlag(ad->iagd->flags,kMeterFl,enaFl);

        if( ad->oagd != nullptr && (inOutEnaFlags & kOutFl) )
          ad->oagd->flags = cwEnaFlag(ad->oagd->flags,kMeterFl,enaFl);
    
        audio::buf::setFlag( p->audioBufH, devIdx, kInvalidIdx, audioBufFlags );
    
        bool enaState1Fl= _audioDeviceIsMeterEnabled(ad, kInFl | kOutFl);

        if( enaState1Fl and !enaState0Fl )
          p->audioMeterDevEnabledN += 1;
        else
          if( p->audioMeterDevEnabledN > 0 && !enaState1Fl && enaState0Fl )
            p->audioMeterDevEnabledN -= 1;
   
      }

      if( rc != kOkRC )
        rc = cwLogError(rc,"Enable meters failed.");
   
      return rc;
    }
    
    // Add an audioGroup pointer to groupA[] and return the new count of elements in the array.
    unsigned _audioDeviceUpdateGroupArray( audioGroup_t** groupA, unsigned groupN, unsigned curGroupN, audioGroup_t* ag )
    {
      if( ag != nullptr )
      {
        for(unsigned i=0; i<curGroupN; ++i)
          if( groupA[i] == ag )
            return curGroupN;

        if( curGroupN >= groupN )
        {
          cwLogError(kAssertFailRC,"The group array was found to be too small during an audio device callback.");
          goto errLabel;
        }
        
        groupA[curGroupN++] = ag;
      }

    errLabel:
      return curGroupN;
    }

    // If audioDev (devIdx) is ready then update audio_group_dev.readyCnt and store a pointer to it's associated group in groupA[].
    // Return the count of pointers stored in groupA[].
    unsigned _audioDeviceUpdateReadiness( io_t* p, unsigned devIdx, bool inputFl, audioGroup_t** groupA, unsigned groupN, unsigned curGroupN, audioDev_t*& syncAdRef )
    {
      audioDev_t* ad;

      syncAdRef = nullptr;
      
      // get the device record assoc'ed with this device
      if((ad = _audioDeviceIndexToRecd(p, devIdx )) == nullptr )
      {
        cwLogError(kAssertFailRC,"An unexpected audio device index was encountered in an audio device callback.");
        goto errLabel;
      }

      // if an input packet was received on this device
      if( inputFl )
      {
        if( audio::buf::isDeviceReady( p->audioBufH, devIdx, audio::buf::kInFl) && ad->iagd != nullptr )
        {
          // atomic incr  - note that the ordering doesn't matter because the update does not control access to any other variables from another thread
          std::atomic_store_explicit(&ad->iagd->readyCnt, ad->iagd->readyCnt+1,  std::memory_order_relaxed); 
          curGroupN = _audioDeviceUpdateGroupArray( groupA, groupN, curGroupN, ad->iGroup ); 
          ad->iagd->cbCnt += 1; // update the callback count for this device
          syncAdRef = ad->clockInList;
        }
      }
      else // if an output packet was received on this device
      {    
        if( audio::buf::isDeviceReady( p->audioBufH, devIdx, audio::buf::kOutFl ) && ad->oagd != nullptr )
        {
          std::atomic_store_explicit(&ad->oagd->readyCnt, ad->oagd->readyCnt+1, std::memory_order_relaxed); // atomic incr  
          curGroupN = _audioDeviceUpdateGroupArray( groupA, groupN, curGroupN, ad->oGroup );
          ad->oagd->cbCnt += 1;
          syncAdRef = ad->clockOutList;
        }          
      }

    errLabel:
      // return the count of dev indexes in groupA[]
      return curGroupN;
    }


    // Return true if all the devices in the linked list 'agd' are ready to source/sink data.
    bool  _audioGroupIsReady( audio_group_dev_t* agd )
    {
      // are all devices in this group ready to  provide/accept new audio data
      for(; agd!=nullptr; agd=agd->link)
        if( std::atomic_load_explicit(&agd->readyCnt, std::memory_order_acquire) == 0  ) // ACQUIRE
          return false;

      return true;
    }

    // Decrement the ready count on all devices in the linked list pointed to by 'agd'.
    void _audioGroupDecrReadyCount( audio_group_dev_t* agd )
    {
      for(; agd !=nullptr; agd=agd->link)
        std::atomic_store_explicit(&agd->readyCnt,agd->readyCnt--, std::memory_order_release); // REALEASE
    }

    // This function is called by the audio device driver callback _audioDeviceCallback().
    // If all devices in any of the groups contained in groupA[] are ready to source/sink
    // audio data then this function triggers the condition var on the associated
    // group thread to trigger audio processing on those devices.  See _audioGroupThreadFunc().
    void _audioGroupNotifyIfReady( io_t* p, audioGroup_t** groupA, unsigned groupN )
    {      
      // for each device whose audio buffer state changed
      for(unsigned i=0; i<groupN; ++i)
      {
        audioGroup_t* ag = groupA[i];

        if( _audioGroupIsReady( ag->msg.iDevL ) && _audioGroupIsReady( ag->msg.oDevL ) )
        {
          // we now know the group is ready and so the ready count maybe decremented on each  device 
          _audioGroupDecrReadyCount( ag->msg.iDevL);
          _audioGroupDecrReadyCount( ag->msg.oDevL);

          // notify the audio group thread that all devices are ready by signaling the condition var that it is blocked on
          mutex::signalCondVar(ag->mutexH);

        }
      }
        
    }

    void _audioDevSync( io_t* p, audioDev_t** syncDevA, unsigned syncDevN )
    {
      for(unsigned i=0; i<syncDevN; ++i)        
        for(audioDev_t* ad = syncDevA[i]; ad!=nullptr; ad=ad->clockLink)
          if(audio::device::execute( p->audioH, ad->devIdx ) != kOkRC )
            cwLogWarning("Synced audio device '%s' execution failed.",ad->label);
        
          
    }

    // This function is called by the audio device drivers when incoming audio arrives
    // or when there is available space to write outgoing audio.
    // If all in/out devices in a group are ready to be source/sink audio data then this function
    // triggers the group thread condition var thereby causing an application callback
    // to process the audio data.
    void _audioDeviceCallback( void* cbArg, audio::device::audioPacket_t* inPktArray, unsigned inPktCnt, audio::device::audioPacket_t* outPktArray, unsigned outPktCnt )
    {
      io_t* p = reinterpret_cast<io_t*>(cbArg);

      unsigned      groupN = 2 * inPktCnt + outPktCnt;
      audioGroup_t* groupA[ groupN ];
      unsigned curGroupN = 0;

      // These arrays are allocated with more space than they need - to avoid 0 length array when inPktCnt or outPktCnt is 0.
      audioDev_t* iSyncDevListA[ inPktCnt+outPktCnt ];
      audioDev_t* oSyncDevListA[ inPktCnt+outPktCnt ];

      // update the audio buffer
      audio::buf::update( p->audioBufH, inPktArray, inPktCnt, outPktArray, outPktCnt );

      // update the readiness of the input devices
      for(unsigned i=0; i<inPktCnt; ++i)
        curGroupN = _audioDeviceUpdateReadiness( p, inPktArray[i].devIdx, true, groupA, groupN, curGroupN, iSyncDevListA[i] );
      
      // update the readiness of the output devices
      for(unsigned i=0; i<outPktCnt; ++i)
        curGroupN = _audioDeviceUpdateReadiness( p, outPktArray[i].devIdx, false, groupA, groupN, curGroupN, oSyncDevListA[i] );
      
      // groupA[] contains the set of groups which may have been made ready during this callback
      _audioGroupNotifyIfReady( p, groupA, curGroupN );

      // trigger any devices synced to the calling device
      _audioDevSync( p, iSyncDevListA, inPktCnt  );
      _audioDevSync( p, oSyncDevListA, outPktCnt );      
    }



    // Allocate a group-device record and link it to the associated group record.
    rc_t _audioGroupAddDevice( audioGroup_t* ag, bool inputFl, audioDev_t* ad, unsigned chCnt )
    {
      rc_t               rc      = kOkRC;
      audio_group_dev_t* new_agd = mem::allocZ<audio_group_dev_t>();
      
      new_agd->label  = ad->label;
      new_agd->userId = ad->userId;
      new_agd->devName= ad->devName;
      new_agd->devIdx = ad->devIdx;
      new_agd->flags  = inputFl ? kInFl : kOutFl;
      new_agd->chCnt  = chCnt;
      new_agd->chIdx  = inputFl ? ag->msg.iBufChCnt : ag->msg.oBufChCnt;
      new_agd->meterA = mem::allocZ<sample_t>(chCnt);

      audio_group_dev_t*& agd = inputFl ? ag->msg.iDevL : ag->msg.oDevL;
      
      if( agd == nullptr )
        agd = new_agd;
      else
      {      
        for(; agd!=nullptr; agd=agd->link)
          if( agd->link == nullptr )
          {            
            agd->link = new_agd;            
            break;              
          }   
      }

      // update the audio group channel count and set the pointers from
      // the private audioDevice_t to the public audio_group_dev_t.
      if( inputFl )
      {
        ag->msg.iBufChCnt += chCnt;
        ad->iagd = new_agd;
      }
      else
      {
        ag->msg.oBufChCnt += chCnt;
        ad->oagd = new_agd;
      }
      
      return rc;
    }

    // Given an audio group id return the associated audio group.
    audioGroup_t* _audioGroupFromId( io_t* p, unsigned userId, bool reportMissingFl=true )
    {
      for(unsigned i=0; i<p->audioGroupN; ++i)
        if( p->audioGroupA[i].msg.userId == userId )
          return p->audioGroupA + i;

      if( reportMissingFl )
        cwLogError(kInvalidArgRC,"An audio group with user id %i could not be found.", userId );
      
      return nullptr;
    }

    audioGroup_t* _audioGroupFromIndex( io_t* p, unsigned groupIdx, bool reportMissingFl=true )
    {
      if( groupIdx < p->audioGroupN )
        return p->audioGroupA + groupIdx;

      if( reportMissingFl )
        cwLogError(kInvalidArgRC,"'%i' is not a valid audio group index.",groupIdx);
      
      return nullptr;
    }

    // Given an audio group id return the associated audio group.
    audioGroup_t* _audioGroupFromLabel( io_t* p, const char* label, bool reportMissingFl=true )
    {
      for(unsigned i=0; i<p->audioGroupN; ++i)
        if( textCompare(p->audioGroupA[i].msg.label, label) == 0 )
          return p->audioGroupA + i;

      if( reportMissingFl )
        cwLogError(kInvalidArgRC,"An audio group with labe '%s' could not be found.", label );
      
      return nullptr;
    }
    
    
    // Create the audio group records by parsing the cfg. audio.groupL[] list.
    rc_t _audioDeviceParseAudioGroupList( io_t* p, const object_t* c )
    {
      rc_t            rc     = kOkRC;
      const object_t* groupL = nullptr;

      if((groupL = c->find("groupL")) == nullptr )
        return cwLogError(kSyntaxErrorRC,"Audio Group list 'groupL' not found.");

      p->audioGroupN = groupL->child_count();
      p->audioGroupA = mem::allocZ<audioGroup_t>(p->audioGroupN);

      for(unsigned i=0; i<p->audioGroupN; ++i)
      {
        const object_t* node;
        
        if((node = groupL->child_ele(i)) != nullptr )
        {
          if(( rc = node->getv(
                "enableFl",    p->audioGroupA[i].enableFl,
                "asyncFl",     p->audioGroupA[i].asyncFl,
                "label",       p->audioGroupA[i].msg.label,
                "id",          p->audioGroupA[i].msg.userId,
                "srate",       p->audioGroupA[i].msg.srate,
                "dspFrameCnt", p->audioGroupA[i].msg.dspFrameCnt )) != kOkRC )
          {
            rc = cwLogError(rc,"Error parsing audio group cfg record at index:%i",i);
            goto errLabel;
          }          

          // create the audio group thread mutex/cond var
          if((rc = mutex::create(p->audioGroupA[i].mutexH)) != kOkRC )
          {
            rc = cwLogError(rc,"Error creating audio group mutex.");
            goto errLabel;
          }

          // Lock the mutex so that it is already locked when it is used to block the audio thread
          // This avoids having to use logic in the thread callback to lock it on the first entry
          // while not locking it on all following entries.
          if((rc = mutex::lock(p->audioGroupA[i].mutexH)) != kOkRC )
          {
            rc = cwLogError(rc,"Error locking audio group mutex.");
            goto errLabel;
          }

          // create the audio group thread
          if((rc = thread_mach::add(p->threadMachH,_audioGroupThreadFunc,p->audioGroupA+i,"io_audio_group")) != kOkRC )
          {
            rc = cwLogError(rc,"Error creating audio group thread.");
            goto errLabel;
          }

          p->audioGroupA[i].p                = p;
          p->audioGroupA[i].threadTimeOutMs  = p->audioThreadTimeOutMs;
          p->audioGroupA[i].msg.groupIndex   = i;
          
        }
      }

    errLabel:
      return rc;
    }


    // Link dstAudioDev onto the list of devices that depend on srcAudioDev's clock.
    rc_t _audioDeviceConnectToClockSource( io_t* p, const char* srcAudioDevLabel, bool clockSrcInputFl, audioDev_t* dstAudioDev )
    {
      rc_t rc = kOkRC;
      audioDev_t* srcAudioDev = nullptr;
      
      if((srcAudioDev = _audioDeviceLabelToRecd(p,srcAudioDevLabel)) == nullptr )
      {
        rc = cwLogError(kInvalidArgRC,"The clock source '%s' could not be found for the audio device '%s'.",cwStringNullGuard(srcAudioDevLabel),cwStringNullGuard(dstAudioDev->label));
        goto errLabel;
      }

      if( clockSrcInputFl )
      {
        dstAudioDev->clockLink = srcAudioDev->clockInList;
        srcAudioDev->clockInList = dstAudioDev;
      }
      else
      {
        dstAudioDev->clockLink = srcAudioDev->clockOutList;
        srcAudioDev->clockOutList = dstAudioDev;
      }

    errLabel:
      return rc;      
    }

    rc_t _audioDeviceConfigure( io_t* p, audioDev_t*   ad, audioGroup_t* iag, audioGroup_t* oag, unsigned cycleCnt, unsigned framesPerCycle )
    {
      rc_t          rc             = kOkRC;

      double        israte         = 0;
      double        osrate         = 0;
      double        srate          = 0;
        
      unsigned      iDspFrameCnt   = 0;
      unsigned      oDspFrameCnt   = 0;
      unsigned      dspFrameCnt    = 0;

      unsigned      iChCnt         = 0;      
      unsigned      oChCnt         = 0;

      const char* inGroupLabel  = iag==nullptr || iag->msg.label==nullptr ? "<no in-group>" : iag->msg.label;
      const char* outGroupLabel = oag==nullptr || oag->msg.label==nullptr ? "<no out-group>" : oag->msg.label;
      
      
      // get the ingroup
      if( iag != nullptr )
      {            
        israte       = iag->msg.srate;
        iDspFrameCnt = iag->msg.dspFrameCnt;
      }

      // get the outgroup
      if( oag != nullptr )
      {
        osrate       = oag->msg.srate;
        oDspFrameCnt = oag->msg.dspFrameCnt;
      }
          
      // in-srate and out-srate must be equal or one must be 0
      if( osrate==0 || israte==0 || osrate==israte )
      {
        // the true sample rate is the non-zero sample rate
        srate = std::max(israte,osrate);
      }
      else
      {
        rc = cwLogError(kInvalidArgRC,"The device '%s' belongs to two groups (%s and %s) at different sample rates (%f != %f).", cwStringNullGuard(ad->devName), cwStringNullGuard(inGroupLabel), cwStringNullGuard(outGroupLabel), israte, osrate );
        goto errLabel;
      }

      // in-dspFrameCnt an out-dspFrameCnt must be equal or one must be 0
      if( oDspFrameCnt==0 || iDspFrameCnt==0 || oDspFrameCnt==iDspFrameCnt)
      {
        // the true sample rate is the non-zero sample rate
        dspFrameCnt = std::max(iDspFrameCnt,oDspFrameCnt);
      }
      else
      {
        rc = cwLogError(kInvalidArgRC,"The device '%s' belongs to two groups (%s and %s) width different dspFrameCnt values (%i != %i).", cwStringNullGuard(ad->devName), cwStringNullGuard(inGroupLabel), cwStringNullGuard(outGroupLabel), iDspFrameCnt, oDspFrameCnt );
        goto errLabel;
      }
          
      // setup the device based on the configuration
      if((rc = audio::device::setup(p->audioH, ad->devIdx, srate, framesPerCycle, _audioDeviceCallback, p)) != kOkRC )
      {
        rc = cwLogError(rc,"Unable to setup the audio hardware device:'%s'.", ad->devName);
        goto errLabel;
      }

      // get the device channel counts
      iChCnt  = audio::device::channelCount(p->audioH,ad->devIdx,true);
      oChCnt  = audio::device::channelCount(p->audioH,ad->devIdx,false);
          
      // initialize the audio bufer for this device
      if((rc = audio::buf::setup( p->audioBufH, ad->devIdx, srate, dspFrameCnt, cycleCnt, iChCnt, framesPerCycle, oChCnt, framesPerCycle )) != kOkRC )
      {
        rc = cwLogError(rc,"Audio device buffer channel setup failed.");
        goto errLabel;
      }

    errLabel:
      return rc;
    }
      
    // Create the audio device records by parsing the cfg audio.deviceL[] list.
    rc_t _audioDeviceParseAudioDeviceList( io_t* p, const object_t* cfg )
    {
      rc_t rc = kOkRC;
      const object_t* deviceL_Node;
      
      // get the audio device list
      if((deviceL_Node = cfg->find("deviceL")) == nullptr )
      {
        rc = cwLogError(kSyntaxErrorRC,"Audio 'deviceL' failed.");
        goto errLabel;
      }

      // create an audio device cfg list
      p->audioDevN = audio::device::count(p->audioH);
      p->audioDevA = mem::allocZ<audioDev_t>(p->audioDevN);

      // Initial audioDev record setup
      for(unsigned i=0; i<p->audioDevN; ++i)
      {
        p->audioDevA[i].devName = mem::duplStr(audio::device::label(p->audioH,i));
        p->audioDevA[i].devIdx  = i;
        p->audioDevA[i].userId  = kInvalidId;
      }

      // fill in the audio device cfg list
      for(unsigned i=0; i<deviceL_Node->child_count(); ++i)
      {
        audioDev_t*   ad             = nullptr;
        bool          activeFl       = false;
        bool          meterFl        = false;
        char*         userLabel      = nullptr;
        unsigned      userId         = kInvalidId;
        char*         devName        = nullptr;
        unsigned      framesPerCycle = 0;
        unsigned      cycleCnt       = 0;
        
        audioGroup_t* iag            = nullptr;
        audioGroup_t* oag            = nullptr;
/*        
        double        israte         = 0;
        double        osrate         = 0;
        double        srate          = 0;
        
        unsigned      iDspFrameCnt   = 0;
        unsigned      oDspFrameCnt   = 0;
        unsigned      dspFrameCnt    = 0;
*/      
        char*         inGroupLabel   = nullptr;
        char*         outGroupLabel  = nullptr;

        const char*   clockSrcDev = nullptr;
        bool          clockSrcInputFl = false;
        
        const object_t* node;
        
        if((node = deviceL_Node->child_ele(i)) != nullptr )
        {
          if(( rc = node->getv(
                "activeFl",       activeFl,
                "label",          userLabel,
                "device",         devName,
                "framesPerCycle", framesPerCycle,
                "cycleCnt",       cycleCnt )) != kOkRC )
          {
            rc = cwLogError(rc,"Error parsing required audio cfg record at index:%i",i);
            goto errLabel;
          }


          if((rc = node->getv_opt(
                "userId",     userId,
                "meterFl",    meterFl,
                "inGroup",    inGroupLabel,
                "outGroup",   outGroupLabel,
                "clockSrcDev",clockSrcDev,
                "syncToClockSrcDevInputFl", clockSrcInputFl )) != kOkRC )
          {
            rc = cwLogError(rc,"Error parsing optional audio cfg record at index:%i",i);
            goto errLabel;
          }
          
        }
        
        // if the configuration is enabled
        if( activeFl )
        {
          // locate the record assoc'd with devName
          if((ad = _audioDeviceNameToRecd(p,devName)) == nullptr )
          {
            rc = kInvalidArgRC;
            goto errLabel;
          }
          
          // get the hardware device index
          if((ad->devIdx = audio::device::labelToIndex( p->audioH, ad->devName)) == kInvalidIdx )
          {
            rc = cwLogError(rc,"Unable to locate the audio hardware device:'%s'.", cwStringNullGuard(ad->devName));
            goto errLabel;
          }
          
          if( inGroupLabel != nullptr )
            iag = _audioGroupFromLabel(p, inGroupLabel );

          // get the outgroup
          if( outGroupLabel != nullptr )
            oag = _audioGroupFromLabel(p, outGroupLabel);

          if((rc = _audioDeviceConfigure(p, ad, iag, oag, cycleCnt, framesPerCycle )) != kOkRC )
            goto errLabel;

          /*
          // get the ingroup
          if( inGroupLabel != nullptr )
            if((iag = _audioGroupFromLabel(p, inGroupLabel )) != nullptr )
            {            
              israte       = iag->msg.srate;
              iDspFrameCnt = iag->msg.dspFrameCnt;
            }

          // get the outgroup
          if( outGroupLabel != nullptr )
            if((oag = _audioGroupFromLabel(p, outGroupLabel)) != nullptr )
            {
              osrate       = oag->msg.srate;
              oDspFrameCnt = oag->msg.dspFrameCnt;
            }
          
          // in-srate and out-srate must be equal or one must be 0
          if( osrate==0 || israte==0 || osrate==israte )
          {
            // the true sample rate is the non-zero sample rate
            srate = std::max(israte,osrate);
          }
          else
          {
            rc = cwLogError(kInvalidArgRC,"The device '%s' belongs to two groups (%s and %s) at different sample rates (%f != %f).", cwStringNullGuard(ad->devName), cwStringNullGuard(inGroupLabel), cwStringNullGuard(outGroupLabel), israte, osrate );
            goto errLabel;
          }

          // in-dspFrameCnt an out-dspFrameCnt must be equal or one must be 0
          if( oDspFrameCnt==0 || iDspFrameCnt==0 || oDspFrameCnt==iDspFrameCnt)
          {
            // the true sample rate is the non-zero sample rate
            dspFrameCnt = std::max(iDspFrameCnt,oDspFrameCnt);
          }
          else
          {
            rc = cwLogError(kInvalidArgRC,"The device '%s' belongs to two groups (%s and %s) width different dspFrameCnt values (%i != %i).", cwStringNullGuard(ad->devName), cwStringNullGuard(inGroupLabel), cwStringNullGuard(outGroupLabel), iDspFrameCnt, oDspFrameCnt );
            goto errLabel;
          }
          
          // setup the device based on the configuration
          if((rc = audio::device::setup(p->audioH, ad->devIdx, srate, framesPerCycle, _audioDeviceCallback, p)) != kOkRC )
          {
            rc = cwLogError(rc,"Unable to setup the audio hardware device:'%s'.", ad->devName);
            goto errLabel;
          }

          // get the device channel counts
          unsigned iChCnt  = audio::device::channelCount(p->audioH,ad->devIdx,true);
          unsigned oChCnt  = audio::device::channelCount(p->audioH,ad->devIdx,false);

          
          // initialize the audio bufer for this device
          if((rc = audio::buf::setup( p->audioBufH, ad->devIdx, srate, dspFrameCnt, cycleCnt, iChCnt, framesPerCycle, oChCnt, framesPerCycle )) != kOkRC )
          {
            rc = cwLogError(rc,"Audio device buffer channel setup failed.");
            goto errLabel;
          }
          */

          unsigned iChCnt  = audio::device::channelCount(p->audioH,ad->devIdx,true);
          unsigned oChCnt  = audio::device::channelCount(p->audioH,ad->devIdx,false);

          
          // if an input group was assigned to this device then create a assoc'd audio_group_dev_t
          if( iag != nullptr )
          {            
            if((rc = _audioGroupAddDevice( iag, true, ad, iChCnt )) != kOkRC )
              goto errLabel;
          }
          
          // if an output group was assigned to this device then create a assoc'd audio_group_dev_t
          if( oag != nullptr )
          {
            if((rc = _audioGroupAddDevice( oag, false, ad, oChCnt )) != kOkRC )
              goto errLabel;            
          }

          // if this device is dependent on another device as a clock
          if( clockSrcDev != nullptr )
          {
            // ... then link it to the device
            if((rc = _audioDeviceConnectToClockSource( p, clockSrcDev, clockSrcInputFl, ad )) != kOkRC )
              goto errLabel;
          }

          // set the device group pointers
          ad->activeFl = activeFl;
          ad->meterFl  = meterFl;
          ad->label    = userLabel;
          ad->userId   = userId;
          ad->iGroup   = iag;
          ad->oGroup   = oag;
          ad->cycleCnt = cycleCnt;
          ad->framesPerCycle = framesPerCycle;

        }
      }

    errLabel:
      return rc;
    }

    rc_t _audioDeviceEnableMeters( io_t* p )
    {
      rc_t rc = kOkRC;
      
      for(unsigned i=0; i<p->audioDevN; ++i)
        if( p->audioDevA[i].activeFl && p->audioDevA[i].meterFl )
          if((rc = _audioDeviceEnableMeter(p, p->audioDevA[i].devIdx, kEnableFl | kInFl | kOutFl )) != kOkRC )
          {
            cwLogError(rc,"Audio enable on device '%s' failed.",p->audioDevA[i].label);
            goto errLabel;
          }
      
    errLabel:
      return rc;
	      
    }
    
    // Allocate the sample ptr buffers for each audio group.
    rc_t _audioGroupAllocBuffer( io_t* p )
    {
      rc_t rc = kOkRC;
      
      for(unsigned i=0; i<p->audioGroupN; ++i)        
      {
        audioGroup_t* ag = p->audioGroupA + i;

        if( ag->msg.iBufChCnt )
          ag->msg.iBufArray   = mem::allocZ<sample_t*>( ag->msg.iBufChCnt );
        
        if( ag->msg.oBufChCnt )
          ag->msg.oBufArray   = mem::allocZ<sample_t*>( ag->msg.oBufChCnt );
      }
      
      return rc;
    }

    rc_t _audioParseConfig( io_t* p, const object_t* node )
    {
      rc_t            rc          = kOkRC;      
      
      // get the meterMs value
      if((rc = node->getv("meterMs", p->audioMeterCbPeriodMs, "threadTimeOutMs", p->audioThreadTimeOutMs )) != kOkRC )
      {
        rc = cwLogError(kSyntaxErrorRC,"Audio 'meterMs' or 'dspFrameCnt' parse failed.");
        goto errLabel;
      }

      // initialize the audio buffer
      if((rc = audio::buf::create( p->audioBufH, audio::device::count(p->audioH), p->audioMeterCbPeriodMs )) != kOkRC )
      {
        rc = cwLogError(rc,"Audio device buffer failed.");
        goto errLabel;
      }

      // parse the audio group list
      if((rc = _audioDeviceParseAudioGroupList( p, node )) != kOkRC )
      {
        rc = cwLogError(rc,"Parse audio group list.");
        goto errLabel;
      }
      
      // parse the audio device list
      if((rc = _audioDeviceParseAudioDeviceList( p, node )) != kOkRC )
      {
        rc = cwLogError(rc,"Parse audio device list.");
        goto errLabel;
      }

      // create the audio buffer pointer arrays
      if((rc = _audioGroupAllocBuffer(p)) != kOkRC )
      {
        rc = cwLogError(rc,"Audio group buffer allocation failed.");
        goto errLabel;
      }

      // initialize enabled audio meters
      if((rc = _audioDeviceEnableMeters(p)) != kOkRC )
      {
        rc = cwLogError(rc,"Audio device enabled failed.");
        goto errLabel;
      }

    errLabel:
      return rc;
    }

    rc_t _audioCreateDeviceFiles(io_t* p, const object_t* cfg, audio::device::driver_t*& audioDrvRef )
    {
      rc_t            rc    = kOkRC;
      const object_t* flist = nullptr;
        
      audioDrvRef = nullptr;

      // locate the audio device file cfg. node
      if((rc = cfg->getv_opt( "files", flist)) != kOkRC )
      {
        rc = cwLogError(rc,"Error obtaining audio device file cfg. node.");
        goto errLabel;
      }

      // if no audio device file cfg. node was given - then there are no audio device files
      if( flist == nullptr )
        goto errLabel;


      // create the audio devicce file mgr
      if((rc = create( p->audioDevFileH, audioDrvRef )) != kOkRC )
      {
        rc = cwLogError(rc,"The audio device file manager create failed.");
        goto errLabel;
      }

      for(unsigned i=0; i<flist->child_count(); ++i)
      {
        const object_t* fnode = flist->child_ele(i);

        if( fnode == nullptr )
        {
          cwLogWarning("The audio device file descriptor at index %i is empty.",i);
        }
        else
        {
          bool        enableFl          = false;
          const char* label             = nullptr;
          const char* iFname            = nullptr;
          bool        iRwdOnStartFl     = false;
          bool        iCacheFl          = true;
          bool        iUseInternalClkFl = false;
            
          const char* oFname            = nullptr;
          unsigned    oChCnt            = 0;
          unsigned    oCacheBlockSec    = 10;
          bool        oRwdOnStartFl     = false;
          bool        oCacheFl          = true;
          bool        oUseInternalClkFl = false;

          if((rc = fnode->getv( "device_label",label)) != kOkRC || label == nullptr )
          {
            cwLogError(rc,"The label parse failed on audio device descriptor at index %i.",i);
            goto errLabel;
          }

          if((rc = fnode->getv_opt("enableFl", enableFl,
                                   "in_fname", iFname,
                                   "in_rewind_on_start_fl",iRwdOnStartFl,
                                   "in_cache_fl",iCacheFl,
                                   "out_use_internal_clock_fl",iUseInternalClkFl,
                                   "out_fname", oFname,
                                   "out_rewind_on_start_fl",oRwdOnStartFl,
                                   "out_cache_fl",oCacheFl,
                                   "out_use_internal_clock_fl",oUseInternalClkFl,
                                   "out_cache_block_sec",oCacheBlockSec,
                                   "out_ch_count", oChCnt )) != kOkRC )
          {
            cwLogError(rc,"The optional variables parse failed on audio device descriptor at index %i label:%s.",i,cwStringNullGuard(label));
            goto errLabel;
          }


          if( enableFl )
          {
            if( iFname != nullptr )
            {
              unsigned iFlags = iRwdOnStartFl ? audio::device::file::kRewindOnStartFl : 0;

              iFlags += iCacheFl          ? audio::device::file::kCacheFl            : 0;
              iFlags += iUseInternalClkFl ? audio::device::file::kUseInternalClockFl : 0;
                                   
              if((rc = createInDevice( p->audioDevFileH, label, iFname,  iFlags )) != kOkRC )
              {
                cwLogError(rc,"Create failed on input audio device file '%s'.",cwStringNullGuard(label));
                goto errLabel;
              }
            }

            if( oFname != nullptr )
            {
              unsigned oFlags = oRwdOnStartFl ? audio::device::file::kRewindOnStartFl : 0;

              oFlags += oCacheFl          ? audio::device::file::kCacheFl            : 0;
              oFlags += oUseInternalClkFl ? audio::device::file::kUseInternalClockFl : 0;

              // bitsPerSample==0 indicates the output file should use a floating point sample format
              if((rc = createOutDevice( p->audioDevFileH, label, oFname, oFlags, oChCnt, 0, oCacheBlockSec )) != kOkRC )
              {
                cwLogError(rc,"Create failed on output audio device file '%s'.",cwStringNullGuard(label));
                goto errLabel;              
              }            
            }            
          }
          
        }
      }
      
      
    errLabel:
      if( rc != kOkRC )
      {
        audioDrvRef = nullptr;
        rc = cwLogError(rc,"Audio device file iniitaliztion failed.");
      }
      
      return rc;
    }

    rc_t _audioCreate( io_t* p, const object_t* c )
    {      
      rc_t                     rc       = kOkRC;
      audio::device::driver_t* audioDrv = nullptr;
      const object_t*          cfg      = nullptr;
      bool                     enableFl = false;
      
      // get the audio port node
      if((cfg = c->find("audio")) == nullptr )
      {
        cwLogWarning("No 'audio' configuration node.");
        goto errLabel;
      }

      if((rc = cfg->getv("enableFl",enableFl)) != kOkRC )
      {
        rc = cwLogError(rc,"Error reading top level audio cfg.");
        goto errLabel;
      }

      if( !enableFl )
      {
        cwLogInfo("Audio sub-system disabled.");
        goto errLabel;
      }
      
      // initialize the audio device interface  
      if((rc = audio::device::create(p->audioH)) != kOkRC )
      {
        rc = cwLogError(rc,"Initialize failed.");
        goto errLabel;
      }

      // initialize the ALSA device driver interface
      if((rc = audio::device::alsa::create(p->alsaH, audioDrv )) != kOkRC )
      {
        rc = cwLogError(rc,"ALSA initialize failed.");
        goto errLabel;
      }

      // register the ALSA device driver with the audio interface
      if((rc = audio::device::registerDriver( p->audioH, audioDrv )) != kOkRC )
      {
        rc = cwLogError(rc,"ALSA driver registration failed.");
        goto errLabel;
      }

      // create the audio device sub-system - audio device files must be created
      // before they can be referenced in _audioParseConfig().
      if((rc = _audioCreateDeviceFiles(p,cfg,audioDrv)) != kOkRC )
        {
          rc = cwLogError(rc,"The audio device file creation failed.");
          goto errLabel;
        }

      // register audio device file driver
      if( audioDrv != nullptr )
        if((rc = audio::device::registerDriver( p->audioH, audioDrv )) != kOkRC )
        {
          rc = cwLogError(rc,"The audio device file driver registration failed.");
          goto errLabel;
        }

      // read the configuration information and setup the audio hardware
      if((rc = _audioParseConfig( p, cfg )) != kOkRC )
      {
        rc = cwLogError(rc,"Audio device configuration failed.");
        goto errLabel;
      }
      
    errLabel:

      if( rc != kOkRC && p->audioH.isValid()  )
        audio::device::report( p->audioH );
      
      return rc;
    }

    //----------------------------------------------------------------------------------------------------------
    //
    // UI
    //

    // This function is called by the websocket with messages coming from a remote UI.
    rc_t _uiCallback( void* cbArg, unsigned wsSessId, ui::opId_t opId, unsigned parentAppId, unsigned uuId, unsigned appId, unsigned chanId, const ui::value_t* v )
    {
      io_t* p = (io_t*)cbArg;
      msg_t r;
      rc_t rc = kOkRC;
      rc_t app_rc = kOkRC;
      
      r.tid  = kUiTId;
      r.u.ui = { .opId=opId, .wsSessId=wsSessId, .parentAppId=parentAppId, .uuId=uuId, .appId=appId, .chanId=chanId, .value=v };

      if((rc = _ioCallback( p, p->uiAsyncFl, &r, &app_rc )) != kOkRC )
        cwLogError(rc,"UI app callback failed.");

      return app_rc;
    }

    rc_t _uiConfig( io_t* p, const object_t* c, const ui::appIdMap_t* mapA, unsigned mapN )
    {
      rc_t            rc         = kOkRC;
      const char*     uiCfgLabel = "ui";
      ui::ws::args_t  args       = {};
      const object_t* ui_cfg     = nullptr;
      bool            enableFl   = false;

      // Duplicate the application id map
      if( mapN > 0 )
      {
        p->uiMapA   = mem::allocZ<ui::appIdMap_t>( mapN );
        p->uiMapN   = mapN;
        for(unsigned i=0; i<mapN; ++i)
        {
          p->uiMapA[i] = mapA[i];
          p->uiMapA[i].eleName = mem::duplStr(mapA[i].eleName);
        }
      }

     
      // if a UI cfg record was given
      if((ui_cfg = c->find(uiCfgLabel)) != nullptr )
      {

        if((rc = ui_cfg->getv("enableFl",enableFl,
                              "asyncFl",p->uiAsyncFl)) != kOkRC )
        {
          rc = cwLogError(rc,"UI configuration parse failed.");
          goto errLabel;
        }

        if( !enableFl )
        {
          cwLogInfo("UI sub-system disabled.");
          goto errLabel;
        }

        // parse the ui 
        if((rc = ui::ws::parseArgs( *c, args, uiCfgLabel )) == kOkRC )
        {  
          rc = ui::ws::create(p->wsUiH, args, p, _uiCallback, args.uiRsrc, p->uiMapA, p->uiMapN);
          
          ui::ws::releaseArgs(args);

          //ui::enableCache( ui::ws::uiHandle(p->wsUiH) );
        
        }
      }

    errLabel:
      return rc;

    }

    rc_t _handleToWsUiHandle( handle_t h, ui::ws::handle_t& uiH_Ref )
    {
      rc_t rc = kOkRC;
      io_t* p = _handleToPtr(h);
      if( p != nullptr && p->wsUiH.isValid())
        uiH_Ref = p->wsUiH;
      else
        rc = cwLogError(kInvalidStateRC,"Invalid ui::ws handle in io request.");

      return rc;
    }

    rc_t _handleToUiHandle( handle_t h, ui::handle_t& uiHRef )
    {
      rc_t rc;
      ui::ws::handle_t wsUiH;
      uiHRef.clear();
      if((rc = _handleToWsUiHandle(h, wsUiH)) != kOkRC )
        return rc;

      uiHRef = ui::ws::uiHandle(wsUiH);

      return uiHRef.isValid() ? rc : cwLogError(rc,"Invalid ui handle in io request.");
    }
    

    //----------------------------------------------------------------------------------------------------------
    //
    // IO
    //
    
    rc_t _destroy( io_t* p )
    {
      rc_t rc = kOkRC;

      // stop thread callbacks
      if((rc = thread_mach::destroy(p->threadMachH)) != kOkRC )
        return rc;


      _thread_once_cleanup(p,true);
      
      for(unsigned i=0; i<p->timerN; ++i)
        mem::release(p->timerA[i].label);
      
      mem::release(p->timerA);
      p->timerN = 0;

      destroy(p->cbMutexH);

      _serialPortDestroy(p);

      _audioDestroy(p);

      midi::device::destroy(p->midiH);

      sock::destroyMgr( p->sockH );

      mem::release(p->sockA);
      
      for(unsigned i=0; i<p->uiMapN; ++i)
        mem::free(const_cast<char*>(p->uiMapA[i].eleName));
      mem::release(p->uiMapA);
      p->uiMapN = 0;

      ui::ws::destroy(p->wsUiH);


      // free the cfg object 
      if( p->cfg != nullptr )
        p->cfg->free();

      
      mem::release(p);

      return rc;
    }

    
    
    
  }
}


//----------------------------------------------------------------------------------------------------------
//
// IO
//

cw::rc_t cw::io::create(
  handle_t&             h,
  const object_t*       o,
  cbFunc_t              cbFunc,
  void*                 cbArg,
  const ui::appIdMap_t* mapA,
  unsigned              mapN,
  const char*           cfgLabel )
{
  rc_t            rc;
  
  if((rc = destroy(h)) != kOkRC )
    return rc;

  // create the io_t object
  io_t* p = mem::allocZ<io_t>();

  // duplicate the cfg object so that we can maintain pointers into its elements without
  // any chance that they will be delted before the application completes
  p->cfg    = o->duplicate();
  p->cbFunc = cbFunc;
  p->cbArg  = cbArg;

  // parse the 'io' configuration block
  if((rc = _ioParse(p,o)) != kOkRC )
    goto errLabel;

  // create the callback mutex
  if((rc = mutex::create( p->cbMutexH )) != kOkRC )
    goto errLabel;

  // create the the thread machine
  if((rc = thread_mach::create( p->threadMachH )) != kOkRC )
    goto errLabel;
  
  // create the serial port device
  if((rc = _serialPortCreate(p,p->cfg)) != kOkRC )
    goto errLabel;

  // create the MIDI port device
  if((rc = _midiPortCreate(p,p->cfg)) != kOkRC )
    goto errLabel;

  // create the Audio device interface
  if((rc = _audioCreate(p,p->cfg)) != kOkRC )
    goto errLabel;

  // create the Socket manager
  if((rc= _socketParseConfig(p, p->cfg )) != kOkRC )
    goto errLabel;
  
  // create the UI interface
  if((rc = _uiConfig(p,p->cfg, mapA, mapN)) != kOkRC )
    goto errLabel;
  

  p->quitFl.store(false);
  p->startedFl.store(false);
  time::get(p->t0);  
  
  h.set(p);
  
 errLabel:
  if(rc != kOkRC )
    _destroy(p);
  
  return rc;
}


cw::rc_t cw::io::destroy( handle_t& h )
{
  rc_t rc = kOkRC;
  
  if( !h.isValid() )
    return rc;

  io_t* p = _handleToPtr(h);

  if((rc = _destroy(p)) != kOkRC )
    return rc;

  h.clear();
  
  return rc;
}

cw::rc_t cw::io::start( handle_t h )
{
  rc_t rc = kOkRC;
  
  io_t* p = _handleToPtr(h);

  if((rc = _audioDeviceStartStop(p,true)) != kOkRC )
    goto errLabel;

  if((rc = _serialPortStart(p)) != kOkRC )
    goto errLabel;
  
  if((rc = thread_mach::start( p->threadMachH )) != kOkRC )
  {
    cwLogError(rc,"Thread machine start failed.");
    goto errLabel;
  }
  
  p->startedFl.store(true);
  
 errLabel:

  if( rc != kOkRC )
    stop(h);
  
  return rc;
}

cw::rc_t cw::io::pause( handle_t h )
{
  io_t* p = _handleToPtr(h);
  rc_t rc;
  if((rc = thread_mach::stop( p->threadMachH )) != kOkRC )
  {
    cwLogError(rc,"Thread machine stop failed.");
    goto errLabel;    
  }

  p->startedFl.store(false);
  
 errLabel:
  return rc;
}

cw::rc_t cw::io::stop( handle_t h )
{
  io_t* p = _handleToPtr(h);
  p->quitFl.store(true);

  rc_t rc0 = thread_mach::stop(p->threadMachH );
  
  // stop the audio devices
  rc_t rc1 = _audioDeviceStartStop(p,false);

  // clear the UI
  //if( p->wsUiH.isValid() )
  //  uiDestroyElement(h,ui::kRootUuId);
  
  rc_t rc = rcSelect(rc0,rc1);

  if(rc == kOkRC )
    p->startedFl.store(false);

  return rc;
}

bool cw::io::is_started_flag( handle_t h )
{
  if( h.isValid() )
  {
    io_t* p = _handleToPtr(h);
    return p->startedFl.load();
  }
  return false;
}

cw::rc_t cw::io::exec( handle_t h, unsigned timeOutMs, void* execCbArg )
{
  rc_t rc = kOkRC;
  io_t* p = _handleToPtr(h);
  
  if( p->wsUiH.isValid() )
  {
    ui::flushCache( ui::ws::uiHandle( p->wsUiH ));
    
    // Note this call blocks on the websocket handle: See cwUi.h:ws:exec()
    rc = ui::ws::exec( p->wsUiH, timeOutMs );
  }
  
  time::get(p->t0);

  if( p->audioMeterDevEnabledN ) 
    _audioDeviceProcessMeters(p);

  if( p->threadOnceList != nullptr )
    _thread_once_cleanup(p);
  
  msg_t m;
  m.tid = kExecTId;
  m.u.exec.execArg = execCbArg;
  _ioCallback(p,false,&m);
  
  return rc;
}

bool cw::io::isShuttingDown( handle_t h )
{
  io_t* p = _handleToPtr(h);

  return p->quitFl.load();

  
  //return thread_mach::is_shutdown(p->threadMachH);
}

void cw::io::report( handle_t h )
{
  for(unsigned i=0; i<serialDeviceCount(h); ++i)
    printf("serial: %s\n", serialDeviceLabel(h,i));

  for(unsigned i=0; i<midiDeviceCount(h); ++i)
    for(unsigned j=0; j<2; ++j)
    {
      bool     inputFl = j==0;
      unsigned m       = midiDevicePortCount(h,i,inputFl);
      for(unsigned k=0; k<m; ++k)
        printf("midi: %s: %s : %s\n", inputFl ? "in ":"out", midiDeviceName(h,i), midiDevicePortName(h,i,inputFl,k));
        
    }

  for(unsigned i=0; i<audioDeviceCount(h); ++i)
    printf("audio: %s\n", cwStringNullGuard(audioDeviceName(h,i)));  
}


void cw::io::hardwareReport( handle_t h )
{
  io_t* p = _handleToPtr(h);
  audio::device::report( p->audioH );
  midi::device::report(p->midiH);  
}


void cw::io::realTimeReport( handle_t h )
{
  io_t* p = _handleToPtr(h);
  audio::device::realTimeReport(p->audioH);
  uiRealTimeReport(h);
}


//----------------------------------------------------------------------------------------------------------
//
// Thread
//

cw::rc_t cw::io::threadCreate( handle_t h, unsigned id, bool asyncFl, void* arg, const char* label )
{
  rc_t      rc = kOkRC;
  io_t*     p  = _handleToPtr(h);
  thread_t* t  = mem::allocZ<thread_t>(1);  // BUG BUG BUG: where does this get released()
  
  t->id      = id;
  t->asyncFl = asyncFl;
  t->arg     = arg;
  t->p       = p;
  t->link    = p->threadL;
  p->threadL = t;

  if((rc = thread_mach::add( p->threadMachH, _threadFunc, t, label )) != kOkRC )
    rc = cwLogError(rc,"Thread create failed.");
 
  return rc;
}

cw::rc_t  cw::io::threadRunOnce( handle_t h, unsigned id, bool asyncFl, thread_once_func_t func, void* arg,  const char* thread_label )
{
  
  rc_t      rc = kOkRC;
  io_t*     p  = _handleToPtr(h);
  thread_once_t* t = mem::allocZ<thread_once_t>();

  t->p       = p;
  t->arg     = arg;
  t->cbFunc  = func;
  t->asyncFl = asyncFl;
  t->doneFl  = false;
  t->id      = id;
  t->link    = p->threadOnceList;
  p->threadOnceList = t;

  if((rc = thread::create(t->threadH,_thread_once_func,t,thread_label)) != kOkRC )
  {
    rc = cwLogError(rc,"One-time thread '%s' create failed.",cwStringNullGuard(thread_label));
    goto errLabel;
  }

  sleepMs(10);

  if((rc = thread::unpause(t->threadH)) != kOkRC )
  {
    rc = cwLogError(rc,"One-time '%s' thread start failed.",cwStringNullGuard(thread_label));
    goto errLabel;
  }
  
errLabel:
  if( rc != kOkRC )
  {
    p->threadOnceList = t->link;
    thread::destroy(t->threadH);
    mem::release(t);
  }
  return rc;  
}


//----------------------------------------------------------------------------------------------------------
//
// Timer
//

cw::rc_t    cw::io::timerCreate( handle_t h, const char* label, unsigned id, unsigned periodMicroSec, bool asyncFl )
{
  io_t* p = _handleToPtr(h);
  return  _timerCreate(p, label, id, periodMicroSec, asyncFl );

}

cw::rc_t    cw::io::timerDestroy( handle_t h, unsigned timerIdx )
{
  io_t*    p = _handleToPtr(h);
  timer_t* t = _timerIndexToPtr( p, timerIdx );

  if( t != nullptr )
  {
    t->startedFl = false;
    t->deletedFl = true;
  }
  
  return t==nullptr ? kInvalidIdRC : kOkRC;
}

unsigned    cw::io::timerCount( handle_t h )
{
  io_t* p = _handleToPtr(h);
  return p->timerN;
}

unsigned    cw::io::timerLabelToIndex( handle_t h, const char* label )
{
  io_t* p = _handleToPtr(h);
  for(unsigned i=0; i<p->timerN; ++i)
    if( !p->timerA[i].deletedFl && strcmp(label,p->timerA[i].label) == 0 )
      return i;
  
  return kInvalidIdx;
}

unsigned    cw::io::timerIdToIndex(         handle_t h, unsigned timerId )
{
  io_t* p = _handleToPtr(h);
  for(unsigned i=0; i<p->timerN; ++i)
  {
    timer_t* t = p->timerA + i;
    if( !t->deletedFl && t->id == timerId )
      return i;    
  }

  return kInvalidIdx;
}


const char* cw::io::timerLabel(             handle_t h, unsigned timerIdx )
{
  io_t*    p = _handleToPtr(h);
  timer_t* t = _timerIndexToPtr( p, timerIdx );
  
  return t==nullptr ? nullptr : t->label;    
}

unsigned    cw::io::timerId(                handle_t h, unsigned timerIdx )
{
  io_t*    p = _handleToPtr(h);
  timer_t* t = _timerIndexToPtr( p, timerIdx );
  
  return t==nullptr ? kInvalidId : t->id;    
}

unsigned    cw::io::timerPeriodMicroSec(    handle_t h, unsigned timerIdx )
{
  io_t*    p = _handleToPtr(h);
  timer_t* t = _timerIndexToPtr( p, timerIdx );
  
  return t==nullptr ? 0 : t->periodMicroSec;    
}

cw::rc_t    cw::io::timerSetPeriodMicroSec( handle_t h, unsigned timerIdx, unsigned periodMicroSec )
{
  rc_t     rc = kOkRC;
  io_t*    p  = _handleToPtr(h);
  timer_t* t  = _timerIndexToPtr(p, timerIdx);

  if( t == nullptr )
    rc = kInvalidIdRC;
  else
  {
    p->timerA[ timerIdx ].periodMicroSec = periodMicroSec;
  }
    
  return rc;
}

cw::rc_t    cw::io::timerSetNextTime( handle_t h, unsigned timerIdx, const time::spec_t& time  )
{
  rc_t     rc = kOkRC;
  io_t*    p  = _handleToPtr(h);
  timer_t* t  = _timerIndexToPtr(p, timerIdx);

  if( t == nullptr )
    rc = kInvalidIdRC;
  else
  {
    p->timerA[ timerIdx ].nextTime = time;
  }
    
  return rc;
}

cw::rc_t    cw::io::timerStart( handle_t h, unsigned timerIdx )
{
  io_t*    p  = _handleToPtr(h);
  return _timerStart( p, timerIdx, true );
}

cw::rc_t    cw::io::timerStop( handle_t h, unsigned timerIdx )
{
  io_t*    p  = _handleToPtr(h);
  return _timerStart( p, timerIdx, false );
}



//----------------------------------------------------------------------------------------------------------
//
// Serial
//
bool cw::io::serialIsEnabled( handle_t h )
{
  io_t* p = _handleToPtr(h);
  return p->serialN != 0;
}

unsigned cw::io::serialDeviceCount( handle_t h )
{
  io_t* p = _handleToPtr(h);
  return p->serialN;
}

unsigned cw::io::serialDeviceIndex( handle_t h, const char* label )
{
  io_t* p = _handleToPtr(h);
  for(unsigned i=0; i<p->serialN; ++i)
    if( textCompare(label,p->serialA[i].label) == 0 )
      return i;

  return kInvalidIdx;
}

const char* cw::io::serialDeviceLabel(  handle_t h, unsigned devIdx )
{
  io_t* p = _handleToPtr(h);
  return p->serialA[devIdx].label;
}


unsigned    cw::io::serialDeviceId(    handle_t h, unsigned devIdx )
{
  io_t* p = _handleToPtr(h);
  return p->serialA[devIdx].userId;
}

void        cw::io::serialDeviceSetId( handle_t h, unsigned devIdx, unsigned id )
{
  io_t* p = _handleToPtr(h);
  p->serialA[devIdx].userId = id;
}


cw::rc_t cw::io::serialDeviceSend(  handle_t h, unsigned devIdx, const void* byteA, unsigned byteN )
{
  rc_t  rc = kOkRC;
  io_t* p  = _handleToPtr(h);

  if( devIdx >= p->serialN )
  {
    rc = cwLogError(kInvalidArgRC,"%i is an invalid serial device index.",devIdx);
    goto errLabel;
  }

  if((rc = send( p->serialPortSrvH, p->serialA[devIdx].userId, byteA, byteN )) != kOkRC )
  {
    rc = cwLogError(rc,"Send on serial device index %i failed.",devIdx);
    goto errLabel;
  }

errLabel:
  return rc;
}


//----------------------------------------------------------------------------------------------------------
//
// MIDI
//

bool        cw::io::midiIsEnabled(       handle_t h )
{
  io_t* p = _handleToPtr(h);
  return p->midiH.isValid();
}

unsigned    cw::io::midiDeviceCount( handle_t h )
{
  io_t* p = _handleToPtr(h);
  if( !p->midiH.isValid() )
    return 0;
  
  return midi::device::count(p->midiH);
}

const char* cw::io::midiDeviceName( handle_t h, unsigned devIdx )
{
  io_t* p = _handleToPtr(h);
  return midi::device::name(p->midiH,devIdx);
}

unsigned cw::io::midiDeviceIndex( handle_t h, const char* devName )
{
  io_t* p = _handleToPtr(h);
  return midi::device::nameToIndex(p->midiH, devName);
}

unsigned cw::io::midiDevicePortCount( handle_t h, unsigned devIdx, bool inputFl )
{
  io_t* p = _handleToPtr(h);
  return midi::device::portCount(p->midiH, devIdx, inputFl ? midi::kInMpFl : midi::kOutMpFl );
}

const char* cw::io::midiDevicePortName( handle_t h, unsigned devIdx, bool inputFl, unsigned portIdx )
{
  io_t* p = _handleToPtr(h);
  return midi::device::portName( p->midiH, devIdx, inputFl ? midi::kInMpFl : midi::kOutMpFl, portIdx ); 
}

unsigned cw::io::midiDevicePortIndex( handle_t h, unsigned devIdx, bool inputFl, const char* portName )
{
  io_t* p = _handleToPtr(h);
  return midi::device::portNameToIndex( p->midiH, devIdx, inputFl ? midi::kInMpFl : midi::kOutMpFl, portName );
}
      
cw::rc_t cw::io::midiDeviceSend( handle_t h, unsigned devIdx, unsigned portIdx, uint8_t status, uint8_t d0, uint8_t d1 )
{
  io_t* p = _handleToPtr(h);
  return midi::device::send( p->midiH, devIdx, portIdx, status, d0, d1 );
}

cw::rc_t cw::io::midiDeviceAllNotesOff( handle_t h, unsigned devIdx, unsigned portIdx )
{
  rc_t rc = kOkRC;
  unsigned devN = midiDeviceCount(h);
  for(unsigned i=0; i<devN; ++i)
    if( devIdx==kInvalidIdx || devIdx==i )
    {
      unsigned portN = midiDevicePortCount(h,i,false);
      for(unsigned j=0; j<portN; ++j)
        if( portIdx==kInvalidIdx || portIdx==j )
        {
          rc_t rc0;
          if((rc0 = midiDeviceSend(h,i,j,midi::kCtlMdId,midi::kResetAllCtlsMdId,0)) != kOkRC) // reset all controllers
            rc = cwLogError(rc0,"Send reset all controllers failed on %i:%i.",i,j);
          
          if((rc0 =  midiDeviceSend(h,i,j,midi::kCtlMdId,midi::kAllNotesOffMdId,0)) != kOkRC) // all notes off
            rc = cwLogError(rc0,"Send all-notes-off failed on %i:%i.",i,j);
        }
            
    }
  
  return rc;
}

unsigned cw::io::midiDeviceMaxBufferMsgCount( handle_t h )
{
  io_t* p = _handleToPtr(h);
  return midi::device::maxBufferMsgCount(p->midiH );
}

const cw::midi::ch_msg_t* cw::io::midiDeviceBuffer(      handle_t h, unsigned& msgCntRef )
{
  io_t* p = _handleToPtr(h);
  return midi::device::getBuffer(p->midiH, msgCntRef );
}

cw::rc_t                  cw::io::midiDeviceClearBuffer( handle_t h, unsigned msgCnt )
{
  io_t* p = _handleToPtr(h);
  return midi::device::clearBuffer(p->midiH, msgCnt );
}

cw::rc_t  cw::io::midiOpenMidiFile( handle_t h, unsigned devIdx, unsigned portIdx, const char* fname )
{
  return midi::device::openMidiFile( _handleToPtr(h)->midiH, devIdx, portIdx, fname );
}

cw::rc_t  cw::io::midiLoadMsgPacket( handle_t h, const midi::packet_t& pkt )
{
  return midi::device::loadMsgPacket( _handleToPtr(h)->midiH, pkt );
}

unsigned  cw::io::midiMsgCount( handle_t h, unsigned devIdx, unsigned portIdx )
{
  return midi::device::msgCount( _handleToPtr(h)->midiH, devIdx, portIdx );
}

cw::rc_t  cw::io::midiSeekToMsg( handle_t h, unsigned devIdx, unsigned portIdx, unsigned msgIdx )
{
  return midi::device::seekToMsg( _handleToPtr(h)->midiH, devIdx, portIdx, msgIdx );
}

cw::rc_t  cw::io::midiSetEndMsg( handle_t h, unsigned devIdx, unsigned portIdx, unsigned msgIdx )
{
  return midi::device::setEndMsg( _handleToPtr(h)->midiH, devIdx, portIdx, msgIdx );
}

cw::rc_t   cw::io::midiFileStart( handle_t h )
{
  return midi::device::start( _handleToPtr(h)->midiH );
}

cw::rc_t   cw::io::midiFileStop( handle_t h )
{
  return midi::device::stop( _handleToPtr(h)->midiH );
}

cw::rc_t   cw::io::midiFilePause( handle_t h, bool pauseFl )
{
  return midi::device::pause( _handleToPtr(h)->midiH, pauseFl );
}



//----------------------------------------------------------------------------------------------------------
//
// Audio
//

bool        cw::io::audioIsEnabled(            handle_t h )
{
  io_t* p = _handleToPtr(h);
  for(unsigned devIdx=0; devIdx<p->audioDevN; ++devIdx)
  {
    audioDev_t* ad;
    if((ad = _audioDeviceIndexToRecd(p,devIdx)) != nullptr && ad->activeFl )
      return true;
  }

  return false;
}

unsigned    cw::io::audioDeviceCount(          handle_t h )
{
  io_t* p = _handleToPtr(h);
  return p->audioDevN;
}

unsigned    cw::io::audioDeviceLabelToIndex(   handle_t h, const char* label )
{
  io_t*       p = _handleToPtr(h);
  audioDev_t* ad;
  
  if((ad = _audioDeviceLabelToRecd(p,label)) != nullptr )
    return ad->devIdx;

  return kInvalidIdx;
}

const char* cw::io::audioDeviceLabel( handle_t h, unsigned devIdx )
{
  io_t*       p = _handleToPtr(h);
  audioDev_t* ad;
  
  if((ad = _audioDeviceIndexToRecd(p,devIdx)) != nullptr )
    return ad->label;

  return nullptr;
}

cw::rc_t  cw::io::audioDeviceSetUserId( handle_t h, unsigned devIdx, unsigned userId )
{
  io_t*       p = _handleToPtr(h);
  audioDev_t* ad;
  
  if((ad = _audioDeviceIndexToRecd(p,devIdx)) != nullptr )
  {
    ad->userId = userId;
    
    if( ad->iagd != nullptr )
      ad->iagd->userId = userId;
    
    if( ad->oagd != nullptr )
      ad->oagd->userId = userId;
  }

  return kInvalidArgRC;  
}

bool  cw::io::audioDeviceIsActive( handle_t h, unsigned devIdx )
{
  io_t*       p = _handleToPtr(h);
  audioDev_t* ad;
  if((ad = _audioDeviceIndexToRecd(p,devIdx)) != nullptr )
    return ad->activeFl;
  
  return false;
}


const char* cw::io::audioDeviceName( handle_t h, unsigned devIdx )
{
  io_t*       p = _handleToPtr(h);
  audioDev_t* ad;
  
  if((ad = _audioDeviceIndexToRecd(p, devIdx )) == nullptr )
    return nullptr;

  return ad->devName;  
}

unsigned   cw::io::audioDeviceUserId( handle_t h, unsigned devIdx )
{
  io_t*       p = _handleToPtr(h);
  audioDev_t* ad;
  
  if((ad = _audioDeviceIndexToRecd(p, devIdx )) == nullptr )
    return kInvalidId;

  return ad->userId;    
}

cw::rc_t  cw::io::audioDeviceEnable(  handle_t h, unsigned devIdx, bool inputFl, bool enableFl )
{
  io_t* p = _handleToPtr(h);
  return audio::device::enable(p->audioH, devIdx, inputFl, enableFl );
}

cw::rc_t  cw::io::audioDeviceSeek(    handle_t h, unsigned devIdx, bool inputFl, unsigned frameOffset )
{
  io_t* p = _handleToPtr(h);
  return audio::device::seek(p->audioH, devIdx, inputFl, frameOffset );
}

double cw::io::audioDeviceSampleRate( handle_t h, unsigned devIdx )
{
  io_t* p = _handleToPtr(h);
  return audio::device::sampleRate(p->audioH, devIdx );
}

unsigned cw::io::audioDeviceFramesPerCycle( handle_t h, unsigned devIdx )
{
  io_t* p = _handleToPtr(h);
  audioDev_t* ad;
   
  if((ad = _audioDeviceIndexToRecd(p, devIdx )) == nullptr )
    return audio::device::framesPerCycle(p->audioH, devIdx, ad->iGroup != nullptr );

  return 0;
}

unsigned cw::io::audioDeviceChannelCount(   handle_t h, unsigned devIdx, unsigned inOrOutFlag )
{
  io_t* p = _handleToPtr(h);
  
  return audio::device::channelCount(p->audioH, devIdx, inOrOutFlag & kInFl );
}

cw::rc_t cw::io::audioDeviceEnableMeters( handle_t h, unsigned devIdx, unsigned inOutEnaFlags )
{
  io_t* p = _handleToPtr(h);
  return _audioDeviceEnableMeter(p, devIdx, inOutEnaFlags );
}


const cw::io::sample_t* cw::io::audioDeviceMeters( handle_t h, unsigned devIdx, unsigned& chCntRef, unsigned inOrOutFlag )
{
  rc_t rc = kOkRC;
  io_t* p = _handleToPtr(h);
  sample_t* meterA = nullptr;
  
  audioDev_t* ad;
  if((ad = _audioDeviceIndexToRecd(p,devIdx)) == nullptr )
    rc = kInvalidArgRC;
  else
  {
    bool               inputFl = inOrOutFlag & kInFl;
    audio_group_dev_t* agd     = inputFl ? ad->iagd     : ad->oagd;

    if( agd != nullptr )
    {
      if( !cwIsFlag(agd->flags,kMeterFl) )
        rc = cwLogError(kInvalidArgRC,"The %s meters on device %s are not enabled.", inputFl ? "input" : "output", cwStringNullGuard(ad->label));
      else
      {
        meterA   = agd->meterA;
        chCntRef = agd->chCnt;
      }
    }
  }

  if( rc != kOkRC )
    rc = cwLogError(rc,"Get meters failed.");
  
  return meterA;
}


cw::rc_t cw::io::audioDeviceEnableTone( handle_t h, unsigned devIdx, unsigned inOutEnaFlags )
{
  rc_t        rc            = kOkRC;
  io_t*       p             = _handleToPtr(h);
  audioDev_t* ad            = nullptr;
  unsigned    audioBufFlags = 0;
  
  if((rc = _audioDeviceParams( p, devIdx, inOutEnaFlags, ad, audioBufFlags )) != kOkRC )
    rc = cwLogError(rc,"Enable tone failed.");
  else    
  {
    audioBufFlags += audio::buf::kToneFl;
    audio::buf::setFlag( p->audioBufH, devIdx, kInvalidIdx, audioBufFlags );        
  }
  
  return rc;
}

cw::rc_t cw::io::audioDeviceToneFlags( handle_t h, unsigned devIdx, unsigned inOrOutFlag,  bool* toneFlA, unsigned chCnt )
{

  rc_t        rc            = kOkRC;
  io_t*       p             = _handleToPtr(h);
  audioDev_t* ad            = nullptr;
  unsigned    audioBufFlags = 0;
  
  if((rc = _audioDeviceParams( p, devIdx, inOrOutFlag, ad, audioBufFlags )) != kOkRC )
    rc = cwLogError(rc,"Get tone flags failed.");
  else    
  {
    audioBufFlags += audio::buf::kToneFl;
    audio::buf::toneFlags( p->audioBufH, devIdx, audioBufFlags, toneFlA, chCnt );
  }
  
  return rc;
}

cw::rc_t cw::io::audioDeviceEnableMute( handle_t h, unsigned devIdx, unsigned inOutEnaFlags )
{
  rc_t        rc            = kOkRC;
  io_t*       p             = _handleToPtr(h);
  audioDev_t* ad            = nullptr;
  unsigned    audioBufFlags = 0;
  
  if((rc = _audioDeviceParams( p, devIdx, inOutEnaFlags, ad, audioBufFlags )) != kOkRC )
    rc = cwLogError(rc,"Enable mute failed.");
  else    
  {
    audioBufFlags += audio::buf::kMuteFl;
    audio::buf::setFlag( p->audioBufH, devIdx, kInvalidIdx, audioBufFlags );        
  }
  
  return rc;
}

cw::rc_t cw::io::audioDeviceMuteFlags( handle_t h, unsigned devIdx, unsigned inOrOutFlag,  bool* muteFlA, unsigned chCnt )
{
  rc_t        rc            = kOkRC;
  io_t*       p             = _handleToPtr(h);
  audioDev_t* ad            = nullptr;
  unsigned    audioBufFlags = 0;
  
  if((rc = _audioDeviceParams( p, devIdx, inOrOutFlag, ad, audioBufFlags )) != kOkRC )
    rc = cwLogError(rc,"Get mute flags failed.");
  else    
  {
    audioBufFlags += audio::buf::kMuteFl;
    audio::buf::muteFlags( p->audioBufH, devIdx, audioBufFlags, muteFlA, chCnt );
  }
  
  return rc;
}


cw::rc_t cw::io::audioDeviceSetGain( handle_t h, unsigned devIdx, unsigned inOrOutFlag, double gain )
{
  rc_t        rc            = kOkRC;
  io_t*       p             = _handleToPtr(h);
  audioDev_t* ad            = nullptr;
  unsigned    audioBufFlags = 0;
  
  if((rc = _audioDeviceParams( p, devIdx, inOrOutFlag, ad, audioBufFlags )) != kOkRC )
    rc = cwLogError(rc,"Set gain failed.");
  else    
  {
    audio::buf::setGain( p->audioBufH, devIdx, kInvalidIdx, audioBufFlags, gain );
  }
  
  return rc;
}

cw::rc_t cw::io::audioDeviceGain( handle_t h, unsigned devIdx, unsigned inOrOutFlag, double* gainA, unsigned chCnt )
{
  rc_t        rc            = kOkRC;
  io_t*       p             = _handleToPtr(h);
  audioDev_t* ad            = nullptr;
  unsigned    audioBufFlags = 0;
  
  if((rc = _audioDeviceParams( p, devIdx, inOrOutFlag, ad, audioBufFlags )) != kOkRC )
    rc = cwLogError(rc,"Get gain failed.");
  else    
  {
    audioBufFlags += audio::buf::kMuteFl;
    audio::buf::gain( p->audioBufH, devIdx, audioBufFlags, gainA, chCnt );
  }
  
  return rc;
}

unsigned cw::io::audioGroupCount( handle_t h )
{
  io_t* p = _handleToPtr(h);
  return p->audioGroupN;
}

unsigned cw::io::audioGroupLabelToIndex(  handle_t h, const char* label )
{
  audioGroup_t* ag;
  io_t*         p = _handleToPtr(h);

  if((ag = _audioGroupFromLabel(p,label)) == nullptr )
    return kInvalidIdx;

  return ag->msg.groupIndex; 
}
  
const char* cw::io::audioGroupLabel( handle_t h, unsigned groupIdx )
{
  audioGroup_t* ag;
  io_t*         p = _handleToPtr(h);
  if((ag = _audioGroupFromIndex( p, groupIdx )) != nullptr )
    return ag->msg.label;
  return nullptr;
}

bool cw::io::audioGroupIsEnabled( handle_t h, unsigned groupIdx )
{
  audioGroup_t* ag;
  io_t*         p = _handleToPtr(h);
  if((ag = _audioGroupFromIndex( p, groupIdx )) != nullptr )
    return ag->enableFl;
  return false;
}

unsigned cw::io::audioGroupUserId( handle_t h, unsigned groupIdx )
{
  audioGroup_t* ag;
  io_t*         p = _handleToPtr(h);
  if((ag = _audioGroupFromIndex( p, groupIdx )) != nullptr )
    return ag->msg.userId;
  return kInvalidIdx;
}

cw::rc_t cw::io::audioGroupSetUserId( handle_t h, unsigned groupIdx, unsigned userId )
{
  audioGroup_t* ag;
  io_t*         p = _handleToPtr(h);
  if((ag = _audioGroupFromIndex( p, groupIdx )) != nullptr )
    ag->msg.userId = userId;
  
  return kInvalidArgRC;
}

double cw::io::audioGroupSampleRate(    handle_t h, unsigned groupIdx )
{
  audioGroup_t* ag;
  io_t*         p = _handleToPtr(h);
  if((ag = _audioGroupFromIndex( p, groupIdx )) != nullptr )
    return ag->msg.srate;
  return 0;
}

unsigned cw::io::audioGroupDspFrameCount( handle_t h, unsigned groupIdx )
{
  audioGroup_t* ag;
  io_t*         p = _handleToPtr(h);
  if((ag = _audioGroupFromIndex( p, groupIdx )) != nullptr )
    return ag->msg.dspFrameCnt;
  return 0;
}

cw::rc_t  cw::io::audioGroupReconfigure( handle_t h, unsigned groupIdx, double srate, unsigned dspFrameN )
{
  rc_t rc = kOkRC;
  audioGroup_t* ag = nullptr;
  
  io_t*  p = _handleToPtr(h);

  // locate the group record
  if((ag = _audioGroupFromIndex( p, groupIdx )) == nullptr )
    goto errLabel;

  // if the parameters are not changing then there is nothing to do
  if( ag->msg.dspFrameCnt == dspFrameN && ag->msg.srate == srate )
    goto errLabel;
  
  // change the parameters in the group record
  ag->msg.dspFrameCnt = dspFrameN;
  ag->msg.srate       = srate;

  // stop the audio sub-system
  if((rc = _audioDeviceStartStop(p,false)) != kOkRC )
    goto errLabel;
   
  // TODO: be sure the audio subsystem is really stopped
    
  // for each audio device
  for(unsigned i=0; i<p->audioDevN; ++i)
  {
    audioDev_t* ad       = p->audioDevA + i;

    // if this devices in-group/out-group was reconfigured
    bool        iGroupFl = ad->iGroup != nullptr && ad->iGroup->msg.groupIndex == groupIdx;
    bool        oGroupFl = ad->oGroup != nullptr && ad->oGroup->msg.groupIndex == groupIdx;
    
    if( iGroupFl || oGroupFl )
    {
      // reconfigure the device with the updated srate and framesPerCycle
      if((rc = _audioDeviceConfigure(p, ad, ad->oGroup, ad->oGroup, ad->cycleCnt, ad->framesPerCycle )) != kOkRC )
        goto errLabel;

      cwLogInfo("The audio device: '%s' was reconfigured srate=%f dspFrameCnt:%i.",cwStringNullGuard(ad->label), srate,dspFrameN);

      
    }
  }

  // restart the audio sub-system
  if((rc = _audioDeviceStartStop(p,true)) != kOkRC )
    goto errLabel;
  
errLabel:
  if( rc != kOkRC )
    rc = cwLogError(rc,"Audio group reconfiguration failed.");
    
  return rc;  
}


unsigned cw::io::audioGroupDeviceCount( handle_t h, unsigned groupIdx, unsigned inOrOutFl )
{
  audioGroup_t* ag;
  io_t*         p = _handleToPtr(h);
  unsigned      n = 0;
  
  if((ag = _audioGroupFromIndex( p, groupIdx )) != nullptr )
  {
    audio_group_dev_t* agd = cwIsFlag(inOrOutFl,kInFl) ? ag->msg.iDevL : ag->msg.oDevL;
    for(; agd!=nullptr; agd=agd->link)
      ++n;
  }
  
  return n;  
}

unsigned cw::io::audioGroupDeviceIndex( handle_t h, unsigned groupIdx, unsigned inOrOutFl, unsigned groupDevIdx )
{
  audioGroup_t* ag;
  io_t*         p = _handleToPtr(h);
  unsigned      n = 0;
  
  if((ag = _audioGroupFromIndex( p, groupIdx )) != nullptr )
  {
    audio_group_dev_t* agd = cwIsFlag(inOrOutFl,kInFl) ? ag->msg.iDevL : ag->msg.oDevL;
    
    for(; agd!=nullptr; agd=agd->link)
    {
      if( n == groupDevIdx )
        return agd->devIdx;
      ++n;
    }
  }

  cwLogError(kInvalidIdRC,"The audio group device index '%i' could found on group index: '%i' .",groupDevIdx,groupIdx);

              
  return kInvalidIdx;    
}


//----------------------------------------------------------------------------------------------------------
//
// Socket
//

bool     cw::io::socketIsEnabled(    handle_t h )
{
  io_t* p = _handleToPtr(h);
  return p->sockN != 0;
}

unsigned cw::io::socketCount(        handle_t h )
{
  io_t* p = _handleToPtr(h);
  return p->sockN;
}

unsigned cw::io::socketLabelToIndex( handle_t h, const char* label )
{
  io_t* p = _handleToPtr(h);
  for(unsigned i=0; i<p->sockN; ++i)
    if( textCompare(p->sockA[i].label,label) == 0 )
      return i;

  cwLogError(kInvalidArgRC,"'%s' is not a valid socket label.", cwStringNullGuard(label));
  return kInvalidIdx;
}

unsigned cw::io::socketUserId( handle_t h, unsigned sockIdx )
{
  io_t* p = _handleToPtr(h);
  socket_t* s;
  if((s = _socketIndexToRecd(p,sockIdx)) == nullptr )
    return kInvalidId;

  return s->userId;
}

cw::rc_t cw::io::socketSetUserId( handle_t h, unsigned sockIdx, unsigned userId )
{
  io_t* p = _handleToPtr(h);
  socket_t* s;
  if((s = _socketIndexToRecd(p,sockIdx)) == nullptr )
    return kInvalidArgRC;

  s->userId = userId;
  return kOkRC;  
}

const char*  cw::io::socketLabel( handle_t h, unsigned sockIdx )
{
  io_t* p = _handleToPtr(h);
  socket_t* s;
  if((s = _socketIndexToRecd(p,sockIdx)) == nullptr )
    return nullptr;

  return s->label;
}

const char*  cw::io::socketHostName(     handle_t h, unsigned sockIdx )
{
  io_t* p = _handleToPtr(h);
  socket_t* s;
  if((s = _socketIndexToRecd(p,sockIdx)) == nullptr )
    return nullptr;

  return sock::hostName( p->sockH, s->userId );
}

const char*  cw::io::socketIpAddress(    handle_t h, unsigned sockIdx )
{
  io_t* p = _handleToPtr(h);
  socket_t* s;
  if((s = _socketIndexToRecd(p,sockIdx)) == nullptr )
    return nullptr;

  return sock::ipAddress( p->sockH, s->userId );
}

unsigned  cw::io::socketInetAddress(  handle_t h, unsigned sockIdx )
{
  io_t* p = _handleToPtr(h);
  socket_t* s;
  if((s = _socketIndexToRecd(p,sockIdx)) == nullptr )
    return 0;

  return sock::inetAddress( p->sockH, s->userId );
}

cw::sock::portNumber_t cw::io::socketPort( handle_t h, unsigned sockIdx )
{
  io_t* p = _handleToPtr(h);
  socket_t* s;
  if((s = _socketIndexToRecd(p,sockIdx)) == nullptr )
    return sock::kInvalidPortNumber;

  return sock::port( p->sockH, s->userId );  
}

cw::rc_t cw::io::socketPeername( handle_t h, unsigned sockIdx, struct sockaddr_in* addr )
{
  io_t* p = _handleToPtr(h);
  socket_t* s;
  if((s = _socketIndexToRecd(p,sockIdx)) == nullptr )
    return kInvalidArgRC;

  return sock::peername( p->sockH, s->userId, addr );
}

bool     cw::io::socketIsConnected(  handle_t h, unsigned sockIdx )
{ 
  io_t* p = _handleToPtr(h);
  socket_t* s;
  if((s = _socketIndexToRecd(p,sockIdx)) == nullptr )
    return sock::isConnected( p->sockH, s->userId );

  return false;
}

cw::rc_t cw::io::socketSend(    handle_t h, unsigned sockIdx, unsigned connId, const void* data, unsigned dataByteCnt )
{
  io_t* p = _handleToPtr(h);
  socket_t* s;
  if((s = _socketIndexToRecd(p,sockIdx)) == nullptr )
    return kInvalidArgRC;
  return sock::send(p->sockH, s->userId, connId, data, dataByteCnt );
}

cw::rc_t cw::io::socketSend(    handle_t h, unsigned sockIdx, const void* data, unsigned dataByteCnt, const struct sockaddr_in* remoteAddr )
{
  io_t* p = _handleToPtr(h);
  socket_t* s;
  if((s = _socketIndexToRecd(p,sockIdx)) == nullptr )
    return kInvalidArgRC;
  return sock::send(p->sockH, s->userId, data, dataByteCnt, remoteAddr );
}

cw::rc_t cw::io::socketSend(    handle_t h, unsigned sockIdx, const void* data, unsigned dataByteCnt, const char* remoteAddr, sock::portNumber_t remotePort )
{
  io_t* p = _handleToPtr(h);
  socket_t* s;
  if((s = _socketIndexToRecd(p,sockIdx)) == nullptr )
    return kInvalidArgRC;
  return sock::send(p->sockH, s->userId, data, dataByteCnt, remoteAddr, remotePort );
}

//----------------------------------------------------------------------------------------------------------
//
// WebSocket
//



//----------------------------------------------------------------------------------------------------------
//
// UI
//

bool cw::io::uiIsEnabled( handle_t h )
{
  io_t* p = _handleToPtr(h);
  return p->wsUiH.isValid();
}

unsigned    cw::io::parentAndNameToAppId( handle_t h, unsigned parentAppId, const char* eleName )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    return ui::parentAndNameToAppId(uiH,parentAppId,eleName);
  return kInvalidId;
}

unsigned    cw::io::parentAndNameToUuId(  handle_t h, unsigned parentAppId, const char* eleName )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    return ui::parentAndNameToUuId(uiH,parentAppId,eleName);
  return kInvalidId;
}

unsigned    cw::io::parentAndAppIdToUuId( handle_t h, unsigned parentAppId, unsigned appId )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    return ui::parentAndAppIdToUuId(uiH,parentAppId,appId);
  return kInvalidId;
}

unsigned    cw::io::uiFindElementAppId(  handle_t h, unsigned uuId )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    return ui::findElementAppId(uiH, uuId );
  return kInvalidId;  
}

unsigned    cw::io::uiFindElementUuId( handle_t h, const char* eleName, unsigned chanId )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    return ui::findElementUuId(uiH, eleName, chanId );
  return kInvalidId;  
}

unsigned    cw::io::uiFindElementUuId( handle_t h, unsigned appId, unsigned chanId )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    return ui::findElementUuId(uiH, kInvalidId, appId, chanId );
  return kInvalidId;  
}

unsigned    cw::io::uiFindElementUuId( handle_t h, unsigned parentUuId, const char* eleName, unsigned chanId )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    return ui::findElementUuId(uiH, parentUuId, eleName, chanId );
  return kInvalidId;  
}

unsigned    cw::io::uiFindElementUuId( handle_t h, unsigned parentUuId, unsigned appId, unsigned chanId )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    return ui::findElementUuId(uiH, parentUuId, appId, chanId );
  return kInvalidId;  
}


cw::rc_t cw::io::uiCreateFromObject( handle_t h, const object_t* o, unsigned parentUuId, unsigned chanId, const char* eleName)
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc = ui::createFromObject(uiH,o,parentUuId,chanId,eleName);
  return rc;
}

cw::rc_t cw::io::uiCreateFromFile(   handle_t h, const char* fn,    unsigned parentUuId, unsigned chanId)
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc = ui::createFromFile(uiH,fn,parentUuId,chanId);
  return rc;
}

cw::rc_t cw::io::uiCreateFromText(   handle_t h, const char* text,  unsigned parentUuId, unsigned chanId)
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc  = ui::createFromText(uiH,text,parentUuId, chanId);
  return rc;
}

cw::rc_t cw::io::uiCreateFromRsrc(   handle_t h, const char* label,  unsigned parentUuId, unsigned chanId)
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc  = ui::createFromRsrc(uiH,label,parentUuId, chanId);
  return rc;
}


cw::rc_t cw::io::uiCreateDiv(        handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc  = ui::createDiv(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title);
  return rc;  
}

cw::rc_t cw::io::uiCreateLabel(      handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc  = ui::createLabel(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title);
  return rc;  
}

cw::rc_t cw::io::uiCreateButton(     handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc  = ui::createButton(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title);
  return rc;  
}

cw::rc_t cw::io::uiCreateCheck(      handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc  = ui::createCheck(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title);
  return rc;  
}

cw::rc_t cw::io::uiCreateCheck(      handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title, bool value )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc  = ui::createCheck(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title,value);
  return rc;  
}

cw::rc_t cw::io::uiCreateSelect(     handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc  = ui::createSelect(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title);
  return rc;  
}

cw::rc_t cw::io::uiCreateOption(     handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc  = ui::createOption(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title);
  return rc;  
}

cw::rc_t cw::io::uiCreateStrDisplay( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc  = ui::createStrDisplay(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title);
  return rc;  
}

cw::rc_t cw::io::uiCreateStrDisplay( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title, const char* value )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc  = ui::createStrDisplay(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title,value);
  return rc;  
}

cw::rc_t cw::io::uiCreateStr(        handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc  = ui::createStr(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title);
  return rc;  
}

cw::rc_t cw::io::uiCreateStr(        handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title, const char* value )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc  = ui::createStr(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title,value);
  return rc;  
}

cw::rc_t cw::io::uiCreateNumbDisplay(handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title, unsigned decPl )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc  = ui::createNumbDisplay(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title,decPl);
  return rc;  
}

cw::rc_t cw::io::uiCreateNumbDisplay(handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title, unsigned decPl, double value )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc  = ui::createNumbDisplay(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title,value);
  return rc;  
}

cw::rc_t cw::io::uiCreateNumb(       handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title, double minValue, double maxValue, double stepValue, unsigned decPl )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc  = ui::createNumb(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title,minValue,maxValue,stepValue,decPl);
  return rc;  
}

cw::rc_t cw::io::uiCreateNumb(       handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title, double minValue, double maxValue, double stepValue, unsigned decPl, double value )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc  = ui::createNumb(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title,minValue,maxValue,stepValue,decPl,value);
  return rc;  
}

cw::rc_t cw::io::uiCreateProg(       handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title, double minValue, double maxValue )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc  = ui::createProg(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title,minValue,maxValue);
  return rc;  
}
  
cw::rc_t cw::io::uiCreateProg(       handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title, double minValue, double maxValue, double value )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc  = ui::createProg(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title,minValue,maxValue,value);
  return rc;  
}

cw::rc_t cw::io::uiCreateLog(       handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc  = ui::createLog(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title);
  return rc;  
}

cw::rc_t cw::io::uiCreateVList(       handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc  = ui::createVList(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title);
  return rc;  
}

cw::rc_t cw::io::uiCreateHList(       handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc  = ui::createHList(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title);
  return rc;  
}

cw::rc_t cw::io::uiSetNumbRange( handle_t h, unsigned uuId, double minValue, double maxValue, double stepValue, unsigned decPl, double value )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc = ui::setNumbRange(uiH,uuId,minValue,maxValue,stepValue,decPl,value);
  return rc;
}

cw::rc_t cw::io::uiSetProgRange( handle_t h, unsigned uuId, double minValue, double maxValue, double value )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc = ui::setProgRange(uiH,uuId,minValue,maxValue,value);
  return rc;
}

cw::rc_t cw::io::uiSetLogLine(     handle_t h, unsigned uuId, const char* text )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc = ui::setLogLine(uiH,uuId,text);
  return rc;
}

cw::rc_t cw::io::uiEmptyParent(  handle_t h, unsigned uuId)
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc = ui::emptyParent(uiH,uuId);
  return rc;
}

    
cw::rc_t cw::io::uiSetClickable(   handle_t h, unsigned uuId, bool clickableFl )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc = ui::setClickable(uiH,uuId,clickableFl);
  return rc;
}

cw::rc_t cw::io::uiClearClickable( handle_t h, unsigned uuId )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc = ui::clearClickable(uiH,uuId);
  return rc;
}

bool cw::io::uiIsClickable(    handle_t h, unsigned uuId )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    return ui::isClickable(uiH,uuId);
  return false;
}
    
cw::rc_t cw::io::uiSetSelect(      handle_t h, unsigned uuId, bool enableFl )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc = ui::setSelect(uiH,uuId,enableFl);
  return rc;
}

cw::rc_t cw::io::uiClearSelect(    handle_t h, unsigned uuId )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc = ui::clearSelect(uiH,uuId);
  return rc;
}

bool cw::io::uiIsSelected(     handle_t h, unsigned uuId )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    return ui::isSelected(uiH,uuId);
  return false;
}

cw::rc_t cw::io::uiSetVisible(     handle_t h, unsigned uuId, bool enableFl )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc = ui::setVisible(uiH,uuId,enableFl);
  return rc;
}

cw::rc_t cw::io::uiClearVisible(   handle_t h, unsigned uuId )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc = ui::clearVisible(uiH,uuId);
  return rc;

}

bool cw::io::uiIsVisible(      handle_t h, unsigned uuId )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    return ui::isVisible(uiH,uuId);
  return false;
}
    
cw::rc_t cw::io::uiSetEnable(      handle_t h, unsigned uuId, bool enableFl )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc = ui::setEnable(uiH,uuId,enableFl);
  return rc;
}

cw::rc_t cw::io::uiClearEnable(    handle_t h, unsigned uuId )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc = ui::clearEnable(uiH,uuId);
  return rc;

}

bool cw::io::uiIsEnabled(      handle_t h, unsigned uuId )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    return ui::isEnabled(uiH,uuId);
  return false;
}

cw::rc_t cw::io::uiSetOrderKey(    handle_t h, unsigned uuId, int orderKey )
{
  rc_t rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc = ui::setOrderKey(uiH,uuId,orderKey);
  return rc;
}

int  cw::io::uiGetOrderKey(    handle_t h, unsigned uuId )
{
  rc_t rc;
  ui::handle_t uiH;
  int orderKey = 0;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    orderKey = ui::getOrderKey(uiH,uuId);
  return orderKey;
}

cw::rc_t cw::io::uiSetScrollTop(   handle_t h, unsigned uuId )
{
  rc_t rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc = ui::setScrollTop(uiH,uuId);
  return rc;
}

cw::rc_t cw::io::uiSetTitle( handle_t h, unsigned uuId, const char* title )
{
  rc_t rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc = ui::setTitle(uiH,uuId,title);
  return rc;
}

cw::rc_t    cw::io::uiSetBlob(   handle_t h, unsigned uuId, const void* blob, unsigned blobByteN )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc = ui::setBlob( uiH, uuId, blob, blobByteN );
  return rc;
}

const void* cw::io::uiGetBlob(   handle_t h, unsigned uuId, unsigned& blobByteN_Ref )
{
  ui::handle_t uiH;
  if( _handleToUiHandle(h,uiH) == kOkRC )
    return ui::getBlob( uiH, uuId, blobByteN_Ref );
  
  blobByteN_Ref = 0;
  return nullptr;
}

cw::rc_t   cw::io::uiGetBlob(   handle_t h, unsigned uuId, void* buf, unsigned& bufByteN_Ref )
{
  unsigned bN = 0;
  const void* b = uiGetBlob(h,uuId,bN);
  if( bN > bufByteN_Ref )
  {
    bufByteN_Ref = 0;
    return cwLogError(kBufTooSmallRC,"UI blob buffer is too small.");
  }
  memcpy(buf,b,bN);
  bufByteN_Ref = bN;
  return kOkRC;
}


cw::rc_t cw::io::uiClearBlob( handle_t h, unsigned uuId )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc = ui::clearBlob( uiH, uuId );
  return rc;
}


cw::rc_t cw::io::uiRegisterAppIdMap(  handle_t h, const ui::appIdMap_t* map, unsigned mapN )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc  = ui::registerAppIdMap(uiH,map,mapN);
  return rc;  
}

cw::rc_t cw::io::uiSendValue(   handle_t h, unsigned uuId, bool value )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc = ui::sendValueBool(uiH,  uuId, value );
  return rc;
}

cw::rc_t cw::io::uiDestroyElement( handle_t h, unsigned uuId )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc = ui::destroyElement(uiH,  uuId );
  return rc;
}


cw::rc_t cw::io::uiSendValue(    handle_t h, unsigned uuId, int value )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc = ui::sendValueInt(uiH,  uuId, value );
  return rc;
}

cw::rc_t cw::io::uiSendValue(   handle_t h, unsigned uuId, unsigned value )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc = ui::sendValueUInt(uiH,  uuId, value );
  return rc;
}

cw::rc_t cw::io::uiSendValue(  handle_t h, unsigned uuId, float value )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc = ui::sendValueFloat(uiH,  uuId, value );
  return rc;
}

cw::rc_t cw::io::uiSendValue( handle_t h, unsigned uuId, double value )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc = ui::sendValueDouble(uiH,  uuId, value );
  return rc;
}

cw::rc_t cw::io::uiSendValue( handle_t h, unsigned uuId, const char* value )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc = ui::sendValueString(uiH,  uuId, value );
  return rc;
}

cw::rc_t cw::io::uiSendMsg( handle_t h, const char* msg )
{
  rc_t         rc;
  ui::handle_t uiH;
  if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
    rc = ui::sendMsg(uiH, msg);
  return rc;
}

void cw::io::latency_measure_setup(handle_t h)
{
  io_t* p = _handleToPtr(h);
  p->latency_meas_enable_fl                = true;
  p->latency_meas_thresh_db  = -50;
  p->latency_meas_thresh_lin = pow(10.0,p->latency_meas_thresh_db/20.0);
  p->latency_meas_result.note_on_input_ts  = {};
  p->latency_meas_result.note_on_output_ts = {};
  p->latency_meas_result.audio_in_ts       = {};
  p->latency_meas_result.audio_out_ts      = {};
  p->latency_meas_result.audio_in_rms_max  = 0;
  p->latency_meas_result.audio_out_rms_max = 0;

  if( p->midiH.isValid() )
    latency_measure_reset(p->midiH);  
}

cw::io::latency_meas_result_t cw::io::latency_measure_result(handle_t h)
{
  io_t* p = _handleToPtr(h);

  if( p->midiH.isValid() )
  {
    midi::device::latency_meas_combined_result_t r = latency_measure_result(p->midiH);

    p->latency_meas_result.note_on_input_ts  = r.alsa_dev.note_on_input_ts;
    p->latency_meas_result.note_on_output_ts = r.alsa_dev.note_on_output_ts;
  }
  
  return p->latency_meas_result;
}

void cw::io::latency_measure_report(handle_t h)
{
  io_t* p = _handleToPtr(h);  
  latency_meas_result_t r = latency_measure_result(h);
  unsigned t0,t1;

  if( r.note_on_input_ts.tv_nsec )
  {
    if( r.note_on_output_ts.tv_nsec )
    {
      t0 = time::elapsedMicros(r.note_on_input_ts,r.note_on_output_ts);
      printf("midi in  - midi out:  %6i\n",t0);
    }

    if( r.audio_in_ts.tv_nsec )
    {
      t0 = time::elapsedMicros(r.note_on_output_ts,r.audio_in_ts);
      t1 = time::elapsedMicros(r.note_on_input_ts,r.audio_in_ts);
      printf("midi out - audio in:  %6i %6i\n",t0,t1);
    }

    if( r.audio_out_ts.tv_nsec )
    {
      t0 = time::elapsedMicros(r.audio_in_ts,r.audio_out_ts);
      t1 = time::elapsedMicros(r.note_on_input_ts,r.audio_out_ts);
      printf("audio in - audio out: %6i %6i\n",t0,t1);
    }

  }

  printf("Thresh: %f %f db\n",p->latency_meas_thresh_db,p->latency_meas_thresh_lin);
  if( r.audio_in_rms_max != 0 )
    printf("audio in max:%f %f dB\n", r.audio_in_rms_max, 20.0*log10(r.audio_in_rms_max));
  
  if( r.audio_out_rms_max != 0 )
    printf("audio out max:%f %f dB\n", r.audio_out_rms_max, 20.0*log10(r.audio_out_rms_max));
  
}


void cw::io::uiReport( handle_t h )
{
  ui::handle_t uiH;
  if(_handleToUiHandle(h,uiH) == kOkRC )
    ui::report(uiH);
}

void cw::io::uiRealTimeReport( handle_t h )
{
  ui::ws::handle_t uiH;
  if(_handleToWsUiHandle(h,uiH) == kOkRC )
    ui::ws::realTimeReport(uiH);
}

