#include <FL/Fl.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Menu_Item.H>
#include <Fl/Fl_Menu_Bar.H>
#include <Fl/Fl_Button.H>
#include <Fl/Fl_Check_Button.H>
#include <Fl/Fl_Text_Buffer.H>
#include <Fl/Fl_Text_Display.H>
#include <Fl/Fl_Box.H>
#include <Fl/Fl_Tabs.H>
#include <Fl/Fl_File_Chooser.H>
#include <Fl/Fl_Value_Slider.H>
#include <Fl/Fl_Menu_Button.H>
#include <Fl/Fl_Select_Browser.H>
#include <Fl/Fl_Value_Input.H>
#include <Fl/Fl_Progress.H>

#include "Fl_Vert_Progress.h"
#include "Fl_File_Btn.h"
#include "Fl_Splitter.h"

#include "cmGlobal.h"
#include "cmFloatTypes.h"
#include "cmRpt.h"
#include "cmErr.h"
#include "cmCtx.h"
#include "cmMem.h"
#include "cmMallocDebug.h"
#include "cmLinkedHeap.h"
#include "cmText.h"
#include "cmSymTbl.h"
#include "cmJson.h"
#include "cmFileSys.h"
#include "cmPrefs.h"
#include "cmAudioFile.h"
#include "cmThread.h"
#include "cmSymTbl.h"
#include "cmProcTest.h"

#include "cmDspValue.h"
#include "cmMsgProtocol.h"
#include "cmAudDspIF.h"
#include "cmAudDspLocal.h"
#include "cmAudioFile.h"
#include "cmAudioFileMgr.h"
#include "cmTime.h"
#include "cmMidi.h"
#include "cmMidiFile.h"
#include "cmTimeLine.h"
#include "cmScore.h"
#include "cmTakeSeqBldr.h"
#include "cmdIf.h"
#include "tlCtl.h"
#include "kcApp.h"


#include "cmGr.h"
#include "cmGrDevCtx.h"
#include "cmGrPlot.h"
#include "cmGrPage.h"
#include "cmGrFltk.h"
#include "cmGrFltk.h"
#include "gvHashFunc.h"
#include "cmGrTksbFltk.h"
#include "cmGr2dFltk.h"


#define TIMER_PERIOD (1.0/20.0)   // 50ms


kcApp::kcApp(cmCtx_t* ctx, cmPrH_t prH, cmTsMp1cH_t printqH, int w, int h, const cmChar_t* title, cmAiH_t aiH, int argc, char* argv[] )
  : Fl_Double_Window(w, h,title),
    _ctx(ctx),_prH(prH),_aiH(aiH),
    _menu(NULL),_con(NULL),_tabs(NULL),_mstr_grp(NULL),
    _as_btn(NULL),_ai_btn(NULL),_ao_btn(NULL),_pgm_btn(NULL),
    _ss_btn(NULL),_sr_btn(NULL),_ena_chk(NULL),
    _pageList(NULL),_ctlList(NULL),
    _stopTimerFl(false),_newPageFl(true),
    _incrColW(0),_colW(0),_horzBordFl(false),_horzBord(0),
    _ssCnt(0),_ssArray(0),_ssPhase(0),_ssUpdateFl(false),
    _printqH(printqH),_printFl(0),
    _tlCtl(NULL)
{
  // install a callback to cleanup when the app window closes
  // (the btn callbacks and _getApp() rely on a pointer to 'this' being found in kcApp.user_data())
  callback(_s_callback,this);

  // the main window is divided between the menu bar on top
  // and a horizontal splitter on the bottom
  begin();
  _createMenu(w,kMenuH);
  end();

  // Create a tab view 
  add(_tabs = new Fl_Tabs(this->x(),this->y()+kMenuH,this->w(),this->h()-kMenuH));
  _tabs->callback(_s_tab_cb,this);
  _tabs->end();

  int tx=x(),ty=y(),th=this->h(),tw=this->w();
  _tabs->client_area(tx,ty,tw,th);

  // Create the 'Setup' tab group
  Fl_Group* setup_grp = new Fl_Group(tx,ty,tw,th,"Setup");
  _tabs->add(setup_grp);
  _createSetupDlg(setup_grp);

  // Create the console window
  Fl_Group* con_grp = new Fl_Group(tx,ty,tw,th,"Console");
  _tabs->add(con_grp);
  Fl_Text_Buffer* buf = new Fl_Text_Buffer();
  _con = new Fl_Text_Display(tx,ty,tw,th);
  _con->buffer(buf);
  con_grp->add(_con);

  // Create the master group
  _tabs->add(_mstr_grp  = new Fl_Group(tx,ty,tw,th,"Master"));

  // Create an empty tab group and make it resizable 
  // to prevent the other tab groups from being resizable.
  Fl_Group* wdgt = new Fl_Group(tx,ty+30,1,1);
  _tabs->add(wdgt);
  _tabs->resizable(wdgt);  // make other tabs non-resizable

  // make the splitter the resizable group element (thereby making the menu non-resizable).
  // see:http://fltk.org/articles.php?L415+I0+T+M1000+P1
  resizable(_tabs); 
  show(argc, argv);

  // direct all output to the console window
  cmRptSetup(&_ctx->rpt,_s_print,_s_print, this);
  cmTsMp1cSetCbFunc(_printqH, _s_print_queue_cb, this );

  // install a timer to check for messages from the engine
  Fl::add_timeout(TIMER_PERIOD,_s_status_timeout_cb,this);

}

kcApp::~kcApp()
{
  _clearCtlList(true);
  _clearSsArray();
}


void kcApp::resize(int x, int y, int w, int h)
{
  Fl_Double_Window::resize(x, y, w, h);
  cmPrefsPathSetInt(_prH,"appWndW",w);
  cmPrefsPathSetInt(_prH,"appWndH",h);
  cmPrefsPathSetInt(_prH,"appWndX",x);
  cmPrefsPathSetInt(_prH,"appWndY",y);

}

void kcApp::tlCtlNewTimeLineFile( tlCtl* tlCtl, const cmChar_t* fn )
{}

void kcApp::tlCtlNewScoreFile(    tlCtl* tlCtl, const cmChar_t* fn )
{}


void kcApp::_createSetupDlg( Fl_Group* grp )
{
  int ctl_width = 200;
  int vBord = 5;
  int xx = grp->x() + 5;
  int yy = grp->y() + vBord;

  grp->begin();

  Fl_Button* btn = new Fl_Button(xx,yy,200,kMenuH,"Audio Device Report");
  btn->callback( _s_btn_cb, kAudDevRptBtnId );
  yy += btn->h() + vBord;

  _as_btn  = new Fl_Menu_Button(xx,yy,ctl_width,kMenuH,"Audio System Cfg");
  _as_btn->callback( _s_btn_cb, kAudioSysCfgBtnId);
  yy += _as_btn->h() + vBord;

  _ai_btn  = new Fl_Menu_Button(xx,yy,ctl_width,kMenuH,"Audio Input Devices");
  _ai_btn->callback( _s_btn_cb, kInAudDevBtnId);
  yy += _ai_btn->h() + vBord;

  _ao_btn  = new Fl_Menu_Button(xx,yy,ctl_width,kMenuH,"Audio Output Devices");
  _ao_btn->callback( _s_btn_cb, kOutAudDevBtnId);
  yy += _ao_btn->h() + vBord;

  _pgm_btn  = new Fl_Menu_Button(xx,yy,ctl_width,kMenuH,"Programs");
  _pgm_btn->callback( _s_btn_cb, kPgmBtnId);
  yy += _pgm_btn->h() + vBord;

  _ss_btn  = new Fl_Menu_Button(xx,yy,ctl_width,kMenuH,"Sub-System");
  _ss_btn->callback( _s_btn_cb, kSubSystemIdxBtnId);
  yy += _ss_btn->h() + vBord;

  _sr_btn = new Fl_Menu_Button(xx,yy,ctl_width,kMenuH,"Sample Rate");
  _sr_btn->callback( _s_btn_cb, kSrateBtnId);
  _sr_btn->add("44100",0,NULL,(void*)44100,0);
  _sr_btn->add("48000",0,NULL,(void*)48000,0);
  _sr_btn->add("96000",0,NULL,(void*)96000,0);
     
  yy += _sr_btn->h() + vBord;

  btn = new Fl_Button(xx,yy,ctl_width,kMenuH,"Test");
  btn->callback( _s_btn_cb, kTestBtnId );
  yy += btn->h() + vBord;

  btn = new Fl_Button(xx,yy,200,kMenuH,"Print program");
  btn->callback( _s_btn_cb, kPrintPgmBtnId );
  yy += btn->h() + vBord;


  _ena_chk = new Fl_Check_Button(xx,yy,ctl_width,kMenuH,"Enable Audio");
  _ena_chk->callback( _s_btn_cb, kEnableBtnId );
  yy += _ena_chk->h() + vBord;

  // place this ctrl in the lower left corner to prevent the other controls from resizing
  Fl_Box* bx = new Fl_Box(FL_NO_BOX,xx+ctl_width,yy,10,10,NULL);
  grp->resizable(bx);

  grp->end();
}


void kcApp::_createMenu(int w, int h)
{
  Fl_Menu_Item items[] =
  {
    { "kc", 0, 0, 0, FL_SUBMENU },
    { "&About", FL_COMMAND + 'a', (Fl_Callback*)_s_menu_cb, (void*)kAboutMenuId },
    { "&Quit",  FL_COMMAND + 'q', (Fl_Callback*)_s_menu_cb, (void*)kQuitMenuId },
    { 0 },
    { 0 }
  };
  
  _menu = new Fl_Menu_Bar(0,0,w,h);
  _menu->copy(items);
  this->add(_menu);

}

kcApp::page_t*  kcApp::_createPage( const char* title )
{
  int tx,ty,th,tw;
  _tabs->client_area(tx,ty,tw,th);

  page_t* page = new page_t;
  page->link = _pageList;
  _pageList = page;

  _newPageFl = true;
    
  // Create the 'Controls' tab group
  _tabs->begin();
  page->grp = new Fl_Group(tx,ty,tw,th,title);  
  page->grp->begin();
  Fl_Box* bx = new Fl_Box(FL_NO_BOX,page->grp->x()+page->grp->w(),page->grp->y()+page->grp->h(),1,1,NULL);
  page->grp->resizable(bx); 
  page->grp->end();
  _tabs->end();

  return page;
}

// return the count of engine ctls
unsigned kcApp::_getCtlCount() const
{
  unsigned cnt = 0;
  ctl_t* cp = _ctlList;
  for(; cp!=NULL; cp=cp->linkPtr)
    ++cnt;
  return cnt;
}

// delete all engine ctls - (except maybe the master ctrl's)
void kcApp::_clearCtlList(bool mstrFl)
{
  ctl_t* ncl = NULL;
  ctl_t* cp = _ctlList;

  while( cp != NULL )
  {
    ctl_t* np = cp->linkPtr;

    // delete the ctrl unless it is a master ctl and the mstrFl is set
    if( cp->mstrFl==false || mstrFl )
    {
      delete cp;
    }
    else
    {
      // append the master ctl to the new ctl list
      cp->linkPtr = ncl;
      ncl         = cp;
    }
    
    cp = np;
  }

  // clear the page groups and pages
  page_t* pg = _pageList;
  while( pg != NULL )
  {
    page_t* np = pg->link;
    pg->grp->clear();
    delete pg->grp;
    delete pg;
    pg = np;
  }
  _pageList = NULL; 
  
  //ctl_grp->clear();
   _ctlList = ncl;     // make the new ctl list the current ctl list

  if( mstrFl )
    _mstr_grp->clear();
 
  _tlCtl = NULL;
}


void kcApp::_clearSsArray()
{
  if( _ssArray != NULL )
  {
    delete[] _ssArray;
    _ssArray = NULL;
  }

  _ssCnt = 0;

}

// find a engine ui ctl given a dsp instance id
kcApp::ctl_t* kcApp::_findCtl( unsigned instId, unsigned asSubIdx, unsigned mstrFl )
{
  ctl_t* cp = _ctlList;
  for(; cp!=NULL; cp=cp->linkPtr)
    if( cp->instId == instId && cp->asSubIdx==asSubIdx && cp->mstrFl==mstrFl )
      return cp;

  //printf("not found:0x%x ssi:%i mstr:%i\n",instId,asSubIdx,mstrFl);

  return NULL;
}

// calc. the position and size of a new ctl being created
// at the request of the engine
void kcApp::_getNewCtlPosn( const cmDspUiHdr_t* m, int& x, int& y, int& w, int& h )
{
  enum { kPageH=700, kCtlH=30, kCtlW=200, kHBord=2, kVBord=2 };

  // if this is control is being placed in the 'master' group
  if( m->uiId == cmInvalidId )
  {

    //unsigned devIdx = cmAudioSysUiInstIdToDevIndex(m->instId);
    unsigned chIdx  = cmAudioSysUiInstIdToChIndex(m->instId);
    unsigned outFl  = !cmAudioSysUiInstIdToInFlag(m->instId);
    unsigned ctlId  = cmAudioSysUiInstIdToCtlId(m->instId);

    
    int xOffs = 20;
    int yOffs = 100;
    int chH   = 205;
    int chW   = 44;
    int sldrH = 120;
    int sldrW = 20;
    int chkH  = 20;
    int chkW  = 35;

    x = xOffs + (chIdx * chW);
    y = yOffs + (m->asSubIdx * 2 * chH) + (outFl * chH);

    switch( ctlId )
    {
      // slider
      case kSliderUiAsId:  w=sldrW; h=sldrH; break;

        // meter
      case kMeterUiAsId:  w=sldrW; h=sldrH; x+=sldrW; break;

        // mute
      case kMuteUiAsId:  w=chkW; h=chkH;   y+=sldrH + 2; break;

        // tone
      case kToneUiAsId:  w=chkW; h=chkH;   y+=sldrH + chkH + 4; break;

        // pass
      case kPassUiAsId:  w=chkW; h=chkH;   y+=sldrH + 2*chkH + 6; break;

      default:
        { assert(0); }
    }

    return;
  }

  // if this control is being placed in the 'controls' group
  ctl_t*   cp    = _ctlList;

  if( w == 0 )
  {
    if( _colW == 0 )
      _colW = kCtlW;

    w = _colW;
  }

  if( h == 0 )
    h = kCtlH;

  // skip over controls that are assigned to the 'master' group
  while(cp != NULL && cp->mstrFl )
    cp = cp->linkPtr;

  // if the page list is empty - create a default blank page
  if( _pageList == NULL )
    _createPage("Controls");

  Fl_Group* ctl_grp = _pageList->grp;

  // if the ctl list is empty
  if( cp == NULL || _newPageFl )
  {
    _newPageFl = false;
    x = kHBord + ctl_grp->x();
    y = kVBord + ctl_grp->y(); 
    _horzBord = y;
  }
  else
  {
    // if a new horizontal - upper border was requested
    if( _horzBordFl )
    {
      _horzBordFl = false;

      // locate the current control with the max bottom coordinate
      ctl_t* tp = cp;
      if( tp != NULL )
      {
        int maxY = tp->wdgtPtr->y() + tp->wdgtPtr->h();

        while(tp!=NULL)
        {
          if( tp->mstrFl == false && (tp->wdgtPtr->y() + tp->wdgtPtr->h()) > maxY )
              maxY = tp->wdgtPtr->y() + tp->wdgtPtr->h();

          tp = tp->linkPtr;
        }
        // set the new upper boundary to just below the bottom of
        // the lowest control
        _horzBord = maxY + kVBord*2;

      }


      x = kHBord + ctl_grp->x();
      y = _horzBord;
        
    }
    else
    {
      // use the last ctl inserted in the list 
      // (the first ctl in the list)
      // to position the next ctl
      x = cp->wdgtPtr->x();
      y = cp->wdgtPtr->y() + cp->wdgtPtr->h() + kVBord;

      // if this control goes off the page or a new col was requested
      if( y+h > kPageH || _incrColW )
      {

        x += _colW + kHBord;
        _colW = _incrColW;
        _incrColW = 0;
        y  = _horzBord;
      }

    }  
  }
}

kcApp::ctl_t* kcApp::_createCtl( const cmDspUiHdr_t* m, unsigned typeId, int& x, int& y, int& w, int& h, bool posnFl )
{
  ctl_t* cp = new ctl_t;

  cp->thisPtr     = this;
  cp->asSubIdx    = m->asSubIdx;
  cp->instId      = m->instId;
  cp->typeId      = typeId;
  cp->mstrFl      = m->uiId == cmInvalidId;

  if( posnFl )
    _getNewCtlPosn(m,x,y,w,h);

  return cp;
}

void kcApp::_insertNewCtl( ctl_t* cp, const cmDspUiHdr_t* m, Fl_Widget* wdgt, unsigned* varIdArray, unsigned varIdCnt )
{
  unsigned i;

  // _genNewCtlPosn() should have been called before this - where a new page list would have been created.
  assert( cp->mstrFl || (cp->mstrFl==false && _pageList != NULL) );

  Fl_Group* grp = cp->mstrFl ? _mstr_grp : _pageList->grp;

  cp->wdgtPtr = wdgt;
  cp->wdgtPtr->callback(_s_ctl_cb, cp );
  grp->add(cp->wdgtPtr);

  const unsigned* srcVarIdArray = cmDsvUIntCMtx(&m->value);
  
  assert( varIdArray != NULL && cmDsvEleCount(&m->value)==varIdCnt);

  for(i=0; i<varIdCnt; ++i)
    varIdArray[i] = srcVarIdArray[i];

  cp->linkPtr = _ctlList;
  _ctlList    = cp;
}

// create a slider ctl at the request of the engine
void kcApp::_createSlider( const cmDspUiHdr_t* m )
{
  int x,y,w=0,h=0;
  ctl_t* cp = _createCtl(m, m->selId==kSliderDuiId? kSldrTypeId : kNumbTypeId, x,y,w,h );

  switch(m->selId)
  {
    case kSliderDuiId:
      cp->u.sldr.u.sldr = new Fl_Value_Slider(x,y,w,h);
      cp->u.sldr.val    = static_cast<Fl_Valuator*>(cp->u.sldr.u.sldr);
      cp->u.sldr.u.sldr->type(cp->mstrFl ? FL_VERT_NICE_SLIDER : FL_HOR_NICE_SLIDER);
      break;

    case kNumberDuiId:
      cp->u.sldr.u.numb = new Fl_Value_Input(x,y,w/2,h);
      cp->u.sldr.u.numb->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE ); 
      cp->u.sldr.val    = static_cast<Fl_Valuator*>(cp->u.sldr.u.numb);
      
      break;
  }

  _insertNewCtl(cp, m, cp->u.sldr.val, cp->u.sldr.varIdArray, kSldrVarCnt );
}


// handle slider value msg's arriving from the engine
void kcApp::_setSldrValue( ctl_t* cp, unsigned instVarId, const cmDspValue_t* vp )
{
  unsigned i=0;
  Fl_Valuator* sp = cp->u.sldr.val;

  for(i=0; i<kSldrVarCnt; ++i)
    if( cp->u.sldr.varIdArray[i] == instVarId )
    {
      switch(i)
      {
        case kSldrMinArgIdx:
          sp->minimum(cmDsvGetDouble(vp));
          //sp->step((sp->maximum()-sp->minimum())/sp->w());
          break;

        case kSldrMaxArgIdx:
          sp->maximum(cmDsvGetDouble(vp));
          //sp->step((sp->maximum()-sp->minimum())/sp->w());
          break;

        case kSldrStpArgIdx:
          sp->step(cmDsvGetDouble(vp));
          break;

        case kSldrValArgIdx: 
          sp->value(cmDsvGetDouble(vp));
          break;

        case kSldrLblArgIdx:
          {
            int ww=0,hh=0;
            if( cmDsvStrcz(vp) == NULL )
            {
              sp->label(NULL);
              sp->align(FL_ALIGN_INSIDE | FL_ALIGN_CENTER );
            }
            else
            {
              sp->copy_label( cmDsvStrcz(vp) );
              sp->align(FL_ALIGN_RIGHT);
            }

            if( cp->typeId == kSldrTypeId )
            {
              sp->measure_label(ww,hh);
              sp->resize( sp->x(), sp->y(), sp->w() - ww, sp->h());
              sp->redraw();
            }
          }

          break;

        default:
          { assert(0); }
      }

      break;
    }
}

void kcApp::_createText(  const cmDspUiHdr_t* m )
{
  int x,y,w=0,h=0;
  ctl_t* cp = _createCtl(m, kTextTypeId, x,y,w,h );

  cp->u.text.text = new Fl_Input(x,y,w,h);
  cp->u.text.text->when(FL_WHEN_RELEASE | FL_WHEN_ENTER_KEY | FL_WHEN_NOT_CHANGED);

  _insertNewCtl(cp, m, cp->u.text.text, cp->u.text.varIdArray, kTextVarCnt );  
}

void kcApp::_setTextValue( ctl_t* cp, unsigned instVarId, const cmDspValue_t* vp )
{
  unsigned i=0;
  Fl_Input* tp = cp->u.text.text;

  for(i=0; i<kTextVarCnt; ++i)
    if( cp->u.text.varIdArray[i] == instVarId )
    {
      switch(i)
      {
        case kTextValArgIdx:
          tp->value( cmDsvStrcz(vp));
          break;

        case kTextLblArgIdx:
          {
            const char* lbl = cmDsvStrcz(vp);
            if( lbl == NULL )
              tp->label(NULL);
            else
              tp->copy_label( lbl );

            tp->align(FL_ALIGN_RIGHT);
            tp->resize( tp->x(), tp->y(), tp->w()/2, tp->h());
            tp->redraw();
            
          }
          break;

        default:
          { assert(0); }
      }
    }
}

void kcApp::_createFnameCtl( const cmDspUiHdr_t* m )
{
  int x,y,w=0,h=0;
  ctl_t* cp = _createCtl(m, kFnamTypeId, x,y,w,h );

  cp->u.fnam.fnam = new Fl_File_Btn(x,y,w,h);

  _insertNewCtl(cp, m, cp->u.fnam.fnam, cp->u.fnam.varIdArray, kFnamVarCnt );
}

void kcApp::_setFnamValue( ctl_t* cp, unsigned instVarId, const cmDspValue_t* vp )
{
  unsigned i=0;
  Fl_File_Btn* bp = cp->u.fnam.fnam;

  for(i=0; i<kFnamVarCnt; ++i)
    if( cp->u.fnam.varIdArray[i] == instVarId )
    {
      switch(i)
      {
        case kFnamValArgIdx:
          assert( cmDsvIsType( vp, kStrzDsvFl ) );
          bp->filename( cmDsvStrcz(vp) );
          bp->redraw();
          break;

        case kFnamPatArgIdx:
          assert( cmDsvIsType( vp, kStrzDsvFl )  );
          bp->pattern_string( cmDsvStrcz(vp) );
          break;

        case kFnamDirArgIdx:
          bp->type( cmDsvBool(vp) ? Fl_File_Btn::kDir_Type_Id : Fl_File_Btn::kFile_Type_Id );
          break;

        default:
          { assert(0); }
      }
    }
}


void kcApp::_createMlistCtl( const cmDspUiHdr_t* m )
{
  const unsigned* varIdArray = cmDsvUIntCMtx(&m->value);

  assert( varIdArray != NULL && cmDsvEleCount(&m->value)==kMlstVarCnt);

  int x,y,w=0;
  int h = varIdArray[kMlstHgtArgIdx];
  bool menuBtnFl = h==0;

  h = menuBtnFl ? kMenuH : kMenuH * h;

  ctl_t* cp = _createCtl(m, kMlstTypeId, x,y,w,h );

  Fl_Widget* wdgt;

  if(menuBtnFl)
  {
    cp->u.mlst.mlst = NULL;
    wdgt = cp->u.mlst.mbtn = new Fl_Menu_Button(x,y,w,h);
  }
  else
  {
    cp->u.mlst.mbtn = NULL;
    wdgt = cp->u.mlst.mlst = new Fl_Select_Browser(x,y,w,h);
  }

  _insertNewCtl(cp, m, wdgt, cp->u.mlst.varIdArray, kMlstVarCnt );
}

kcApp::kcKmRC_t kcApp::_loadMlist( ctl_t* cp, const cmJsonNode_t* np )
{
  assert( cmJsonIsArray(np) );

  kcKmRC_t rc = kOkKmRC;
  unsigned n  = cmJsonChildCount(np);
  unsigned ri = 0;
  
  // empty the ctl data list
  if( cp->u.mlst.mbtn == NULL )
    cp->u.mlst.mlst->clear(); 
  else
    cp->u.mlst.mbtn->clear();

  // for each element (row) in the JSON array
  for(ri=0; ri<n && rc==kOkKmRC; ++ri)
  {
    const cmJsonNode_t* cnp        = cmJsonArrayElementC(np,ri);
    unsigned            m          = cmJsonChildCount(cnp);
    unsigned            bufCharCnt = 511;
    cmChar_t            buf[ bufCharCnt + 1 ];          // buffer to hold one row of text
    cmChar_t*           bbp        = buf;
    cmChar_t*           bep        = bbp + bufCharCnt;
    unsigned            j;

    buf[0] = 0;

    // for each element (column) of row i
    for(j=0; j<m; ++j)
    {
      const cmJsonNode_t* ep = cmJsonArrayElementC(cnp,j);
      cmJsRC_t            jsRC;

      // convert the element to a string
      if( (jsRC = cmJsonLeafToString( ep, bbp, bep - bbp )) != kOkJsRC )
      {
        switch(jsRC)
        {
          case kBufTooSmallJsRC:
            rc = cmErrMsg(&_ctx->err,kMlistLoadFailKmRC,"The msg list line character buffer is too small at JSON array element index %i.",ri);
            break;

          case kInvalidNodeTypeJsRC:
            rc = cmErrMsg(&_ctx->err,kMlistLoadFailKmRC,"The msg list array element at index %i is not a JSON leaf node.",ri);
            break;

          default:
            rc = cmErrMsg(&_ctx->err,kMlistLoadFailKmRC,"JSON to msg list text conversion failed on array element %i.",ri);
            break;            
        }
        break;
      }

      
      bbp = buf + strlen(buf);
      assert(bbp <= bep);

      // add the tab column marker
      if( j+1 < m )
      {
        if( bep - bbp < 1 )
          rc = cmErrMsg(&_ctx->err,kMlistLoadFailKmRC,"The msg list line buffer is too small.");
        else
        {
          *bbp++ = '\t';
          *bbp   = 0;
        }
      }      
    }
    
    // insert the text for row i and skip the title row
    // See the _setMListValue() and _ctl_cb() for the associated index incr/decr.
    // so that the engine list indexes and UI list indexes match
    if( rc == kOkKmRC && ri > 0)
    {
      if( cp->u.mlst.mbtn==NULL)
        cp->u.mlst.mlst->add(buf);
      else
        cp->u.mlst.mbtn->add(buf);
    }
  }
  
  if( rc == kOkKmRC )
    if( cp->u.mlst.mbtn != NULL )
    {
      if( cp->u.mlst.sel < cp->u.mlst.mbtn->size() )
        cp->u.mlst.mbtn->label(cp->u.mlst.mbtn->text(cp->u.mlst.sel));;
    }
  return rc;
}

void kcApp::_setMlistValue( ctl_t* cp, unsigned instVarId, const cmDspValue_t* vp )
{
  unsigned i=0;

  for(i=0; i<kMlstVarCnt; ++i)
    if( cp->u.mlst.varIdArray[i] == instVarId )
    {
      switch(i)
      {
        case kMlstHgtArgIdx:
          break;

        case kMlstSelArgIdx:
          {
            unsigned idx = cmDsvUInt(vp);

            // decr. to account for skipping title row 
            // (see the complementary incrementn in _ctl_cb())
            if( idx > 0 && cp->u.mlst.mbtn!=NULL)
              --idx;

            if( cp->u.mlst.mbtn==NULL)
              cp->u.mlst.mlst->value(idx);
            else
            {
              cp->u.mlst.sel = idx;

              if( cp->u.mlst.sel < cp->u.mlst.mbtn->size() )
              {
                cp->u.mlst.mbtn->value(cp->u.mlst.sel);
                cp->u.mlst.mbtn->label( cp->u.mlst.mbtn->text(cp->u.mlst.sel) );
              }
            }
            cp->wdgtPtr->redraw();
          }
          break;

        case kMlstLstArgIdx:
          _loadMlist( cp, cmDsvJson(vp) );
          break;


        default:
          { assert(0); }
      }
    }
}

// create a meter ctl at the request of the engine
void kcApp::_createMeter( const cmDspUiHdr_t* m )
{
  int x,y,w=0,h=15;
  ctl_t* cp = _createCtl(m,kMetrTypeId, x,y,w,h);

  if( cp->mstrFl )
    cp->u.metr.prog = new Fl_Vert_Progress(x,y,w,h);
  else
    cp->u.metr.prog   = new Fl_Progress(x,y,w,h);

  cp->u.metr.prog->color( fl_rgb_color((unsigned char)256),fl_rgb_color((unsigned char)128));

  _insertNewCtl(cp,m,cp->u.metr.prog,cp->u.metr.varIdArray,kMetrVarCnt);
}


// handle meter value msg's arriving from the engine
void kcApp::_setMeterValue( ctl_t* cp, unsigned instVarId, const cmDspValue_t* vp )
{
  unsigned     i  = 0;
  Fl_Progress* sp = cp->u.metr.prog;
  bool         redrawFl = false;

  for(i=0; i<kMetrVarCnt; ++i)
    if( cp->u.metr.varIdArray[i] == instVarId )
    {
      switch(i)
      {
        case kMetrMinArgIdx:
          sp->minimum(cmDsvGetDouble(vp));
          redrawFl = true;
          break;

        case kMetrMaxArgIdx:
          sp->maximum(cmDsvGetDouble(vp));
          redrawFl = true;
          break;

        case kMetrValArgIdx: 
          {
            double v = cmDsvGetDouble(vp);
             
            if( sp->value() != v )
            {
              sp->value(v);
              redrawFl = true;
            }
            break;
          }

        case kMetrLblArgIdx:
          {
            int ww=0,hh=0;
            sp->copy_label( cmDsvStrcz(vp) );
            sp->align(FL_ALIGN_RIGHT);

            sp->measure_label(ww,hh);
            sp->resize( sp->x(), sp->y(), sp->w() - ww, sp->h());
            redrawFl = true;            
          }

          break;

        default:
          { assert(0); }
      }

      break;
    }

  if( redrawFl )
    sp->redraw();
}

// create a button ctl at the request of the engine
void kcApp::_createButton( const cmDspUiHdr_t* m )
{
  int x,y,w=0,h=0;
  ctl_t* cp = _createCtl(m,kButnTypeId, x,y,w,h);

  cp->u.butn.butn = new Fl_Button(x,y,w,h);

  _insertNewCtl(cp,m,cp->u.butn.butn,cp->u.butn.varIdArray,kButnVarCnt);

}

// handle button value msg's arriving from the engine
void kcApp::_setButtonValue( ctl_t* cp, unsigned instVarId, const cmDspValue_t* vp )
{
  unsigned     i  = 0;
  Fl_Button*   sp = cp->u.butn.butn;
  bool         redrawFl = false;

  for(i=0; i<kButnVarCnt; ++i)
    if( cp->u.butn.varIdArray[i] == instVarId )
    {
      switch(i)
      {
        case kButnValArgIdx:
          cp->u.butn.val = cmDsvGetDouble(vp);
          break;

        case kButnLblArgIdx:
          {
            sp->copy_label( cmDsvStrcz(vp) );
            redrawFl = true;
          }

          break;

        default:
          { assert(0); }
      }

      break;
    }

  if( redrawFl )
    sp->redraw();

}

// create a button ctl at the request of the engine
void kcApp::_createCheck( const cmDspUiHdr_t* m )
{
  int x,y,w=0,h=0;
  ctl_t* cp = _createCtl(m,kChckTypeId, x,y,w,h);

  cp->u.chck.chck = new Fl_Check_Button(x,y,w,h);

  _insertNewCtl(cp,m,cp->u.chck.chck,cp->u.chck.varIdArray,kChckVarCnt);
}

// handle check button value msg's arriving from the engine
void kcApp::_setCheckValue( ctl_t* cp, unsigned instVarId, const cmDspValue_t* vp )
{
  unsigned     i  = 0;
  Fl_Button*   sp = cp->u.chck.chck;
  bool         redrawFl = false;

  for(i=0; i<kChckVarCnt; ++i)
    if( cp->u.chck.varIdArray[i] == instVarId )
    {
      switch(i)
      {
        case kChckValArgIdx:
          cp->u.chck.val = cmDsvGetDouble(vp);
          cp->u.chck.chck->value(cmDsvGetDouble(vp)>0);
          break;

        case kChckLblArgIdx:
          {
            int ww=0,hh=0;
            sp->copy_label( cmDsvStrcz(vp) );
            sp->align(FL_ALIGN_RIGHT);

            sp->measure_label(ww,hh);
            sp->resize( sp->x(), sp->y(), sp->w() - ww, sp->h());
            redrawFl = true;
            
          }

          break;

        default:
          { assert(0); }
      }

      break;
    }

  if( redrawFl )
    sp->redraw();

}

void kcApp::_createLabel( const cmDspUiHdr_t* m )
{
  int x,y,w=0,h=0;
  ctl_t* cp = _createCtl(m,kLablTypeId, x,y,w,h);

  cp->u.labl.box = new Fl_Box(x,y,w,h);

  _insertNewCtl(cp,m,cp->u.labl.box,cp->u.labl.varIdArray,kLablVarCnt);

}

void kcApp::_setLabelValue( ctl_t* cp, unsigned instVarId, const cmDspValue_t* vp )
{
  unsigned     i  = 0;
  Fl_Box*      sp = cp->u.labl.box;
  bool         redrawFl = false;

  for(i=0; i<kLablVarCnt; ++i)
    if( cp->u.labl.varIdArray[i] == instVarId )
    {
      switch(i)
      {
        case kLablValArgIdx:          
          sp->copy_label(cmDsvStrcz(vp));
          redrawFl = true;
          break;

        case kLablAlignArgIdx:
          switch( cmDsvUInt(vp) )
          {
            case kRightAlignDuiId:
              sp->align(FL_ALIGN_RIGHT | FL_ALIGN_INSIDE);
              break;

            case kLeftAlignDuiId:
              sp->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE);
              break;

            case kCenterAlignDuiId:
              sp->align(FL_ALIGN_CENTER | FL_ALIGN_INSIDE);
              break;

            default:
              { assert(0); }
          }
          redrawFl = true;
          break;


        default:
          {assert(0);}
      }
    }

  if( redrawFl )
    sp->redraw();
}


void kcApp::_createTmln(  const cmDspUiHdr_t* m )
{
  int x,y,w=0,h=0;

  page_t* pg = _createPage("TimeLine");

  ctl_t*  cp = _createCtl(m, kTmlnTypeId, x,y,w,h, true );

  w = pg->grp->w();
  h = pg->grp->h();

  // currently we only support one score control because
  // we have not yet implmenented a method of providing
  // timer callbacks to a list of UI controls
  if( _tlCtl != NULL )
    cp->u.tmln.tlctl = _tlCtl;
  else
  {
    _tlCtl = new tlCtl(_ctx,this,_menu,this);

    cp->u.tmln.tlctl = _tlCtl; 
  }

  Fl_Widget* wdgt = cp->u.tmln.tlctl->initTimeLineCtlr(x,y,w,h);

  _insertNewCtl(cp,m,wdgt,cp->u.tmln.varIdArray,kTmlnVarCnt);

}

void kcApp::_setTmlnValue( ctl_t* cp, unsigned instVarId, const cmDspValue_t* vp )
{
  unsigned i=0;

  for(i=0; i<kTmlnVarCnt; ++i)
    if( cp->u.tmln.varIdArray[i] == instVarId )
    {
      switch(i)
      {

        case kTmlnFileArgIdx:
          {
            const char* fn;
            if((fn = cmDsvStrcz(vp)) != NULL )
            {
              cp->u.tmln.tlctl->openTlFile(fn);
              //cp->u.tmln.tlctl->redraw();
            }
          }
          break;

        case kTmlnPathArgIdx:
          {
            const char* path;
            if((path = cmDsvStrcz(vp)) != NULL )
            {
              cp->u.tmln.tlctl->setAudioFilePath(path);
              
            }
          }
          break;

        case kTmlnSelArgIdx:
          //tp->value( cmDsvStrcz(vp));
          break;

        case kTmlnMeasArgIdx:
          cp->u.tmln.tlctl->setTimeLineSelectBar( cmDsvUInt(vp));
          break;

        case kTmlnCursArgIdx:
          cp->u.tmln.tlctl->setAudioFileCursor(cmDsvUInt(vp));
          break;

        default:
          { assert(0); }
      }
    }
}

void kcApp::_createScor(  const cmDspUiHdr_t* m )
{
  int x,y,w=0,h=0;

  page_t* pg = _createPage("Score");

  ctl_t* cp = _createCtl(m, kScorTypeId, x,y,w,h, true );

  w = pg->grp->w();
  h = pg->grp->h();

  // currently we only support one score control because
  // we have not yet implmenented a method of providing
  // timer callbacks to a list of UI controls
  if( _tlCtl != NULL )
    cp->u.scor.tlctl = _tlCtl;
  else
  {
    _tlCtl = new tlCtl(_ctx,this,_menu,this);

    cp->u.scor.tlctl  = _tlCtl; 
    cp->u.scor.smpIdx = cmInvalidIdx;
  }

  Fl_Widget* wdgt = cp->u.scor.tlctl->initScoreCtlr(x,y,w,h);

  _insertNewCtl(cp,m,wdgt,cp->u.scor.varIdArray,kScorVarCnt);

}

void kcApp::_setScorValue( ctl_t* cp, unsigned instVarId, const cmDspValue_t* vp )
{
  unsigned i=0;
  //tlCtl* tp = cp->u.scor.tlctl;

  for(i=0; i<kScorVarCnt; ++i)
    if( cp->u.scor.varIdArray[i] == instVarId )
    {
      switch(i)
      {
        case kScorSelArgIdx:
          //tp->value( cmDsvStrcz(vp));
          break;


        case kScorFileArgIdx:
          {
            const char* fn;
            if((fn = cmDsvStrcz(vp)) != NULL )
            {
              cp->u.scor.tlctl->openScoreFile(fn);
              //cp->u.scor.tlctl->redraw();
            }
          }
          break;

        case kScorSmpIdxArgIdx:
          cp->u.scor.smpIdx = cmDsvUInt(vp);
          break;

        case kScorPitchArgIdx:
          cp->u.scor.pitch = cmDsvUInt(vp);
          break;

        case kScorVelArgIdx:
          cp->u.scor.vel = cmDsvUInt(vp);
          break;

        case kScorEvtIdxArgIdx:
          cp->u.scor.evtIdx = cmDsvUInt(vp);
          break;

        case kScorDynArgIdx:
          {
            assert(cp->u.scor.evtIdx!=cmInvalidIdx);
            _tlCtl->setScoreDynLevel(cp->u.scor.evtIdx,cmDsvUInt(vp));
            cp->u.scor.evtIdx = cmInvalidIdx;
          }
          break;

        case kScorLocIdxArgIdx:
          {
            assert( cp->u.scor.smpIdx != cmInvalidIdx );
            cp->u.scor.locIdx = cmDsvUInt(vp);
            _tlCtl->setScoreLocation(cp->u.scor.locIdx,cp->u.scor.smpIdx,cp->u.scor.pitch,cp->u.scor.vel);
            cp->u.scor.smpIdx = cmInvalidIdx;
          }
          break;

        case kScorValTypeArgIdx:
          cp->u.scor.varId = cmDsvUInt(vp);
          break;

        case kScorValueArgIdx:
          _tlCtl->setScoreVarValue(cp->u.scor.locIdx,cp->u.scor.varId, cmDsvDouble(vp));
          break;

        case kScorMeasArgIdx:
          _tlCtl->setScoreSelectBar( cmDsvUInt(vp));
          break;
          
        default:
          { assert(0); }
      }
    }
}

void kcApp::_createTksb(  const cmDspUiHdr_t* m )
{
  int x,y,w=0,h=0;

  page_t* pg = _createPage("Bldr");

  ctl_t*  cp = _createCtl(m, kTksbTypeId, x,y,w,h, true );

  w = pg->grp->w();
  h = pg->grp->h();

  // currently we only support one Take sequence builder control because
  // we have not yet implmenented a method of providing
  // timer callbacks to a list of UI controls
  if( _tlCtl != NULL )
    cp->u.tksb.tlctl = _tlCtl;
  else
  {
    _tlCtl = new tlCtl(_ctx,this,_menu,this);

    cp->u.tksb.tlctl  = _tlCtl; 
  }

  Fl_Widget* wdgt = cp->u.tksb.tlctl->initTakeSeqBldrCtlr(x,y,w,h);

  _insertNewCtl(cp,m,wdgt,cp->u.tksb.varIdArray,kTksbVarCnt);

}

void kcApp::_setTksbValue( ctl_t* cp, unsigned instVarId, const cmDspValue_t* vp )
{
  unsigned i;

  for(i=0; i<kTksbVarCnt; ++i)
    if( cp->u.tksb.varIdArray[i] == instVarId )
    {
      switch(i)
      {
        case kTksbFileArgIdx:
          break;

        case kTksbPtrArgIdx:
          if( _tlCtl != NULL )
            _tlCtl->openTakeSeqBldr(cmDsvPtr(vp));
          break;

        case kTksbSelArgIdx:
          // TODO: call cmGrTksbFltk from here to to animate the time cursor
          //       with a new location
          break;

        case kTksbRefreshArgIdx:
          break;

        default:
          { assert(0); }
      }
    }
}


void kcApp::_createTksr(  const cmDspUiHdr_t* m )
{
  int x,y,w=0,h=0;

  page_t* pg = _createPage("Rndr");

  ctl_t*  cp = _createCtl(m, kTksrTypeId, x,y,w,h, true );

  w = pg->grp->w();
  h = pg->grp->h();

  // currently we only support one Take sequence builder control because
  // we have not yet implmenented a method of providing
  // timer callbacks to a list of UI controls
  if( _tlCtl != NULL )
    cp->u.tksr.tlctl = _tlCtl;
  else
  {
    _tlCtl = new tlCtl(_ctx,this,_menu,this);

    cp->u.tksr.tlctl  = _tlCtl; 
  }

  Fl_Widget* wdgt = cp->u.tksr.tlctl->initTakeSeqRendCtlr(x,y,w,h);

  _insertNewCtl(cp,m,wdgt,cp->u.tksr.varIdArray,kTksrVarCnt);

}

void kcApp::_setTksrValue( ctl_t* cp, unsigned instVarId, const cmDspValue_t* vp )
{
  unsigned i;

  for(i=0; i<kTksrVarCnt; ++i)
    if( cp->u.tksr.varIdArray[i] == instVarId )
    {
      switch(i)
      {
        case kTksrPtrArgIdx:
          if( _tlCtl != NULL )
            _tlCtl->openTakeSeqRend(cmDsvPtr(vp));
          break;

        case kTksrRefreshArgIdx:
          if( _tlCtl != NULL )
            _tlCtl->refreshTakeSeqRend();
          break;

        case kTksrSelArgIdx:
          break;

        default:
          { assert(0); }
      }
    }
}

void kcApp::_createTwod(  const cmDspUiHdr_t* m )
{
  int x,y,w=0,h=0;

  page_t* pg = _createPage("2-D");

  ctl_t*  cp = _createCtl(m, kTwodTypeId, x,y,w,h, true );

  w = pg->grp->w();
  h = pg->grp->h();

  // currently we only support one Take sequence builder control because
  // we have not yet implmenented a method of providing
  // timer callbacks to a list of UI controls
  if( _tlCtl != NULL )
    cp->u.twod.tlctl = _tlCtl;
  else
  {
    _tlCtl = new tlCtl(_ctx,this,_menu,this);

    cp->u.twod.tlctl  = _tlCtl; 
  }

  Fl_Widget* wdgt = cp->u.twod.tlctl->init2dCtlr(x,y,w,h);

  _insertNewCtl(cp,m,wdgt,cp->u.twod.varIdArray,kTwodVarCnt);

}

void kcApp::_setTwodValue( ctl_t* cp, unsigned instVarId, const cmDspValue_t* vp )
{
  unsigned i;

  for(i=0; i<kTwodVarCnt; ++i)
    if( cp->u.twod.varIdArray[i] == instVarId )
    {
      switch(i)
      {
        case kTwodXArgIdx:
        case kTwodYArgIdx:
        case kTwodAngleArgIdx:
        case kTwodRadiusArgIdx:
          break;

        default:
          { assert(0); }
      }
    }
}

void kcApp::_newColumn( const cmDspUiHdr_t* m )
{
  _incrColW = cmDsvGetUInt(&m->value);

  if( _incrColW == 0 )
    _incrColW = 200;
}

void kcApp::_insertAudioSysCfgLabel(unsigned long idx, const char* label)
{
  if( idx == 0 )
    _as_btn->clear();

  _as_btn->add(label,0,NULL,(void*)idx,0);
}

void kcApp::_setDfltAudioSysCfg(unsigned long idx, const char* label )
{
  int i;

  if((i = _as_btn->find_index(label)) != -1 )
  {
    _setMenuButton(_as_btn,i,"Audio System Cfg");
    cmAdIfSetAudioSysCfg(_aiH,_as_btn->mvalue()->argument());
  }  
}

void kcApp::_insertDeviceLabel( unsigned long devIdx, bool inputFl, const cmChar_t* label )
{
  Fl_Menu_Button* bp = inputFl ? _ai_btn : _ao_btn;

  // ??? if( idx == 0 )
  //  bp->clear();


  bp->add(label,0,NULL,(void*)devIdx,0);
}

void kcApp::_insertProgramLabel( unsigned long idx, const cmChar_t* label )
{
  if( idx == 0 )
    _pgm_btn->clear();
  
  _pgm_btn->add(label,0,NULL,(void*)idx,0);
}

void kcApp::_setDfltProgram(unsigned long idx, const char* label )
{
  int i;

  if((i = _pgm_btn->find_index(label)) != -1 )
  {
    _setMenuButton(_pgm_btn,i,"Programs");
    cmAdIfLoadProgram(_aiH,_getCurAudioSubSysIdx(),_pgm_btn->mvalue()->argument());
  }  
}


void kcApp::_insertSubSysCnt( unsigned long subSysCnt )
{
  long unsigned i;
  int bufByteCnt = 15;
  char buf[bufByteCnt+1];

  for(i=0; i<subSysCnt+1; ++i)
  {
    const char* label;
    unsigned long id;

    if( i==0 )
    {
      label = "All";
      id = cmInvalidIdx;
    }
    else
    {
      snprintf(buf,bufByteCnt,"%li",i);
      label = buf;
      id    = i-1;
    }

    _ss_btn->add(label,0,NULL,(void*)id);
  }

  _ss_btn->value(0);
  _ss_btn->copy_label( _ss_btn->mvalue()->label() );
}

unsigned kcApp::_getCurAudioSubSysIdx()
{
  const Fl_Menu_Item* mip;
  unsigned retVal = 0;

  if( (mip = _ss_btn->mvalue()) != NULL)
    retVal = mip->argument();

  return retVal;
}

void   kcApp::_setMenuButton( Fl_Menu_Button* b, unsigned value, const char* dfltLabel )
{
  const Fl_Menu_Item*   mip;
  bool fl  = value != cmInvalidIdx;

  // if value is a valid menu index then make it the current menu selection
  if( fl )
    b->value(value);     

  // get the new current menu item and set its label
  if( (mip = b->mvalue()) != NULL )
    b->copy_label( fl ? mip->label() : dfltLabel );
}

void kcApp::_setDeviceMenuButton( unsigned asSubIdx, bool inputFl, unsigned devIdx )
{
       
  // don't set the device name if the currenly selected sub-system is not the
  // same as the one the device is assigned to
  if( _getCurAudioSubSysIdx() != asSubIdx )
    return;

  Fl_Menu_Button* mbp = inputFl ? _ai_btn : _ao_btn;

  const Fl_Menu_Item* map = mbp->menu();
  unsigned n = mbp->size();
  unsigned i;
  char*    di = 0;  di += devIdx;  // BEWARE: devious int to ptr trick

  for(i=0; i<n; ++i)
    if( map[i].user_data() == di )
      break;

  if( i < n )
  {
    _setMenuButton(mbp,i,"<not found>");
  }
  
  
}

void kcApp::_setSampleRateBtn( unsigned value )
{
  unsigned i;
  unsigned n = _sr_btn->size();
  for(i=0; i<n-1; ++i)
  {
    if( _sr_btn->menu()[i].argument() == (int)value )
    {
      _sr_btn->value(i);
      _sr_btn->copy_label( _sr_btn->mvalue()->label() );
      return;
    }
  }
  
  _sr_btn->copy_label("Sample Rate?");
}


void kcApp::_updateMeters( unsigned asSubIdx, unsigned devIdx, unsigned inFl, const double* meterArray, unsigned meterCnt )
{
  unsigned i;
  for(i=0; i<meterCnt; ++i)
  {
    ctl_t*   cp;
    cmDspValue_t v;
    unsigned instId = cmAudioSysFormUiInstId(devIdx,i,inFl,kMeterUiAsId);

    if((cp = _findCtl(instId,asSubIdx,true)) != NULL )
    {
      cmDsvSetDouble(&v,meterArray[i]);
      _setMeterValue(cp, kMetrValArgIdx, &v);
    }
  }
}

void kcApp::_printStatusCounts()
{
  const ss_t* ssp = _ssArray;
  printf("Upd:%i Wake:%i Msg:%i Audio:%i\n",ssp->cnt[kUpdateSsIdx],ssp->cnt[kWakeupSsIdx],ssp->cnt[kMsgSsIdx],ssp->cnt[kAudioCbSsIdx]);
}

void kcApp::_updateSsStatusIndicator( unsigned asSubIdx, unsigned indicatorIdx, unsigned cnt )
{
  ss_t* ss = _ssArray + asSubIdx;
  float val = ss->cnt[indicatorIdx] != cnt;

  ss->cnt[indicatorIdx] = cnt;

  if( ss->prog[indicatorIdx]->value() != val )
  {
    ss->prog[indicatorIdx]->value(val);
    ss->prog[indicatorIdx]->redraw();
  }
}


void kcApp::_handleStatusMsg(const cmAudioSysStatus_t* st, const double* iMeterArray, const double* oMeterArray  )
{
  //unsigned                  asSubIdx    = ((const unsigned*)msgBuf)[0];
  //const cmAudioSysStatus_t* st          = (const cmAudioSysStatus_t*)(msgBuf + (2 * sizeof(unsigned)));
  //const double*             iMeterArray = (const double*)(st + 1);
  //const double*             oMeterArray = iMeterArray + st->iMeterCnt;
  
  _updateMeters(st->asSubIdx, st->iDevIdx,1,iMeterArray,st->iMeterCnt);
  _updateMeters(st->asSubIdx, st->oDevIdx,0,oMeterArray,st->oMeterCnt);
  
  assert( st->asSubIdx < _ssCnt );

  _updateSsStatusIndicator(st->asSubIdx, kUpdateSsIdx, st->updateCnt );
  _updateSsStatusIndicator(st->asSubIdx, kWakeupSsIdx, st->wakeupCnt );
  _updateSsStatusIndicator(st->asSubIdx, kMsgSsIdx,    st->msgCbCnt );
  _updateSsStatusIndicator(st->asSubIdx, kAudioCbSsIdx,st->audioCbCnt );
}

void kcApp::_clearStatusIndicators()
{
  for(unsigned i=0; i<_ssCnt; ++i)
  {
    _updateSsStatusIndicator(i, kUpdateSsIdx, _ssArray[i].cnt[kUpdateSsIdx] );
    _updateSsStatusIndicator(i, kWakeupSsIdx, _ssArray[i].cnt[kWakeupSsIdx] );
    _updateSsStatusIndicator(i, kMsgSsIdx,    _ssArray[i].cnt[kMsgSsIdx] );
    _updateSsStatusIndicator(i, kAudioCbSsIdx,_ssArray[i].cnt[kAudioCbSsIdx] );

  }
}

// Create a master control UI msg and send it to _handleUiMsg(). This function is intended to
// mimic the reception of a cmDspUiHdr_t msg from the audio system. It is used to create 
// and send values to the controls on the master page.
void kcApp::_sendMasterUiMsg( unsigned asSubIdx, unsigned selId, unsigned instId, unsigned instVarId, const cmDspValue_t* vp )
{
    // Determine the size of the message buffer
  unsigned     valByteCnt = cmDsvSerialDataByteCount(vp);
  unsigned     bufByteCnt = sizeof(cmDspUiHdr_t) + valByteCnt; 

  char          buf[ bufByteCnt ];
  cmDspUiHdr_t* h          = (cmDspUiHdr_t*)buf;

  h->asSubIdx  = asSubIdx;
  h->uiId      = cmInvalidId;  // the uiId field of master controls is always set to cmInvalidId 
  h->selId     = selId;
  h->flags     = 0;
  h->instId    = instId;
  h->instVarId = instVarId;

  // Serialize 'v' into the buffer beginning at the address of h->value.
  // (this function relies on the 'hdr.value' field being the last field in 'h')
  cmDsvSerialize( vp, &h->value, sizeof(cmDspValue_t) + valByteCnt);

  _handleUiMsg(h);

}

// Send a value to a control on the master page.
void kcApp::_sendMasterUiValue( unsigned asSubIdx, unsigned instId, const double* v, unsigned vn, const cmChar_t* text )
{
  cmDspValue_t val;
  unsigned     i;

  for(i=0; i<vn; ++i)
  {
    cmDsvSetDouble(&val,v[i]);
    _sendMasterUiMsg(asSubIdx, kValueDuiId, instId, i, &val );
  }

  if( text != NULL )
  {
    cmDsvSetStrcz(&val,text);
    _sendMasterUiMsg(asSubIdx, kValueDuiId, instId, i, &val );
  }

}

// Create a control on the master page.
void kcApp::_createMasterCtl( unsigned asSubIdx, unsigned selId, unsigned instId, unsigned varCnt, const double* dv, unsigned dn, const cmChar_t* label)
{
  
  // Create the control variable id array.
  // This array gives the id's used to identify each control variable.
  unsigned varIdArray[ varCnt ];
  unsigned i;
  cmDspValue_t v;

  // Set the var id's for master controls to be the same as the var indexes.
  for(i=0; i<varCnt; ++i)
    varIdArray[i] = i;

  // Encode varIdArray[] as a cmDsvValue.
  cmDsvSetUIntMtx(&v,varIdArray,varCnt,1);

  // create the control
  _sendMasterUiMsg(asSubIdx, selId, instId, cmInvalidId, &v );

  // set the controls initial configuration
  _sendMasterUiValue(asSubIdx, instId, dv, dn, label );

}

void kcApp::_handleSsInitMsg( const cmAudioSysSsInitMsg_t* m, const cmChar_t* inDevLabel, const cmChar_t* outDevLabel )
{
  unsigned i,j;

  if( _ssCnt == 0 )
  {
    _clearSsArray();
    _ssCnt   = m->asSubCnt;
    _ssArray = new ss_t[ _ssCnt ]; 
  }

  assert( m->asSubIdx < _ssCnt );

  ss_t* ss = _ssArray + m->asSubIdx;
  
  int x = 20;
  int y = 30 + kMenuH;
  int w = 80;
  int h = 20;
  char lblArray[][10] = { "Update","Wakeup","Mesg","Audio" };

  // create the sub-system status indicators
  for(i=0; i<kProgSsCnt; ++i)
  {
    Fl_Progress* prog = new Fl_Progress(x,y,w,h,NULL);
    prog->color( FL_RED, FL_GREEN );
    prog->minimum(0);
    prog->maximum(1);
    prog->copy_label(lblArray[i]);
    _mstr_grp->add(prog);

    ss->cnt[i]  = 0;
    ss->prog[i] = prog;

    x += ss->prog[i]->w() + 4;
  }


  for(i=0; i<2; ++i)
  {
    unsigned inFl   = i==0;
    unsigned chCnt  = inFl ? m->inChCnt : m->outChCnt;
    unsigned devIdx = inFl ? m->inDevIdx : m->outDevIdx;
    double   zero   = 0;
    
    for(j=0; j<chCnt; ++j)
    {
      // there are limits on the ranges of the dev and ch due to the need to pack dev/ch/infl/ctl into a 32 bit int.
      assert( devIdx < 0xffff && j < 0x0fff);

      unsigned instId = cmAudioSysFormUiInstId(devIdx,j,inFl,0);

      //printf("ssi:%i instId:0x%x\n",m->asSubIdx,instId+kMeterUiAsId);

      // create the volume slider
      double sv[] = { 3, 0, 0.01, 1 };
      unsigned sn = sizeof(sv)/sizeof(sv[0]);
      _createMasterCtl( m->asSubIdx, kSliderDuiId, instId + kSliderUiAsId, kSldrVarCnt, sv, sn, NULL);

      // create the meter
      double mv[] = { 0, 1.0, 0 };
      unsigned mn = sizeof(mv)/sizeof(mv[0]);
      _createMasterCtl( m->asSubIdx, kMeterDuiId,  instId + kMeterUiAsId, kMetrVarCnt, mv, mn, NULL);

      // create the mute button
      _createMasterCtl( m->asSubIdx, kCheckDuiId,  instId + kMuteUiAsId, kChckVarCnt, &zero, 1, "M");

      // create the tone button
      _createMasterCtl( m->asSubIdx, kCheckDuiId,  instId + kToneUiAsId, kChckVarCnt, &zero, 1, "T");

      // create the pass button
      _createMasterCtl( m->asSubIdx, kCheckDuiId,  instId + kPassUiAsId, kChckVarCnt, &zero, 1, "P");

      
    }
  }

}

// handle kValueDuiId messages coming from the engine
void kcApp::_onRecvValue( const cmDspUiHdr_t* m )
{
  ctl_t* cp;

  if((cp = _findCtl(m->instId,m->asSubIdx,m->uiId==cmInvalidId)) == NULL )
    return;

  switch( cp->typeId )
  {
    case kNumbTypeId:
    case kSldrTypeId:
      _setSldrValue(cp, m->instVarId, &m->value );
      break;

    case kTextTypeId:
      _setTextValue(cp, m->instVarId, &m->value );
      break;

    case kButnTypeId:
      _setButtonValue(cp, m->instVarId, &m->value);
      break;

    case kChckTypeId:
      _setCheckValue(cp, m->instVarId, &m->value);
      break;

    case kLablTypeId:
      _setLabelValue(cp, m->instVarId, &m->value);
      break;

    case kFnamTypeId:
      _setFnamValue(cp, m->instVarId, &m->value);
      break;

    case kMlstTypeId:
      _setMlistValue(cp, m->instVarId, &m->value);
      break;

    case kMetrTypeId:
      _setMeterValue(cp, m->instVarId, &m->value);
      break;

    case kTmlnTypeId:
      _setTmlnValue(cp, m->instVarId, &m->value);
      break;

    case kScorTypeId:
      _setScorValue(cp, m->instVarId, &m->value);
      break;

    case kTksbTypeId:
      _setTksbValue(cp, m->instVarId, &m->value);
      break;

    case kTksrTypeId:
      _setTksrValue(cp, m->instVarId, &m->value);
      break;

    case kTwodTypeId:
      _setTwodValue(cp, m->instVarId, &m->value);
      break;

    default:
      assert(0);
  }
}


// This is the main UI<-Engine msg handler/dispatch function
void kcApp::_handleUiMsg( const cmDspUiHdr_t* m )
{

  switch( m->selId )
  {
    case kPrintDuiId:
      cmDsvPrint(&m->value,NULL,&_ctx->rpt);
      break;

    case kNumberDuiId:
    case kSliderDuiId:
      _createSlider(m);
      break;

    case kTextDuiId:
      _createText(m);
      break;

    case kButtonDuiId:
      _createButton(m);
      break;

    case kLabelDuiId:
      _createLabel(m);
      break;

    case kCheckDuiId:
      _createCheck(m);
      break;

    case kFnameDuiId:
      _createFnameCtl(m);
      break;

    case kMsgListDuiId:
      _createMlistCtl(m);
      break;

    case kMeterDuiId:
      _createMeter(m);
      break;

    case kTimeLineDuiId:
      _createTmln(m);
      break;

    case kScoreDuiId:
      _createScor(m);
      break;

    case kTakeSeqBldrDuiId:
      _createTksb(m);
      break;

    case kTakeSeqRendDuiId:
      _createTksr(m);
      break;

    case kTwodDuiId:
      _createTwod(m);
      break;

    case kValueDuiId:
      _onRecvValue(m);
      break;

    case kColumnDuiId:
      _newColumn(m);
      break;

    case kHBorderDuiId:
      _horzBordFl = true;
      break;

    case kPageDuiId:
      _createPage(cmDsvStrcz(&m->value));
      break;

    case kAudioSysCfgDuiId:
      _insertAudioSysCfgLabel(m->instId,cmDsvStrcz(&m->value));
      break;

    case kAudioSysCfgDfltDuiId:
      _setDfltAudioSysCfg(m->instId,cmDsvStrcz(&m->value));
      break;
      
    case kDeviceDuiId:
      _insertDeviceLabel(m->instId,m->flags,cmDsvStrcz(&m->value));
      break;

    case kProgramDuiId:
      _insertProgramLabel(m->instId,cmDsvStrcz(&m->value));
      break;

    case kProgramDfltDuiId:
      _setDfltProgram(m->instId,cmDsvStrcz(&m->value));
      break;
      
      // the below codes are used to notify the application 
      // of changes in state of the audio DSP system

    case kSubSysCntDuiId:
      _insertSubSysCnt(cmDsvUInt(&m->value));
      break;

    case kSetAudioCfgDuiId:
      _setMenuButton(_as_btn,cmDsvUInt(&m->value),"Audio System Cfg");
      break;

    case kSetAudioDevDuiId:
      _setDeviceMenuButton( m->asSubIdx, m->flags, cmDsvUInt(&m->value) );
      break;

    case kSetSampleRateDuiId:
      _setSampleRateBtn(cmDsvUInt(&m->value));
      break;

    case kSetPgmDuiId:
      _setMenuButton(_pgm_btn,cmDsvUInt(&m->value),"Program?");
      _clearCtlList(false);
      break;

    case kEnableDuiId:
      _ena_chk->value(m->flags);
      break;

  }
}


// Check for and forward any messages sent to the UI 
// that are waiting in the audio DSP msg queue.
void kcApp::_getEngMsg()
{
  if( cmAdIfIsValid(_aiH) )
  {
    unsigned i;

    for(i=0; i<10; ++i)
    {
      cmAiRC_t aiRC;

      // cmAdIfDispatchMsgToHost() results in calls to 
      // the _s_handleXXX() message handlers
      if((aiRC = cmAdIfDispatchMsgToHost(_aiH)) != kOkAiRC)
      {
        if( aiRC == kNoMsgAiRC )
          break;

        cmErrMsg(&_ctx->err,kEngFailKmRC,"Audio DSP dispatch message request failed.");
        break;
      }
    }
  }
}


kcApp*   kcApp::_getApp( Fl_Widget* w )
{
  // walk up the widget tree until the top widget is  found
  Fl_Group* gp = w->parent();
  while( gp->parent() != NULL )
    gp=gp->parent();

  // the user data for the top widget is a pointer kcApp() 
  return (kcApp*)gp->user_data();
}

void kcApp::_onCloseApp()
{
  if( _tlCtl != NULL )
  {
    delete _tlCtl;
    _tlCtl = NULL;
  }

  _stopTimerFl = true;


  // When all windows are windows are closed then the app.
  // will close - so hiding the application window
  // causes the program to close.  
  //
  // Note that simply returning from this callback will
  // prevent the application from closing. Because the existence
  // of the callback alone is enough to disable default 
  // event handling.
  hide();  

}


void kcApp::_testStub()
{
  /*
  cmAudioFileTest(
    //  "/Users/kevin/media/audio/20100819-Kreisberg/fragments/Bass End_21a.wav",
    "/Users/kevin/media/audio/McGill-1/1 Audio Track.aiff",
    "/Users/kevin/src/octave/cm_audio_file_test.m",_ctx->err.rpt );
  */

  //cmSymTblTest(&_ctx);
  
  //_kcTranslateFile("/Users/kevin/src/kc/src/data/Sec1_14.txt","/Users/kevin/src/kc/src/data/Sec1_14_out.txt",&_ctx);
  //_kcTranslateFile("/Users/kevin/src/kc/src/data/Mix_17-20.txt","/Users/kevin/src/kc/src/data/Mix_17-20_out.txt",&_ctx);

  /*
  ctl_t* cp = _ctlList;
  for(; cp!=NULL; cp=cp->linkPtr)
    printf("%i %i %i\n", cp->instId,cp->asSubIdx,cp->mstrFl );
  */

  //_printStatusCounts();

  //cmProcTestNoInit(&_ctx);
  

  _tlCtl->testStub();
}

void kcApp::_printPgm()
{
  const char* pathStr   = cmFsUserDir(); // make the default dir the user's home dir
  const char patStr[]   = ""; //"Text Files (*.txt)\tAudio Files (*.{wav,aif,aiff})"; // All Files (*.*) is append to this automatically
  const char titleStr[] = "JSON output file name";

  Fl_File_Chooser fc = Fl_File_Chooser(pathStr,patStr,Fl_File_Chooser::CREATE,titleStr);
        
  fc.preview(0); // default the previous option to 'off'.
  fc.show();     // show the chooser

  // make the chooser modal
  while( fc.shown() )
          Fl::wait();

  if( fc.count() > 0)
    cmAdIfPrintPgm(_aiH, cmInvalidIdx, fc.value(0) );

}



void kcApp::_s_callback(Fl_Widget* wp, void* data)
{ ((kcApp*)data)->_callback(NULL); }

// this callback is called when the window is closing
void kcApp::_callback(void* data)
{
  if( Fl::event() == FL_CLOSE )
  {
    _onCloseApp();
  }

}



void kcApp::_s_status_timeout_cb(void* userPtr)
{ 
  if( ((kcApp*)userPtr)->_status_timeout_cb() )
    Fl::repeat_timeout(TIMER_PERIOD,_s_status_timeout_cb,userPtr);
}

bool kcApp::_status_timeout_cb()
{
  if( cmTsMp1cIsValid(_printqH) )
    _checkPrintQueue();

  if( !_stopTimerFl )
  {
    _getEngMsg();
    if( _tlCtl != NULL )
      _tlCtl->onIdle();
  }

  if( _ssUpdateFl )
  {
    ++_ssPhase;
    if( _ssPhase >= kSsPhaseMax )
    {
      _clearStatusIndicators();
      _ssPhase = 0;
    }
  }

  if( _stopTimerFl==false && cmAdIfIsValid(_aiH) )
    cmAdIfDispatchMsgToHost(_aiH); 

  //if( !_stopTimerFl )
  //  _getEngStatus();

  return _stopTimerFl==false || cmTsMp1cIsValid(_printqH);
}

void kcApp::_s_menu_cb(Fl_Widget *w, void *data) 
{
  const Fl_Menu_Item* mip;
  kcApp*                p;

  if((p=_getApp(w)) == NULL )
    return;

  if((mip = p->_menu->mvalue()) == NULL )
    return;
 
  unsigned id = (long int)mip->user_data();

  switch(id)
  {
    case kAboutMenuId:
      fl_message("%s Compiled:%s %s",PACKAGE_STRING,__DATE__,__TIME__); 
      break;

    case kQuitMenuId:
      p->_onCloseApp();
      break;
  }

 
}

void kcApp::_s_tab_cb(Fl_Widget* w, void* data)
{ ((kcApp*)data)->_tab_cb(w); }

void kcApp::_tab_cb(Fl_Widget*)
{
  Fl_Widget* w = _tabs->value();
  _ssUpdateFl = w == (Fl_Widget*)_mstr_grp;
  _ssPhase    = kSsPhaseMax;
  if( cmAdIfIsValid(_aiH) )
  {
    if( cmAdIfEnableStatusNotify(_aiH, _ssUpdateFl ) != kOkAiRC )
      cmErrMsg(&_ctx->err,kEngFailKmRC,"A request to enable/disable status notification failed.");
  }
}

void kcApp::_s_btn_cb(Fl_Widget* w, long data)
{ 
  _getApp(w)->_btn_cb(w,data);
}

void kcApp::_btn_cb(Fl_Widget* w, long data)
{
  unsigned arg = w->argument();
  switch( arg )
  {
    case kAudDevRptBtnId:
      cmAdIfDeviceReport(_aiH);
      break;

    case kEnableBtnId:
      cmAdIfEnableAudio(_aiH,static_cast<Fl_Check_Button*>(w)->value()!=0);
      break;

    case kAudioSysCfgBtnId:
      cmAdIfSetAudioSysCfg(_aiH,static_cast<Fl_Menu_Button*>(w)->mvalue()->argument());
      break;

    case kInAudDevBtnId:
      cmAdIfSetAudioDevice(_aiH,_getCurAudioSubSysIdx(),true,static_cast<Fl_Menu_Button*>(w)->mvalue()->argument());
      break;

    case kOutAudDevBtnId:
      cmAdIfSetAudioDevice(_aiH,_getCurAudioSubSysIdx(),false,static_cast<Fl_Menu_Button*>(w)->mvalue()->argument());
      break;

    case kPgmBtnId:
      cmAdIfLoadProgram(_aiH,_getCurAudioSubSysIdx(),static_cast<Fl_Menu_Button*>(w)->mvalue()->argument());
      break;

    case kSubSystemIdxBtnId:
      // TODO: change device and sample rate menu's to reflect the device and srate assigned to this asSubIdx.
      break;

    case kSrateBtnId:
      cmAdIfSetSampleRate(_aiH,_getCurAudioSubSysIdx(),static_cast<Fl_Menu_Button*>(w)->mvalue()->argument());
      break;

    case kTestBtnId:
      _testStub();
      break;

    case kPrintPgmBtnId:
      _printPgm();
      break;

    default:
      { assert(0); }
  }
}

void kcApp::_s_ctl_cb(Fl_Widget* w, void* data)
{
  ctl_t* cp = ((ctl_t*)data);
  cp->thisPtr->_ctl_cb(cp);
}

void kcApp::_ctl_cb(ctl_t* cp)
{
  cmDspValue_t value = cmDspNullValue;
  unsigned instVarId = cmInvalidId;

  switch( cp->typeId )
  {
    case kNumbTypeId:
    case kSldrTypeId:
      instVarId = cp->u.sldr.varIdArray[ kSldrValArgIdx ];
      cmDsvSetDouble(&value,cp->u.sldr.val->value());
      break;

    case kTextTypeId:
      instVarId = cp->u.text.varIdArray[ kTextValArgIdx ];
      cmDsvSetStrz(&value,(cmChar_t*)cp->u.text.text->value());
      break;

    case kButnTypeId:
      instVarId = cp->u.butn.varIdArray[ kButnValArgIdx ];
      cmDsvSetDouble(&value,cp->u.butn.val);
      break;

    case kChckTypeId:
      {
        bool fl = cp->u.chck.chck->value();
        instVarId = cp->u.chck.varIdArray[ kChckValArgIdx ];
        cmDsvSetDouble(&value, fl ? 1.0 : 0.0 );
      }
      break;

    case kFnamTypeId:
      instVarId = cp->u.fnam.varIdArray[ kFnamValArgIdx ];
      cmDsvSetStrz( &value, (cmChar_t*)cp->u.fnam.fnam->filename());
      break;

    case kMlstTypeId:
      instVarId = cp->u.mlst.varIdArray[ kMlstSelArgIdx ];

      // add one to the selected index to account for skipping title row
      if( cp->u.mlst.mbtn==NULL)
      {
        unsigned idx = cp->u.mlst.mlst->value();
        //printf("list:%i\n",idx); 
        cmDsvSetUInt( &value, idx);
      }
      else
      {
        unsigned idx = cp->u.mlst.mbtn->value();
        //printf("mbtn:%i\n",idx);
        cmDsvSetUInt( &value, idx + 1);
        cp->u.mlst.mbtn->label( cp->u.mlst.mbtn->text(idx) );
      }
      break;

    case kTmlnTypeId:
      {
        instVarId = cp->u.tmln.varIdArray[ kTmlnSelArgIdx ];
        unsigned selMarkerId = cp->u.tmln.tlctl->timeLineSelectedMarkerId();
        cmDsvSetUInt(&value, selMarkerId );
      }
      break;

    case kScorTypeId:
      {
        instVarId = cp->u.scor.varIdArray[ kScorSelArgIdx ];
        unsigned selEleIdx = cp->u.scor.tlctl->scoreSelectedEleIndex();
        cmDsvSetUInt(&value, selEleIdx );
      }
      break;

    case kTksbTypeId:
      {
        cmGrTksbFltk* tksbCtl = dynamic_cast<cmGrTksbFltk*>(cp->wdgtPtr);
        
        switch( tksbCtl->cbTypeId() )
        {
          case cmGrTksbFltk::kSelectTId:
            {
              instVarId = cp->u.tksb.varIdArray[ kTksbSelArgIdx ];
              unsigned selEleIdx = cp->u.tksb.tlctl->tksbSelectedEleIndex();
              cmDsvSetUInt(&value, selEleIdx );
            }
            break;

          case cmGrTksbFltk::kRefreshTId:
            instVarId = cp->u.tksb.varIdArray[ kTksbRefreshArgIdx ];
            cmDsvSetInt(&value,0);
            break;

          default:
            { assert(0); }
        }
        

      }
      break;

    case kTksrTypeId:
      {
        
      }
      break;

    case kTwodTypeId:
      {
        cmGr2dFltk* twodCtl = dynamic_cast<cmGr2dFltk*>(cp->wdgtPtr);

        unsigned i;
        for(i=0; i<kTwodVarCnt; ++i)
        {
          switch( i )
          {
            case kTwodXArgIdx:
              cmDsvSetDouble(&value,twodCtl->x());
              break;

            case kTwodYArgIdx:
              cmDsvSetDouble(&value,twodCtl->y());
              break;

            case kTwodAngleArgIdx:
              cmDsvSetDouble(&value,twodCtl->angle());
              break;

            case kTwodRadiusArgIdx:
              cmDsvSetDouble(&value,twodCtl->radius());
              break;
          }

          cmAdIfSendMsgToAudioDSP(_aiH,cp->asSubIdx,kUiSelAsId,kValueDuiId,0,cp->instId,cp->u.twod.varIdArray[i],&value);
        }

        instVarId = cmInvalidId;
      }
      break;
      
    default:
      {assert(0);}

  }

  if( instVarId != cmInvalidId )
    if( cmAdIfSendMsgToAudioDSP( 
        _aiH, 
        cp->asSubIdx, 
        cp->mstrFl ? kUiMstrSelAsId : kUiSelAsId,
        kValueDuiId,
        0,
        cp->instId,
        instVarId,
        &value) != kOkAiRC)
    {
      cmErrMsg(&_ctx->err,kEngFailKmRC,"An attempt to send a UI message to the audio DSP interface failed.");
    }

}


void kcApp::vprint(const char* fmt, va_list vl )
{
  int bufCharCnt = 511;
  char buf[bufCharCnt+1];

  int n = vsnprintf(buf,bufCharCnt,fmt,vl);

  if( n > 0 )
  {
    // if the print queue exists (it might not during startup or shutdown) ...
    if( cmTsMp1cIsValid(_printqH) )
    {
      // ... enqueue the text to print 
      if( cmTsMp1cEnqueueMsg(_printqH,buf,n+1) != kOkThRC && _printFl==0 )
      {
        // use _printFl to guard against recursion which would eventually overflow the stack.
        ++_printFl;
        cmErrMsg(&_ctx->err,kQueueFailKmRC,"Print enqueue failed on msg:%s.",buf);
        --_printFl;

      }
    }
    else
      _print(buf); // ... otherwise just send the text directly to the output console
  }

}

void kcApp::print( const char* fmt, ... )
{
  va_list vl;
  va_start(vl,fmt);
  vprint(fmt,vl);
  va_end(vl);
}

void kcApp::_s_print( void* userPtr, const char* text )
{ ((kcApp*)userPtr)->print(text); }

cmRC_t kcApp::_s_print_queue_cb(void* userCbPtr, unsigned msgByteCnt, const void* msgDataPtr )
{
  kcApp* ap = (kcApp*)userCbPtr;
  ap->_print((const char*)msgDataPtr);
  return cmOkRC;
}

void kcApp::_checkPrintQueue()
{
  while( cmTsMp1cMsgWaiting(_printqH) )
    if( cmTsMp1cDequeueMsg(_printqH, NULL, 0) != kOkThRC && _printFl==0 )
    {
      ++_printFl;
      cmErrMsg(&_ctx->err,kPrintQueFailKmRC,"Print dequeue failed.");
      --_printFl;
    }
}

void kcApp::_print( const char* text )
{
  if( _con != NULL )
    _con->insert(text);
#ifndef NDEBUG
  fputs(text,stdout);
#endif
}
